diff --git a/CHANGELOG.md b/CHANGELOG.md
index 86c1484f89..83a22cca25 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (ante) [#164](https://github.com/EscanBE/evermint/pull/164) Introduce Dual-Lane AnteHandler
- (ante) [#165](https://github.com/EscanBE/evermint/pull/165) Simulate EVM txs before accept mempool
+- (evm) [#167](https://github.com/EscanBE/evermint/pull/167) Introduce Context-based StateDB
### Improvement
diff --git a/README.md b/README.md
index 97307c1152..d6782cf092 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,8 @@ parent:
+Requires [Go 1.22+](https://golang.org/dl/)
+
### Create your own fully customized EVM-enabled blockchain network in just 2 steps
[> Quick rename](https://github.com/EscanBE/evermint/blob/main/RENAME_CHAIN.md)
@@ -24,38 +26,16 @@ parent:
### About Evermint
-Evermint is a fork of open source Evmos v12.1.6, maintained by Escan team with bug fixes, customization and enable developers to fork and transform to their chain, fully customized, in just 2 steps.
-
-_Important Note: Evermint was born for development and research purpose so maintainers do not support migration for new upgrade/breaking changes._
-
-### About Evmos
-
-Evmos is a scalable, high-throughput Proof-of-Stake blockchain
-that is fully compatible and interoperable with Ethereum.
-It's built using the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk/)
-which runs on top of the [CometBFT](https://github.com/cometbft/cometbft) consensus engine.
-
-### Different of Evermint & Evmos
-
-- Evermint is for research and development purpose.
-- Evermint is fork of open source Evmos v12.1.6 plus bug fixes.
-- Evermint is [currently removing some modules](https://github.com/EscanBE/evermint/issues/41) from Evmos codebase and only keep `x/evm`, `x/erc20`, `x/feemarket`. The goal is to make it more simple for research and only focus on the skeleton of Evmos.
-- After [upgraded to Cosmos-SDK v0.50](https://github.com/EscanBE/evermint/pull/148), Evermint is now has many breaking change to the Legacy Evmos v12.1.6. If you want to use the open-source libraries previously developed for Evmos v12.1.6, you might need to use [Evermint version which uses Cosmos-SDK v0.47 and below](https://github.com/EscanBE/evermint/tree/v12.3.0-cosmos47).
+Evermint originally a fork of open source Evmos v12.1.6 plus lots of magic.
-## Documentation
+Many important pieces of code was replaced by Evermint, such as:
+- Replaced legacy AnteHandler with [Dual-Lane AnteHandler](https://github.com/EscanBE/evermint/pull/164).
+- Replaced legacy StateDB with [Context-based StateDB](https://github.com/EscanBE/evermint/pull/167).
+- Used go-ethereum code for [state-transition](https://github.com/EscanBE/evermint/pull/156).
+- Some project structure was replaced during upgrade to Cosmos-SDK v0.50, CometBFT v0.38, ibc-go v8.5.
+- Data structure, code logic of `MsgEthereumTx` and some proto msgs were replaced.
-Evermint does not maintain its own documentation site, user can refer to Evmos documentation hosted at [evmos/docs](https://github.com/evmos/docs) and can be found at [docs.evmos.org](https://docs.evmos.org).
-Head over there and check it out.
-
-**Note**: Requires [Go 1.22+](https://golang.org/dl/)
-
-## Quick Start
-
-To learn how the Evmos works from a high-level perspective,
-go to the [Protocol Overview](https://docs.evmos.org/protocol) section from the documentation.
-You can also check the instructions to [Run a Node](https://docs.evmos.org/protocol/evmos-cli#run-an-evmos-node).
-
-### Additional feature provided by Evermint:
+#### Some other features provided by Evermint:
1. Command convert between 0x address and bech32 address, or any custom bech32 HRP
```bash
evmd convert-address evm1sv9m0g7ycejwr3s369km58h5qe7xj77hxrsmsz evmos
@@ -66,4 +46,11 @@ evmd convert-address evm1sv9m0g7ycejwr3s369km58h5qe7xj77hxrsmsz evmos
4. [`snapshots` command](https://github.com/EscanBE/evermint/pull/12)
5. [`inspect` command](https://github.com/EscanBE/evermint/pull/14)
6. [Flag `--allow-insecure-unlock`](https://github.com/EscanBE/evermint/pull/142)
-7. Dependencies updated: `Cosmos-SDK v0.50.9`, `CometBFT v0.38.12`, `ibc-go v8.5.0`, `go-ethereum v1.10.26`
\ No newline at end of file
+
+### About Evmos
+_v12.1.6_
+
+Evmos is a scalable, high-throughput Proof-of-Stake blockchain
+that is fully compatible and interoperable with Ethereum.
+It's built using the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk/)
+which runs on top of the [Tendermint](https://github.com/tendermint/tendermint) consensus engine.
diff --git a/app/antedl/ante.go b/app/antedl/ante.go
index e500491d86..be17d8ef1d 100644
--- a/app/antedl/ante.go
+++ b/app/antedl/ante.go
@@ -39,8 +39,8 @@ func NewAnteHandler(options HandlerOptions) sdk.AnteHandler {
// EVM-only lane
evmlane.NewEvmLaneSetupExecutionDecorator(*options.EvmKeeper),
- evmlane.NewEvmLaneEmitEventDecorator(*options.EvmKeeper), // must be the last effective Ante
- evmlane.NewEvmLaneExecWithoutErrorDecorator(*options.AccountKeeper, *options.EvmKeeper), // simulation ante
+ evmlane.NewEvmLaneEmitEventDecorator(*options.EvmKeeper), // must be the last effective Ante
+ evmlane.NewEvmLaneExecWithoutErrorDecorator(*options.AccountKeeper, options.BankKeeper, *options.EvmKeeper), // simulation ante
// Cosmos-only lane
cosmoslane.NewCosmosLaneRejectEthereumMsgsDecorator(),
diff --git a/app/antedl/evmlane/993e_exec_without_error.go b/app/antedl/evmlane/993e_exec_without_error.go
index 0bea3c05cd..1da1544aff 100644
--- a/app/antedl/evmlane/993e_exec_without_error.go
+++ b/app/antedl/evmlane/993e_exec_without_error.go
@@ -3,11 +3,13 @@ package evmlane
import (
"errors"
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
+ bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
+
errorsmod "cosmossdk.io/errors"
dlanteutils "github.com/EscanBE/evermint/v12/app/antedl/utils"
evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
@@ -15,11 +17,12 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
)
type ELExecWithoutErrorDecorator struct {
ak authkeeper.AccountKeeper
+ bk bankkeeper.Keeper
ek evmkeeper.Keeper
}
@@ -27,9 +30,10 @@ type ELExecWithoutErrorDecorator struct {
// This decorator only executes in (re)check-tx and simulation mode.
// - If the input transaction is a Cosmos transaction, it calls next ante handler.
// - If the input transaction is an Ethereum transaction, it runs simulate the state transition to ensure tx can be executed.
-func NewEvmLaneExecWithoutErrorDecorator(ak authkeeper.AccountKeeper, ek evmkeeper.Keeper) ELExecWithoutErrorDecorator {
+func NewEvmLaneExecWithoutErrorDecorator(ak authkeeper.AccountKeeper, bk bankkeeper.Keeper, ek evmkeeper.Keeper) ELExecWithoutErrorDecorator {
return ELExecWithoutErrorDecorator{
ak: ak,
+ bk: bk,
ek: ek,
}
}
@@ -72,13 +76,11 @@ func (ed ELExecWithoutErrorDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, sim
ed.ek.SetFlagSenderNonceIncreasedByAnteHandle(simulationCtx, false)
}
- var evm *vm.EVM
+ var evm *corevm.EVM
{ // initialize EVM
- txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(simulationCtx.HeaderHash()))
- txConfig = txConfig.WithTxTypeFromMessage(ethCoreMsg)
- stateDB := statedb.New(simulationCtx, &ed.ek, txConfig)
+ stateDB := evmvm.NewStateDB(simulationCtx, &ed.ek, ed.ak, ed.bk)
evmParams := ed.ek.GetParams(simulationCtx)
- evmCfg := &statedb.EVMConfig{
+ evmCfg := &evmvm.EVMConfig{
Params: evmParams,
ChainConfig: evmParams.ChainConfig.EthereumConfig(ed.ek.ChainID()),
CoinBase: common.Address{},
diff --git a/app/antedl/evmlane/993e_exec_without_error_it_test.go b/app/antedl/evmlane/993e_exec_without_error_it_test.go
index ed1add1e6a..d8b71926df 100644
--- a/app/antedl/evmlane/993e_exec_without_error_it_test.go
+++ b/app/antedl/evmlane/993e_exec_without_error_it_test.go
@@ -150,7 +150,9 @@ func (s *ELTestSuite) Test_ELExecWithoutErrorDecorator() {
cachedCtx, _ := s.Ctx().CacheContext()
tt.decoratorSpec.WithDecorator(
- evmlane.NewEvmLaneExecWithoutErrorDecorator(*s.App().AccountKeeper(), *s.App().EvmKeeper()),
+ evmlane.NewEvmLaneExecWithoutErrorDecorator(
+ *s.App().AccountKeeper(), s.App().BankKeeper(), *s.App().EvmKeeper(),
+ ),
)
if tt.reCheckTx {
diff --git a/app/modules.go b/app/modules.go
index 9f2f6925df..c132ad09e0 100644
--- a/app/modules.go
+++ b/app/modules.go
@@ -68,7 +68,7 @@ var maccPerms = map[string][]string{
minttypes.ModuleName: {authtypes.Minter},
ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner},
icatypes.ModuleName: nil,
- evmtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, // used for secure addition and subtraction of balance using module account
+ evmtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, // used for secure addition and subtraction of balance
erc20types.ModuleName: {authtypes.Minter, authtypes.Burner},
vauthtypes.ModuleName: {authtypes.Burner},
}
diff --git a/rpc/backend/call_tx.go b/rpc/backend/call_tx.go
index 72a702c5a3..7d17c007c4 100644
--- a/rpc/backend/call_tx.go
+++ b/rpc/backend/call_tx.go
@@ -16,7 +16,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -381,7 +381,7 @@ func (b *Backend) DoCall(
}
if res.Failed() {
- if res.VmError != vm.ErrExecutionReverted.Error() {
+ if res.VmError != corevm.ErrExecutionReverted.Error() {
return nil, status.Error(codes.Internal, res.VmError)
}
return nil, evmtypes.NewExecErrorWithReason(res.Ret)
diff --git a/testutil/tx/signer.go b/testutil/tx/signer.go
index 2687fad632..1dd0a41da6 100644
--- a/testutil/tx/signer.go
+++ b/testutil/tx/signer.go
@@ -41,6 +41,12 @@ func GenerateAddress() common.Address {
return addr
}
+// GenerateHash generates an Ethereum hash.
+func GenerateHash() common.Hash {
+ _, pk := NewAddrKey()
+ return common.BytesToHash(pk.Bytes())
+}
+
var _ keyring.Signer = &Signer{}
// Signer defines a type that is used on testing for signing MsgEthereumTx
diff --git a/types/noop_event_manager.go b/types/noop_event_manager.go
new file mode 100644
index 0000000000..58347dc1ae
--- /dev/null
+++ b/types/noop_event_manager.go
@@ -0,0 +1,37 @@
+package types
+
+import (
+ abci "github.com/cometbft/cometbft/abci/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ gogoproto "github.com/cosmos/gogoproto/proto"
+)
+
+var _ sdk.EventManagerI = &noOpEventManager{}
+
+type noOpEventManager struct{}
+
+func NewNoOpEventManager() sdk.EventManagerI {
+ return &noOpEventManager{}
+}
+
+func (n noOpEventManager) Events() sdk.Events {
+ return []sdk.Event{}
+}
+
+func (n noOpEventManager) ABCIEvents() []abci.Event {
+ return []abci.Event{}
+}
+
+func (n noOpEventManager) EmitTypedEvent(_ gogoproto.Message) error {
+ return nil
+}
+
+func (n noOpEventManager) EmitTypedEvents(_ ...gogoproto.Message) error {
+ return nil
+}
+
+func (n noOpEventManager) EmitEvent(_ sdk.Event) {
+}
+
+func (n noOpEventManager) EmitEvents(_ sdk.Events) {
+}
diff --git a/x/erc20/keeper/mock_test.go b/x/erc20/keeper/mock_test.go
index f7f2037802..2dbeba3c60 100644
--- a/x/erc20/keeper/mock_test.go
+++ b/x/erc20/keeper/mock_test.go
@@ -7,13 +7,12 @@ import (
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
erc20types "github.com/EscanBE/evermint/v12/x/erc20/types"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
"github.com/stretchr/testify/mock"
)
@@ -29,12 +28,9 @@ func (m *MockEVMKeeper) GetParams(_ sdk.Context) evmtypes.Params {
return args.Get(0).(evmtypes.Params)
}
-func (m *MockEVMKeeper) GetAccountWithoutBalance(_ sdk.Context, _ common.Address) *statedb.Account {
+func (m *MockEVMKeeper) IsEmptyAccount(_ sdk.Context, _ common.Address) bool {
args := m.Called(mock.Anything, mock.Anything)
- if args.Get(0) == nil {
- return nil
- }
- return args.Get(0).(*statedb.Account)
+ return args.Get(0).(bool)
}
func (m *MockEVMKeeper) EstimateGas(_ context.Context, _ *evmtypes.EthCallRequest) (*evmtypes.EstimateGasResponse, error) {
@@ -45,7 +41,7 @@ func (m *MockEVMKeeper) EstimateGas(_ context.Context, _ *evmtypes.EthCallReques
return args.Get(0).(*evmtypes.EstimateGasResponse), args.Error(1)
}
-func (m *MockEVMKeeper) ApplyMessage(_ sdk.Context, _ core.Message, _ vm.EVMLogger, _ bool) (*evmtypes.MsgEthereumTxResponse, error) {
+func (m *MockEVMKeeper) ApplyMessage(_ sdk.Context, _ core.Message, _ corevm.EVMLogger, _ bool) (*evmtypes.MsgEthereumTxResponse, error) {
args := m.Called(mock.Anything, mock.Anything, mock.Anything, mock.Anything)
if args.Get(0) == nil {
diff --git a/x/erc20/keeper/msg_server.go b/x/erc20/keeper/msg_server.go
index b72ef5d16f..dd5ed5a08d 100644
--- a/x/erc20/keeper/msg_server.go
+++ b/x/erc20/keeper/msg_server.go
@@ -37,9 +37,7 @@ func (k Keeper) ConvertCoin(
// Remove token pair if contract is suicided
erc20 := common.HexToAddress(pair.Erc20Address)
- acc := k.evmKeeper.GetAccountWithoutBalance(ctx, erc20)
-
- if acc == nil || !acc.IsContract() {
+ if k.evmKeeper.IsEmptyAccount(ctx, erc20) {
k.DeleteTokenPair(ctx, pair)
k.Logger(ctx).Debug(
"deleting selfdestructed token pair from state",
@@ -79,9 +77,7 @@ func (k Keeper) ConvertERC20(
// Remove token pair if contract is suicided
erc20 := common.HexToAddress(pair.Erc20Address)
- acc := k.evmKeeper.GetAccountWithoutBalance(ctx, erc20)
-
- if acc == nil || !acc.IsContract() {
+ if k.evmKeeper.IsEmptyAccount(ctx, erc20) {
k.DeleteTokenPair(ctx, pair)
k.Logger(ctx).Debug(
"deleting selfdestructed token pair from state",
diff --git a/x/erc20/keeper/msg_server_test.go b/x/erc20/keeper/msg_server_test.go
index f0db56e1c2..3616639a16 100644
--- a/x/erc20/keeper/msg_server_test.go
+++ b/x/erc20/keeper/msg_server_test.go
@@ -1,7 +1,6 @@
package keeper_test
import (
- "fmt"
"math/big"
sdkmath "cosmossdk.io/math"
@@ -10,14 +9,9 @@ import (
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
sdk "github.com/cosmos/cosmos-sdk/types"
- "github.com/stretchr/testify/mock"
-
"github.com/ethereum/go-ethereum/common"
- erc20keeper "github.com/EscanBE/evermint/v12/x/erc20/keeper"
erc20types "github.com/EscanBE/evermint/v12/x/erc20/types"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
- evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
)
func (suite *KeeperTestSuite) TestConvertCoinNativeCoin() {
@@ -56,7 +50,7 @@ func (suite *KeeperTestSuite) TestConvertCoinNativeCoin() {
stateDB := suite.StateDB()
ok := stateDB.Suicide(erc20)
suite.Require().True(ok)
- suite.Require().NoError(stateDB.Commit())
+ suite.Require().NoError(stateDB.CommitMultiStore(false))
},
extra: func() {},
expPass: true,
@@ -97,80 +91,6 @@ func (suite *KeeperTestSuite) TestConvertCoinNativeCoin() {
expPass: false,
selfdestructed: false,
},
- {
- name: "fail - force evm fail",
- mint: 100,
- burn: 10,
- malleate: func(common.Address) {},
- extra: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("forced ApplyMessage error"))
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- selfdestructed: false,
- },
- {
- name: "fail - force evm balance error",
- mint: 100,
- burn: 10,
- malleate: func(common.Address) {},
- extra: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- // first balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- // convert coin
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil).Once()
- // second balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, fmt.Errorf("third")).Once()
- // Extra call on test
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- selfdestructed: false,
- },
- {
- name: "fail - force balance error",
- mint: 100,
- burn: 10,
- malleate: func(common.Address) {},
- extra: func() {
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Times(4)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- selfdestructed: false,
- },
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
@@ -208,21 +128,19 @@ func (suite *KeeperTestSuite) TestConvertCoinNativeCoin() {
if tc.expPass {
suite.Require().NoError(err)
- acc := suite.app.EvmKeeper.GetAccountWithoutBalance(suite.ctx, erc20)
+ isEmptyAcc := suite.app.EvmKeeper.IsEmptyAccount(suite.ctx, erc20)
if tc.selfdestructed {
- suite.Require().Nil(acc, "expected contract to be destroyed")
- } else {
- suite.Require().NotNil(acc)
- }
+ suite.Require().True(isEmptyAcc, "expected contract to be destroyed")
- if tc.selfdestructed || !acc.IsContract() {
id := suite.app.Erc20Keeper.GetTokenPairID(suite.ctx, erc20.String())
_, found := suite.app.Erc20Keeper.GetTokenPair(suite.ctx, id)
suite.Require().False(found)
} else {
+ suite.Require().False(isEmptyAcc)
+
suite.Require().Equal(&erc20types.MsgConvertCoinResponse{}, res)
- suite.Require().Equal(cosmosBalance.Amount.Int64(), sdkmath.NewInt(tc.mint-tc.burn).Int64())
- suite.Require().Equal(balance.(*big.Int).Int64(), big.NewInt(tc.burn).Int64())
+ suite.Require().Equal(sdkmath.NewInt(tc.mint-tc.burn).Int64(), cosmosBalance.Amount.Int64())
+ suite.Require().Equal(big.NewInt(tc.burn).Int64(), balance.(*big.Int).Int64())
}
} else {
suite.Require().Error(err)
@@ -284,123 +202,6 @@ func (suite *KeeperTestSuite) TestConvertERC20NativeCoin() {
},
expPass: false,
},
- {
- name: "fail - force evm fail",
- mint: 100,
- burn: 10,
- reconvert: 5,
- malleate: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("forced ApplyMessage error"))
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- },
- {
- name: "fail - force fail second balance",
- mint: 100,
- burn: 10,
- reconvert: 5,
- malleate: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- // first balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- // convert coin
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil).Once()
- // second balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, fmt.Errorf("third")).Once()
- // Extra call on test
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- },
- {
- name: "fail - force fail second balance",
- mint: 100,
- burn: 10,
- reconvert: 5,
- malleate: func() {
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- // first balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- // convert coin
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil).Once()
- // second balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- // Extra call on test
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- },
- {
- name: "fail - force fail unescrow",
- mint: 100,
- burn: 10,
- reconvert: 5,
- malleate: func() {
- mockBankKeeper := &MockBankKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- mockBankKeeper, suite.app.EvmKeeper,
- )
-
- mockBankKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(nil)
- mockBankKeeper.On("SendCoinsFromModuleToAccount", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("failed to unescrow"))
- mockBankKeeper.On("BlockedAddr", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(false)
- mockBankKeeper.On("GetBalance", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(sdk.Coin{Denom: "coin", Amount: sdkmath.OneInt()})
- },
- expPass: false,
- },
- {
- name: "fail - force fail balance after transfer",
- mint: 100,
- burn: 10,
- reconvert: 5,
- malleate: func() {
- mockBankKeeper := &MockBankKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- mockBankKeeper, suite.app.EvmKeeper,
- )
-
- mockBankKeeper.On("SendCoinsFromModuleToAccount", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
- mockBankKeeper.On("BlockedAddr", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(false)
- mockBankKeeper.On("GetBalance", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(sdk.Coin{Denom: "acoin", Amount: sdkmath.OneInt()})
- },
- expPass: false,
- },
}
for _, tc := range testCases { //nolint:dupl
suite.Run(tc.name, func() {
@@ -515,7 +316,7 @@ func (suite *KeeperTestSuite) TestConvertERC20NativeERC20() {
stateDB := suite.StateDB()
ok := stateDB.Suicide(erc20)
suite.Require().True(ok)
- suite.Require().NoError(stateDB.Commit())
+ suite.Require().NoError(stateDB.CommitMultiStore(false))
},
extra: func() {},
contractType: contractMinterBurner,
@@ -590,171 +391,6 @@ func (suite *KeeperTestSuite) TestConvertERC20NativeERC20() {
expPass: false,
selfdestructed: false,
},
- {
- name: "fail - force evm fail",
- mint: 100,
- transfer: 10,
- malleate: func(common.Address) {},
- extra: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("forced ApplyMessage error"))
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- contractType: contractMinterBurner,
- expPass: false,
- selfdestructed: false,
- },
- {
- name: "fail - force get balance fail",
- mint: 100,
- transfer: 10,
- malleate: func(common.Address) {},
- extra: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- balance[31] = uint8(1)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Twice()
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("forced balance error"))
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- contractType: contractMinterBurner,
- expPass: false,
- selfdestructed: false,
- },
- {
- name: "fail - force transfer unpack fail",
- mint: 100,
- transfer: 10,
- malleate: func(common.Address) {},
- extra: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- contractType: contractMinterBurner,
- expPass: false,
- selfdestructed: false,
- },
- {
- name: "fail - force invalid transfer fail",
- mint: 100,
- transfer: 10,
- malleate: func(common.Address) {},
- extra: func() {
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- contractType: contractMinterBurner,
- expPass: false,
- selfdestructed: false,
- },
- {
- name: "fail - force mint fail",
- mint: 100,
- transfer: 10,
- malleate: func(common.Address) {},
- extra: func() {
- mockBankKeeper := &MockBankKeeper{}
-
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- mockBankKeeper, suite.app.EvmKeeper,
- )
-
- mockBankKeeper.On("MintCoins", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("failed to mint"))
- mockBankKeeper.On("SendCoinsFromModuleToAccount", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("failed to unescrow"))
- mockBankKeeper.On("BlockedAddr", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(false)
- mockBankKeeper.On("GetBalance", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(sdk.Coin{Denom: "coin", Amount: sdkmath.OneInt()})
- },
- contractType: contractMinterBurner,
- expPass: false,
- selfdestructed: false,
- },
- {
- name: "fail - force send minted fail",
- mint: 100,
- transfer: 10,
- malleate: func(common.Address) {},
- extra: func() {
- mockBankKeeper := &MockBankKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- mockBankKeeper, suite.app.EvmKeeper,
- )
-
- mockBankKeeper.On("MintCoins", mock.Anything, mock.Anything, mock.Anything).Return(nil)
- mockBankKeeper.On("SendCoinsFromModuleToAccount", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("failed to unescrow"))
- mockBankKeeper.On("BlockedAddr", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(false)
- mockBankKeeper.On("GetBalance", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(sdk.Coin{Denom: "coin", Amount: sdkmath.OneInt()})
- },
- contractType: contractMinterBurner,
- expPass: false,
- selfdestructed: false,
- },
- {
- name: "fail - force bank balance fail",
- mint: 100,
- transfer: 10,
- malleate: func(common.Address) {},
- extra: func() {
- mockBankKeeper := &MockBankKeeper{}
-
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- mockBankKeeper, suite.app.EvmKeeper,
- )
-
- mockBankKeeper.On("MintCoins", mock.Anything, mock.Anything, mock.Anything).Return(nil)
- mockBankKeeper.On("SendCoinsFromModuleToAccount", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
- mockBankKeeper.On("BlockedAddr", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(false)
- mockBankKeeper.On("GetBalance", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(sdk.Coin{Denom: coinName, Amount: sdkmath.NewInt(int64(10))})
- },
- contractType: contractMinterBurner,
- expPass: false,
- selfdestructed: false,
- },
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
@@ -792,21 +428,19 @@ func (suite *KeeperTestSuite) TestConvertERC20NativeERC20() {
if tc.expPass {
suite.Require().NoError(err, tc.name)
- acc := suite.app.EvmKeeper.GetAccountWithoutBalance(suite.ctx, contractAddr)
+ isEmptyAcc := suite.app.EvmKeeper.IsEmptyAccount(suite.ctx, contractAddr)
if tc.selfdestructed {
- suite.Require().Nil(acc, "expected contract to be destroyed")
- } else {
- suite.Require().NotNil(acc)
- }
+ suite.Require().True(isEmptyAcc, "expected contract to be destroyed")
- if tc.selfdestructed || !acc.IsContract() {
id := suite.app.Erc20Keeper.GetTokenPairID(suite.ctx, contractAddr.String())
_, found := suite.app.Erc20Keeper.GetTokenPair(suite.ctx, id)
suite.Require().False(found)
} else {
+ suite.Require().False(isEmptyAcc)
+
suite.Require().Equal(&erc20types.MsgConvertERC20Response{}, res)
- suite.Require().Equal(cosmosBalance.Amount, sdkmath.NewInt(tc.transfer))
- suite.Require().Equal(balance.(*big.Int).Int64(), big.NewInt(tc.mint-tc.transfer).Int64())
+ suite.Require().Equal(sdkmath.NewInt(tc.transfer), cosmosBalance.Amount)
+ suite.Require().Equal(big.NewInt(tc.mint-tc.transfer).Int64(), balance.(*big.Int).Int64())
}
} else {
suite.Require().Error(err, tc.name)
@@ -885,98 +519,6 @@ func (suite *KeeperTestSuite) TestConvertCoinNativeERC20() {
contractType: contractMinterBurner,
expPass: false,
},
- {
- name: "fail - force evm fail",
- mint: 100,
- convert: 10,
- malleate: func(common.Address) {},
- extra: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("forced ApplyMessage error"))
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- contractType: contractMinterBurner,
- expPass: false,
- },
- {
- name: "fail - force invalid transfer",
- mint: 100,
- convert: 10,
- malleate: func(common.Address) {},
- extra: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- contractType: contractMinterBurner,
- expPass: false,
- },
- {
- name: "fail - force fail second balance",
- mint: 100,
- convert: 10,
- malleate: func(common.Address) {},
- extra: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- balance[31] = uint8(1)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Twice()
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("fail second balance"))
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- contractType: contractMinterBurner,
- expPass: false,
- },
- {
- name: "fail - force fail transfer",
- mint: 100,
- convert: 10,
- malleate: func(common.Address) {},
- extra: func() {
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- contractType: contractMinterBurner,
- expPass: false,
- },
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
@@ -1132,7 +674,7 @@ func (suite *KeeperTestSuite) TestConvertCoinNativeIBCVoucher() {
stateDB := suite.StateDB()
ok := stateDB.Suicide(erc20)
suite.Require().True(ok)
- suite.Require().NoError(stateDB.Commit())
+ suite.Require().NoError(stateDB.CommitMultiStore(false))
},
extra: func() {},
expPass: true,
@@ -1172,80 +714,6 @@ func (suite *KeeperTestSuite) TestConvertCoinNativeIBCVoucher() {
expPass: false,
selfdestructed: false,
},
- {
- name: "fail - force evm fail",
- mint: 100,
- burn: 10,
- malleate: func(common.Address) {},
- extra: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("forced ApplyMessage error"))
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- selfdestructed: false,
- },
- {
- name: "fail - force evm balance error",
- mint: 100,
- burn: 10,
- malleate: func(common.Address) {},
- extra: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- // first balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- // convert coin
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil).Once()
- // second balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, fmt.Errorf("third")).Once()
- // Extra call on test
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- selfdestructed: false,
- },
- {
- name: "fail - force balance error",
- mint: 100,
- burn: 10,
- malleate: func(common.Address) {},
- extra: func() {
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Times(4)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- selfdestructed: false,
- },
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
@@ -1265,8 +733,10 @@ func (suite *KeeperTestSuite) TestConvertCoinNativeIBCVoucher() {
sender,
)
- suite.app.BankKeeper.MintCoins(suite.ctx, erc20types.ModuleName, coins) //nolint:errcheck
- suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, erc20types.ModuleName, sender, coins)
+ err := suite.app.BankKeeper.MintCoins(suite.ctx, erc20types.ModuleName, coins)
+ suite.Require().NoError(err)
+ err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, erc20types.ModuleName, sender, coins)
+ suite.Require().NoError(err)
tc.extra()
res, err := suite.app.Erc20Keeper.ConvertCoin(suite.ctx, msg)
@@ -1278,21 +748,19 @@ func (suite *KeeperTestSuite) TestConvertCoinNativeIBCVoucher() {
if tc.expPass {
suite.Require().NoError(err, tc.name)
- acc := suite.app.EvmKeeper.GetAccountWithoutBalance(suite.ctx, erc20)
+ isEmptyAcc := suite.app.EvmKeeper.IsEmptyAccount(suite.ctx, erc20)
if tc.selfdestructed {
- suite.Require().Nil(acc, "expected contract to be destroyed")
- } else {
- suite.Require().NotNil(acc)
- }
+ suite.Require().True(isEmptyAcc, "expected contract to be destroyed")
- if tc.selfdestructed || !acc.IsContract() {
id := suite.app.Erc20Keeper.GetTokenPairID(suite.ctx, erc20.String())
_, found := suite.app.Erc20Keeper.GetTokenPair(suite.ctx, id)
suite.Require().False(found)
} else {
+ suite.Require().False(isEmptyAcc)
+
suite.Require().Equal(expRes, res)
- suite.Require().Equal(cosmosBalance.Amount.Int64(), sdkmath.NewInt(tc.mint-tc.burn).Int64())
- suite.Require().Equal(balance.(*big.Int).Int64(), big.NewInt(tc.burn).Int64())
+ suite.Require().Equal(sdkmath.NewInt(tc.mint-tc.burn).Int64(), cosmosBalance.Amount.Int64())
+ suite.Require().Equal(big.NewInt(tc.burn).Int64(), balance.(*big.Int).Int64())
}
} else {
suite.Require().Error(err, tc.name)
@@ -1354,124 +822,6 @@ func (suite *KeeperTestSuite) TestConvertERC20NativeIBCVoucher() {
},
expPass: false,
},
- {
- name: "fail - force evm fail",
- mint: 100,
- burn: 10,
- reconvert: 5,
- malleate: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("forced ApplyMessage error"))
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- },
- {
- name: "fail - force fail second balance",
- mint: 100,
- burn: 10,
- reconvert: 5,
- malleate: func() { //nolint:dupl
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- // first balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- // convert coin
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil).Once()
- // second balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, fmt.Errorf("third")).Once()
- // Extra call on test
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- },
- {
- name: "fail - force fail second balance",
- mint: 100,
- burn: 10,
- reconvert: 5,
- malleate: func() {
- mockEVMKeeper := &MockEVMKeeper{}
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- suite.app.BankKeeper, mockEVMKeeper,
- )
-
- existingAcc := &statedb.Account{Nonce: uint64(1), Balance: common.Big1}
- balance := make([]uint8, 32)
- mockEVMKeeper.On("EstimateGas", mock.Anything, mock.Anything).Return(&evmtypes.EstimateGasResponse{Gas: uint64(200)}, nil)
- // first balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- // convert coin
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil).Once()
- // second balance of
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{Ret: balance}, nil).Once()
- // Extra call on test
- mockEVMKeeper.On("ApplyMessage", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&evmtypes.MsgEthereumTxResponse{}, nil)
- mockEVMKeeper.On("GetAccountWithoutBalance", mock.Anything, mock.Anything).Return(existingAcc, nil)
- },
- expPass: false,
- },
- {
- name: "fail - force fail unescrow",
- mint: 100,
- burn: 10,
- reconvert: 5,
- malleate: func() {
- mockBankKeeper := &MockBankKeeper{}
-
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- mockBankKeeper, suite.app.EvmKeeper,
- )
-
- mockBankKeeper.On("SendCoinsFromModuleToAccount", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("failed to unescrow"))
- mockBankKeeper.On("BlockedAddr", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(false)
- mockBankKeeper.On("GetBalance", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(sdk.Coin{Denom: "coin", Amount: sdkmath.OneInt()})
- },
- expPass: false,
- },
- {
- name: "fail - force fail balance after transfer",
- mint: 100,
- burn: 10,
- reconvert: 5,
- malleate: func() {
- mockBankKeeper := &MockBankKeeper{}
-
- suite.app.Erc20Keeper = erc20keeper.NewKeeper(
- suite.app.GetKey("erc20"), suite.app.AppCodec(),
- authtypes.NewModuleAddress(govtypes.ModuleName), suite.app.AccountKeeper,
- mockBankKeeper, suite.app.EvmKeeper,
- )
-
- mockBankKeeper.On("SendCoinsFromModuleToAccount", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
- mockBankKeeper.On("BlockedAddr", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(false)
- mockBankKeeper.On("GetBalance", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(sdk.Coin{Denom: ibcBase, Amount: sdkmath.OneInt()})
- },
- expPass: false,
- },
}
for _, tc := range testCases { //nolint:dupl
suite.Run(tc.name, func() {
diff --git a/x/erc20/keeper/utils_test.go b/x/erc20/keeper/utils_test.go
index d153ae2d27..b659c34790 100644
--- a/x/erc20/keeper/utils_test.go
+++ b/x/erc20/keeper/utils_test.go
@@ -3,6 +3,7 @@ package keeper_test
import (
"bytes"
"encoding/json"
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
"math/big"
"strconv"
"time"
@@ -21,7 +22,6 @@ import (
utiltx "github.com/EscanBE/evermint/v12/testutil/tx"
teststypes "github.com/EscanBE/evermint/v12/types/tests"
erc20types "github.com/EscanBE/evermint/v12/x/erc20/types"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types"
"github.com/cosmos/cosmos-sdk/baseapp"
@@ -242,8 +242,8 @@ func (suite *KeeperTestSuite) SetupIBCTest() {
var timeoutHeight = clienttypes.NewHeight(1000, 1000)
-func (suite *KeeperTestSuite) StateDB() *statedb.StateDB {
- return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash())))
+func (suite *KeeperTestSuite) StateDB() evmvm.CStateDB {
+ return evmvm.NewStateDB(suite.ctx, suite.app.EvmKeeper, suite.app.AccountKeeper, suite.app.BankKeeper)
}
func (suite *KeeperTestSuite) MintFeeCollector(coins sdk.Coins) {
diff --git a/x/erc20/types/interfaces.go b/x/erc20/types/interfaces.go
index ac44afe710..96772425f6 100644
--- a/x/erc20/types/interfaces.go
+++ b/x/erc20/types/interfaces.go
@@ -8,9 +8,8 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
)
@@ -24,9 +23,9 @@ type AccountKeeper interface {
// EVMKeeper defines the expected EVM keeper interface used on erc20
type EVMKeeper interface {
GetParams(ctx sdk.Context) evmtypes.Params
- GetAccountWithoutBalance(ctx sdk.Context, addr common.Address) *statedb.Account
+ IsEmptyAccount(ctx sdk.Context, addr common.Address) bool
EstimateGas(c context.Context, req *evmtypes.EthCallRequest) (*evmtypes.EstimateGasResponse, error)
- ApplyMessage(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool) (*evmtypes.MsgEthereumTxResponse, error)
+ ApplyMessage(ctx sdk.Context, msg core.Message, tracer corevm.EVMLogger, commit bool) (*evmtypes.MsgEthereumTxResponse, error)
SetFlagEnableNoBaseFee(ctx sdk.Context, enable bool)
IsNoBaseFeeEnabled(ctx sdk.Context) bool
}
diff --git a/x/evm/genesis_test.go b/x/evm/genesis_test.go
index 434a58539a..8660a397b4 100644
--- a/x/evm/genesis_test.go
+++ b/x/evm/genesis_test.go
@@ -1,6 +1,7 @@
package evm_test
import (
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
"math/big"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
@@ -9,7 +10,6 @@ import (
"github.com/EscanBE/evermint/v12/crypto/ethsecp256k1"
"github.com/EscanBE/evermint/v12/x/evm"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)
@@ -20,7 +20,7 @@ func (suite *EvmTestSuite) TestInitGenesis() {
address := common.HexToAddress(privkey.PubKey().Address().String())
- var vmdb *statedb.StateDB
+ var vmdb evmvm.CStateDB
testCases := []struct {
name string
@@ -148,7 +148,7 @@ func (suite *EvmTestSuite) TestInitGenesis() {
vmdb = suite.StateDB()
tc.malleate()
- err := vmdb.Commit()
+ err := vmdb.CommitMultiStore(false)
suite.Require().NoError(err)
if tc.expPanic {
diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go
index 9a20a12721..3db09b35a1 100644
--- a/x/evm/handler_test.go
+++ b/x/evm/handler_test.go
@@ -1,6 +1,7 @@
package evm_test
import (
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
"math/big"
"testing"
"time"
@@ -13,8 +14,6 @@ import (
"github.com/EscanBE/evermint/v12/app/helpers"
"github.com/EscanBE/evermint/v12/constants"
- evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper"
-
sdkmath "cosmossdk.io/math"
abci "github.com/cometbft/cometbft/abci/types"
tmjson "github.com/cometbft/cometbft/libs/json"
@@ -33,7 +32,6 @@ import (
chainapp "github.com/EscanBE/evermint/v12/app"
"github.com/EscanBE/evermint/v12/crypto/ethsecp256k1"
utiltx "github.com/EscanBE/evermint/v12/testutil/tx"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
"github.com/cometbft/cometbft/crypto/tmhash"
@@ -152,8 +150,8 @@ func (suite *EvmTestSuite) SignTx(tx *evmtypes.MsgEthereumTx) {
suite.Require().NoError(err)
}
-func (suite *EvmTestSuite) StateDB() *statedb.StateDB {
- return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash())))
+func (suite *EvmTestSuite) StateDB() evmvm.CStateDB {
+ return evmvm.NewStateDB(suite.ctx, suite.app.EvmKeeper, suite.app.AccountKeeper, suite.app.BankKeeper)
}
func TestEvmTestSuite(t *testing.T) {
@@ -590,20 +588,19 @@ func (suite *EvmTestSuite) deployERC20Contract() common.Address {
return crypto.CreateAddress(suite.from, nonce)
}
-// TestERC20TransferReverted checks:
-// - when transaction reverted, gas refund works.
-// - when transaction reverted, nonce is still increased.
func (suite *EvmTestSuite) TestERC20TransferReverted() {
intrinsicGas := uint64(21572)
testCases := []struct {
- name string
- gasLimit uint64
- expErr string
+ name string
+ gasLimit uint64
+ expErr string
+ wantGasConsumed uint64
}{
{
- name: "default",
- gasLimit: intrinsicGas, // enough for intrinsicGas, but not enough for execution
- expErr: "out of gas",
+ name: "default",
+ gasLimit: intrinsicGas, // enough for intrinsicGas, but not enough for execution
+ expErr: "out of gas",
+ wantGasConsumed: intrinsicGas,
},
}
@@ -612,10 +609,6 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() {
suite.SetupTest()
k := suite.app.EvmKeeper
- // add some fund to pay gas fee
- err := k.SetBalance(suite.ctx, suite.from, big.NewInt(1000000000000001))
- suite.Require().NoError(err)
-
suite.zeroFeeMarket()
contract := suite.deployERC20Contract()
@@ -638,17 +631,6 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() {
tx := evmtypes.NewTx(ethTxParams)
suite.SignTx(tx)
- before := k.GetBalance(suite.ctx, suite.from)
-
- baseFee := suite.app.EvmKeeper.GetBaseFee(suite.ctx)
-
- ethTx := tx.AsTransaction()
-
- fees, err := evmkeeper.VerifyFee(ethTx, constants.BaseDenom, baseFee, suite.ctx.IsCheckTx())
- suite.Require().NoError(err)
- err = k.DeductTxCostsFromUserBalance(suite.ctx, fees, sdk.MustAccAddressFromBech32(tx.From))
- suite.Require().NoError(err)
-
res, err := k.EthereumTx(suite.ctx, tx)
suite.Require().NoError(err)
@@ -662,16 +644,7 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() {
suite.Require().Empty(receipt.Logs)
suite.Require().Equal(evmtypes.EmptyBlockBloom, receipt.Bloom)
- after := k.GetBalance(suite.ctx, suite.from)
-
- if tc.expErr == "out of gas" {
- suite.Require().Equal(tc.gasLimit, res.GasUsed)
- } else {
- suite.Require().Greater(tc.gasLimit, res.GasUsed)
- }
-
- // tx fee should be 100% consumed
- suite.Require().Equal(new(big.Int).Sub(before, fees[0].Amount.BigInt()), after)
+ suite.Require().Equal(tc.gasLimit, res.GasUsed)
// nonce should be increased.
nonceLater := k.GetNonce(suite.ctx, suite.from)
@@ -715,7 +688,7 @@ func (suite *EvmTestSuite) TestContractDeploymentRevert() {
// simulate nonce increment and flag set in ante handler
db := suite.StateDB()
db.SetNonce(suite.from, nonce+1)
- suite.Require().NoError(db.Commit())
+ suite.Require().NoError(db.CommitMultiStore(false))
suite.app.EvmKeeper.SetFlagSenderNonceIncreasedByAnteHandle(suite.ctx, true)
rsp, err := k.EthereumTx(suite.ctx, tx)
diff --git a/x/evm/keeper/config.go b/x/evm/keeper/config.go
index 2af12b2e49..3f1a885b96 100644
--- a/x/evm/keeper/config.go
+++ b/x/evm/keeper/config.go
@@ -4,15 +4,17 @@ import (
"math/big"
errorsmod "cosmossdk.io/errors"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
- evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
+
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/core"
+ ethtypes "github.com/ethereum/go-ethereum/core/types"
+
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
)
// EVMConfig creates the EVMConfig based on current state
-func (k *Keeper) EVMConfig(ctx sdk.Context, proposerAddress sdk.ConsAddress, chainID *big.Int) (*statedb.EVMConfig, error) {
+func (k *Keeper) EVMConfig(ctx sdk.Context, proposerAddress sdk.ConsAddress, chainID *big.Int) (*evmvm.EVMConfig, error) {
params := k.GetParams(ctx)
ethCfg := params.ChainConfig.EthereumConfig(chainID)
@@ -23,7 +25,7 @@ func (k *Keeper) EVMConfig(ctx sdk.Context, proposerAddress sdk.ConsAddress, cha
}
baseFee := k.GetBaseFee(ctx)
- return &statedb.EVMConfig{
+ return &evmvm.EVMConfig{
Params: params,
ChainConfig: ethCfg,
CoinBase: coinbase,
@@ -32,30 +34,39 @@ func (k *Keeper) EVMConfig(ctx sdk.Context, proposerAddress sdk.ConsAddress, cha
}, nil
}
-// TxConfig loads `TxConfig` from current transient storage
-func (k *Keeper) TxConfig(ctx sdk.Context, txHash common.Hash) statedb.TxConfig {
- return statedb.NewTxConfig(
- common.BytesToHash(ctx.HeaderHash()), // BlockHash
- txHash, // TxHash
- uint(k.GetTxCountTransient(ctx)-1), // TxIndex
- uint(k.GetCumulativeLogCountTransient(ctx, true)), // LogIndex
- )
+// NewTxConfig loads `TxConfig` from current transient storage.
+// Note: if tx is nil, the tx hash and tx type is not set in the TxConfig.
+func (k *Keeper) NewTxConfig(ctx sdk.Context, tx *ethtypes.Transaction) evmvm.TxConfig {
+ txConfig := evmvm.TxConfig{
+ BlockHash: common.BytesToHash(ctx.HeaderHash()),
+ TxHash: common.Hash{},
+ TxIndex: uint(k.GetTxCountTransient(ctx) - 1),
+ LogIndex: uint(k.GetCumulativeLogCountTransient(ctx, true)),
+ TxType: nil,
+ }
+
+ if tx != nil {
+ txConfig.TxHash = tx.Hash()
+ txConfig = txConfig.WithTxTypeFromTransaction(tx)
+ }
+
+ return txConfig
}
-// VMConfig creates an EVM configuration from the debug setting and the extra EIPs enabled on the
-// module parameters. The config generated uses the default JumpTable from the EVM.
-func (k Keeper) VMConfig(ctx sdk.Context, cfg *statedb.EVMConfig, tracer vm.EVMLogger) vm.Config {
- var debug bool
- if tracer != nil {
- if _, ok := tracer.(evmtypes.NoOpTracer); !ok {
- debug = true
- }
+// NewTxConfigFromMessage loads `TxConfig` from current transient storage, based on the input core message.
+// Note: since the message does not contain the tx hash, it is not set in the TxConfig.
+func (k *Keeper) NewTxConfigFromMessage(ctx sdk.Context, msg core.Message) evmvm.TxConfig {
+ txConfig := evmvm.TxConfig{
+ BlockHash: common.BytesToHash(ctx.HeaderHash()),
+ TxHash: common.Hash{},
+ TxIndex: uint(k.GetTxCountTransient(ctx) - 1),
+ LogIndex: uint(k.GetCumulativeLogCountTransient(ctx, true)),
+ TxType: nil,
}
- return vm.Config{
- Debug: debug,
- Tracer: tracer,
- NoBaseFee: cfg.NoBaseFee || k.IsNoBaseFeeEnabled(ctx),
- ExtraEips: cfg.Params.EIPs(),
+ if msg != nil {
+ txConfig = txConfig.WithTxTypeFromMessage(msg)
}
+
+ return txConfig
}
diff --git a/x/evm/keeper/fees.go b/x/evm/keeper/fees.go
deleted file mode 100644
index f4f6d1ee16..0000000000
--- a/x/evm/keeper/fees.go
+++ /dev/null
@@ -1,117 +0,0 @@
-package keeper
-
-import (
- "math/big"
-
- evmutils "github.com/EscanBE/evermint/v12/x/evm/utils"
- "github.com/ethereum/go-ethereum/core"
- ethtypes "github.com/ethereum/go-ethereum/core/types"
-
- errorsmod "cosmossdk.io/errors"
- sdkmath "cosmossdk.io/math"
- sdk "github.com/cosmos/cosmos-sdk/types"
- errortypes "github.com/cosmos/cosmos-sdk/types/errors"
- authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
-)
-
-// CheckSenderBalance validates that the tx cost value is positive and that the
-// sender has enough funds to pay for the fees and value of the transaction.
-func CheckSenderBalance(
- balance sdkmath.Int,
- ethTx *ethtypes.Transaction,
-) error {
- cost := new(big.Int).Add(evmutils.EthTxFee(ethTx), ethTx.Value())
-
- if cost.Sign() < 0 {
- return errorsmod.Wrapf(
- errortypes.ErrInvalidCoins,
- "tx cost (%s) is negative and invalid", cost,
- )
- }
-
- if balance.IsNegative() || balance.BigInt().Cmp(cost) < 0 {
- return errorsmod.Wrapf(
- errortypes.ErrInsufficientFunds,
- "sender balance < tx cost (%s < %s)", balance, cost,
- )
- }
- return nil
-}
-
-// DeductTxCostsFromUserBalance deducts the fees from the user balance. Returns an
-// error if the specified sender address does not exist or the account balance is not sufficient.
-func (k *Keeper) DeductTxCostsFromUserBalance(
- ctx sdk.Context,
- fees sdk.Coins,
- from sdk.AccAddress,
-) error {
- // fetch sender account
- signerAcc, err := authante.GetSignerAcc(ctx, k.accountKeeper, from)
- if err != nil {
- return errorsmod.Wrapf(err, "account not found for sender %s", from)
- }
-
- // deduct the full gas cost from the user balance
- if err := authante.DeductFees(k.bankKeeper, ctx, signerAcc, fees); err != nil {
- return errorsmod.Wrapf(err, "failed to deduct full gas cost %s from the user %s balance", fees, from)
- }
-
- return nil
-}
-
-// VerifyFee is used to return the fee for the given transaction data in sdk.Coins.
-// It checks:
-// - Gas limit vs intrinsic gas
-// - Base fee vs gas fee cap
-//
-// TODO ES: remove?
-func VerifyFee(
- ethTx *ethtypes.Transaction,
- denom string,
- baseFee sdkmath.Int,
- isCheckTx bool,
-) (sdk.Coins, error) {
- isContractCreation := ethTx.To() == nil
-
- gasLimit := ethTx.Gas()
-
- var accessList ethtypes.AccessList
- if ethTx.AccessList() != nil {
- accessList = ethTx.AccessList()
- }
-
- // intrinsic gas verification during CheckTx
- if isCheckTx {
- const homestead = true
- const istanbul = true
- intrinsicGas, err := core.IntrinsicGas(ethTx.Data(), accessList, isContractCreation, homestead, istanbul)
- if err != nil {
- return nil, errorsmod.Wrapf(
- err,
- "failed to retrieve intrinsic gas, contract creation = %t", isContractCreation,
- )
- }
-
- if gasLimit < intrinsicGas {
- return nil, errorsmod.Wrapf(
- errortypes.ErrOutOfGas,
- "gas limit too low: %d (gas limit) < %d (intrinsic gas)", gasLimit, intrinsicGas,
- )
- }
- }
-
- if ethTx.GasFeeCap().Cmp(baseFee.BigInt()) < 0 {
- return nil, errorsmod.Wrapf(errortypes.ErrInsufficientFee,
- "the tx gasfeecap is lower than the tx baseFee: %s (gasfeecap), %s (basefee) ",
- ethTx.GasFeeCap(),
- baseFee)
- }
-
- feeAmt := evmutils.EthTxEffectiveFee(ethTx, baseFee)
- if feeAmt.Sign() == 0 {
- // zero fee, no need to deduct
- return sdk.Coins{}, nil
- }
-
- return sdk.Coins{{Denom: denom, Amount: sdkmath.NewIntFromBigInt(feeAmt)}}, nil
-}
diff --git a/x/evm/keeper/fees_test.go b/x/evm/keeper/fees_test.go
deleted file mode 100644
index 6ecdf97d29..0000000000
--- a/x/evm/keeper/fees_test.go
+++ /dev/null
@@ -1,568 +0,0 @@
-package keeper_test
-
-import (
- "fmt"
- "math/big"
-
- evmutils "github.com/EscanBE/evermint/v12/x/evm/utils"
-
- sdkmath "cosmossdk.io/math"
- evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper"
- evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
- sdk "github.com/cosmos/cosmos-sdk/types"
- "github.com/ethereum/go-ethereum/common"
- ethtypes "github.com/ethereum/go-ethereum/core/types"
- ethparams "github.com/ethereum/go-ethereum/params"
-)
-
-func (suite *KeeperTestSuite) TestCheckSenderBalance() {
- hundredInt := sdkmath.NewInt(100)
- zeroInt := sdkmath.ZeroInt()
- oneInt := sdkmath.OneInt()
- fiveInt := sdkmath.NewInt(5)
- fiftyInt := sdkmath.NewInt(50)
- negInt := sdkmath.NewInt(-10)
-
- testCases := []struct {
- name string
- to string
- gasLimit uint64
- gasPrice *sdkmath.Int
- gasFeeCap *big.Int
- gasTipCap *big.Int
- cost *sdkmath.Int
- from string
- accessList *ethtypes.AccessList
- expectPass bool
- expectPanic bool
- enableFeemarket bool
- }{
- {
- name: "Enough balance",
- to: suite.address.String(),
- gasLimit: 10,
- gasPrice: &oneInt,
- cost: &oneInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: true,
- },
- {
- name: "Equal balance",
- to: suite.address.String(),
- gasLimit: 99,
- gasPrice: &oneInt,
- cost: &oneInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: true,
- },
- {
- name: "negative cost",
- to: suite.address.String(),
- gasLimit: 1,
- gasPrice: &oneInt,
- cost: &negInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPanic: true,
- },
- {
- name: "Higher gas limit, not enough balance",
- to: suite.address.String(),
- gasLimit: 100,
- gasPrice: &oneInt,
- cost: &oneInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: false,
- },
- {
- name: "Higher gas price, enough balance",
- to: suite.address.String(),
- gasLimit: 10,
- gasPrice: &fiveInt,
- cost: &oneInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: true,
- },
- {
- name: "Higher gas price, not enough balance",
- to: suite.address.String(),
- gasLimit: 20,
- gasPrice: &fiveInt,
- cost: &oneInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: false,
- },
- {
- name: "Higher cost, enough balance",
- to: suite.address.String(),
- gasLimit: 10,
- gasPrice: &fiveInt,
- cost: &fiftyInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: true,
- },
- {
- name: "Higher cost, not enough balance",
- to: suite.address.String(),
- gasLimit: 10,
- gasPrice: &fiveInt,
- cost: &hundredInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: false,
- },
- {
- name: "Enough balance w/ enableFeemarket",
- to: suite.address.String(),
- gasLimit: 10,
- gasFeeCap: big.NewInt(1),
- cost: &oneInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: true,
- enableFeemarket: true,
- },
- {
- name: "Equal balance w/ enableFeemarket",
- to: suite.address.String(),
- gasLimit: 99,
- gasFeeCap: big.NewInt(1),
- cost: &oneInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: true,
- enableFeemarket: true,
- },
- {
- name: "negative cost w/ enableFeemarket",
- to: suite.address.String(),
- gasLimit: 1,
- gasFeeCap: big.NewInt(1),
- cost: &negInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPanic: true,
- enableFeemarket: true,
- },
- {
- name: "Higher gas limit, not enough balance w/ enableFeemarket",
- to: suite.address.String(),
- gasLimit: 100,
- gasFeeCap: big.NewInt(1),
- cost: &oneInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: false,
- enableFeemarket: true,
- },
- {
- name: "Higher gas price, enough balance w/ enableFeemarket",
- to: suite.address.String(),
- gasLimit: 10,
- gasFeeCap: big.NewInt(5),
- cost: &oneInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: true,
- enableFeemarket: true,
- },
- {
- name: "Higher gas price, not enough balance w/ enableFeemarket",
- to: suite.address.String(),
- gasLimit: 20,
- gasFeeCap: big.NewInt(5),
- cost: &oneInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: false,
- enableFeemarket: true,
- },
- {
- name: "Higher cost, enough balance w/ enableFeemarket",
- to: suite.address.String(),
- gasLimit: 10,
- gasFeeCap: big.NewInt(5),
- cost: &fiftyInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: true,
- enableFeemarket: true,
- },
- {
- name: "Higher cost, not enough balance w/ enableFeemarket",
- to: suite.address.String(),
- gasLimit: 10,
- gasFeeCap: big.NewInt(5),
- cost: &hundredInt,
- from: suite.address.String(),
- accessList: ðtypes.AccessList{},
- expectPass: false,
- enableFeemarket: true,
- },
- }
-
- vmdb := suite.StateDB()
- vmdb.AddBalance(suite.address, hundredInt.BigInt())
- balance := vmdb.GetBalance(suite.address)
- suite.Require().Equal(balance, hundredInt.BigInt())
- err := vmdb.Commit()
- suite.Require().NoError(err, "Unexpected error while committing to vmdb: %d", err)
-
- for _, tc := range testCases {
- suite.Run(tc.name, func() {
- to := common.HexToAddress(tc.from)
-
- var amount, gasPrice, gasFeeCap, gasTipCap *big.Int
- if tc.cost != nil {
- amount = tc.cost.BigInt()
- }
-
- if tc.enableFeemarket {
- gasFeeCap = tc.gasFeeCap
- if tc.gasTipCap == nil {
- gasTipCap = oneInt.BigInt()
- } else {
- gasTipCap = tc.gasTipCap
- }
- } else if tc.gasPrice != nil {
- gasPrice = tc.gasPrice.BigInt()
- }
-
- ethTxParams := &evmtypes.EvmTxArgs{
- From: common.HexToAddress(tc.from),
- ChainID: zeroInt.BigInt(),
- Nonce: 1,
- To: &to,
- Amount: amount,
- GasLimit: tc.gasLimit,
- GasPrice: gasPrice,
- GasFeeCap: gasFeeCap,
- GasTipCap: gasTipCap,
- Accesses: tc.accessList,
- }
-
- if tc.expectPanic {
- suite.Require().Panics(func() {
- _ = evmtypes.NewTx(ethTxParams)
- })
- return
- }
-
- tx := evmtypes.NewTx(ethTxParams)
- ethTx := tx.AsTransaction()
-
- acct := suite.app.EvmKeeper.GetAccountOrEmpty(suite.ctx, suite.address)
- err := evmkeeper.CheckSenderBalance(
- sdkmath.NewIntFromBigInt(acct.Balance),
- ethTx,
- )
-
- if tc.expectPass {
- suite.Require().NoError(err)
- } else {
- suite.Require().Error(err)
- }
- })
- }
-}
-
-// TestVerifyFeeAndDeductTxCostsFromUserBalance is a test method for both the VerifyFee
-// function and the DeductTxCostsFromUserBalance method.
-//
-// NOTE: This method combines testing for both functions, because these used to be
-// in one function and share a lot of the same setup.
-// In practice, the two tested functions will also be sequentially executed.
-func (suite *KeeperTestSuite) TestVerifyFeeAndDeductTxCostsFromUserBalance() {
- hundredInt := sdkmath.NewInt(100)
- zeroInt := sdkmath.ZeroInt()
- oneInt := sdkmath.NewInt(1)
- fiveInt := sdkmath.NewInt(5)
- fiftyInt := sdkmath.NewInt(50)
-
- // should be enough to cover all test cases
- initBalance := sdkmath.NewInt((ethparams.InitialBaseFee + 10) * 105)
-
- testCases := []struct {
- name string
- gasLimit uint64
- gasPrice *sdkmath.Int
- gasFeeCap *big.Int
- gasTipCap *big.Int
- cost *sdkmath.Int
- accessList *ethtypes.AccessList
- expectPassVerify bool
- expectPassDeduct bool
- enableFeemarket bool
- from string
- malleate func()
- }{
- {
- name: "Enough balance",
- gasLimit: 10,
- gasPrice: &oneInt,
- cost: &oneInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: true,
- from: suite.address.String(),
- },
- {
- name: "zero fee without fee market enable",
- gasLimit: 10,
- gasPrice: &zeroInt,
- cost: &zeroInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: true,
- enableFeemarket: false,
- from: suite.address.String(),
- },
- {
- name: "Equal balance",
- gasLimit: 100,
- gasPrice: &oneInt,
- cost: &oneInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: true,
- from: suite.address.String(),
- },
- {
- name: "Higher gas limit, not enough balance",
- gasLimit: 105,
- gasPrice: &oneInt,
- cost: &oneInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: false,
- from: suite.address.String(),
- },
- {
- name: "Higher gas price, enough balance",
- gasLimit: 20,
- gasPrice: &fiveInt,
- cost: &oneInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: true,
- from: suite.address.String(),
- },
- {
- name: "Higher gas price, not enough balance",
- gasLimit: 20,
- gasPrice: &fiftyInt,
- cost: &oneInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: false,
- from: suite.address.String(),
- },
- // This case is expected to be true because the fees can be deducted, but the tx
- // execution is going to fail because there is no more balance to pay the cost
- {
- name: "Higher cost, enough balance",
- gasLimit: 100,
- gasPrice: &oneInt,
- cost: &fiftyInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: true,
- from: suite.address.String(),
- },
- // testcases with enableFeemarket enabled.
- {
- name: "Invalid gasFeeCap w/ enableFeemarket",
- gasLimit: 10,
- gasFeeCap: big.NewInt(1),
- gasTipCap: big.NewInt(1),
- cost: &oneInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: false,
- expectPassDeduct: true,
- enableFeemarket: true,
- from: suite.address.String(),
- },
- {
- name: "empty tip fee is valid to deduct",
- gasLimit: 10,
- gasFeeCap: big.NewInt(ethparams.InitialBaseFee),
- gasTipCap: big.NewInt(1),
- cost: &oneInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: true,
- enableFeemarket: true,
- from: suite.address.String(),
- },
- {
- name: "zero fee with fee market enabled",
- gasLimit: 10,
- gasFeeCap: big.NewInt(ethparams.InitialBaseFee),
- gasPrice: &zeroInt,
- cost: &zeroInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: true,
- enableFeemarket: true,
- from: suite.address.String(),
- },
- {
- name: "effectiveTip equal to gasTipCap",
- gasLimit: 100,
- gasFeeCap: big.NewInt(ethparams.InitialBaseFee + 2),
- cost: &oneInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: true,
- enableFeemarket: true,
- from: suite.address.String(),
- },
- {
- name: "effectiveTip equal to (gasFeeCap - baseFee)",
- gasLimit: 105,
- gasFeeCap: big.NewInt(ethparams.InitialBaseFee + 1),
- gasTipCap: big.NewInt(2),
- cost: &oneInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: true,
- enableFeemarket: true,
- from: suite.address.String(),
- },
- {
- name: "Invalid from address",
- gasLimit: 10,
- gasPrice: &oneInt,
- cost: &oneInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: true,
- expectPassDeduct: false,
- from: "abcdef",
- },
- {
- name: "Enough balance - with access list",
- gasLimit: 10,
- gasPrice: &oneInt,
- cost: &oneInt,
- accessList: ðtypes.AccessList{
- ethtypes.AccessTuple{
- Address: suite.address,
- StorageKeys: []common.Hash{},
- },
- },
- expectPassVerify: true,
- expectPassDeduct: true,
- from: suite.address.String(),
- },
- {
- name: "gasLimit < intrinsicGas during IsCheckTx",
- gasLimit: 1,
- gasPrice: &oneInt,
- cost: &oneInt,
- accessList: ðtypes.AccessList{},
- expectPassVerify: false,
- expectPassDeduct: true,
- from: suite.address.String(),
- malleate: func() {
- suite.ctx = suite.ctx.WithIsCheckTx(true)
- },
- },
- }
-
- for _, tc := range testCases {
- suite.Run(tc.name, func() {
- suite.enableFeemarket = tc.enableFeemarket
- suite.SetupTest()
- vmdb := suite.StateDB()
-
- if tc.malleate != nil {
- tc.malleate()
- }
- var amount, gasPrice, gasFeeCap, gasTipCap *big.Int
- if tc.cost != nil {
- amount = tc.cost.BigInt()
- }
-
- if suite.enableFeemarket {
- if tc.gasFeeCap != nil {
- gasFeeCap = tc.gasFeeCap
- }
- if tc.gasTipCap == nil {
- gasTipCap = oneInt.BigInt()
- } else {
- gasTipCap = tc.gasTipCap
- }
- vmdb.AddBalance(suite.address, initBalance.BigInt())
- balance := vmdb.GetBalance(suite.address)
- suite.Require().Equal(balance, initBalance.BigInt())
- } else {
- if tc.gasPrice != nil {
- gasPrice = tc.gasPrice.BigInt()
- }
-
- vmdb.AddBalance(suite.address, hundredInt.BigInt())
- balance := vmdb.GetBalance(suite.address)
- suite.Require().Equal(balance, hundredInt.BigInt())
- }
- err := vmdb.Commit()
- suite.Require().NoError(err)
-
- ethTxParams := &evmtypes.EvmTxArgs{
- From: common.HexToAddress(tc.from),
- ChainID: zeroInt.BigInt(),
- Nonce: 1,
- To: &suite.address,
- Amount: amount,
- GasLimit: tc.gasLimit,
- GasPrice: gasPrice,
- GasFeeCap: gasFeeCap,
- GasTipCap: gasTipCap,
- Accesses: tc.accessList,
- }
- tx := evmtypes.NewTx(ethTxParams)
- ethTx := tx.AsTransaction()
-
- baseFee := suite.app.EvmKeeper.GetBaseFee(suite.ctx)
- priority := evmutils.EthTxPriority(ethTx, baseFee)
- suite.Require().Equal(evmutils.EthTxEffectiveGasPrice(ethTx, baseFee).String(), fmt.Sprintf("%d", priority))
-
- fees, err := evmkeeper.VerifyFee(ethTx, evmtypes.DefaultEVMDenom, baseFee, suite.ctx.IsCheckTx())
- if tc.expectPassVerify {
- suite.Require().NoError(err)
- if tc.enableFeemarket {
- baseFee := suite.app.FeeMarketKeeper.GetBaseFee(suite.ctx)
- suite.Require().Equal(
- fees,
- sdk.NewCoins(
- sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewIntFromBigInt(evmutils.EthTxEffectiveFee(ethTx, baseFee))),
- ),
- )
- } else {
- suite.Require().Equal(
- fees,
- sdk.NewCoins(
- sdk.NewCoin(evmtypes.DefaultEVMDenom, tc.gasPrice.Mul(sdkmath.NewIntFromUint64(tc.gasLimit))),
- ),
- )
- }
- } else {
- suite.Require().Error(err)
- suite.Require().Nil(fees)
- }
-
- err = suite.app.EvmKeeper.DeductTxCostsFromUserBalance(suite.ctx, fees, sdk.MustAccAddressFromBech32(tx.From))
- if tc.expectPassDeduct {
- suite.Require().NoError(err)
- } else {
- suite.Require().Error(err)
- }
- })
- }
- suite.enableFeemarket = false // reset flag
-}
diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go
index 42561cf714..bccd40c3bb 100644
--- a/x/evm/keeper/grpc_query.go
+++ b/x/evm/keeper/grpc_query.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
"math/big"
"time"
@@ -25,11 +26,10 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
ethparams "github.com/ethereum/go-ethereum/params"
evertypes "github.com/EscanBE/evermint/v12/types"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
)
@@ -51,15 +51,18 @@ func (k Keeper) Account(c context.Context, req *evmtypes.QueryAccountRequest) (*
)
}
+ ctx := sdk.UnwrapSDKContext(c)
addr := common.HexToAddress(req.Address)
- ctx := sdk.UnwrapSDKContext(c)
- acct := k.GetAccountOrEmpty(ctx, addr)
+ var nonce uint64
+ if acc := k.accountKeeper.GetAccount(ctx, addr.Bytes()); acc != nil {
+ nonce = acc.GetSequence()
+ }
return &evmtypes.QueryAccountResponse{
- Balance: acct.Balance.String(),
- CodeHash: common.BytesToHash(acct.CodeHash).Hex(),
- Nonce: acct.Nonce,
+ Nonce: nonce,
+ Balance: k.GetBalance(ctx, addr).String(),
+ CodeHash: k.GetCodeHash(ctx, addr.Bytes()).Hex(),
}, nil
}
@@ -146,10 +149,10 @@ func (k Keeper) Balance(c context.Context, req *evmtypes.QueryBalanceRequest) (*
ctx := sdk.UnwrapSDKContext(c)
- balanceInt := k.GetBalance(ctx, common.HexToAddress(req.Address))
+ balance := k.GetBalance(ctx, common.HexToAddress(req.Address))
return &evmtypes.QueryBalanceResponse{
- Balance: balanceInt.String(),
+ Balance: balance.String(),
}, nil
}
@@ -195,11 +198,11 @@ func (k Keeper) Code(c context.Context, req *evmtypes.QueryCodeRequest) (*evmtyp
ctx := sdk.UnwrapSDKContext(c)
address := common.HexToAddress(req.Address)
- acct := k.GetAccountWithoutBalance(ctx, address)
+ codeHash := k.GetCodeHash(ctx, address.Bytes())
var code []byte
- if acct != nil && acct.IsContract() {
- code = k.GetCode(ctx, common.BytesToHash(acct.CodeHash))
+ if !evmtypes.IsEmptyCodeHash(codeHash) {
+ code = k.GetCode(ctx, codeHash)
}
return &evmtypes.QueryCodeResponse{
@@ -250,7 +253,7 @@ func (k Keeper) EthCall(c context.Context, req *evmtypes.EthCallRequest) (*evmty
return nil, status.Error(codes.InvalidArgument, err.Error())
}
- txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash())).WithTxTypeFromMessage(msg)
+ txConfig := k.NewTxConfigFromMessage(ctx, msg)
// pass false to not commit StateDB
res, err := k.ApplyMessageWithConfig(ctx, msg, nil, false, cfg, txConfig)
@@ -323,8 +326,6 @@ func (k Keeper) EstimateGas(c context.Context, req *evmtypes.EthCallRequest) (*e
nonce := k.GetNonce(ctx, args.GetFrom())
args.Nonce = (*hexutil.Uint64)(&nonce)
- txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))
-
// convert the tx args to an ethereum message
msg, err := args.ToMessage(req.GasCap, cfg.BaseFee)
if err != nil {
@@ -350,7 +351,8 @@ func (k Keeper) EstimateGas(c context.Context, req *evmtypes.EthCallRequest) (*e
msg.AccessList(),
msg.IsFake(),
)
- txConfig = txConfig.WithTxTypeFromMessage(msg)
+
+ txConfig := k.NewTxConfigFromMessage(ctx, msg)
// pass false to not commit StateDB
rsp, err = k.ApplyMessageWithConfig(ctx, msg, nil, false, cfg, txConfig)
@@ -377,8 +379,8 @@ func (k Keeper) EstimateGas(c context.Context, req *evmtypes.EthCallRequest) (*e
}
if failed {
- if result != nil && result.VmError != vm.ErrOutOfGas.Error() {
- if result.VmError == vm.ErrExecutionReverted.Error() {
+ if result != nil && result.VmError != corevm.ErrOutOfGas.Error() {
+ if result.VmError == corevm.ErrExecutionReverted.Error() {
return nil, evmtypes.NewExecErrorWithReason(result.Ret)
}
return nil, errors.New(result.VmError)
@@ -434,8 +436,7 @@ func (k Keeper) TraceTx(c context.Context, req *evmtypes.QueryTraceTxRequest) (*
cfg.BaseFee = k.feeMarketKeeper.GetBaseFee(ctx).BigInt()
- txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))
-
+ txConfig := k.NewTxConfig(ctx, nil)
for i, tx := range req.Predecessors {
ethTx := tx.AsTransaction()
msg, err := ethTx.AsMessage(signer, cfg.BaseFee)
@@ -444,7 +445,7 @@ func (k Keeper) TraceTx(c context.Context, req *evmtypes.QueryTraceTxRequest) (*
}
txConfig.TxHash = ethTx.Hash()
txConfig.TxIndex = uint(i)
- txConfig = txConfig.WithTxTypeFromMessage(msg)
+ txConfig = txConfig.WithTxTypeFromTransaction(ethTx)
// reset gas meter per transaction to avoid stacking the gas used of every predecessor in the same gas meter
ctx = ctx.WithGasMeter(evertypes.NewInfiniteGasMeterWithLimit(msg.Gas()))
@@ -538,12 +539,13 @@ func (k Keeper) TraceBlock(c context.Context, req *evmtypes.QueryTraceBlockReque
txsLength := len(req.Txs)
results := make([]*evmtypes.TxTraceResult, 0, txsLength)
- txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))
+ txConfig := k.NewTxConfig(ctx, nil)
for i, tx := range req.Txs {
result := evmtypes.TxTraceResult{}
ethTx := tx.AsTransaction()
txConfig.TxHash = ethTx.Hash()
txConfig.TxIndex = uint(i)
+ txConfig = txConfig.WithTxTypeFromTransaction(ethTx)
traceResult, logIndex, err := k.traceTx(ctx, cfg, txConfig, signer, ethTx, req.TraceConfig, true, nil)
if err != nil {
result.Error = err.Error()
@@ -567,8 +569,8 @@ func (k Keeper) TraceBlock(c context.Context, req *evmtypes.QueryTraceBlockReque
// traceTx do trace on one transaction, it returns a tuple: (traceResult, nextLogIndex, error).
func (k *Keeper) traceTx(
ctx sdk.Context,
- cfg *statedb.EVMConfig,
- txConfig statedb.TxConfig,
+ cfg *evmvm.EVMConfig,
+ txConfig evmvm.TxConfig,
signer ethtypes.Signer,
tx *ethtypes.Transaction,
traceConfig *evmtypes.TraceConfig,
@@ -582,11 +584,13 @@ func (k *Keeper) traceTx(
err error
timeout = defaultTraceTimeout
)
+
+ txConfig = txConfig.WithTxTypeFromTransaction(tx)
+
msg, err := tx.AsMessage(signer, cfg.BaseFee)
if err != nil {
return nil, 0, status.Error(codes.Internal, err.Error())
}
- txConfig = txConfig.WithTxTypeFromMessage(msg)
if traceConfig == nil {
traceConfig = &evmtypes.TraceConfig{}
diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go
index 73a772c001..a1f25a655a 100644
--- a/x/evm/keeper/grpc_query_test.go
+++ b/x/evm/keeper/grpc_query_test.go
@@ -2,6 +2,7 @@ package keeper_test
import (
"encoding/json"
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
"math/big"
storetypes "cosmossdk.io/store/types"
@@ -13,14 +14,13 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
ethlogger "github.com/ethereum/go-ethereum/eth/tracers/logger"
ethparams "github.com/ethereum/go-ethereum/params"
"github.com/EscanBE/evermint/v12/server/config"
utiltx "github.com/EscanBE/evermint/v12/testutil/tx"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
)
@@ -245,12 +245,12 @@ func (suite *KeeperTestSuite) TestQueryStorage() {
testCases := []struct {
name string
- malleate func(vm.StateDB)
+ malleate func(corevm.StateDB)
expPass bool
}{
{
name: "fail - invalid address",
- malleate: func(vm.StateDB) {
+ malleate: func(corevm.StateDB) {
req = &evmtypes.QueryStorageRequest{
Address: invalidAddress,
}
@@ -259,7 +259,7 @@ func (suite *KeeperTestSuite) TestQueryStorage() {
},
{
name: "pass",
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
key := common.BytesToHash([]byte("key"))
value := common.BytesToHash([]byte("value"))
expValue = value.String()
@@ -279,7 +279,7 @@ func (suite *KeeperTestSuite) TestQueryStorage() {
vmdb := suite.StateDB()
tc.malleate(vmdb)
- suite.Require().NoError(vmdb.Commit())
+ suite.Require().NoError(vmdb.CommitMultiStore(false))
res, err := suite.queryClient.Storage(suite.ctx, req)
@@ -303,12 +303,12 @@ func (suite *KeeperTestSuite) TestQueryCode() {
testCases := []struct {
name string
- malleate func(vm.StateDB)
+ malleate func(corevm.StateDB)
expPass bool
}{
{
name: "fail - invalid address",
- malleate: func(vm.StateDB) {
+ malleate: func(corevm.StateDB) {
req = &evmtypes.QueryCodeRequest{
Address: invalidAddress,
}
@@ -319,7 +319,7 @@ func (suite *KeeperTestSuite) TestQueryCode() {
},
{
name: "pass",
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
expCode = []byte("code")
vmdb.SetCode(suite.address, expCode)
@@ -337,7 +337,7 @@ func (suite *KeeperTestSuite) TestQueryCode() {
vmdb := suite.StateDB()
tc.malleate(vmdb)
- suite.Require().NoError(vmdb.Commit())
+ suite.Require().NoError(vmdb.CommitMultiStore(false))
res, err := suite.queryClient.Code(suite.ctx, req)
@@ -361,17 +361,17 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() {
testCases := []struct {
name string
- malleate func(vm.StateDB)
+ malleate func(corevm.StateDB)
}{
{
name: "pass - empty logs",
- malleate: func(vm.StateDB) {
- expLogs = nil
+ malleate: func(corevm.StateDB) {
+ expLogs = []*ethtypes.Log{}
},
},
{
name: "pass - correct log",
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
expLogs = []*ethtypes.Log{
{
Address: suite.address,
@@ -397,11 +397,11 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
- vmdb := statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewTxConfig(common.BytesToHash(suite.ctx.HeaderHash()), txHash, txIndex, logIndex))
+ vmdb := evmvm.NewStateDB(suite.ctx, suite.app.EvmKeeper, suite.app.AccountKeeper, suite.app.BankKeeper)
tc.malleate(vmdb)
- suite.Require().NoError(vmdb.Commit())
+ suite.Require().NoError(vmdb.CommitMultiStore(false))
- logs := vmdb.Logs()
+ logs := vmdb.GetTransactionLogs()
suite.Require().Equal(expLogs, logs)
})
}
@@ -859,7 +859,7 @@ func (suite *KeeperTestSuite) TestTraceTx() {
// increase nonce to avoid address collision
vmdb := suite.StateDB()
vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1)
- suite.Require().NoError(vmdb.Commit())
+ suite.Require().NoError(vmdb.CommitMultiStore(false))
contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()
@@ -927,7 +927,7 @@ func (suite *KeeperTestSuite) TestTraceTx() {
// increase nonce to avoid address collision
vmdb := suite.StateDB()
vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1)
- suite.Require().NoError(vmdb.Commit())
+ suite.Require().NoError(vmdb.CommitMultiStore(false))
chainID := suite.app.EvmKeeper.ChainID()
nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
@@ -1101,7 +1101,7 @@ func (suite *KeeperTestSuite) TestTraceBlock() {
// increase nonce to avoid address collision
vmdb := suite.StateDB()
vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1)
- suite.Require().NoError(vmdb.Commit())
+ suite.Require().NoError(vmdb.CommitMultiStore(false))
contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()
diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go
index ce0a66ce05..ccd88bd47b 100644
--- a/x/evm/keeper/keeper.go
+++ b/x/evm/keeper/keeper.go
@@ -14,19 +14,14 @@ import (
"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
+ evertypes "github.com/EscanBE/evermint/v12/types"
"github.com/EscanBE/evermint/v12/utils"
+ evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- ethparams "github.com/ethereum/go-ethereum/params"
-
- evertypes "github.com/EscanBE/evermint/v12/types"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
- evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
)
// Keeper grants access to the EVM module state and implements the go-ethereum StateDB interface.
@@ -206,13 +201,6 @@ func (k Keeper) SetLogCountForCurrentTxTransient(ctx sdk.Context, count uint64)
store.Set(evmtypes.TxLogCountTransientKey(txIdx), sdk.Uint64ToBigEndian(count))
}
-// GetLogCountForTdxIndexTransient returns log count for tx by index from the transient store.
-func (k Keeper) GetLogCountForTdxIndexTransient(ctx sdk.Context, txIdx uint64) uint64 {
- store := ctx.TransientStore(k.transientKey)
- bz := store.Get(evmtypes.TxLogCountTransientKey(txIdx))
- return sdk.BigEndianToUint64(bz)
-}
-
// GetCumulativeLogCountTransient returns the total log count for all transactions in the current block.
func (k Keeper) GetCumulativeLogCountTransient(ctx sdk.Context, exceptCurrent bool) uint64 {
store := ctx.TransientStore(k.transientKey)
@@ -335,44 +323,29 @@ func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) evmty
// Account
// ----------------------------------------------------------------------------
-// Tracer return a default vm.Tracer based on current keeper state
-func (k Keeper) Tracer(ctx sdk.Context, msg core.Message, ethCfg *ethparams.ChainConfig) vm.EVMLogger {
- return evmtypes.NewTracer(k.tracer, msg, ethCfg, ctx.BlockHeight())
-}
-
-// GetAccountWithoutBalance load nonce and codehash without balance,
-// more efficient in cases where balance is not needed.
-func (k *Keeper) GetAccountWithoutBalance(ctx sdk.Context, addr common.Address) *statedb.Account {
- cosmosAddr := sdk.AccAddress(addr.Bytes())
- acct := k.accountKeeper.GetAccount(ctx, cosmosAddr)
- if acct == nil {
- return nil
+// IsEmptyAccount returns true if the account is empty, decided by:
+// - Nonce is zero
+// - Balance is zero
+// - CodeHash is empty
+func (k *Keeper) IsEmptyAccount(ctx sdk.Context, addr common.Address) bool {
+ if codeHash := k.GetCodeHash(ctx, addr.Bytes()); !evmtypes.IsEmptyCodeHash(codeHash) {
+ return false
}
- return &statedb.Account{
- Nonce: acct.GetSequence(),
- CodeHash: k.GetCodeHash(ctx, addr.Bytes()).Bytes(),
+ if coins := k.bankKeeper.GetAllBalances(ctx, addr.Bytes()); !coins.IsZero() {
+ return false
}
-}
-// GetAccountOrEmpty returns empty account if not exist
-func (k *Keeper) GetAccountOrEmpty(ctx sdk.Context, addr common.Address) statedb.Account {
- acct := k.GetAccount(ctx, addr)
- if acct != nil {
- return *acct
+ if acc := k.accountKeeper.GetAccount(ctx, addr.Bytes()); acc != nil && acc.GetSequence() > 0 {
+ return false
}
- // empty account
- return statedb.Account{
- Balance: new(big.Int),
- CodeHash: evmtypes.EmptyCodeHash,
- }
+ return true
}
// GetNonce returns the sequence number of an account, returns 0 if not exists.
func (k *Keeper) GetNonce(ctx sdk.Context, addr common.Address) uint64 {
- cosmosAddr := sdk.AccAddress(addr.Bytes())
- acct := k.accountKeeper.GetAccount(ctx, cosmosAddr)
+ acct := k.accountKeeper.GetAccount(ctx, addr.Bytes())
if acct == nil {
return 0
}
@@ -380,17 +353,15 @@ func (k *Keeper) GetNonce(ctx sdk.Context, addr common.Address) uint64 {
return acct.GetSequence()
}
-// GetBalance load account's balance of gas token
+// GetBalance returns account token balance based on EVM denom.
func (k *Keeper) GetBalance(ctx sdk.Context, addr common.Address) *big.Int {
cosmosAddr := sdk.AccAddress(addr.Bytes())
evmParams := k.GetParams(ctx)
- evmDenom := evmParams.GetEvmDenom()
// if node is pruned, params is empty. Return invalid value
- if evmDenom == "" {
+ if evmParams.EvmDenom == "" {
return big.NewInt(-1)
}
- coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, evmDenom)
- return coin.Amount.BigInt()
+ return k.bankKeeper.GetBalance(ctx, cosmosAddr, evmParams.EvmDenom).Amount.BigInt()
}
// GetBaseFee returns current base fee.
diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go
index 9aec8dc280..7efc21bff9 100644
--- a/x/evm/keeper/keeper_test.go
+++ b/x/evm/keeper/keeper_test.go
@@ -2,6 +2,8 @@ package keeper_test
import (
_ "embed"
+ "github.com/EscanBE/evermint/v12/testutil"
+ utiltx "github.com/EscanBE/evermint/v12/testutil/tx"
"math/big"
storetypes "cosmossdk.io/store/types"
@@ -12,7 +14,6 @@ import (
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
"github.com/ethereum/go-ethereum/common"
@@ -158,22 +159,19 @@ func (suite *KeeperTestSuite) TestGetAccountStorage() {
}
}
-func (suite *KeeperTestSuite) TestGetAccountOrEmpty() {
- empty := statedb.Account{
- Balance: new(big.Int),
- CodeHash: evmtypes.EmptyCodeHash,
- }
-
- supply := big.NewInt(100)
- contractAddr := suite.DeployTestContract(suite.T(), suite.address, supply)
+func (suite *KeeperTestSuite) TestIsEmptyAccount() {
+ contractAddr := suite.DeployTestContract(suite.T(), suite.address, common.Big1)
+ randomAddr1 := utiltx.GenerateAddress()
+ randomAddr2 := utiltx.GenerateAddress()
testCases := []struct {
name string
addr common.Address
+ malleate func()
expEmpty bool
}{
{
- name: "unexisting account - get empty",
+ name: "non-existing account",
addr: common.Address{},
expEmpty: true,
},
@@ -182,16 +180,40 @@ func (suite *KeeperTestSuite) TestGetAccountOrEmpty() {
addr: contractAddr,
expEmpty: false,
},
+ {
+ name: "account with balance",
+ addr: randomAddr1,
+ malleate: func() {
+ err := testutil.FundAccount(
+ suite.ctx,
+ suite.app.BankKeeper,
+ randomAddr1.Bytes(),
+ sdk.NewCoins(sdk.NewInt64Coin(constants.BaseDenom, 1)),
+ )
+ suite.Require().NoError(err)
+ },
+ expEmpty: false,
+ },
+ {
+ name: "account with nonce",
+ addr: randomAddr2,
+ malleate: func() {
+ acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, randomAddr2.Bytes())
+ err := acc.SetSequence(1)
+ suite.Require().NoError(err)
+ suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
+ },
+ expEmpty: false,
+ },
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
- res := suite.app.EvmKeeper.GetAccountOrEmpty(suite.ctx, tc.addr)
- if tc.expEmpty {
- suite.Require().Equal(empty, res)
- } else {
- suite.Require().NotEqual(empty, res)
+ if tc.malleate != nil {
+ tc.malleate()
}
+ gotEmpty := suite.app.EvmKeeper.IsEmptyAccount(suite.ctx, tc.addr)
+ suite.Require().Equal(tc.expEmpty, gotEmpty)
})
}
}
diff --git a/x/evm/keeper/msg_server_test.go b/x/evm/keeper/msg_server_test.go
index c44f831e3c..2e1b9d81e7 100644
--- a/x/evm/keeper/msg_server_test.go
+++ b/x/evm/keeper/msg_server_test.go
@@ -1,12 +1,12 @@
package keeper_test
import (
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
"math/big"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethparams "github.com/ethereum/go-ethereum/params"
@@ -17,7 +17,7 @@ func (suite *KeeperTestSuite) TestEthereumTx() {
err error
msg *evmtypes.MsgEthereumTx
signer ethtypes.Signer
- vmdb *statedb.StateDB
+ vmdb evmvm.CStateDB
expectedGasUsed uint64
)
diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go
index cdee478b5a..90eba9a120 100644
--- a/x/evm/keeper/state_transition.go
+++ b/x/evm/keeper/state_transition.go
@@ -1,22 +1,23 @@
package keeper
import (
- "math"
+ "fmt"
"math/big"
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
+
cmttypes "github.com/cometbft/cometbft/types"
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
evertypes "github.com/EscanBE/evermint/v12/types"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
)
// NewEVM generates a go-ethereum VM from the provided Message fields and the chain parameters
@@ -27,15 +28,15 @@ import (
// NOTE: the RANDOM opcode is currently not supported since it requires
// RANDAO implementation. See https://github.com/evmos/ethermint/pull/1520#pullrequestreview-1200504697
// for more information.
-
+// TODO ES: support RANDOM opcode
func (k *Keeper) NewEVM(
ctx sdk.Context,
msg core.Message,
- cfg *statedb.EVMConfig,
- tracer vm.EVMLogger,
- stateDB vm.StateDB,
-) *vm.EVM {
- blockCtx := vm.BlockContext{
+ cfg *evmvm.EVMConfig,
+ tracer corevm.EVMLogger,
+ stateDB corevm.StateDB,
+) *corevm.EVM {
+ blockCtx := corevm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
GetHash: k.GetHashFn(ctx),
@@ -50,17 +51,33 @@ func (k *Keeper) NewEVM(
txCtx := core.NewEVMTxContext(msg)
if tracer == nil {
- tracer = k.Tracer(ctx, msg, cfg.ChainConfig)
+ tracer = evmtypes.NewTracer(k.tracer, msg, cfg.ChainConfig, ctx.BlockHeight())
+ }
+
+ coreVmConfig := corevm.Config{
+ Debug: func() bool {
+ if tracer != nil {
+ if _, ok := tracer.(evmtypes.NoOpTracer); !ok {
+ return true
+ }
+ }
+ return false
+ }(),
+ Tracer: tracer,
+ NoBaseFee: cfg.NoBaseFee || k.IsNoBaseFeeEnabled(ctx),
+ ExtraEips: cfg.Params.EIPs(),
}
- vmConfig := k.VMConfig(ctx, cfg, tracer)
- return vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig)
+
+ return corevm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, coreVmConfig)
}
-// GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases:
+// GetHashFn implements vm.GetHashFunc for Evermint. It handles 3 cases:
// 1. The requested height matches the current height from context (and thus same epoch number)
-// 2. The requested height is from an previous height from the same chain epoch
+// 2. The requested height is from a previous height from the same chain epoch
// 3. The requested height is from a height greater than the latest one
-func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
+//
+// TODO ES: improve this
+func (k Keeper) GetHashFn(ctx sdk.Context) corevm.GetHashFunc {
return func(height uint64) common.Hash {
h, err := evertypes.SafeInt64(height)
if err != nil {
@@ -70,8 +87,8 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
switch {
case ctx.BlockHeight() == h:
- // Case 1: The requested height matches the one from the context so we can retrieve the header
- // hash directly from the context.
+ // Case 1: The requested height matches the one from the context,
+ // so we can retrieve the header hash directly from the context.
// Note: The headerHash is only set at begin block, it will be nil in case of a query context
headerHash := ctx.HeaderHash()
if len(headerHash) != 0 {
@@ -134,7 +151,7 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*e
if err != nil {
return nil, errorsmod.Wrap(err, "failed to load evm config")
}
- txConfig := k.TxConfig(ctx, tx.Hash())
+ txConfig := k.NewTxConfig(ctx, tx)
// get the signer according to the chain rules from the config and block height
signer := ethtypes.MakeSigner(cfg.ChainConfig, big.NewInt(ctx.BlockHeight()))
@@ -162,14 +179,13 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*e
}
// ApplyMessage calls ApplyMessageWithConfig with an empty TxConfig.
-func (k *Keeper) ApplyMessage(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool) (*evmtypes.MsgEthereumTxResponse, error) {
+func (k *Keeper) ApplyMessage(ctx sdk.Context, msg core.Message, tracer corevm.EVMLogger, commit bool) (*evmtypes.MsgEthereumTxResponse, error) {
cfg, err := k.EVMConfig(ctx, sdk.ConsAddress(ctx.BlockHeader().ProposerAddress), k.eip155ChainID)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to load evm config")
}
- txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))
- txConfig = txConfig.WithTxTypeFromMessage(msg)
+ txConfig := k.NewTxConfigFromMessage(ctx, msg)
return k.ApplyMessageWithConfig(ctx, msg, tracer, commit, cfg, txConfig)
}
@@ -179,7 +195,7 @@ func (k *Keeper) ApplyMessage(ctx sdk.Context, msg core.Message, tracer vm.EVMLo
//
// # Reverted state
//
-// The snapshot and rollback are supported by the `statedb.StateDB`.
+// The snapshot and rollback are supported by the `legacy_statedb.StateDB`.
//
// # Different Callers
//
@@ -213,10 +229,10 @@ func (k *Keeper) ApplyMessage(ctx sdk.Context, msg core.Message, tracer vm.EVMLo
// If commit is true, the `StateDB` will be committed, otherwise discarded.
func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context,
msg core.Message,
- tracer vm.EVMLogger,
+ tracer corevm.EVMLogger,
commit bool,
- cfg *statedb.EVMConfig,
- txConfig statedb.TxConfig,
+ cfg *evmvm.EVMConfig,
+ txConfig evmvm.TxConfig,
) (*evmtypes.MsgEthereumTxResponse, error) {
// return error if contract creation or call are disabled through governance
if !cfg.Params.EnableCreate && msg.To() == nil {
@@ -225,7 +241,8 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context,
return nil, errorsmod.Wrap(evmtypes.ErrCallDisabled, "failed to call contract")
}
- stateDB := statedb.New(ctx, k, txConfig)
+ stateDB := evmvm.NewStateDB(ctx, k, k.accountKeeper, k.bankKeeper)
+ // stateDB := statedb.New(ctx, k, txConfig)
evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB)
var gasPool core.GasPool
@@ -249,10 +266,18 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context,
}
var txType uint8
- if txConfig.TxType < 0 || txConfig.TxType > math.MaxUint8 {
+ if txConfig.TxType == nil {
panic("require tx type set")
- } else {
- txType = uint8(txConfig.TxType)
+ }
+ switch *txConfig.TxType {
+ case ethtypes.DynamicFeeTxType:
+ txType = ethtypes.DynamicFeeTxType
+ case ethtypes.AccessListTxType:
+ txType = ethtypes.AccessListTxType
+ case ethtypes.LegacyTxType:
+ txType = ethtypes.LegacyTxType
+ default:
+ panic(fmt.Sprintf("invalid tx type: %d", *txConfig.TxType))
}
receipt := ethtypes.Receipt{
@@ -262,7 +287,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context,
Status: 0, // to be filled below
CumulativeGasUsed: cumulativeGasUsed,
Bloom: ethtypes.Bloom{}, // compute bellow
- Logs: stateDB.Logs(),
+ Logs: stateDB.GetTransactionLogs(),
}
if execResult.Err == nil {
receipt.Status = ethtypes.ReceiptStatusSuccessful
@@ -273,7 +298,8 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context,
// The dirty states in `StateDB` is either committed or discarded after return
if commit {
- if err := stateDB.Commit(); err != nil {
+ // TODO ES: enable delete empty object?
+ if err := stateDB.CommitMultiStore(false); err != nil {
return nil, errorsmod.Wrap(err, "failed to commit stateDB")
}
}
diff --git a/x/evm/keeper/state_transition_benchmark_test.go b/x/evm/keeper/state_transition_benchmark_test.go
index 432995d54d..2ef89019dc 100644
--- a/x/evm/keeper/state_transition_benchmark_test.go
+++ b/x/evm/keeper/state_transition_benchmark_test.go
@@ -11,9 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
- ethparams "github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
@@ -131,30 +129,21 @@ func newEthMsgTx(
return msg, baseFee, msg.Sign(ethSigner, krSigner)
}
-func newNativeMessage(
+func newNativeTransaction(
nonce uint64,
- blockHeight int64,
address common.Address,
- cfg *ethparams.ChainConfig,
krSigner keyring.Signer,
ethSigner ethtypes.Signer,
txType byte,
data []byte,
accessList ethtypes.AccessList,
-) (core.Message, error) {
- msgSigner := ethtypes.MakeSigner(cfg, big.NewInt(blockHeight))
-
+) (*ethtypes.Transaction, *big.Int, error) {
msg, baseFee, err := newEthMsgTx(nonce, address, krSigner, ethSigner, txType, data, accessList)
if err != nil {
- return nil, err
- }
-
- m, err := msg.AsMessage(msgSigner, baseFee)
- if err != nil {
- return nil, err
+ return nil, nil, err
}
- return m, nil
+ return msg.AsTransaction(), baseFee, nil
}
func BenchmarkApplyTransaction(b *testing.B) {
@@ -243,8 +232,6 @@ func BenchmarkApplyMessage(b *testing.B) {
suite := KeeperTestSuite{}
suite.SetupTestWithT(b)
- params := suite.app.EvmKeeper.GetParams(suite.ctx)
- ethCfg := params.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID())
signer := ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID())
b.ResetTimer()
@@ -252,11 +239,9 @@ func BenchmarkApplyMessage(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
- m, err := newNativeMessage(
+ ethTx, baseFee, err := newNativeTransaction(
suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address),
- suite.ctx.BlockHeight(),
suite.address,
- ethCfg,
suite.signer,
signer,
ethtypes.AccessListTxType,
@@ -265,6 +250,9 @@ func BenchmarkApplyMessage(b *testing.B) {
)
require.NoError(b, err)
+ m, err := ethTx.AsMessage(signer, baseFee)
+ require.NoError(b, err)
+
b.StartTimer()
resp, err := suite.app.EvmKeeper.ApplyMessage(suite.ctx, m, nil, true)
b.StopTimer()
@@ -279,8 +267,6 @@ func BenchmarkApplyMessageWithLegacyTx(b *testing.B) {
suite := KeeperTestSuite{}
suite.SetupTestWithT(b)
- params := suite.app.EvmKeeper.GetParams(suite.ctx)
- ethCfg := params.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID())
signer := ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID())
b.ResetTimer()
@@ -288,11 +274,9 @@ func BenchmarkApplyMessageWithLegacyTx(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
- m, err := newNativeMessage(
+ ethTx, baseFee, err := newNativeTransaction(
suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address),
- suite.ctx.BlockHeight(),
suite.address,
- ethCfg,
suite.signer,
signer,
ethtypes.LegacyTxType,
@@ -301,6 +285,9 @@ func BenchmarkApplyMessageWithLegacyTx(b *testing.B) {
)
require.NoError(b, err)
+ m, err := ethTx.AsMessage(signer, baseFee)
+ require.NoError(b, err)
+
b.StartTimer()
resp, err := suite.app.EvmKeeper.ApplyMessage(suite.ctx, m, nil, true)
b.StopTimer()
@@ -314,8 +301,6 @@ func BenchmarkApplyMessageWithDynamicFeeTx(b *testing.B) {
suite := KeeperTestSuite{enableFeemarket: true}
suite.SetupTestWithT(b)
- params := suite.app.EvmKeeper.GetParams(suite.ctx)
- ethCfg := params.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID())
signer := ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID())
b.ResetTimer()
@@ -323,11 +308,9 @@ func BenchmarkApplyMessageWithDynamicFeeTx(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
- m, err := newNativeMessage(
+ ethTx, baseFee, err := newNativeTransaction(
suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address),
- suite.ctx.BlockHeight(),
suite.address,
- ethCfg,
suite.signer,
signer,
ethtypes.DynamicFeeTxType,
@@ -336,6 +319,9 @@ func BenchmarkApplyMessageWithDynamicFeeTx(b *testing.B) {
)
require.NoError(b, err)
+ m, err := ethTx.AsMessage(signer, baseFee)
+ require.NoError(b, err)
+
b.StartTimer()
resp, err := suite.app.EvmKeeper.ApplyMessage(suite.ctx, m, nil, true)
b.StopTimer()
diff --git a/x/evm/keeper/state_transition_core.go b/x/evm/keeper/state_transition_core.go
index ed1a1cdfac..75c1fdfeb3 100644
--- a/x/evm/keeper/state_transition_core.go
+++ b/x/evm/keeper/state_transition_core.go
@@ -25,7 +25,7 @@ import (
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
)
@@ -58,8 +58,8 @@ type StateTransition struct {
initialGas uint64
value *big.Int
data []byte
- state vm.StateDB
- evm *vm.EVM
+ state corevm.StateDB
+ evm *corevm.EVM
// extra fields for Evermint
@@ -71,7 +71,7 @@ type StateTransition struct {
}
// NewStateTransition initialises and returns a new state transition object.
-func NewStateTransition(evm *vm.EVM, msg core.Message, gp *core.GasPool) *StateTransition {
+func NewStateTransition(evm *corevm.EVM, msg core.Message, gp *core.GasPool) *StateTransition {
return &StateTransition{
gp: gp,
evm: evm,
@@ -92,7 +92,7 @@ func NewStateTransition(evm *vm.EVM, msg core.Message, gp *core.GasPool) *StateT
// the gas used (which includes gas refunds) and an error if it failed. An error always
// indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block.
-func ApplyMessage(evm *vm.EVM, msg core.Message, gp *core.GasPool, beforeRun func(transition *StateTransition)) (*core.ExecutionResult, error) {
+func ApplyMessage(evm *corevm.EVM, msg core.Message, gp *core.GasPool, beforeRun func(transition *StateTransition)) (*core.ExecutionResult, error) {
st := NewStateTransition(evm, msg, gp)
if beforeRun != nil {
beforeRun(st)
@@ -224,7 +224,7 @@ func (st *StateTransition) TransitionDb() (*core.ExecutionResult, error) {
var (
msg = st.msg
- sender = vm.AccountRef(msg.From())
+ sender = corevm.AccountRef(msg.From())
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil)
contractCreation = msg.To() == nil
)
@@ -246,7 +246,7 @@ func (st *StateTransition) TransitionDb() (*core.ExecutionResult, error) {
// Set up the initial access list.
if rules.IsBerlin {
- st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
+ st.state.PrepareAccessList(msg.From(), msg.To(), corevm.ActivePrecompiles(rules), msg.AccessList())
}
var (
ret []byte
diff --git a/x/evm/keeper/state_transition_test.go b/x/evm/keeper/state_transition_test.go
index 1135872f78..f0d169aca7 100644
--- a/x/evm/keeper/state_transition_test.go
+++ b/x/evm/keeper/state_transition_test.go
@@ -2,6 +2,7 @@ package keeper_test
import (
"fmt"
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
"math"
"math/big"
@@ -12,7 +13,6 @@ import (
utiltx "github.com/EscanBE/evermint/v12/testutil/tx"
evertypes "github.com/EscanBE/evermint/v12/types"
evmkeeper "github.com/EscanBE/evermint/v12/x/evm/keeper"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
"github.com/cometbft/cometbft/crypto/tmhash"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
@@ -443,8 +443,9 @@ func (suite *KeeperTestSuite) TestApplyTransaction() {
suite.ctx = suite.ctx.WithGasMeter(evertypes.NewInfiniteGasMeterWithLimit(ethTx.Gas()))
if tc.simulateCommitDbError {
- suite.StateDB().ToggleStateDBPreventCommit(true)
- defer suite.StateDB().ToggleStateDBPreventCommit(false)
+ stateDb := suite.StateDB()
+ stateDb.ForTest_ToggleStateDBPreventCommit(true)
+ defer stateDb.ForTest_ToggleStateDBPreventCommit(false)
}
res, err := suite.app.EvmKeeper.ApplyTransaction(suite.ctx, ethTx)
@@ -481,7 +482,8 @@ func (suite *KeeperTestSuite) TestApplyTransaction() {
func (suite *KeeperTestSuite) TestApplyMessage() {
var (
- msg core.Message
+ ethTx *ethtypes.Transaction
+ baseFee *big.Int
err error
keeperParams evmtypes.Params
signer ethtypes.Signer
@@ -500,18 +502,15 @@ func (suite *KeeperTestSuite) TestApplyMessage() {
{
name: "message applied ok",
malleate: func() {
- msg, err = newNativeMessage(
+ ethTx, baseFee, err = newNativeTransaction(
getNonce(suite.address.Bytes()),
- suite.ctx.BlockHeight(),
suite.address,
- chainCfg,
suite.signer,
signer,
ethtypes.AccessListTxType,
nil,
nil,
)
- suite.Require().NoError(err)
},
expErr: false,
expGasUsed: ethparams.TxGas,
@@ -543,9 +542,7 @@ func (suite *KeeperTestSuite) TestApplyMessage() {
err = ethMsg.Sign(msgSigner, suite.signer)
suite.Require().NoError(err)
- var err error
- msg, err = ethMsg.AsMessage(msgSigner, nil)
- suite.Require().NoError(err)
+ ethTx = ethMsg.AsTransaction()
},
expErr: false,
expGasUsed: 21000,
@@ -577,8 +574,7 @@ func (suite *KeeperTestSuite) TestApplyMessage() {
err := ethMsg.Sign(msgSigner, suite.signer)
suite.Require().NoError(err)
- msg, err = ethMsg.AsMessage(msgSigner, nil)
- suite.Require().NoError(err)
+ ethTx = ethMsg.AsTransaction()
},
expErr: false,
expGasUsed: 21_000, // consume just enough gas
@@ -611,8 +607,7 @@ func (suite *KeeperTestSuite) TestApplyMessage() {
err = ethMsg.Sign(msgSigner, suite.signer)
suite.Require().NoError(err)
- msg, err = ethMsg.AsMessage(msgSigner, nil)
- suite.Require().NoError(err)
+ ethTx = ethMsg.AsTransaction()
},
expErr: true,
expErrContains: core.ErrIntrinsicGas.Error(),
@@ -645,8 +640,7 @@ func (suite *KeeperTestSuite) TestApplyMessage() {
err = ethMsg.Sign(msgSigner, suite.signer)
suite.Require().NoError(err)
- msg, err = ethMsg.AsMessage(msgSigner, nil)
- suite.Require().NoError(err)
+ ethTx = ethMsg.AsTransaction()
},
expErr: true,
expErrContains: "failed to commit stateDB",
@@ -660,15 +654,19 @@ func (suite *KeeperTestSuite) TestApplyMessage() {
keeperParams = suite.app.EvmKeeper.GetParams(suite.ctx)
chainCfg = keeperParams.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID())
signer = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID())
+ baseFee = nil
tc.malleate()
- suite.ctx = suite.ctx.WithGasMeter(evertypes.NewInfiniteGasMeterWithLimit(msg.Gas()))
+ suite.ctx = suite.ctx.WithGasMeter(evertypes.NewInfiniteGasMeterWithLimit(ethTx.Gas()))
if tc.simulateCommitDbError {
- suite.StateDB().ToggleStateDBPreventCommit(true)
- defer suite.StateDB().ToggleStateDBPreventCommit(false)
+ stateDb := suite.StateDB()
+ stateDb.ForTest_ToggleStateDBPreventCommit(true)
+ defer stateDb.ForTest_ToggleStateDBPreventCommit(false)
}
+ msg, err := ethTx.AsMessage(signer, baseFee)
+ suite.Require().NoError(err)
res, err := suite.app.EvmKeeper.ApplyMessage(suite.ctx, msg, nil, true)
if tc.expErr {
@@ -697,13 +695,15 @@ func (suite *KeeperTestSuite) TestApplyMessage() {
func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
var (
- msg core.Message
+ ethTx *ethtypes.Transaction
+ //msg core.Message
err error
- config *statedb.EVMConfig
+ config *evmvm.EVMConfig
keeperParams evmtypes.Params
signer ethtypes.Signer
- txConfig statedb.TxConfig
+ txConfig evmvm.TxConfig
chainCfg *ethparams.ChainConfig
+ baseFee *big.Int
)
ptr := func(i int64) *int64 { return &i }
@@ -720,11 +720,9 @@ func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
{
name: "pass - message applied ok",
malleate: func() {
- msg, err = newNativeMessage(
+ ethTx, baseFee, err = newNativeTransaction(
getNonce(suite.address.Bytes()),
- suite.ctx.BlockHeight(),
suite.address,
- chainCfg,
suite.signer,
signer,
ethtypes.AccessListTxType,
@@ -740,11 +738,9 @@ func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
name: "fail - call contract tx with config param EnableCall = false",
malleate: func() {
config.Params.EnableCall = false
- msg, err = newNativeMessage(
+ ethTx, baseFee, err = newNativeTransaction(
getNonce(suite.address.Bytes()),
- suite.ctx.BlockHeight(),
suite.address,
- chainCfg,
suite.signer,
signer,
ethtypes.AccessListTxType,
@@ -759,7 +755,7 @@ func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
{
name: "fail - create contract tx with config param EnableCreate = false",
malleate: func() {
- msg, err = suite.createContractGethMsg(getNonce(suite.address.Bytes()), signer, chainCfg, big.NewInt(1))
+ ethTx, err = suite.createContractGethMsg(getNonce(suite.address.Bytes()), signer, big.NewInt(1))
suite.Require().NoError(err)
config.Params.EnableCreate = false
},
@@ -793,8 +789,7 @@ func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
err = ethMsg.Sign(msgSigner, suite.signer)
suite.Require().NoError(err)
- msg, err = ethMsg.AsMessage(msgSigner, nil)
- suite.Require().NoError(err)
+ ethTx = ethMsg.AsTransaction()
},
expErr: false,
expGasUsed: 21000,
@@ -827,8 +822,7 @@ func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
err = ethMsg.Sign(msgSigner, suite.signer)
suite.Require().NoError(err)
- msg, err = ethMsg.AsMessage(msgSigner, nil)
- suite.Require().NoError(err)
+ ethTx = ethMsg.AsTransaction()
},
expErr: false,
expGasUsed: 21_000, // consume just enough gas
@@ -859,8 +853,7 @@ func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
err = ethMsg.Sign(msgSigner, suite.signer)
suite.Require().NoError(err)
- msg, err = ethMsg.AsMessage(msgSigner, nil)
- suite.Require().NoError(err)
+ ethTx = ethMsg.AsTransaction()
},
expErr: false,
expGasUsed: 21000,
@@ -893,8 +886,7 @@ func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
err = ethMsg.Sign(msgSigner, suite.signer)
suite.Require().NoError(err)
- msg, err = ethMsg.AsMessage(msgSigner, nil)
- suite.Require().NoError(err)
+ ethTx = ethMsg.AsTransaction()
},
expErr: true,
expErrContains: core.ErrIntrinsicGas.Error(),
@@ -928,8 +920,7 @@ func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
err = ethMsg.Sign(msgSigner, suite.signer)
suite.Require().NoError(err)
- msg, err = ethMsg.AsMessage(msgSigner, nil)
- suite.Require().NoError(err)
+ ethTx = ethMsg.AsTransaction()
},
expErr: true,
expErrContains: "failed to commit stateDB",
@@ -948,16 +939,20 @@ func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
keeperParams = suite.app.EvmKeeper.GetParams(suite.ctx)
chainCfg = keeperParams.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID())
signer = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID())
+ baseFee = nil
tc.malleate()
- txConfig = suite.app.EvmKeeper.TxConfig(suite.ctx, common.Hash{}).WithTxTypeFromMessage(msg)
- suite.ctx = suite.ctx.WithGasMeter(evertypes.NewInfiniteGasMeterWithLimit(msg.Gas()))
+ txConfig = suite.app.EvmKeeper.NewTxConfig(suite.ctx, ethTx)
+ suite.ctx = suite.ctx.WithGasMeter(evertypes.NewInfiniteGasMeterWithLimit(ethTx.Gas()))
if tc.simulateCommitDbError {
- suite.StateDB().ToggleStateDBPreventCommit(true)
- defer suite.StateDB().ToggleStateDBPreventCommit(false)
+ stateDb := suite.StateDB()
+ stateDb.ForTest_ToggleStateDBPreventCommit(true)
+ defer stateDb.ForTest_ToggleStateDBPreventCommit(false)
}
+ msg, err := ethTx.AsMessage(signer, baseFee)
+ suite.Require().NoError(err)
res, err := suite.app.EvmKeeper.ApplyMessageWithConfig(suite.ctx, msg, nil, true, config, txConfig)
defer func() {
@@ -990,14 +985,13 @@ func (suite *KeeperTestSuite) TestApplyMessageWithConfig() {
}
}
-func (suite *KeeperTestSuite) createContractGethMsg(nonce uint64, signer ethtypes.Signer, cfg *ethparams.ChainConfig, gasPrice *big.Int) (core.Message, error) {
+func (suite *KeeperTestSuite) createContractGethMsg(nonce uint64, signer ethtypes.Signer, gasPrice *big.Int) (*ethtypes.Transaction, error) {
ethMsg, err := suite.createContractMsgTx(nonce, signer, gasPrice)
if err != nil {
return nil, err
}
- msgSigner := ethtypes.MakeSigner(cfg, big.NewInt(suite.ctx.BlockHeight()))
- return ethMsg.AsMessage(msgSigner, nil)
+ return ethMsg.AsTransaction(), nil
}
func (suite *KeeperTestSuite) createContractMsgTx(nonce uint64, signer ethtypes.Signer, gasPrice *big.Int) (*evmtypes.MsgEthereumTx, error) {
diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go
index 07153061ff..cb04e6f376 100644
--- a/x/evm/keeper/statedb.go
+++ b/x/evm/keeper/statedb.go
@@ -1,43 +1,16 @@
package keeper
import (
- "fmt"
- "math/big"
-
storetypes "cosmossdk.io/store/types"
+ "fmt"
- authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
- vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
- banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
-
- sdkmath "cosmossdk.io/math"
-
- errorsmod "cosmossdk.io/errors"
"cosmossdk.io/store/prefix"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
)
-var _ statedb.Keeper = &Keeper{}
-
-// ----------------------------------------------------------------------------
-// StateDB Keeper implementation
-// ----------------------------------------------------------------------------
-
-// GetAccount returns nil if account is not exist
-func (k *Keeper) GetAccount(ctx sdk.Context, addr common.Address) *statedb.Account {
- acct := k.GetAccountWithoutBalance(ctx, addr)
- if acct == nil {
- return nil
- }
-
- acct.Balance = k.GetBalance(ctx, addr)
- return acct
-}
-
-// GetState loads contract state from database, implements `statedb.Keeper` interface.
+// GetState loads contract state
func (k *Keeper) GetState(ctx sdk.Context, addr common.Address, key common.Hash) common.Hash {
store := prefix.NewStore(ctx.KVStore(k.storeKey), evmtypes.AddressStoragePrefix(addr))
@@ -49,10 +22,21 @@ func (k *Keeper) GetState(ctx sdk.Context, addr common.Address, key common.Hash)
return common.BytesToHash(value)
}
-// GetCode loads contract code from database, implements `statedb.Keeper` interface.
-func (k *Keeper) GetCode(ctx sdk.Context, codeHash common.Hash) []byte {
- store := prefix.NewStore(ctx.KVStore(k.storeKey), evmtypes.KeyPrefixCode)
- return store.Get(codeHash.Bytes())
+// SetState update contract storage, delete if value is empty.
+func (k *Keeper) SetState(ctx sdk.Context, addr common.Address, key common.Hash, value []byte) {
+ store := prefix.NewStore(ctx.KVStore(k.storeKey), evmtypes.AddressStoragePrefix(addr))
+ action := "updated"
+ if len(value) == 0 {
+ store.Delete(key.Bytes())
+ action = "deleted"
+ } else {
+ store.Set(key.Bytes(), value)
+ }
+ k.Logger(ctx).Debug(
+ fmt.Sprintf("state %s", action),
+ "ethereum-address", addr.Hex(),
+ "key", key.Hex(),
+ )
}
// ForEachStorage iterate contract storage, callback return false to break early
@@ -76,89 +60,10 @@ func (k *Keeper) ForEachStorage(ctx sdk.Context, addr common.Address, cb func(ke
}
}
-// SetBalance update account's balance, compare with current balance first, then decide to mint or burn.
-func (k *Keeper) SetBalance(ctx sdk.Context, addr common.Address, amount *big.Int) error {
- cosmosAddr := sdk.AccAddress(addr.Bytes())
-
- params := k.GetParams(ctx)
- coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom)
- balance := coin.Amount.BigInt()
- delta := new(big.Int).Sub(amount, balance)
- switch delta.Sign() {
- case 1:
- // mint
- coins := sdk.NewCoins(sdk.NewCoin(params.EvmDenom, sdkmath.NewIntFromBigInt(delta)))
- if err := k.bankKeeper.MintCoins(ctx, evmtypes.ModuleName, coins); err != nil {
- return err
- }
- if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, evmtypes.ModuleName, cosmosAddr, coins); err != nil {
- return err
- }
- case -1:
- // burn
- coins := sdk.NewCoins(sdk.NewCoin(params.EvmDenom, sdkmath.NewIntFromBigInt(new(big.Int).Neg(delta))))
- if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, cosmosAddr, evmtypes.ModuleName, coins); err != nil {
- return err
- }
- if err := k.bankKeeper.BurnCoins(ctx, evmtypes.ModuleName, coins); err != nil {
- return err
- }
- default:
- // not changed
- }
- return nil
-}
-
-// SetAccount updates nonce/balance/codeHash together.
-func (k *Keeper) SetAccount(ctx sdk.Context, addr common.Address, account statedb.Account) error {
- // update account
- cosmosAddr := sdk.AccAddress(addr.Bytes())
- acct := k.accountKeeper.GetAccount(ctx, cosmosAddr)
- if acct == nil {
- acct = k.accountKeeper.NewAccountWithAddress(ctx, cosmosAddr)
- }
-
- if err := acct.SetSequence(account.Nonce); err != nil {
- return err
- }
-
- codeHash := common.BytesToHash(account.CodeHash)
-
- if _, isBaseAccount := acct.(*authtypes.BaseAccount); isBaseAccount {
- k.SetCodeHash(ctx, addr, codeHash)
- }
-
- k.accountKeeper.SetAccount(ctx, acct)
-
- if err := k.SetBalance(ctx, addr, account.Balance); err != nil {
- return err
- }
-
- k.Logger(ctx).Debug(
- "account updated",
- "ethereum-address", addr.Hex(),
- "nonce", account.Nonce,
- "codeHash", codeHash.Hex(),
- "balance", account.Balance,
- )
- return nil
-}
-
-// SetState update contract storage, delete if value is empty.
-func (k *Keeper) SetState(ctx sdk.Context, addr common.Address, key common.Hash, value []byte) {
- store := prefix.NewStore(ctx.KVStore(k.storeKey), evmtypes.AddressStoragePrefix(addr))
- action := "updated"
- if len(value) == 0 {
- store.Delete(key.Bytes())
- action = "deleted"
- } else {
- store.Set(key.Bytes(), value)
- }
- k.Logger(ctx).Debug(
- fmt.Sprintf("state %s", action),
- "ethereum-address", addr.Hex(),
- "key", key.Hex(),
- )
+// GetCode loads contract code
+func (k *Keeper) GetCode(ctx sdk.Context, codeHash common.Hash) []byte {
+ store := prefix.NewStore(ctx.KVStore(k.storeKey), evmtypes.KeyPrefixCode)
+ return store.Get(codeHash.Bytes())
}
// SetCode set contract code, delete if code is empty.
@@ -179,53 +84,6 @@ func (k *Keeper) SetCode(ctx sdk.Context, codeHash, code []byte) {
)
}
-// DeleteAccount handles contract's suicide call:
-// - clear balance
-// - remove code
-// - remove states
-// - remove auth account
-func (k *Keeper) DeleteAccount(ctx sdk.Context, addr common.Address) error {
- cosmosAddr := sdk.AccAddress(addr.Bytes())
- acct := k.accountKeeper.GetAccount(ctx, cosmosAddr)
- if acct == nil {
- return nil
- }
-
- // NOTE: only Ethereum accounts (contracts) can be selfdestructed
- if isNotProhibitedAccount, reason := isNotProhibitedAccountType(acct); !isNotProhibitedAccount {
- return errorsmod.Wrapf(evmtypes.ErrInvalidAccount, "type %T, address %s, reason: %s", acct, addr, reason)
- }
-
- // clear code-hash
- codeHash := k.GetCodeHash(ctx, addr.Bytes())
- if evmtypes.IsEmptyCodeHash(codeHash) {
- return errorsmod.Wrapf(evmtypes.ErrInvalidAccount, "type %T, address %s, not smart contract", acct, addr)
- }
- k.DeleteCodeHash(ctx, addr.Bytes())
-
- // clear balance
- if err := k.SetBalance(ctx, addr, new(big.Int)); err != nil {
- return err
- }
-
- // clear storage
- k.ForEachStorage(ctx, addr, func(key, _ common.Hash) bool {
- k.SetState(ctx, addr, key, nil)
- return true
- })
-
- // remove auth account
- k.accountKeeper.RemoveAccount(ctx, acct)
-
- k.Logger(ctx).Debug(
- "account suicided",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- )
-
- return nil
-}
-
// GetCodeHash returns the code hash for the corresponding account address.
func (k *Keeper) GetCodeHash(ctx sdk.Context, addr []byte) common.Hash {
store := prefix.NewStore(ctx.KVStore(k.storeKey), evmtypes.KeyPrefixCodeHash)
@@ -233,7 +91,9 @@ func (k *Keeper) GetCodeHash(ctx sdk.Context, addr []byte) common.Hash {
var codeHash common.Hash
if len(bz) == 0 {
- codeHash = common.BytesToHash(evmtypes.EmptyCodeHash)
+ if k.accountKeeper.HasAccount(ctx, addr) {
+ codeHash = common.BytesToHash(evmtypes.EmptyCodeHash)
+ }
} else {
codeHash = common.BytesToHash(bz)
}
@@ -275,24 +135,3 @@ func (k Keeper) IterateContracts(ctx sdk.Context, callback func(addr common.Addr
}
}
}
-
-// isNotProhibitedAccountType returns false if the given account is module account or vesting account
-func isNotProhibitedAccountType(accI sdk.AccountI) (notProhibited bool, explain string) {
- if moduleAccount, isModuleAccount := accI.(sdk.ModuleAccountI); isModuleAccount {
- explain = fmt.Sprintf("%s is module account of %s", moduleAccount.GetAddress().String(), moduleAccount.GetName())
- return
- }
-
- if _, isVestingAccount := accI.(*vestingtypes.BaseVestingAccount); isVestingAccount {
- explain = fmt.Sprintf("%s is vesting account", accI.GetAddress().String())
- return
- }
-
- if _, isVestingAccount := accI.(banktypes.VestingAccount); isVestingAccount {
- explain = fmt.Sprintf("%s is vesting account", accI.GetAddress().String())
- return
- }
-
- notProhibited = true
- return
-}
diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go
index b843a2d2c5..93f0276d06 100644
--- a/x/evm/keeper/statedb_test.go
+++ b/x/evm/keeper/statedb_test.go
@@ -2,14 +2,12 @@ package keeper_test
import (
"fmt"
+ "github.com/EscanBE/evermint/v12/x/evm/vm"
"math/big"
- "github.com/EscanBE/evermint/v12/constants"
-
"cosmossdk.io/store/prefix"
"github.com/EscanBE/evermint/v12/crypto/ethsecp256k1"
utiltx "github.com/EscanBE/evermint/v12/testutil/tx"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
@@ -17,7 +15,7 @@ import (
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
)
@@ -25,27 +23,27 @@ func (suite *KeeperTestSuite) TestCreateAccount() {
testCases := []struct {
name string
addr common.Address
- malleate func(vm.StateDB, common.Address)
- callback func(vm.StateDB, common.Address)
+ malleate func(corevm.StateDB, common.Address)
+ callback func(corevm.StateDB, common.Address)
}{
{
name: "reset account (keep balance)",
addr: suite.address,
- malleate: func(vmdb vm.StateDB, addr common.Address) {
+ malleate: func(vmdb corevm.StateDB, addr common.Address) {
vmdb.AddBalance(addr, big.NewInt(100))
suite.Require().NotZero(vmdb.GetBalance(addr).Int64())
},
- callback: func(vmdb vm.StateDB, addr common.Address) {
+ callback: func(vmdb corevm.StateDB, addr common.Address) {
suite.Require().Equal(vmdb.GetBalance(addr).Int64(), int64(100))
},
},
{
name: "create account",
addr: utiltx.GenerateAddress(),
- malleate: func(vmdb vm.StateDB, addr common.Address) {
+ malleate: func(vmdb corevm.StateDB, addr common.Address) {
suite.Require().False(vmdb.Exist(addr))
},
- callback: func(vmdb vm.StateDB, addr common.Address) {
+ callback: func(vmdb corevm.StateDB, addr common.Address) {
suite.Require().True(vmdb.Exist(addr))
},
},
@@ -63,9 +61,10 @@ func (suite *KeeperTestSuite) TestCreateAccount() {
func (suite *KeeperTestSuite) TestAddBalance() {
testCases := []struct {
- name string
- amount *big.Int
- isNoOp bool
+ name string
+ amount *big.Int
+ isNoOp bool
+ wantPanic bool
}{
{
name: "positive amount",
@@ -78,9 +77,9 @@ func (suite *KeeperTestSuite) TestAddBalance() {
isNoOp: true,
},
{
- name: "negative amount",
- amount: big.NewInt(-1), // seems to be consistent with go-ethereum's implementation
- isNoOp: false,
+ name: "negative amount",
+ amount: big.NewInt(-1),
+ wantPanic: true,
},
}
@@ -88,6 +87,14 @@ func (suite *KeeperTestSuite) TestAddBalance() {
suite.Run(tc.name, func() {
vmdb := suite.StateDB()
prev := vmdb.GetBalance(suite.address)
+
+ if tc.wantPanic {
+ suite.Require().Panics(func() {
+ vmdb.AddBalance(suite.address, tc.amount)
+ })
+ return
+ }
+
vmdb.AddBalance(suite.address, tc.amount)
post := vmdb.GetBalance(suite.address)
@@ -102,21 +109,22 @@ func (suite *KeeperTestSuite) TestAddBalance() {
func (suite *KeeperTestSuite) TestSubBalance() {
testCases := []struct {
- name string
- amount *big.Int
- malleate func(vm.StateDB)
- isNoOp bool
+ name string
+ amount *big.Int
+ malleate func(corevm.StateDB)
+ isNoOp bool
+ wantPanic bool
}{
{
- name: "positive amount, below zero",
- amount: big.NewInt(100),
- malleate: func(vm.StateDB) {},
- isNoOp: false,
+ name: "positive amount, below zero",
+ amount: big.NewInt(100),
+ malleate: func(corevm.StateDB) {},
+ wantPanic: true,
},
{
name: "positive amount, above zero",
amount: big.NewInt(50),
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
vmdb.AddBalance(suite.address, big.NewInt(100))
},
isNoOp: false,
@@ -124,14 +132,14 @@ func (suite *KeeperTestSuite) TestSubBalance() {
{
name: "zero amount",
amount: big.NewInt(0),
- malleate: func(vm.StateDB) {},
+ malleate: func(corevm.StateDB) {},
isNoOp: true,
},
{
- name: "negative amount",
- amount: big.NewInt(-1),
- malleate: func(vm.StateDB) {},
- isNoOp: false,
+ name: "negative amount",
+ amount: big.NewInt(-1),
+ malleate: func(corevm.StateDB) {},
+ wantPanic: true,
},
}
@@ -141,6 +149,14 @@ func (suite *KeeperTestSuite) TestSubBalance() {
tc.malleate(vmdb)
prev := vmdb.GetBalance(suite.address)
+
+ if tc.wantPanic {
+ suite.Require().Panics(func() {
+ vmdb.SubBalance(suite.address, tc.amount)
+ })
+ return
+ }
+
vmdb.SubBalance(suite.address, tc.amount)
post := vmdb.GetBalance(suite.address)
@@ -158,19 +174,19 @@ func (suite *KeeperTestSuite) TestGetNonce() {
name string
address common.Address
expectedNonce uint64
- malleate func(vm.StateDB)
+ malleate func(corevm.StateDB)
}{
{
name: "account not found",
address: utiltx.GenerateAddress(),
expectedNonce: 0,
- malleate: func(vm.StateDB) {},
+ malleate: func(corevm.StateDB) {},
},
{
name: "existing account",
address: suite.address,
expectedNonce: 1,
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
vmdb.SetNonce(suite.address, 1)
},
},
@@ -227,25 +243,25 @@ func (suite *KeeperTestSuite) TestGetCodeHash() {
name string
address common.Address
expHash common.Hash
- malleate func(vm.StateDB)
+ malleate func(corevm.StateDB)
}{
{
name: "account not found",
address: utiltx.GenerateAddress(),
expHash: common.Hash{},
- malleate: func(vm.StateDB) {},
+ malleate: func(corevm.StateDB) {},
},
{
name: "account with EmptyCodeHash",
address: addr,
expHash: common.BytesToHash(evmtypes.EmptyCodeHash),
- malleate: func(vm.StateDB) {},
+ malleate: func(corevm.StateDB) {},
},
{
name: "account with non-empty code hash",
address: suite.address,
expHash: crypto.Keccak256Hash([]byte("codeHash")),
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
vmdb.SetCode(suite.address, []byte("codeHash"))
},
},
@@ -348,13 +364,13 @@ func (suite *KeeperTestSuite) TestKeeperSetCode() {
func (suite *KeeperTestSuite) TestRefund() {
testCases := []struct {
name string
- malleate func(vm.StateDB)
+ malleate func(corevm.StateDB)
expRefund uint64
expPanic bool
}{
{
name: "pass - add and subtract refund",
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
vmdb.AddRefund(11)
},
expRefund: 1,
@@ -362,7 +378,7 @@ func (suite *KeeperTestSuite) TestRefund() {
},
{
name: "fail - subtract amount > current refund",
- malleate: func(vm.StateDB) {
+ malleate: func(corevm.StateDB) {
},
expRefund: 0,
expPanic: true,
@@ -418,7 +434,7 @@ func (suite *KeeperTestSuite) TestCommittedState() {
vmdb := suite.StateDB()
vmdb.SetState(suite.address, key, value1)
- err := vmdb.Commit()
+ err := vmdb.CommitMultiStore(false)
suite.Require().NoError(err)
vmdb = suite.StateDB()
@@ -427,7 +443,7 @@ func (suite *KeeperTestSuite) TestCommittedState() {
suite.Require().Equal(value2, tmp)
tmp = vmdb.GetCommittedState(suite.address, key)
suite.Require().Equal(value1, tmp)
- err = vmdb.Commit()
+ err = vmdb.CommitMultiStore(false)
suite.Require().NoError(err)
vmdb = suite.StateDB()
@@ -446,7 +462,7 @@ func (suite *KeeperTestSuite) TestSuicide() {
db.SetState(suite.address, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))
}
- suite.Require().NoError(db.Commit())
+ suite.Require().NoError(db.CommitMultiStore(false))
db = suite.StateDB()
// Generate 2nd address
@@ -469,7 +485,7 @@ func (suite *KeeperTestSuite) TestSuicide() {
suite.Require().Equal(true, db.HasSuicided(suite.address))
// Commit state
- suite.Require().NoError(db.Commit())
+ suite.Require().NoError(db.CommitMultiStore(false))
db = suite.StateDB()
// Check code is deleted
@@ -494,19 +510,19 @@ func (suite *KeeperTestSuite) TestExist() {
testCases := []struct {
name string
address common.Address
- malleate func(vm.StateDB)
+ malleate func(corevm.StateDB)
exists bool
}{
{
name: "success, account exists",
address: suite.address,
- malleate: func(vm.StateDB) {},
+ malleate: func(corevm.StateDB) {},
exists: true,
},
{
name: "success, has suicided",
address: suite.address,
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
vmdb.Suicide(suite.address)
},
exists: true,
@@ -514,7 +530,7 @@ func (suite *KeeperTestSuite) TestExist() {
{
name: "success, account doesn't exist",
address: utiltx.GenerateAddress(),
- malleate: func(vm.StateDB) {},
+ malleate: func(corevm.StateDB) {},
exists: false,
},
}
@@ -530,32 +546,77 @@ func (suite *KeeperTestSuite) TestExist() {
}
func (suite *KeeperTestSuite) TestEmpty() {
+ randomAddr1 := utiltx.GenerateAddress()
+ randomAddr2 := utiltx.GenerateAddress()
+ randomAddr3 := utiltx.GenerateAddress()
testCases := []struct {
name string
address common.Address
- malleate func(vm.StateDB)
+ malleate func(corevm.StateDB)
empty bool
}{
{
name: "empty, account exists",
address: suite.address,
- malleate: func(vm.StateDB) {},
+ malleate: func(corevm.StateDB) {},
empty: true,
},
{
name: "not empty, positive balance",
address: suite.address,
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
vmdb.AddBalance(suite.address, big.NewInt(100))
},
empty: false,
},
{
name: "empty, account doesn't exist",
- address: utiltx.GenerateAddress(),
- malleate: func(vm.StateDB) {},
+ address: randomAddr1,
+ malleate: func(corevm.StateDB) {},
empty: true,
},
+ {
+ name: "not empty, account = none, nonce = none, balance != 0, code = none",
+ address: randomAddr2,
+ malleate: func(vmdb corevm.StateDB) {
+ vmdb.AddBalance(randomAddr2, big.NewInt(1))
+
+ // delete account
+ ctx := vmdb.(vm.CStateDB).ForTest_GetCurrentContext()
+ acc := suite.app.AccountKeeper.GetAccount(ctx, randomAddr2.Bytes())
+ suite.Require().NotNil(acc) // created by bank transfer
+ suite.app.AccountKeeper.RemoveAccount(ctx, acc)
+ suite.Require().Nil(suite.app.AccountKeeper.GetAccount(ctx, randomAddr2.Bytes()))
+ },
+ empty: false,
+ },
+ {
+ name: "not empty, account = none, nonce = none, balance = 0, code = exists",
+ address: randomAddr3,
+ malleate: func(vmdb corevm.StateDB) {
+ vmdb.SetCode(randomAddr3, []byte("code"))
+
+ // delete account
+ ctx := vmdb.(vm.CStateDB).ForTest_GetCurrentContext()
+ acc := suite.app.AccountKeeper.GetAccount(ctx, randomAddr3.Bytes())
+ suite.Require().NotNil(acc) // created by set code
+ suite.app.AccountKeeper.RemoveAccount(ctx, acc)
+ suite.Require().Nil(suite.app.AccountKeeper.GetAccount(ctx, randomAddr3.Bytes()))
+ },
+ empty: false,
+ },
+ {
+ name: "not empty, account = exists, nonce > 0, balance = 0, code = none",
+ address: suite.address,
+ malleate: func(vmdb corevm.StateDB) {
+ suite.Require().Equal(uint64(0), vmdb.GetNonce(suite.address))
+
+ vmdb.SetNonce(suite.address, 1)
+
+ suite.Require().Equal(uint64(1), vmdb.GetNonce(suite.address))
+ },
+ empty: false,
+ },
}
for _, tc := range testCases {
@@ -576,13 +637,13 @@ func (suite *KeeperTestSuite) TestSnapshot() {
testCases := []struct {
name string
- malleate func(vm.StateDB)
+ malleate func(corevm.StateDB)
}{
{
name: "simple revert",
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
revision := vmdb.Snapshot()
- suite.Require().Zero(revision)
+ suite.Require().Equal(0, revision)
vmdb.SetState(suite.address, key, value1)
suite.Require().Equal(value1, vmdb.GetState(suite.address, key))
@@ -595,9 +656,9 @@ func (suite *KeeperTestSuite) TestSnapshot() {
},
{
name: "nested snapshot/revert",
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
revision1 := vmdb.Snapshot()
- suite.Require().Zero(revision1)
+ suite.Require().Equal(0, revision1)
vmdb.SetState(suite.address, key, value1)
@@ -615,7 +676,7 @@ func (suite *KeeperTestSuite) TestSnapshot() {
},
{
name: "jump revert",
- malleate: func(vmdb vm.StateDB) {
+ malleate: func(vmdb corevm.StateDB) {
revision1 := vmdb.Snapshot()
vmdb.SetState(suite.address, key, value1)
vmdb.Snapshot()
@@ -654,152 +715,6 @@ func (suite *KeeperTestSuite) CreateTestTx(msg *evmtypes.MsgEthereumTx, priv cry
return txBuilder.GetTx()
}
-func (suite *KeeperTestSuite) TestAddLog() {
- addr, privKey := utiltx.NewAddrKey()
- ethTx1Params := &evmtypes.EvmTxArgs{
- From: addr,
- ChainID: big.NewInt(constants.TestnetEIP155ChainId),
- Nonce: 0,
- To: &suite.address,
- Amount: big.NewInt(1),
- GasLimit: 100000,
- GasPrice: big.NewInt(1),
- Input: []byte("test"),
- }
- msg1 := evmtypes.NewTx(ethTx1Params)
- tx1 := suite.CreateTestTx(msg1, privKey)
- msg1, _ = tx1.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
- txHash1 := msg1.AsTransaction().Hash()
-
- ethTx2Params := &evmtypes.EvmTxArgs{
- From: addr,
- ChainID: big.NewInt(constants.TestnetEIP155ChainId),
- Nonce: 2,
- To: &suite.address,
- Amount: big.NewInt(1),
- GasLimit: 100000,
- GasPrice: big.NewInt(1),
- Input: []byte("test"),
- }
- msg2 := evmtypes.NewTx(ethTx2Params)
- tx2 := suite.CreateTestTx(msg2, privKey)
- msg2, _ = tx2.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
- txHash2 := msg2.AsTransaction().Hash()
-
- ethTx3Params := &evmtypes.EvmTxArgs{
- From: addr,
- ChainID: big.NewInt(constants.TestnetEIP155ChainId),
- Nonce: 0,
- To: &suite.address,
- Amount: big.NewInt(1),
- GasLimit: 100000,
- GasFeeCap: big.NewInt(1),
- GasTipCap: big.NewInt(1),
- Input: []byte("test"),
- }
- msg3 := evmtypes.NewTx(ethTx3Params)
- tx3 := suite.CreateTestTx(msg3, privKey)
- msg3, _ = tx3.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
- txHash3 := msg3.AsTransaction().Hash()
-
- ethTx4Params := &evmtypes.EvmTxArgs{
- From: addr,
- ChainID: big.NewInt(constants.TestnetEIP155ChainId),
- Nonce: 1,
- To: &suite.address,
- Amount: big.NewInt(1),
- GasLimit: 100000,
- GasFeeCap: big.NewInt(1),
- GasTipCap: big.NewInt(1),
- Input: []byte("test"),
- }
- msg4 := evmtypes.NewTx(ethTx4Params)
- tx4 := suite.CreateTestTx(msg4, privKey)
- msg4, _ = tx4.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
- txHash4 := msg4.AsTransaction().Hash()
-
- testCases := []struct {
- name string
- hash common.Hash
- log, expLog *ethtypes.Log // pre and post populating log fields
- malleate func(vm.StateDB)
- }{
- {
- name: "legacy tx - tx hash from message",
- hash: txHash1,
- log: ðtypes.Log{
- Address: addr,
- Topics: make([]common.Hash, 0),
- },
- expLog: ðtypes.Log{
- Address: addr,
- TxHash: txHash1,
- Topics: make([]common.Hash, 0),
- },
- malleate: func(vm.StateDB) {},
- },
- {
- name: "legacy tx - tx hash from message",
- hash: txHash2,
- log: ðtypes.Log{
- Address: addr,
- Topics: make([]common.Hash, 0),
- },
- expLog: ðtypes.Log{
- Address: addr,
- TxHash: txHash2,
- Topics: make([]common.Hash, 0),
- },
- malleate: func(vm.StateDB) {},
- },
- {
- name: "Dynamic Fee - tx hash from message",
- hash: txHash3,
- log: ðtypes.Log{
- Address: addr,
- Topics: make([]common.Hash, 0),
- },
- expLog: ðtypes.Log{
- Address: addr,
- TxHash: txHash3,
- Topics: make([]common.Hash, 0),
- },
- malleate: func(vm.StateDB) {},
- },
- {
- name: "Dynamic Fee - tx hash from message",
- hash: txHash4,
- log: ðtypes.Log{
- Address: addr,
- Topics: make([]common.Hash, 0),
- },
- expLog: ðtypes.Log{
- Address: addr,
- TxHash: txHash4,
- Topics: make([]common.Hash, 0),
- },
- malleate: func(vm.StateDB) {},
- },
- }
-
- for _, tc := range testCases {
- suite.Run(tc.name, func() {
- suite.SetupTest()
- vmdb := statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewTxConfig(
- common.BytesToHash(suite.ctx.HeaderHash()),
- tc.hash,
- 0, 0,
- ))
- tc.malleate(vmdb)
-
- vmdb.AddLog(tc.log)
- logs := vmdb.Logs()
- suite.Require().Equal(1, len(logs))
- suite.Require().Equal(tc.expLog, logs[0])
- })
- }
-}
-
func (suite *KeeperTestSuite) TestPrepareAccessList() {
dest := utiltx.GenerateAddress()
precompiles := []common.Address{utiltx.GenerateAddress(), utiltx.GenerateAddress()}
@@ -960,101 +875,3 @@ func (suite *KeeperTestSuite) _TestForEachStorage() {
}
}
*/
-
-func (suite *KeeperTestSuite) TestSetBalance() {
- amount := big.NewInt(-10)
-
- testCases := []struct {
- name string
- addr common.Address
- malleate func()
- expErr bool
- }{
- {
- name: "fail - address without funds - invalid amount",
- addr: suite.address,
- malleate: func() {},
- expErr: true,
- },
- {
- name: "pass - mint to address",
- addr: suite.address,
- malleate: func() {
- amount = big.NewInt(100)
- },
- expErr: false,
- },
- {
- name: "pass - burn from address",
- addr: suite.address,
- malleate: func() {
- amount = big.NewInt(60)
- },
- expErr: false,
- },
- {
- name: "fail - address with funds - invalid amount",
- addr: suite.address,
- malleate: func() {
- amount = big.NewInt(-10)
- },
- expErr: true,
- },
- }
-
- for _, tc := range testCases {
- suite.Run(tc.name, func() {
- suite.SetupTest()
- tc.malleate()
- err := suite.app.EvmKeeper.SetBalance(suite.ctx, tc.addr, amount)
- if tc.expErr {
- suite.Require().Error(err)
- } else {
- balance := suite.app.EvmKeeper.GetBalance(suite.ctx, tc.addr)
- suite.Require().NoError(err)
- suite.Require().Equal(amount, balance)
- }
- })
- }
-}
-
-func (suite *KeeperTestSuite) TestDeleteAccount() {
- supply := big.NewInt(100)
- contractAddr := suite.DeployTestContract(suite.T(), suite.address, supply)
-
- testCases := []struct {
- name string
- addr common.Address
- expErr bool
- }{
- {
- name: "fail - remove address is prohibited, contract only",
- addr: suite.address,
- expErr: true,
- },
- {
- name: "pass - remove non-existence address",
- addr: common.HexToAddress("void"),
- expErr: false,
- },
- {
- name: "pass - remove deployed contract",
- addr: contractAddr,
- expErr: false,
- },
- }
-
- for _, tc := range testCases {
- suite.Run(tc.name, func() {
- suite.SetupTest()
- err := suite.app.EvmKeeper.DeleteAccount(suite.ctx, tc.addr)
- if tc.expErr {
- suite.Require().Error(err)
- } else {
- suite.Require().NoError(err)
- balance := suite.app.EvmKeeper.GetBalance(suite.ctx, tc.addr)
- suite.Require().Equal(new(big.Int), balance)
- }
- })
- }
-}
diff --git a/x/evm/keeper/utils_test.go b/x/evm/keeper/utils_test.go
index 43a6c679fd..bc04fa6607 100644
--- a/x/evm/keeper/utils_test.go
+++ b/x/evm/keeper/utils_test.go
@@ -6,10 +6,10 @@ import (
"time"
evertypes "github.com/EscanBE/evermint/v12/types"
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
"github.com/EscanBE/evermint/v12/server/config"
"github.com/EscanBE/evermint/v12/testutil"
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -36,8 +36,8 @@ func (suite *KeeperTestSuite) Commit() {
suite.queryClient = evmtypes.NewQueryClient(queryHelper)
}
-func (suite *KeeperTestSuite) StateDB() *statedb.StateDB {
- return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash())))
+func (suite *KeeperTestSuite) StateDB() evmvm.CStateDB {
+ return evmvm.NewStateDB(suite.ctx, suite.app.EvmKeeper, suite.app.AccountKeeper, suite.app.BankKeeper)
}
// DeployTestContract deploy a test erc20 contract and returns the contract address
diff --git a/x/evm/statedb/config.go b/x/evm/statedb/config.go
deleted file mode 100644
index 17e8317a9a..0000000000
--- a/x/evm/statedb/config.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package statedb
-
-import (
- "math/big"
-
- core "github.com/ethereum/go-ethereum/core"
- ethtypes "github.com/ethereum/go-ethereum/core/types"
-
- evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
- "github.com/ethereum/go-ethereum/common"
- ethparams "github.com/ethereum/go-ethereum/params"
-)
-
-// TxConfig encapulates the readonly information of current tx for `StateDB`.
-type TxConfig struct {
- BlockHash common.Hash // hash of current block
- TxHash common.Hash // hash of current tx
- TxIndex uint // the index of current transaction
- LogIndex uint // the index of next log within current block
- TxType int16 // transaction type, optionally provided
-}
-
-// NewTxConfig returns a TxConfig
-func NewTxConfig(bhash, thash common.Hash, txIndex, logIndex uint) TxConfig {
- return TxConfig{
- BlockHash: bhash,
- TxHash: thash,
- TxIndex: txIndex,
- LogIndex: logIndex,
- TxType: -1,
- }
-}
-
-func (m TxConfig) WithTxTypeFromMessage(msg core.Message) TxConfig {
- if msg.GasTipCap() != nil || msg.GasFeeCap() != nil {
- m.TxType = ethtypes.DynamicFeeTxType
- } else if msg.AccessList() != nil {
- m.TxType = ethtypes.AccessListTxType
- } else {
- m.TxType = ethtypes.LegacyTxType
- }
- return m
-}
-
-// NewEmptyTxConfig construct an empty TxConfig,
-// used in context where there's no transaction, e.g. `eth_call`/`eth_estimateGas`.
-func NewEmptyTxConfig(bhash common.Hash) TxConfig {
- return TxConfig{
- BlockHash: bhash,
- TxHash: common.Hash{},
- TxIndex: 0,
- LogIndex: 0,
- TxType: -1,
- }
-}
-
-// EVMConfig encapsulates common parameters needed to create an EVM to execute a message
-// It's mainly to reduce the number of method parameters
-type EVMConfig struct {
- Params evmtypes.Params
- ChainConfig *ethparams.ChainConfig
- CoinBase common.Address
- BaseFee *big.Int
- NoBaseFee bool
-}
diff --git a/x/evm/statedb/interfaces.go b/x/evm/statedb/interfaces.go
deleted file mode 100644
index ae016a2c86..0000000000
--- a/x/evm/statedb/interfaces.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package statedb
-
-import (
- sdk "github.com/cosmos/cosmos-sdk/types"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/vm"
-)
-
-// ExtStateDB defines an extension to the interface provided by the go-ethereum
-// codebase to support additional state transition functionalities. In particular
-// it supports appending a new entry to the state journal through
-// AppendJournalEntry so that the state can be reverted after running
-// stateful precompiled contracts.
-type ExtStateDB interface {
- vm.StateDB
- AppendJournalEntry(JournalEntry)
-}
-
-// Keeper provide underlying storage of StateDB
-type Keeper interface {
- // Read methods
- GetAccount(ctx sdk.Context, addr common.Address) *Account
- GetState(ctx sdk.Context, addr common.Address, key common.Hash) common.Hash
- GetCode(ctx sdk.Context, codeHash common.Hash) []byte
- // the callback returns false to break early
- ForEachStorage(ctx sdk.Context, addr common.Address, cb func(key, value common.Hash) bool)
-
- // Write methods, only called by `StateDB.Commit()`
- SetAccount(ctx sdk.Context, addr common.Address, account Account) error
- SetState(ctx sdk.Context, addr common.Address, key common.Hash, value []byte)
- SetCode(ctx sdk.Context, codeHash []byte, code []byte)
- DeleteAccount(ctx sdk.Context, addr common.Address) error
-}
diff --git a/x/evm/statedb/journal.go b/x/evm/statedb/journal.go
deleted file mode 100644
index 14bb7b1df3..0000000000
--- a/x/evm/statedb/journal.go
+++ /dev/null
@@ -1,241 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package statedb
-
-import (
- "bytes"
- "math/big"
- "sort"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-// JournalEntry is a modification entry in the state change journal that can be
-// Reverted on demand.
-type JournalEntry interface {
- // Revert undoes the changes introduced by this journal entry.
- Revert(*StateDB)
-
- // Dirtied returns the Ethereum address modified by this journal entry.
- Dirtied() *common.Address
-}
-
-// journal contains the list of state modifications applied since the last state
-// commit. These are tracked to be able to be reverted in the case of an execution
-// exception or request for reversal.
-type journal struct {
- entries []JournalEntry // Current changes tracked by the journal
- dirties map[common.Address]int // Dirty accounts and the number of changes
-}
-
-// newJournal creates a new initialized journal.
-func newJournal() *journal {
- return &journal{
- dirties: make(map[common.Address]int),
- }
-}
-
-// sortedDirties sort the dirty addresses for deterministic iteration
-func (j *journal) sortedDirties() []common.Address {
- keys := make([]common.Address, 0, len(j.dirties))
- for k := range j.dirties {
- keys = append(keys, k)
- }
- sort.Slice(keys, func(i, j int) bool {
- return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) < 0
- })
- return keys
-}
-
-// append inserts a new modification entry to the end of the change journal.
-func (j *journal) append(entry JournalEntry) {
- j.entries = append(j.entries, entry)
- if addr := entry.Dirtied(); addr != nil {
- j.dirties[*addr]++
- }
-}
-
-// Revert undoes a batch of journalled modifications along with any Reverted
-// dirty handling too.
-func (j *journal) Revert(statedb *StateDB, snapshot int) {
- for i := len(j.entries) - 1; i >= snapshot; i-- {
- // Undo the changes made by the operation
- j.entries[i].Revert(statedb)
-
- // Drop any dirty tracking induced by the change
- if addr := j.entries[i].Dirtied(); addr != nil {
- if j.dirties[*addr]--; j.dirties[*addr] == 0 {
- delete(j.dirties, *addr)
- }
- }
- }
- j.entries = j.entries[:snapshot]
-}
-
-// length returns the current number of entries in the journal.
-func (j *journal) length() int {
- return len(j.entries)
-}
-
-type (
- // Changes to the account trie.
- createObjectChange struct {
- account *common.Address
- }
- resetObjectChange struct {
- prev *stateObject
- }
- suicideChange struct {
- account *common.Address
- prev bool // whether account had already suicided
- prevbalance *big.Int
- }
-
- // Changes to individual accounts.
- balanceChange struct {
- account *common.Address
- prev *big.Int
- }
- nonceChange struct {
- account *common.Address
- prev uint64
- }
- storageChange struct {
- account *common.Address
- key, prevalue common.Hash
- }
- codeChange struct {
- account *common.Address
- prevcode, prevhash []byte
- }
-
- // Changes to other state values.
- refundChange struct {
- prev uint64
- }
- addLogChange struct{}
-
- // Changes to the access list
- accessListAddAccountChange struct {
- address *common.Address
- }
- accessListAddSlotChange struct {
- address *common.Address
- slot *common.Hash
- }
-)
-
-func (ch createObjectChange) Revert(s *StateDB) {
- delete(s.stateObjects, *ch.account)
-}
-
-func (ch createObjectChange) Dirtied() *common.Address {
- return ch.account
-}
-
-func (ch resetObjectChange) Revert(s *StateDB) {
- s.setStateObject(ch.prev)
-}
-
-func (ch resetObjectChange) Dirtied() *common.Address {
- return nil
-}
-
-func (ch suicideChange) Revert(s *StateDB) {
- obj := s.getStateObject(*ch.account)
- if obj != nil {
- obj.suicided = ch.prev
- obj.setBalance(ch.prevbalance)
- }
-}
-
-func (ch suicideChange) Dirtied() *common.Address {
- return ch.account
-}
-
-func (ch balanceChange) Revert(s *StateDB) {
- s.getStateObject(*ch.account).setBalance(ch.prev)
-}
-
-func (ch balanceChange) Dirtied() *common.Address {
- return ch.account
-}
-
-func (ch nonceChange) Revert(s *StateDB) {
- s.getStateObject(*ch.account).setNonce(ch.prev)
-}
-
-func (ch nonceChange) Dirtied() *common.Address {
- return ch.account
-}
-
-func (ch codeChange) Revert(s *StateDB) {
- s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
-}
-
-func (ch codeChange) Dirtied() *common.Address {
- return ch.account
-}
-
-func (ch storageChange) Revert(s *StateDB) {
- s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
-}
-
-func (ch storageChange) Dirtied() *common.Address {
- return ch.account
-}
-
-func (ch refundChange) Revert(s *StateDB) {
- s.refund = ch.prev
-}
-
-func (ch refundChange) Dirtied() *common.Address {
- return nil
-}
-
-func (ch addLogChange) Revert(s *StateDB) {
- s.logs = s.logs[:len(s.logs)-1]
-}
-
-func (ch addLogChange) Dirtied() *common.Address {
- return nil
-}
-
-func (ch accessListAddAccountChange) Revert(s *StateDB) {
- /*
- One important invariant here, is that whenever a (addr, slot) is added, if the
- addr is not already present, the add causes two journal entries:
- - one for the address,
- - one for the (address,slot)
- Therefore, when unrolling the change, we can always blindly delete the
- (addr) at this point, since no storage adds can remain when come upon
- a single (addr) change.
- */
- s.accessList.DeleteAddress(*ch.address)
-}
-
-func (ch accessListAddAccountChange) Dirtied() *common.Address {
- return nil
-}
-
-func (ch accessListAddSlotChange) Revert(s *StateDB) {
- s.accessList.DeleteSlot(*ch.address, *ch.slot)
-}
-
-func (ch accessListAddSlotChange) Dirtied() *common.Address {
- return nil
-}
diff --git a/x/evm/statedb/mock_test.go b/x/evm/statedb/mock_test.go
deleted file mode 100644
index 620d4aabf5..0000000000
--- a/x/evm/statedb/mock_test.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package statedb_test
-
-import (
- "errors"
- "math/big"
-
- evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
-
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
- sdk "github.com/cosmos/cosmos-sdk/types"
- "github.com/ethereum/go-ethereum/common"
-)
-
-var (
- _ statedb.Keeper = &MockKeeper{}
- errAddress common.Address = common.BigToAddress(big.NewInt(100))
-)
-
-type MockAcount struct {
- account statedb.Account
- states statedb.Storage
-}
-
-type MockKeeper struct {
- accounts map[common.Address]MockAcount
- codes map[common.Hash][]byte
-}
-
-func NewMockKeeper() *MockKeeper {
- return &MockKeeper{
- accounts: make(map[common.Address]MockAcount),
- codes: make(map[common.Hash][]byte),
- }
-}
-
-func (k MockKeeper) GetAccount(_ sdk.Context, addr common.Address) *statedb.Account {
- acct, ok := k.accounts[addr]
- if !ok {
- return nil
- }
- return &acct.account
-}
-
-func (k MockKeeper) GetState(_ sdk.Context, addr common.Address, key common.Hash) common.Hash {
- return k.accounts[addr].states[key]
-}
-
-func (k MockKeeper) GetCode(_ sdk.Context, codeHash common.Hash) []byte {
- return k.codes[codeHash]
-}
-
-func (k MockKeeper) ForEachStorage(_ sdk.Context, addr common.Address, cb func(key, value common.Hash) bool) {
- if acct, ok := k.accounts[addr]; ok {
- for k, v := range acct.states {
- if !cb(k, v) {
- return
- }
- }
- }
-}
-
-func (k MockKeeper) SetAccount(_ sdk.Context, addr common.Address, account statedb.Account) error {
- if addr == errAddress {
- return errors.New("mock db error")
- }
- acct, exists := k.accounts[addr]
- if exists {
- // update
- acct.account = account
- k.accounts[addr] = acct
- } else {
- k.accounts[addr] = MockAcount{account: account, states: make(statedb.Storage)}
- }
- return nil
-}
-
-func (k MockKeeper) SetState(_ sdk.Context, addr common.Address, key common.Hash, value []byte) {
- if acct, ok := k.accounts[addr]; ok {
- if len(value) == 0 {
- delete(acct.states, key)
- } else {
- acct.states[key] = common.BytesToHash(value)
- }
- }
-}
-
-func (k MockKeeper) SetCode(_ sdk.Context, codeHash []byte, code []byte) {
- k.codes[common.BytesToHash(codeHash)] = code
-}
-
-func (k MockKeeper) DeleteAccount(_ sdk.Context, addr common.Address) error {
- if addr == errAddress {
- return errors.New("mock db error")
- }
- old := k.accounts[addr]
- delete(k.accounts, addr)
- if !evmtypes.IsEmptyCodeHash(common.BytesToHash(old.account.CodeHash)) {
- delete(k.codes, common.BytesToHash(old.account.CodeHash))
- }
- return nil
-}
-
-func (k MockKeeper) Clone() *MockKeeper {
- accounts := make(map[common.Address]MockAcount, len(k.accounts))
- for k, v := range k.accounts {
- accounts[k] = v
- }
- codes := make(map[common.Hash][]byte, len(k.codes))
- for k, v := range k.codes {
- codes[k] = v
- }
- return &MockKeeper{accounts, codes}
-}
diff --git a/x/evm/statedb/state_object.go b/x/evm/statedb/state_object.go
deleted file mode 100644
index 6ffaef33f3..0000000000
--- a/x/evm/statedb/state_object.go
+++ /dev/null
@@ -1,234 +0,0 @@
-package statedb
-
-import (
- "bytes"
- "math/big"
- "sort"
-
- evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-// Account is the Ethereum consensus representation of accounts.
-// These objects are stored in the storage of auth module.
-type Account struct {
- Nonce uint64
- Balance *big.Int
- CodeHash []byte
-}
-
-// NewEmptyAccount returns an empty account.
-func NewEmptyAccount() *Account {
- return &Account{
- Balance: new(big.Int),
- CodeHash: evmtypes.EmptyCodeHash,
- }
-}
-
-// IsContract returns if the account contains contract code.
-func (acct Account) IsContract() bool {
- return !evmtypes.IsEmptyCodeHash(common.BytesToHash(acct.CodeHash))
-}
-
-// Storage represents in-memory cache/buffer of contract storage.
-type Storage map[common.Hash]common.Hash
-
-// SortedKeys sort the keys for deterministic iteration
-func (s Storage) SortedKeys() []common.Hash {
- keys := make([]common.Hash, 0, len(s))
- for k := range s {
- keys = append(keys, k)
- }
- sort.Slice(keys, func(i, j int) bool {
- return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) < 0
- })
- return keys
-}
-
-// stateObject is the state of an acount
-type stateObject struct {
- db *StateDB
-
- account Account
- code []byte
-
- // state storage
- originStorage Storage
- dirtyStorage Storage
-
- address common.Address
-
- // flags
- dirtyCode bool
- suicided bool
-}
-
-// newObject creates a state object.
-func newObject(db *StateDB, address common.Address, account Account) *stateObject {
- if account.Balance == nil {
- account.Balance = new(big.Int)
- }
- if account.CodeHash == nil {
- account.CodeHash = evmtypes.EmptyCodeHash
- }
- return &stateObject{
- db: db,
- address: address,
- account: account,
- originStorage: make(Storage),
- dirtyStorage: make(Storage),
- }
-}
-
-// empty returns whether the account is considered empty.
-func (s *stateObject) empty() bool {
- return s.account.Nonce == 0 && s.account.Balance.Sign() == 0 && !s.account.IsContract()
-}
-
-func (s *stateObject) markSuicided() {
- s.suicided = true
-}
-
-// AddBalance adds amount to s's balance.
-// It is used to add funds to the destination account of a transfer.
-func (s *stateObject) AddBalance(amount *big.Int) {
- if amount.Sign() == 0 {
- return
- }
- s.SetBalance(new(big.Int).Add(s.Balance(), amount))
-}
-
-// SubBalance removes amount from s's balance.
-// It is used to remove funds from the origin account of a transfer.
-func (s *stateObject) SubBalance(amount *big.Int) {
- if amount.Sign() == 0 {
- return
- }
- s.SetBalance(new(big.Int).Sub(s.Balance(), amount))
-}
-
-// SetBalance update account balance.
-func (s *stateObject) SetBalance(amount *big.Int) {
- s.db.journal.append(balanceChange{
- account: &s.address,
- prev: new(big.Int).Set(s.account.Balance),
- })
- s.setBalance(amount)
-}
-
-func (s *stateObject) setBalance(amount *big.Int) {
- s.account.Balance = amount
-}
-
-//
-// Attribute accessors
-//
-
-// Returns the address of the contract/account
-func (s *stateObject) Address() common.Address {
- return s.address
-}
-
-// Code returns the contract code associated with this object, if any.
-func (s *stateObject) Code() []byte {
- if s.code != nil {
- return s.code
- }
- if !s.account.IsContract() {
- return nil
- }
- code := s.db.keeper.GetCode(s.db.ctx, common.BytesToHash(s.CodeHash()))
- s.code = code
- return code
-}
-
-// CodeSize returns the size of the contract code associated with this object,
-// or zero if none.
-func (s *stateObject) CodeSize() int {
- return len(s.Code())
-}
-
-// SetCode set contract code to account
-func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
- prevcode := s.Code()
- s.db.journal.append(codeChange{
- account: &s.address,
- prevhash: s.CodeHash(),
- prevcode: prevcode,
- })
- s.setCode(codeHash, code)
-}
-
-func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
- s.code = code
- s.account.CodeHash = codeHash[:]
- s.dirtyCode = true
-}
-
-// SetCode set nonce to account
-func (s *stateObject) SetNonce(nonce uint64) {
- s.db.journal.append(nonceChange{
- account: &s.address,
- prev: s.account.Nonce,
- })
- s.setNonce(nonce)
-}
-
-func (s *stateObject) setNonce(nonce uint64) {
- s.account.Nonce = nonce
-}
-
-// CodeHash returns the code hash of account
-func (s *stateObject) CodeHash() []byte {
- return s.account.CodeHash
-}
-
-// Balance returns the balance of account
-func (s *stateObject) Balance() *big.Int {
- return s.account.Balance
-}
-
-// Nonce returns the nonce of account
-func (s *stateObject) Nonce() uint64 {
- return s.account.Nonce
-}
-
-// GetCommittedState query the committed state
-func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
- if value, cached := s.originStorage[key]; cached {
- return value
- }
- // If no live objects are available, load it from keeper
- value := s.db.keeper.GetState(s.db.ctx, s.Address(), key)
- s.originStorage[key] = value
- return value
-}
-
-// GetState query the current state (including dirty state)
-func (s *stateObject) GetState(key common.Hash) common.Hash {
- if value, dirty := s.dirtyStorage[key]; dirty {
- return value
- }
- return s.GetCommittedState(key)
-}
-
-// SetState sets the contract state
-func (s *stateObject) SetState(key common.Hash, value common.Hash) {
- // If the new value is the same as old, don't set
- prev := s.GetState(key)
- if prev == value {
- return
- }
- // New value is different, update and journal the change
- s.db.journal.append(storageChange{
- account: &s.address,
- key: key,
- prevalue: prev,
- })
- s.setState(key, value)
-}
-
-func (s *stateObject) setState(key, value common.Hash) {
- s.dirtyStorage[key] = value
-}
diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go
deleted file mode 100644
index 372cc6357b..0000000000
--- a/x/evm/statedb/statedb.go
+++ /dev/null
@@ -1,486 +0,0 @@
-package statedb
-
-import (
- "fmt"
- "math/big"
- "sort"
-
- "github.com/EscanBE/evermint/v12/utils"
-
- errorsmod "cosmossdk.io/errors"
- sdk "github.com/cosmos/cosmos-sdk/types"
- "github.com/ethereum/go-ethereum/common"
- ethtypes "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/crypto"
-)
-
-// revision is the identifier of a version of state.
-// it consists of an auto-increment id and a journal index.
-// it's safer to use than using journal index alone.
-type revision struct {
- id int
- journalIndex int
-}
-
-var _ vm.StateDB = &StateDB{}
-
-// preventCommit is a flag to prevent committing state changes to the underlying storage.
-// This is used for testing purposes to simulate cases where Commit() is failed.
-var preventCommit bool
-
-// StateDB structs within the ethereum protocol are used to store anything
-// within the merkle trie. StateDBs take care of caching and storing
-// nested states. It's the general query interface to retrieve:
-// * Contracts
-// * Accounts
-type StateDB struct {
- keeper Keeper
- ctx sdk.Context
-
- // Journal of state modifications. This is the backbone of
- // Snapshot and RevertToSnapshot.
- journal *journal
- validRevisions []revision
- nextRevisionID int
-
- stateObjects map[common.Address]*stateObject
-
- txConfig TxConfig
-
- // The refund counter, also used by state transitioning.
- refund uint64
-
- // Per-transaction logs
- logs []*ethtypes.Log
-
- // Per-transaction access list
- accessList *accessList
-}
-
-// New creates a new state from a given trie.
-func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB {
- return &StateDB{
- keeper: keeper,
- ctx: ctx,
- stateObjects: make(map[common.Address]*stateObject),
- journal: newJournal(),
- accessList: newAccessList(),
-
- txConfig: txConfig,
- }
-}
-
-// Keeper returns the underlying `Keeper`
-func (s *StateDB) Keeper() Keeper {
- return s.keeper
-}
-
-// GetContext returns the transaction Context.
-func (s *StateDB) GetContext() sdk.Context {
- return s.ctx
-}
-
-// AddLog adds a log, called by evm.
-func (s *StateDB) AddLog(log *ethtypes.Log) {
- s.journal.append(addLogChange{})
-
- log.TxHash = s.txConfig.TxHash
- log.BlockHash = s.txConfig.BlockHash
- log.TxIndex = s.txConfig.TxIndex
- log.Index = s.txConfig.LogIndex + uint(len(s.logs))
- s.logs = append(s.logs, log)
-}
-
-// Logs returns the logs of current transaction.
-func (s *StateDB) Logs() []*ethtypes.Log {
- return s.logs
-}
-
-// AddRefund adds gas to the refund counter
-func (s *StateDB) AddRefund(gas uint64) {
- s.journal.append(refundChange{prev: s.refund})
- s.refund += gas
-}
-
-// SubRefund removes gas from the refund counter.
-// This method will panic if the refund counter goes below zero
-func (s *StateDB) SubRefund(gas uint64) {
- s.journal.append(refundChange{prev: s.refund})
- if gas > s.refund {
- panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund))
- }
- s.refund -= gas
-}
-
-// Exist reports whether the given account address exists in the state.
-// Notably this also returns true for suicided accounts.
-func (s *StateDB) Exist(addr common.Address) bool {
- return s.getStateObject(addr) != nil
-}
-
-// Empty returns whether the state object is either non-existent
-// or empty according to the EIP161 specification (balance = nonce = code = 0)
-func (s *StateDB) Empty(addr common.Address) bool {
- so := s.getStateObject(addr)
- return so == nil || so.empty()
-}
-
-// GetBalance retrieves the balance from the given address or 0 if object not found
-func (s *StateDB) GetBalance(addr common.Address) *big.Int {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.Balance()
- }
- return common.Big0
-}
-
-// GetNonce returns the nonce of account, 0 if not exists.
-func (s *StateDB) GetNonce(addr common.Address) uint64 {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.Nonce()
- }
-
- return 0
-}
-
-// GetCode returns the code of account, nil if not exists.
-func (s *StateDB) GetCode(addr common.Address) []byte {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.Code()
- }
- return nil
-}
-
-// GetCodeSize returns the code size of account.
-func (s *StateDB) GetCodeSize(addr common.Address) int {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.CodeSize()
- }
- return 0
-}
-
-// GetCodeHash returns the code hash of account.
-func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
- stateObject := s.getStateObject(addr)
- if stateObject == nil {
- return common.Hash{}
- }
- return common.BytesToHash(stateObject.CodeHash())
-}
-
-// GetState retrieves a value from the given account's storage trie.
-func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.GetState(hash)
- }
- return common.Hash{}
-}
-
-// GetCommittedState retrieves a value from the given account's committed storage trie.
-func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.GetCommittedState(hash)
- }
- return common.Hash{}
-}
-
-// GetRefund returns the current value of the refund counter.
-func (s *StateDB) GetRefund() uint64 {
- return s.refund
-}
-
-// HasSuicided returns if the contract is suicided in current transaction.
-func (s *StateDB) HasSuicided(addr common.Address) bool {
- stateObject := s.getStateObject(addr)
- if stateObject != nil {
- return stateObject.suicided
- }
- return false
-}
-
-// AddPreimage records a SHA3 preimage seen by the VM.
-// AddPreimage performs a no-op since the EnablePreimageRecording flag is disabled
-// on the vm.Config during state transitions. No store trie preimages are written
-// to the database.
-func (s *StateDB) AddPreimage(_ common.Hash, _ []byte) {}
-
-// getStateObject retrieves a state object given by the address, returning nil if
-// the object is not found.
-func (s *StateDB) getStateObject(addr common.Address) *stateObject {
- // Prefer live objects if any is available
- if obj := s.stateObjects[addr]; obj != nil {
- return obj
- }
- // If no live objects are available, load it from keeper
- account := s.keeper.GetAccount(s.ctx, addr)
- if account == nil {
- return nil
- }
- // Insert into the live set
- obj := newObject(s, addr, *account)
- s.setStateObject(obj)
- return obj
-}
-
-// getOrNewStateObject retrieves a state object or create a new state object if nil.
-func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject {
- stateObject := s.getStateObject(addr)
- if stateObject == nil {
- stateObject, _ = s.createObject(addr)
- }
- return stateObject
-}
-
-// createObject creates a new state object. If there is an existing account with
-// the given address, it is overwritten and returned as the second return value.
-func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
- prev = s.getStateObject(addr)
-
- newobj = newObject(s, addr, Account{})
- if prev == nil {
- s.journal.append(createObjectChange{account: &addr})
- } else {
- s.journal.append(resetObjectChange{prev: prev})
- }
- s.setStateObject(newobj)
- if prev != nil {
- return newobj, prev
- }
- return newobj, nil
-}
-
-// CreateAccount explicitly creates a state object. If a state object with the address
-// already exists the balance is carried over to the new account.
-//
-// CreateAccount is called during the EVM CREATE operation. The situation might arise that
-// a contract does the following:
-//
-// 1. sends funds to sha(account ++ (nonce + 1))
-// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
-//
-// Carrying over the balance ensures that Ether doesn't disappear.
-func (s *StateDB) CreateAccount(addr common.Address) {
- newObj, prev := s.createObject(addr)
- if prev != nil {
- newObj.setBalance(prev.account.Balance)
- }
-}
-
-// ForEachStorage iterate the contract storage, the iteration order is not defined.
-func (s *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
- so := s.getStateObject(addr)
- if so == nil {
- return nil
- }
- s.keeper.ForEachStorage(s.ctx, addr, func(key, value common.Hash) bool {
- if value, dirty := so.dirtyStorage[key]; dirty {
- return cb(key, value)
- }
- if len(value) > 0 {
- return cb(key, value)
- }
- return true
- })
- return nil
-}
-
-func (s *StateDB) setStateObject(object *stateObject) {
- s.stateObjects[object.Address()] = object
-}
-
-/*
- * SETTERS
- */
-
-// AddBalance adds amount to the account associated with addr.
-func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) {
- stateObject := s.getOrNewStateObject(addr)
- if stateObject != nil {
- stateObject.AddBalance(amount)
- }
-}
-
-// SubBalance subtracts amount from the account associated with addr.
-func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) {
- stateObject := s.getOrNewStateObject(addr)
- if stateObject != nil {
- stateObject.SubBalance(amount)
- }
-}
-
-// SetNonce sets the nonce of account.
-func (s *StateDB) SetNonce(addr common.Address, nonce uint64) {
- stateObject := s.getOrNewStateObject(addr)
- if stateObject != nil {
- stateObject.SetNonce(nonce)
- }
-}
-
-// SetCode sets the code of account.
-func (s *StateDB) SetCode(addr common.Address, code []byte) {
- stateObject := s.getOrNewStateObject(addr)
- if stateObject != nil {
- stateObject.SetCode(crypto.Keccak256Hash(code), code)
- }
-}
-
-// SetState sets the contract state.
-func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
- stateObject := s.getOrNewStateObject(addr)
- if stateObject != nil {
- stateObject.SetState(key, value)
- }
-}
-
-// Suicide marks the given account as suicided.
-// This clears the account balance.
-//
-// The account's state object is still available until the state is committed,
-// getStateObject will return a non-nil account after Suicide.
-func (s *StateDB) Suicide(addr common.Address) bool {
- stateObject := s.getStateObject(addr)
- if stateObject == nil {
- return false
- }
- s.journal.append(suicideChange{
- account: &addr,
- prev: stateObject.suicided,
- prevbalance: new(big.Int).Set(stateObject.Balance()),
- })
- stateObject.markSuicided()
- stateObject.account.Balance = new(big.Int)
-
- return true
-}
-
-// PrepareAccessList handles the preparatory steps for executing a state transition with
-// regards to both EIP-2929 and EIP-2930:
-//
-// - Add sender to access list (2929)
-// - Add destination to access list (2929)
-// - Add precompiles to access list (2929)
-// - Add the contents of the optional tx access list (2930)
-//
-// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number.
-func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list ethtypes.AccessList) {
- s.AddAddressToAccessList(sender)
- if dst != nil {
- s.AddAddressToAccessList(*dst)
- // If it's a create-tx, the destination will be added inside evm.create
- }
- for _, addr := range precompiles {
- s.AddAddressToAccessList(addr)
- }
- for _, el := range list {
- s.AddAddressToAccessList(el.Address)
- for _, key := range el.StorageKeys {
- s.AddSlotToAccessList(el.Address, key)
- }
- }
-}
-
-// AddAddressToAccessList adds the given address to the access list
-func (s *StateDB) AddAddressToAccessList(addr common.Address) {
- if s.accessList.AddAddress(addr) {
- s.journal.append(accessListAddAccountChange{&addr})
- }
-}
-
-// AddSlotToAccessList adds the given (address, slot)-tuple to the access list
-func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
- addrMod, slotMod := s.accessList.AddSlot(addr, slot)
- if addrMod {
- // In practice, this should not happen, since there is no way to enter the
- // scope of 'address' without having the 'address' become already added
- // to the access list (via call-variant, create, etc).
- // Better safe than sorry, though
- s.journal.append(accessListAddAccountChange{&addr})
- }
- if slotMod {
- s.journal.append(accessListAddSlotChange{
- address: &addr,
- slot: &slot,
- })
- }
-}
-
-// AddressInAccessList returns true if the given address is in the access list.
-func (s *StateDB) AddressInAccessList(addr common.Address) bool {
- return s.accessList.ContainsAddress(addr)
-}
-
-// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list.
-func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
- return s.accessList.Contains(addr, slot)
-}
-
-// Snapshot returns an identifier for the current revision of the state.
-func (s *StateDB) Snapshot() int {
- id := s.nextRevisionID
- s.nextRevisionID++
- s.validRevisions = append(s.validRevisions, revision{id, s.journal.length()})
- return id
-}
-
-// RevertToSnapshot reverts all state changes made since the given revision.
-func (s *StateDB) RevertToSnapshot(revid int) {
- // Find the snapshot in the stack of valid snapshots.
- idx := sort.Search(len(s.validRevisions), func(i int) bool {
- return s.validRevisions[i].id >= revid
- })
- if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid {
- panic(fmt.Errorf("revision id %v cannot be reverted", revid))
- }
- snapshot := s.validRevisions[idx].journalIndex
-
- // Replay the journal to undo changes and remove invalidated snapshots
- s.journal.Revert(s, snapshot)
- s.validRevisions = s.validRevisions[:idx]
-}
-
-// Commit writes the dirty states to keeper
-// the StateDB object should be discarded after committed.
-func (s *StateDB) Commit() error {
- if preventCommit {
- return fmt.Errorf("failed to commit state changes")
- }
- for _, addr := range s.journal.sortedDirties() {
- obj := s.stateObjects[addr]
- if obj.suicided {
- if err := s.keeper.DeleteAccount(s.ctx, obj.Address()); err != nil {
- return errorsmod.Wrap(err, "failed to delete account")
- }
- } else {
- if obj.code != nil && obj.dirtyCode {
- s.keeper.SetCode(s.ctx, obj.CodeHash(), obj.code)
- }
- if err := s.keeper.SetAccount(s.ctx, obj.Address(), obj.account); err != nil {
- return errorsmod.Wrap(err, "failed to set account")
- }
- for _, key := range obj.dirtyStorage.SortedKeys() {
- value := obj.dirtyStorage[key]
- // Skip noop changes, persist actual changes
- if value == obj.originStorage[key] {
- continue
- }
- s.keeper.SetState(s.ctx, obj.Address(), key, value.Bytes())
- }
- }
- }
- return nil
-}
-
-// ToggleStateDBPreventCommit toggles the flag to prevent committing state changes to the underlying storage.
-// This is used for testing purposes to simulate cases where Commit() is failed.
-func (s *StateDB) ToggleStateDBPreventCommit(prevent bool) {
- if !utils.IsTestnet(s.ctx.ChainID()) {
- panic("can only be called during testing")
- }
- preventCommit = prevent
-}
diff --git a/x/evm/statedb/statedb_test.go b/x/evm/statedb/statedb_test.go
deleted file mode 100644
index 0ec2f92e05..0000000000
--- a/x/evm/statedb/statedb_test.go
+++ /dev/null
@@ -1,586 +0,0 @@
-package statedb_test
-
-import (
- "math/big"
- "testing"
-
- evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
-
- "github.com/EscanBE/evermint/v12/x/evm/statedb"
- sdk "github.com/cosmos/cosmos-sdk/types"
- "github.com/ethereum/go-ethereum/common"
- ethtypes "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/stretchr/testify/suite"
-)
-
-var (
- address common.Address = common.BigToAddress(big.NewInt(101))
- address2 common.Address = common.BigToAddress(big.NewInt(102))
- address3 common.Address = common.BigToAddress(big.NewInt(103))
- blockHash common.Hash = common.BigToHash(big.NewInt(9999))
- emptyTxConfig statedb.TxConfig = statedb.NewEmptyTxConfig(blockHash)
-)
-
-type StateDBTestSuite struct {
- suite.Suite
-}
-
-func (suite *StateDBTestSuite) TestAccount() {
- key1 := common.BigToHash(big.NewInt(1))
- value1 := common.BigToHash(big.NewInt(2))
- key2 := common.BigToHash(big.NewInt(3))
- value2 := common.BigToHash(big.NewInt(4))
- testCases := []struct {
- name string
- malleate func(*statedb.StateDB)
- }{
- {"non-exist account", func(db *statedb.StateDB) {
- suite.Require().Equal(false, db.Exist(address))
- suite.Require().Equal(true, db.Empty(address))
- suite.Require().Equal(big.NewInt(0), db.GetBalance(address))
- suite.Require().Equal([]byte(nil), db.GetCode(address))
- suite.Require().Equal(common.Hash{}, db.GetCodeHash(address))
- suite.Require().Equal(uint64(0), db.GetNonce(address))
- }},
- {"empty account", func(db *statedb.StateDB) {
- db.CreateAccount(address)
- suite.Require().NoError(db.Commit())
-
- keeper := db.Keeper().(*MockKeeper)
- acct := keeper.accounts[address]
- suite.Require().Equal(statedb.NewEmptyAccount(), &acct.account)
- suite.Require().Empty(acct.states)
- suite.Require().False(acct.account.IsContract())
-
- db = statedb.New(sdk.Context{}, keeper, emptyTxConfig)
- suite.Require().Equal(true, db.Exist(address))
- suite.Require().Equal(true, db.Empty(address))
- suite.Require().Equal(big.NewInt(0), db.GetBalance(address))
- suite.Require().Equal([]byte(nil), db.GetCode(address))
- suite.Require().True(evmtypes.IsEmptyCodeHash(db.GetCodeHash(address)))
- suite.Require().Equal(uint64(0), db.GetNonce(address))
- }},
- {"suicide", func(db *statedb.StateDB) {
- // non-exist account.
- suite.Require().False(db.Suicide(address))
- suite.Require().False(db.HasSuicided(address))
-
- // create a contract account
- db.CreateAccount(address)
- db.SetCode(address, []byte("hello world"))
- db.AddBalance(address, big.NewInt(100))
- db.SetState(address, key1, value1)
- db.SetState(address, key2, value2)
- suite.Require().NoError(db.Commit())
-
- // suicide
- db = statedb.New(sdk.Context{}, db.Keeper(), emptyTxConfig)
- suite.Require().False(db.HasSuicided(address))
- suite.Require().True(db.Suicide(address))
-
- // check dirty state
- suite.Require().True(db.HasSuicided(address))
- // balance is cleared
- suite.Require().Equal(big.NewInt(0), db.GetBalance(address))
- // but code and state are still accessible in dirty state
- suite.Require().Equal(value1, db.GetState(address, key1))
- suite.Require().Equal([]byte("hello world"), db.GetCode(address))
-
- suite.Require().NoError(db.Commit())
-
- // not accessible from StateDB anymore
- db = statedb.New(sdk.Context{}, db.Keeper(), emptyTxConfig)
- suite.Require().False(db.Exist(address))
-
- // and cleared in keeper too
- keeper := db.Keeper().(*MockKeeper)
- suite.Require().Empty(keeper.accounts)
- suite.Require().Empty(keeper.codes)
- }},
- }
- for _, tc := range testCases {
- suite.Run(tc.name, func() {
- keeper := NewMockKeeper()
- db := statedb.New(sdk.Context{}, keeper, emptyTxConfig)
- tc.malleate(db)
- })
- }
-}
-
-func (suite *StateDBTestSuite) TestAccountOverride() {
- keeper := NewMockKeeper()
- db := statedb.New(sdk.Context{}, keeper, emptyTxConfig)
- // test balance carry over when overwritten
- amount := big.NewInt(1)
-
- // init an EOA account, account overridden only happens on EOA account.
- db.AddBalance(address, amount)
- db.SetNonce(address, 1)
-
- // override
- db.CreateAccount(address)
-
- // check balance is not lost
- suite.Require().Equal(amount, db.GetBalance(address))
- // but nonce is reset
- suite.Require().Equal(uint64(0), db.GetNonce(address))
-}
-
-func (suite *StateDBTestSuite) TestDBError() {
- testCases := []struct {
- name string
- malleate func(vm.StateDB)
- }{
- {"set account", func(db vm.StateDB) {
- db.SetNonce(errAddress, 1)
- }},
- {"delete account", func(db vm.StateDB) {
- db.SetNonce(errAddress, 1)
- suite.Require().True(db.Suicide(errAddress))
- }},
- }
- for _, tc := range testCases {
- db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig)
- tc.malleate(db)
- suite.Require().Error(db.Commit())
- }
-}
-
-func (suite *StateDBTestSuite) TestBalance() {
- // NOTE: no need to test overflow/underflow, that is guaranteed by evm implementation.
- testCases := []struct {
- name string
- malleate func(*statedb.StateDB)
- expBalance *big.Int
- }{
- {"add balance", func(db *statedb.StateDB) {
- db.AddBalance(address, big.NewInt(10))
- }, big.NewInt(10)},
- {"sub balance", func(db *statedb.StateDB) {
- db.AddBalance(address, big.NewInt(10))
- // get dirty balance
- suite.Require().Equal(big.NewInt(10), db.GetBalance(address))
- db.SubBalance(address, big.NewInt(2))
- }, big.NewInt(8)},
- {"add zero balance", func(db *statedb.StateDB) {
- db.AddBalance(address, big.NewInt(0))
- }, big.NewInt(0)},
- {"sub zero balance", func(db *statedb.StateDB) {
- db.SubBalance(address, big.NewInt(0))
- }, big.NewInt(0)},
- }
-
- for _, tc := range testCases {
- suite.Run(tc.name, func() {
- keeper := NewMockKeeper()
- db := statedb.New(sdk.Context{}, keeper, emptyTxConfig)
- tc.malleate(db)
-
- // check dirty state
- suite.Require().Equal(tc.expBalance, db.GetBalance(address))
- suite.Require().NoError(db.Commit())
- // check committed balance too
- suite.Require().Equal(tc.expBalance, keeper.accounts[address].account.Balance)
- })
- }
-}
-
-func (suite *StateDBTestSuite) TestState() {
- key1 := common.BigToHash(big.NewInt(1))
- value1 := common.BigToHash(big.NewInt(1))
- testCases := []struct {
- name string
- malleate func(*statedb.StateDB)
- expStates statedb.Storage
- }{
- {"empty state", func(db *statedb.StateDB) {
- }, nil},
- {"set empty value", func(db *statedb.StateDB) {
- db.SetState(address, key1, common.Hash{})
- }, statedb.Storage{}},
- {"noop state change", func(db *statedb.StateDB) {
- db.SetState(address, key1, value1)
- db.SetState(address, key1, common.Hash{})
- }, statedb.Storage{}},
- {"set state", func(db *statedb.StateDB) {
- // check empty initial state
- suite.Require().Equal(common.Hash{}, db.GetState(address, key1))
- suite.Require().Equal(common.Hash{}, db.GetCommittedState(address, key1))
-
- // set state
- db.SetState(address, key1, value1)
- // query dirty state
- suite.Require().Equal(value1, db.GetState(address, key1))
- // check committed state is still not exist
- suite.Require().Equal(common.Hash{}, db.GetCommittedState(address, key1))
-
- // set same value again, should be noop
- db.SetState(address, key1, value1)
- suite.Require().Equal(value1, db.GetState(address, key1))
- }, statedb.Storage{
- key1: value1,
- }},
- }
-
- for _, tc := range testCases {
- suite.Run(tc.name, func() {
- keeper := NewMockKeeper()
- db := statedb.New(sdk.Context{}, keeper, emptyTxConfig)
- tc.malleate(db)
- suite.Require().NoError(db.Commit())
-
- // check committed states in keeper
- suite.Require().Equal(tc.expStates, keeper.accounts[address].states)
-
- // check ForEachStorage
- db = statedb.New(sdk.Context{}, keeper, emptyTxConfig)
- collected := CollectContractStorage(db)
- if len(tc.expStates) > 0 {
- suite.Require().Equal(tc.expStates, collected)
- } else {
- suite.Require().Empty(collected)
- }
- })
- }
-}
-
-func (suite *StateDBTestSuite) TestCode() {
- code := []byte("hello world")
- codeHash := crypto.Keccak256Hash(code)
-
- testCases := []struct {
- name string
- malleate func(vm.StateDB)
- expCode []byte
- expCodeHash common.Hash
- }{
- {"non-exist account", func(vm.StateDB) {}, nil, common.Hash{}},
- {"empty account", func(db vm.StateDB) {
- db.CreateAccount(address)
- }, nil, common.BytesToHash(evmtypes.EmptyCodeHash)},
- {"set code", func(db vm.StateDB) {
- db.SetCode(address, code)
- }, code, codeHash},
- }
-
- for _, tc := range testCases {
- suite.Run(tc.name, func() {
- keeper := NewMockKeeper()
- db := statedb.New(sdk.Context{}, keeper, emptyTxConfig)
- tc.malleate(db)
-
- // check dirty state
- suite.Require().Equal(tc.expCode, db.GetCode(address))
- suite.Require().Equal(len(tc.expCode), db.GetCodeSize(address))
- suite.Require().Equal(tc.expCodeHash, db.GetCodeHash(address))
-
- suite.Require().NoError(db.Commit())
-
- // check again
- db = statedb.New(sdk.Context{}, keeper, emptyTxConfig)
- suite.Require().Equal(tc.expCode, db.GetCode(address))
- suite.Require().Equal(len(tc.expCode), db.GetCodeSize(address))
- suite.Require().Equal(tc.expCodeHash, db.GetCodeHash(address))
- })
- }
-}
-
-func (suite *StateDBTestSuite) TestRevertSnapshot() {
- v1 := common.BigToHash(big.NewInt(1))
- v2 := common.BigToHash(big.NewInt(2))
- v3 := common.BigToHash(big.NewInt(3))
- testCases := []struct {
- name string
- malleate func(vm.StateDB)
- }{
- {"set state", func(db vm.StateDB) {
- db.SetState(address, v1, v3)
- }},
- {"set nonce", func(db vm.StateDB) {
- db.SetNonce(address, 10)
- }},
- {"change balance", func(db vm.StateDB) {
- db.AddBalance(address, big.NewInt(10))
- db.SubBalance(address, big.NewInt(5))
- }},
- {"override account", func(db vm.StateDB) {
- db.CreateAccount(address)
- }},
- {"set code", func(db vm.StateDB) {
- db.SetCode(address, []byte("hello world"))
- }},
- {"suicide", func(db vm.StateDB) {
- db.SetState(address, v1, v2)
- db.SetCode(address, []byte("hello world"))
- suite.Require().True(db.Suicide(address))
- }},
- {"add log", func(db vm.StateDB) {
- db.AddLog(ðtypes.Log{
- Address: address,
- })
- }},
- {"add refund", func(db vm.StateDB) {
- db.AddRefund(10)
- db.SubRefund(5)
- }},
- {"access list", func(db vm.StateDB) {
- db.AddAddressToAccessList(address)
- db.AddSlotToAccessList(address, v1)
- }},
- }
- for _, tc := range testCases {
- suite.Run(tc.name, func() {
- ctx := sdk.Context{}
- keeper := NewMockKeeper()
-
- {
- // do some arbitrary changes to the storage
- db := statedb.New(ctx, keeper, emptyTxConfig)
- db.SetNonce(address, 1)
- db.AddBalance(address, big.NewInt(100))
- db.SetCode(address, []byte("hello world"))
- db.SetState(address, v1, v2)
- db.SetNonce(address2, 1)
- suite.Require().NoError(db.Commit())
- }
-
- originalKeeper := keeper.Clone()
-
- // run test
- db := statedb.New(ctx, keeper, emptyTxConfig)
- rev := db.Snapshot()
- tc.malleate(db)
- db.RevertToSnapshot(rev)
-
- // check empty states after revert
- suite.Require().Zero(db.GetRefund())
- suite.Require().Empty(db.Logs())
-
- suite.Require().NoError(db.Commit())
-
- // check keeper should stay the same
- suite.Require().Equal(originalKeeper, keeper)
- })
- }
-}
-
-func (suite *StateDBTestSuite) TestNestedSnapshot() {
- key := common.BigToHash(big.NewInt(1))
- value1 := common.BigToHash(big.NewInt(1))
- value2 := common.BigToHash(big.NewInt(2))
-
- db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig)
-
- rev1 := db.Snapshot()
- db.SetState(address, key, value1)
-
- rev2 := db.Snapshot()
- db.SetState(address, key, value2)
- suite.Require().Equal(value2, db.GetState(address, key))
-
- db.RevertToSnapshot(rev2)
- suite.Require().Equal(value1, db.GetState(address, key))
-
- db.RevertToSnapshot(rev1)
- suite.Require().Equal(common.Hash{}, db.GetState(address, key))
-}
-
-func (suite *StateDBTestSuite) TestInvalidSnapshotId() {
- db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig)
- suite.Require().Panics(func() {
- db.RevertToSnapshot(1)
- })
-}
-
-func (suite *StateDBTestSuite) TestAccessList() {
- value1 := common.BigToHash(big.NewInt(1))
- value2 := common.BigToHash(big.NewInt(2))
-
- testCases := []struct {
- name string
- malleate func(vm.StateDB)
- }{
- {"add address", func(db vm.StateDB) {
- suite.Require().False(db.AddressInAccessList(address))
- db.AddAddressToAccessList(address)
- suite.Require().True(db.AddressInAccessList(address))
-
- addrPresent, slotPresent := db.SlotInAccessList(address, value1)
- suite.Require().True(addrPresent)
- suite.Require().False(slotPresent)
-
- // add again, should be no-op
- db.AddAddressToAccessList(address)
- suite.Require().True(db.AddressInAccessList(address))
- }},
- {"add slot", func(db vm.StateDB) {
- addrPresent, slotPresent := db.SlotInAccessList(address, value1)
- suite.Require().False(addrPresent)
- suite.Require().False(slotPresent)
- db.AddSlotToAccessList(address, value1)
- addrPresent, slotPresent = db.SlotInAccessList(address, value1)
- suite.Require().True(addrPresent)
- suite.Require().True(slotPresent)
-
- // add another slot
- db.AddSlotToAccessList(address, value2)
- addrPresent, slotPresent = db.SlotInAccessList(address, value2)
- suite.Require().True(addrPresent)
- suite.Require().True(slotPresent)
-
- // add again, should be noop
- db.AddSlotToAccessList(address, value2)
- addrPresent, slotPresent = db.SlotInAccessList(address, value2)
- suite.Require().True(addrPresent)
- suite.Require().True(slotPresent)
- }},
- {"prepare access list", func(db vm.StateDB) {
- al := ethtypes.AccessList{{
- Address: address3,
- StorageKeys: []common.Hash{value1},
- }}
- db.PrepareAccessList(address, &address2, vm.PrecompiledAddressesBerlin, al)
-
- // check sender and dst
- suite.Require().True(db.AddressInAccessList(address))
- suite.Require().True(db.AddressInAccessList(address2))
- // check precompiles
- suite.Require().True(db.AddressInAccessList(common.BytesToAddress([]byte{1})))
- // check AccessList
- suite.Require().True(db.AddressInAccessList(address3))
- addrPresent, slotPresent := db.SlotInAccessList(address3, value1)
- suite.Require().True(addrPresent)
- suite.Require().True(slotPresent)
- addrPresent, slotPresent = db.SlotInAccessList(address3, value2)
- suite.Require().True(addrPresent)
- suite.Require().False(slotPresent)
- }},
- }
-
- for _, tc := range testCases {
- db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig)
- tc.malleate(db)
- }
-}
-
-func (suite *StateDBTestSuite) TestLog() {
- txHash := common.BytesToHash([]byte("tx"))
- // use a non-default tx config
- txConfig := statedb.NewTxConfig(
- blockHash,
- txHash,
- 1, 1,
- )
- db := statedb.New(sdk.Context{}, NewMockKeeper(), txConfig)
- data := []byte("hello world")
- db.AddLog(ðtypes.Log{
- Address: address,
- Topics: []common.Hash{},
- Data: data,
- BlockNumber: 1,
- })
- suite.Require().Equal(1, len(db.Logs()))
- expecedLog := ðtypes.Log{
- Address: address,
- Topics: []common.Hash{},
- Data: data,
- BlockNumber: 1,
- BlockHash: blockHash,
- TxHash: txHash,
- TxIndex: 1,
- Index: 1,
- }
- suite.Require().Equal(expecedLog, db.Logs()[0])
-
- db.AddLog(ðtypes.Log{
- Address: address,
- Topics: []common.Hash{},
- Data: data,
- BlockNumber: 1,
- })
- suite.Require().Equal(2, len(db.Logs()))
- expecedLog.Index++
- suite.Require().Equal(expecedLog, db.Logs()[1])
-}
-
-func (suite *StateDBTestSuite) TestRefund() {
- testCases := []struct {
- name string
- malleate func(vm.StateDB)
- expRefund uint64
- expPanic bool
- }{
- {"add refund", func(db vm.StateDB) {
- db.AddRefund(uint64(10))
- }, 10, false},
- {"sub refund", func(db vm.StateDB) {
- db.AddRefund(uint64(10))
- db.SubRefund(uint64(5))
- }, 5, false},
- {"negative refund counter", func(db vm.StateDB) {
- db.AddRefund(uint64(5))
- db.SubRefund(uint64(10))
- }, 0, true},
- }
- for _, tc := range testCases {
- db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig)
- if !tc.expPanic {
- tc.malleate(db)
- suite.Require().Equal(tc.expRefund, db.GetRefund())
- } else {
- suite.Require().Panics(func() {
- tc.malleate(db)
- })
- }
- }
-}
-
-func (suite *StateDBTestSuite) TestIterateStorage() {
- key1 := common.BigToHash(big.NewInt(1))
- value1 := common.BigToHash(big.NewInt(2))
- key2 := common.BigToHash(big.NewInt(3))
- value2 := common.BigToHash(big.NewInt(4))
-
- keeper := NewMockKeeper()
- db := statedb.New(sdk.Context{}, keeper, emptyTxConfig)
- db.SetState(address, key1, value1)
- db.SetState(address, key2, value2)
-
- // ForEachStorage only iterate committed state
- suite.Require().Empty(CollectContractStorage(db))
-
- suite.Require().NoError(db.Commit())
-
- storage := CollectContractStorage(db)
- suite.Require().Equal(2, len(storage))
- suite.Require().Equal(keeper.accounts[address].states, storage)
-
- // break early iteration
- storage = make(statedb.Storage)
- err := db.ForEachStorage(address, func(k, v common.Hash) bool {
- storage[k] = v
- // return false to break early
- return false
- })
- suite.Require().NoError(err)
- suite.Require().Equal(1, len(storage))
-}
-
-func CollectContractStorage(db vm.StateDB) statedb.Storage {
- storage := make(statedb.Storage)
- err := db.ForEachStorage(address, func(k, v common.Hash) bool {
- storage[k] = v
- return true
- })
- if err != nil {
- return nil
- }
-
- return storage
-}
-
-func TestStateDBTestSuite(t *testing.T) {
- suite.Run(t, &StateDBTestSuite{})
-}
diff --git a/x/evm/types/errors.go b/x/evm/types/errors.go
index da8ee9403a..e900c58308 100644
--- a/x/evm/types/errors.go
+++ b/x/evm/types/errors.go
@@ -27,6 +27,7 @@ const (
codeErrGasOverflow
codeErrInvalidAccount
codeErrInvalidGasLimit
+ codeErrEngineFailure
)
var (
@@ -74,6 +75,9 @@ var (
// ErrInvalidGasLimit returns an error if gas limit value is invalid
ErrInvalidGasLimit = errorsmod.Register(ModuleName, codeErrInvalidGasLimit, "invalid gas limit")
+
+ // ErrEngineFailure returns an error if the EVM execution engine fails
+ ErrEngineFailure = errorsmod.Register(ModuleName, codeErrEngineFailure, "EVM execution engine failure")
)
// NewExecErrorWithReason unpacks the revert return bytes and returns a wrapped error
diff --git a/x/evm/types/params.go b/x/evm/types/params.go
index 1be38427c5..dac0d2b59e 100644
--- a/x/evm/types/params.go
+++ b/x/evm/types/params.go
@@ -7,7 +7,7 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types"
sdk "github.com/cosmos/cosmos-sdk/types"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
)
var (
@@ -100,8 +100,8 @@ func validateEIPs(i interface{}) error {
}
for _, eip := range eips {
- if !vm.ValidEip(int(eip)) {
- return fmt.Errorf("EIP %d is not activateable, valid EIPS are: %s", eip, vm.ActivateableEips())
+ if !corevm.ValidEip(int(eip)) {
+ return fmt.Errorf("EIP %d is not activateable, valid EIPS are: %s", eip, corevm.ActivateableEips())
}
}
diff --git a/x/evm/types/tracer.go b/x/evm/types/tracer.go
index 055757c7f8..19e79d4136 100644
--- a/x/evm/types/tracer.go
+++ b/x/evm/types/tracer.go
@@ -9,7 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
ethparams "github.com/ethereum/go-ethereum/params"
)
@@ -22,7 +22,7 @@ const (
// NewTracer creates a new Logger tracer to collect execution traces from an
// EVM transaction.
-func NewTracer(tracer string, msg core.Message, cfg *ethparams.ChainConfig, height int64) vm.EVMLogger {
+func NewTracer(tracer string, msg core.Message, cfg *ethparams.ChainConfig, height int64) corevm.EVMLogger {
// TODO: enable additional log configuration
logCfg := &logger.Config{
Debug: true,
@@ -31,7 +31,7 @@ func NewTracer(tracer string, msg core.Message, cfg *ethparams.ChainConfig, heig
switch tracer {
case TracerAccessList:
const mergeNetsplit = true
- preCompiles := vm.ActivePrecompiles(cfg.Rules(big.NewInt(height), mergeNetsplit))
+ preCompiles := corevm.ActivePrecompiles(cfg.Rules(big.NewInt(height), mergeNetsplit))
return logger.NewAccessListTracer(msg.AccessList(), msg.From(), *msg.To(), preCompiles)
case TracerJSON:
return logger.NewJSONLogger(logCfg, os.Stderr)
@@ -50,7 +50,7 @@ type TxTraceResult struct {
Error string `json:"error,omitempty"` // Trace failure produced by the tracer
}
-var _ vm.EVMLogger = &NoOpTracer{}
+var _ corevm.EVMLogger = &NoOpTracer{}
// NoOpTracer is an empty implementation of vm.Tracer interface
type NoOpTracer struct{}
@@ -63,7 +63,7 @@ func NewNoOpTracer() *NoOpTracer {
// CaptureStart implements vm.Tracer interface
//
//nolint:revive // allow unused parameters to indicate expected signature
-func (dt NoOpTracer) CaptureStart(env *vm.EVM,
+func (dt NoOpTracer) CaptureStart(env *corevm.EVM,
from common.Address,
to common.Address,
create bool,
@@ -75,13 +75,13 @@ func (dt NoOpTracer) CaptureStart(env *vm.EVM,
// CaptureState implements vm.Tracer interface
//
//nolint:revive // allow unused parameters to indicate expected signature
-func (dt NoOpTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+func (dt NoOpTracer) CaptureState(pc uint64, op corevm.OpCode, gas, cost uint64, scope *corevm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureFault implements vm.Tracer interface
//
//nolint:revive // allow unused parameters to indicate expected signature
-func (dt NoOpTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+func (dt NoOpTracer) CaptureFault(pc uint64, op corevm.OpCode, gas, cost uint64, scope *corevm.ScopeContext, depth int, err error) {
}
// CaptureEnd implements vm.Tracer interface
@@ -92,7 +92,7 @@ func (dt NoOpTracer) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration,
// CaptureEnter implements vm.Tracer interface
//
//nolint:revive // allow unused parameters to indicate expected signature
-func (dt NoOpTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+func (dt NoOpTracer) CaptureEnter(typ corevm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
// CaptureExit implements vm.Tracer interface
diff --git a/x/evm/types/tx.go b/x/evm/types/tx.go
index 8efa383148..7630b3abb2 100644
--- a/x/evm/types/tx.go
+++ b/x/evm/types/tx.go
@@ -6,7 +6,7 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/vm"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
)
// EvmTxArgs encapsulates all possible params to create all EVM txs types.
@@ -42,7 +42,7 @@ func (m *MsgEthereumTxResponse) Return() []byte {
// Revert returns the concrete revert reason if the execution is aborted by `REVERT`
// opcode. Note the reason can be nil if no data supplied with revert opcode.
func (m *MsgEthereumTxResponse) Revert() []byte {
- if m.VmError != vm.ErrExecutionReverted.Error() {
+ if m.VmError != corevm.ErrExecutionReverted.Error() {
return nil
}
return common.CopyBytes(m.Ret)
diff --git a/x/evm/types/utils.go b/x/evm/types/utils.go
index 7dd5463e77..f2c876dd99 100644
--- a/x/evm/types/utils.go
+++ b/x/evm/types/utils.go
@@ -76,5 +76,5 @@ func BinSearch(lo, hi uint64, executable func(uint64) (bool, *MsgEthereumTxRespo
// IsEmptyCodeHash returns true if the given code hash is the empty code hash
func IsEmptyCodeHash(codeHash common.Hash) bool {
- return bytes.Equal(codeHash.Bytes(), EmptyCodeHash)
+ return codeHash == (common.Hash{}) || bytes.Equal(codeHash.Bytes(), EmptyCodeHash)
}
diff --git a/x/evm/utils/validation.go b/x/evm/utils/validation.go
new file mode 100644
index 0000000000..f376320bd9
--- /dev/null
+++ b/x/evm/utils/validation.go
@@ -0,0 +1,43 @@
+package utils
+
+import (
+ "reflect"
+ "time"
+
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ vesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
+ vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
+)
+
+// CheckIfAccountIsSuitableForDestroying checking the account is suitable for destroy (EVM) or not.
+//
+// It returns false and the reason if the account:
+// 1. Is a module account.
+// 2. Is a vesting account which still not expired.
+func CheckIfAccountIsSuitableForDestroying(account sdk.AccountI) (destroyable bool, reason string) {
+ if account == nil || reflect.ValueOf(account).IsNil() {
+ panic("account is nil")
+ }
+
+ if _, isModuleAcc := account.(sdk.ModuleAccountI); isModuleAcc {
+ reason = "module account is not suitable for destroying"
+ return
+ }
+
+ if vestingAcc, ok := account.(*vestingtypes.BaseVestingAccount); ok {
+ if vestingAcc.GetEndTime() > time.Now().UTC().Unix() {
+ reason = "unexpired vesting account is not suitable for destroying"
+ return
+ }
+ }
+
+ if vestingAcc, ok := account.(vesting.VestingAccount); ok {
+ if vestingAcc.GetEndTime() > time.Now().UTC().Unix() {
+ reason = "unexpired vesting account is not suitable for destroying"
+ return
+ }
+ }
+
+ destroyable = true
+ return
+}
diff --git a/x/evm/utils/validation_test.go b/x/evm/utils/validation_test.go
new file mode 100644
index 0000000000..8865db6391
--- /dev/null
+++ b/x/evm/utils/validation_test.go
@@ -0,0 +1,157 @@
+package utils
+
+import (
+ "testing"
+ "time"
+
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ vesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
+ vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestCheckIfAccountIsSuitableForDestroying(t *testing.T) {
+ t.Run("panic if passing nil account", func(t *testing.T) {
+ nilAccounts := []sdk.AccountI{
+ (*authtypes.BaseAccount)(nil),
+ (sdk.ModuleAccountI)(nil),
+ (vesting.VestingAccount)(nil),
+ (*vestingtypes.BaseVestingAccount)(nil),
+ (*vestingtypes.ContinuousVestingAccount)(nil),
+ (*vestingtypes.DelayedVestingAccount)(nil),
+ (*vestingtypes.PeriodicVestingAccount)(nil),
+ }
+ for _, nilAccount := range nilAccounts {
+ require.Panicsf(t, func() {
+ _, _ = CheckIfAccountIsSuitableForDestroying(nilAccount)
+ }, "must panic when passing nil account %T", nilAccount)
+ }
+ })
+
+ tests := []struct {
+ name string
+ accFunc func() sdk.AccountI
+ wantDestroyable bool
+ wantReasonContains string
+ }{
+ {
+ name: "any base account",
+ accFunc: func() sdk.AccountI {
+ return &authtypes.BaseAccount{}
+ },
+ wantDestroyable: true,
+ },
+ {
+ name: "prohibit destroying module accounts",
+ accFunc: func() sdk.AccountI {
+ return &authtypes.ModuleAccount{}
+ },
+ wantDestroyable: false,
+ wantReasonContains: "module account is not suitable for destroying",
+ },
+ {
+ name: "prohibit destroying non-expired vesting accounts",
+ accFunc: func() sdk.AccountI {
+ return &vestingtypes.BaseVestingAccount{
+ EndTime: time.Now().Add(time.Hour).UTC().Unix(),
+ }
+ },
+ wantDestroyable: false,
+ wantReasonContains: "unexpired vesting account is not suitable for destroying",
+ },
+ {
+ name: "prohibit destroying non-expired vesting accounts",
+ accFunc: func() sdk.AccountI {
+ return &vestingtypes.ContinuousVestingAccount{
+ BaseVestingAccount: &vestingtypes.BaseVestingAccount{
+ EndTime: time.Now().Add(time.Hour).UTC().Unix(),
+ },
+ }
+ },
+ wantDestroyable: false,
+ wantReasonContains: "unexpired vesting account is not suitable for destroying",
+ },
+ {
+ name: "prohibit destroying non-expired vesting accounts",
+ accFunc: func() sdk.AccountI {
+ return &vestingtypes.DelayedVestingAccount{
+ BaseVestingAccount: &vestingtypes.BaseVestingAccount{
+ EndTime: time.Now().Add(time.Hour).UTC().Unix(),
+ },
+ }
+ },
+ wantDestroyable: false,
+ wantReasonContains: "unexpired vesting account is not suitable for destroying",
+ },
+ {
+ name: "prohibit destroying non-expired vesting accounts",
+ accFunc: func() sdk.AccountI {
+ return &vestingtypes.PeriodicVestingAccount{
+ BaseVestingAccount: &vestingtypes.BaseVestingAccount{
+ EndTime: time.Now().Add(time.Hour).UTC().Unix(),
+ },
+ }
+ },
+ wantDestroyable: false,
+ wantReasonContains: "unexpired vesting account is not suitable for destroying",
+ },
+ {
+ name: "allow destroying expired vesting accounts",
+ accFunc: func() sdk.AccountI {
+ return &vestingtypes.BaseVestingAccount{
+ EndTime: time.Now().Add(-1 * time.Hour).UTC().Unix(),
+ }
+ },
+ wantDestroyable: true,
+ },
+ {
+ name: "allow destroying expired vesting accounts",
+ accFunc: func() sdk.AccountI {
+ return &vestingtypes.ContinuousVestingAccount{
+ BaseVestingAccount: &vestingtypes.BaseVestingAccount{
+ EndTime: time.Now().Add(-1 * time.Hour).UTC().Unix(),
+ },
+ }
+ },
+ wantDestroyable: true,
+ },
+ {
+ name: "allow destroying expired vesting accounts",
+ accFunc: func() sdk.AccountI {
+ return &vestingtypes.DelayedVestingAccount{
+ BaseVestingAccount: &vestingtypes.BaseVestingAccount{
+ EndTime: time.Now().Add(-1 * time.Hour).UTC().Unix(),
+ },
+ }
+ },
+ wantDestroyable: true,
+ },
+ {
+ name: "allow destroying expired vesting accounts",
+ accFunc: func() sdk.AccountI {
+ return &vestingtypes.PeriodicVestingAccount{
+ BaseVestingAccount: &vestingtypes.BaseVestingAccount{
+ EndTime: time.Now().Add(-1 * time.Hour).UTC().Unix(),
+ },
+ }
+ },
+ wantDestroyable: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ gotDestroyable, gotReason := CheckIfAccountIsSuitableForDestroying(tt.accFunc())
+
+ if tt.wantDestroyable {
+ require.True(t, gotDestroyable)
+ require.Empty(t, gotReason, "reason must be empty if account is destroyable")
+ } else {
+ require.False(t, gotDestroyable)
+ require.NotEmpty(t, gotReason, "reason must be provided if account is not destroyable")
+ require.NotEmpty(t, tt.wantReasonContains, "bad setup")
+ require.Contains(t, gotReason, tt.wantReasonContains)
+ }
+ })
+ }
+}
diff --git a/x/evm/vm/evm_config.go b/x/evm/vm/evm_config.go
new file mode 100644
index 0000000000..7690770c29
--- /dev/null
+++ b/x/evm/vm/evm_config.go
@@ -0,0 +1,48 @@
+package vm
+
+import (
+ "math/big"
+
+ evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ ethtypes "github.com/ethereum/go-ethereum/core/types"
+ ethparams "github.com/ethereum/go-ethereum/params"
+)
+
+// TxConfig read-only information of current tx
+type TxConfig struct {
+ BlockHash common.Hash
+ TxHash common.Hash
+ TxIndex uint
+ LogIndex uint // the index of next log within current block
+ TxType *uint8 // transaction type, optionally provided
+}
+
+func (m TxConfig) WithTxTypeFromTransaction(ethTx *ethtypes.Transaction) TxConfig {
+ txType := ethTx.Type()
+ m.TxType = &txType
+ return m
+}
+
+func (m TxConfig) WithTxTypeFromMessage(msg core.Message) TxConfig {
+ var txType uint8
+ if msg.GasTipCap() != nil || msg.GasFeeCap() != nil {
+ txType = ethtypes.DynamicFeeTxType
+ } else if msg.AccessList() != nil {
+ txType = ethtypes.AccessListTxType
+ } else {
+ txType = ethtypes.LegacyTxType
+ }
+ m.TxType = &txType
+ return m
+}
+
+// EVMConfig contains the needed information to initialize core VM
+type EVMConfig struct {
+ Params evmtypes.Params
+ ChainConfig *ethparams.ChainConfig
+ CoinBase common.Address
+ BaseFee *big.Int
+ NoBaseFee bool
+}
diff --git a/x/evm/vm/interfaces.go b/x/evm/vm/interfaces.go
new file mode 100644
index 0000000000..43a2897092
--- /dev/null
+++ b/x/evm/vm/interfaces.go
@@ -0,0 +1,23 @@
+package vm
+
+import (
+ "math/big"
+
+ evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type EvmKeeper interface {
+ GetParams(ctx sdk.Context) (params evmtypes.Params)
+ ChainID() *big.Int
+ ForEachStorage(ctx sdk.Context, addr common.Address, cb func(key, value common.Hash) bool)
+ DeleteCodeHash(ctx sdk.Context, addr []byte)
+ SetState(ctx sdk.Context, addr common.Address, key common.Hash, value []byte)
+ GetCodeHash(ctx sdk.Context, addr []byte) common.Hash
+ GetCode(ctx sdk.Context, codeHash common.Hash) []byte
+ SetCode(ctx sdk.Context, codeHash, code []byte)
+ SetCodeHash(ctx sdk.Context, addr common.Address, codeHash common.Hash)
+ GetState(ctx sdk.Context, addr common.Address, key common.Hash) common.Hash
+ IsEmptyAccount(ctx sdk.Context, addr common.Address) bool
+}
diff --git a/x/evm/vm/state_db.go b/x/evm/vm/state_db.go
new file mode 100644
index 0000000000..82dd026782
--- /dev/null
+++ b/x/evm/vm/state_db.go
@@ -0,0 +1,608 @@
+package vm
+
+import (
+ "fmt"
+ "math/big"
+
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+
+ sdkmath "cosmossdk.io/math"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
+ bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
+
+ "github.com/ethereum/go-ethereum/common"
+ ethtypes "github.com/ethereum/go-ethereum/core/types"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
+ ethcrypto "github.com/ethereum/go-ethereum/crypto"
+ ethparams "github.com/ethereum/go-ethereum/params"
+
+ evertypes "github.com/EscanBE/evermint/v12/types"
+ evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
+ evmutils "github.com/EscanBE/evermint/v12/x/evm/utils"
+)
+
+var (
+ _ corevm.StateDB = &cStateDb{}
+ _ CStateDB = &cStateDb{}
+)
+
+// CStateDB is an interface that extends the vm.StateDB interface with additional methods.
+// The implementation is Context-based, and the name CStateDB stands for Context-based-StateDB.
+//
+//goland:noinspection GoSnakeCaseUsage
+type CStateDB interface {
+ corevm.StateDB
+
+ GetTransactionLogs() []*ethtypes.Log
+
+ CommitMultiStore(deleteEmptyObjects bool) error
+ IntermediateRoot(deleteEmptyObjects bool) (common.Hash, error)
+
+ DestroyAccount(acc common.Address)
+
+ // Not yet available in current version of go-ethereum
+
+ Selfdestruct6780(address common.Address)
+ GetTransientState(addr common.Address, key common.Hash) common.Hash
+ SetTransientState(addr common.Address, key, value common.Hash)
+
+ // For testing purposes only
+
+ ForTest_AddAddressToSelfDestructedList(addr common.Address)
+ ForTest_SetLogs(logs Logs)
+ ForTest_CountRecordsTransientStorage() int
+ ForTest_CloneTransientStorage() TransientStorage
+ ForTest_CloneAccessList() *AccessList2
+ ForTest_CloneTouched() AccountTracker
+ ForTest_CloneSelfDestructed() AccountTracker
+ ForTest_GetSnapshots() []RtStateDbSnapshot
+ ForTest_ToggleStateDBPreventCommit(prevent bool)
+ ForTest_GetOriginalContext() sdk.Context
+ ForTest_GetCurrentContext() sdk.Context
+ ForTest_GetEvmDenom() string
+ ForTest_IsCommitted() bool
+}
+
+type cStateDb struct {
+ originalCtx sdk.Context // the original context that was passed to the constructor, do not change. Reserved for accessing committed state.
+ currentCtx sdk.Context
+ snapshots []RtStateDbSnapshot
+ committed bool
+
+ evmKeeper EvmKeeper
+ accountKeeper authkeeper.AccountKeeper
+ bankKeeper bankkeeper.Keeper
+
+ evmDenom string
+ chainConfig *ethparams.ChainConfig
+
+ // other revertible states
+
+ touched AccountTracker // list of touched address, which then will be considered to remove if empty
+ refund uint64
+ selfDestructed AccountTracker
+ accessList *AccessList2
+ logs Logs
+ // Legacy TODO UPGRADE check code changes for transientStorage at https://github.com/ethereum/go-ethereum/blob/master/core/state/transient_storage.go
+ transientStorage TransientStorage
+}
+
+// preventCommit is a flag to prevent committing state changes to the underlying storage.
+// This is used for testing purposes to simulate cases where Commit() is failed.
+var preventCommit bool
+
+func (d *cStateDb) PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses ethtypes.AccessList) {
+ var coinbase common.Address // TODO ES: handle
+
+ rules := d.chainConfig.Rules(big.NewInt(d.currentCtx.BlockHeight()), true)
+
+ d.prepareByGoEthereum(rules, sender, coinbase, dest, precompiles, txAccesses)
+}
+
+func (d *cStateDb) ForEachStorage(address common.Address, f func(common.Hash, common.Hash) bool) error {
+ d.evmKeeper.ForEachStorage(d.currentCtx, address, f)
+ return nil
+}
+
+//goland:noinspection GoUnusedParameter
+func NewStateDB(
+ ctx sdk.Context,
+ ethKeeper EvmKeeper,
+ accountKeeper authkeeper.AccountKeeper,
+ bankKeeper bankkeeper.Keeper,
+) CStateDB {
+ // remove the event manager in order to prevent crappy events from being fired during state transition.
+ ctx = ctx.WithEventManager(evertypes.NewNoOpEventManager())
+
+ // fetching and cache x/evm params to prevent reload multiple time during execution
+ evmParams := ethKeeper.GetParams(ctx)
+
+ sdb := &cStateDb{
+ originalCtx: ctx,
+
+ evmKeeper: ethKeeper,
+ accountKeeper: accountKeeper,
+ bankKeeper: bankKeeper,
+
+ evmDenom: evmParams.EvmDenom,
+ chainConfig: evmParams.ChainConfig.EthereumConfig(ethKeeper.ChainID()),
+
+ touched: newAccountTracker(),
+ refund: 0,
+ selfDestructed: newAccountTracker(),
+ accessList: newAccessList2(),
+ transientStorage: TransientStorage(newTransientStorage()),
+ }
+
+ firstSnapshot := newStateDbSnapshotFromStateDb(sdb, ctx)
+ firstSnapshot.id = -1
+ sdb.snapshots = append(sdb.snapshots, firstSnapshot)
+ sdb.currentCtx = firstSnapshot.snapshotCtx
+
+ return sdb
+}
+
+// CreateAccount explicitly creates a state object. If a state object with the address
+// already exists the balance is carried over to the new account.
+//
+// CreateAccount is called during the EVM CREATE operation. The situation might arise that
+// a contract does the following:
+//
+// 1. sends funds to sha(account ++ (nonce + 1))
+// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
+//
+// Carrying over the balance ensures that Ether doesn't disappear.
+func (d *cStateDb) CreateAccount(address common.Address) {
+ d.touched.Add(address)
+
+ existingBalance := d.bankKeeper.GetAllBalances(d.currentCtx, address.Bytes())
+
+ d.DestroyAccount(address)
+
+ d.createAccountIfNotExists(address)
+ if !existingBalance.IsZero() { // carry over the balance
+ d.mintCoins(address.Bytes(), existingBalance)
+ }
+}
+
+// DestroyAccount removes the account from the state.
+// It removes the auth account, bank state, and evm state.
+func (d *cStateDb) DestroyAccount(addr common.Address) {
+ // remove auth account
+ acc := d.accountKeeper.GetAccount(d.currentCtx, addr.Bytes())
+ if acc != nil {
+ destroyable, protectedReason := evmutils.CheckIfAccountIsSuitableForDestroying(acc)
+ if !destroyable {
+ panic(
+ sdkerrors.ErrLogic.Wrapf(
+ "prohibited to destroy existing account %s with reason: %s",
+ addr.Hex(), protectedReason,
+ ),
+ )
+ }
+ d.accountKeeper.RemoveAccount(d.currentCtx, acc)
+ }
+
+ // remove bank state
+ existingBalances := d.bankKeeper.GetAllBalances(d.currentCtx, addr.Bytes())
+ if !existingBalances.IsZero() {
+ d.burnCoins(addr.Bytes(), existingBalances)
+ }
+
+ // remove evm state
+ d.evmKeeper.DeleteCodeHash(d.currentCtx, addr.Bytes())
+ d.evmKeeper.ForEachStorage(d.currentCtx, addr, func(key, _ common.Hash) bool {
+ d.evmKeeper.SetState(d.currentCtx, addr, key, nil)
+ return true
+ })
+}
+
+func (d *cStateDb) createAccountIfNotExists(address common.Address) {
+ if d.accountKeeper.HasAccount(d.currentCtx, address.Bytes()) {
+ // no-op
+ return
+ }
+
+ accountI := d.accountKeeper.NewAccountWithAddress(d.currentCtx, address.Bytes())
+ d.accountKeeper.SetAccount(d.currentCtx, accountI)
+}
+
+// SubBalance subtracts amount from the account associated with addr.
+func (d *cStateDb) SubBalance(address common.Address, b *big.Int) {
+ d.touched.Add(address)
+
+ if b.Sign() == 0 {
+ return
+ }
+
+ coinsToBurn := sdk.NewCoins(sdk.NewCoin(d.evmDenom, sdkmath.NewIntFromBigInt(b)))
+ d.burnCoins(address.Bytes(), coinsToBurn)
+}
+
+func (d cStateDb) burnCoins(accAddr sdk.AccAddress, coins sdk.Coins) {
+ if coins.IsZero() {
+ return
+ }
+
+ err := d.bankKeeper.SendCoinsFromAccountToModule(d.currentCtx, accAddr, evmtypes.ModuleName, coins)
+ if err != nil {
+ panic(evmtypes.ErrEngineFailure.Wrapf("failed to send coins: %s", err.Error()))
+ }
+ err = d.bankKeeper.BurnCoins(d.currentCtx, evmtypes.ModuleName, coins)
+ if err != nil {
+ panic(evmtypes.ErrEngineFailure.Wrapf("failed to mint coins: %s", err.Error()))
+ }
+}
+
+// AddBalance adds amount to the account associated with addr.
+func (d *cStateDb) AddBalance(address common.Address, b *big.Int) {
+ d.touched.Add(address)
+
+ if b.Sign() == 0 {
+ return
+ }
+
+ coinsToMint := sdk.NewCoins(sdk.NewCoin(d.evmDenom, sdkmath.NewIntFromBigInt(b)))
+ d.mintCoins(address.Bytes(), coinsToMint)
+}
+
+func (d cStateDb) mintCoins(accAddr sdk.AccAddress, coins sdk.Coins) {
+ if coins.IsZero() {
+ return
+ }
+
+ err := d.bankKeeper.MintCoins(d.currentCtx, evmtypes.ModuleName, coins)
+ if err != nil {
+ panic(evmtypes.ErrEngineFailure.Wrapf("failed to mint coins: %s", err.Error()))
+ }
+ err = d.bankKeeper.SendCoinsFromModuleToAccount(d.currentCtx, evmtypes.ModuleName, accAddr, coins)
+ if err != nil {
+ panic(evmtypes.ErrEngineFailure.Wrapf("failed to send coins: %s", err.Error()))
+ }
+}
+
+// GetBalance retrieves the balance from the given address or 0 if object not found
+func (d *cStateDb) GetBalance(address common.Address) *big.Int {
+ return d.bankKeeper.GetBalance(d.currentCtx, address.Bytes(), d.evmDenom).Amount.BigInt()
+}
+
+// GetNonce retrieves the nonce from the given address or 0 if object not found
+func (d *cStateDb) GetNonce(address common.Address) uint64 {
+ acc := d.accountKeeper.GetAccount(d.currentCtx, address.Bytes())
+ if acc == nil {
+ return 0
+ }
+ return acc.GetSequence()
+}
+
+// SetNonce sets the nonce for the given address, if account is not found it will be created
+func (d *cStateDb) SetNonce(address common.Address, n uint64) {
+ d.touched.Add(address)
+
+ d.createAccountIfNotExists(address)
+
+ if !d.accountKeeper.HasAccount(d.currentCtx, address.Bytes()) {
+ panic(evmtypes.ErrEngineFailure.Wrapf("failed to get account for %s", address.String()))
+ }
+
+ acc := d.accountKeeper.GetAccount(d.currentCtx, address.Bytes())
+ if err := acc.SetSequence(n); err != nil {
+ panic(evmtypes.ErrEngineFailure.Wrapf("failed to set nonce for %s: %s", address.String(), err.Error()))
+ }
+ d.accountKeeper.SetAccount(d.currentCtx, acc)
+}
+
+func (d *cStateDb) GetCodeHash(address common.Address) common.Hash {
+ return d.evmKeeper.GetCodeHash(d.currentCtx, address.Bytes())
+}
+
+func (d *cStateDb) GetCode(address common.Address) []byte {
+ codeHash := d.GetCodeHash(address)
+ return d.evmKeeper.GetCode(d.currentCtx, codeHash)
+}
+
+func (d *cStateDb) SetCode(address common.Address, code []byte) {
+ d.touched.Add(address)
+
+ d.createAccountIfNotExists(address)
+ codeHash := computeCodeHash(code)
+
+ d.evmKeeper.SetCode(d.currentCtx, codeHash.Bytes(), code)
+ d.evmKeeper.SetCodeHash(d.currentCtx, address, codeHash)
+}
+
+func (d *cStateDb) GetCodeSize(address common.Address) int {
+ return len(d.GetCode(address))
+}
+
+// AddRefund adds gas to the refund counter.
+// This method will panic if the refund counter goes above max uint64.
+func (d *cStateDb) AddRefund(gas uint64) {
+ newRefund := d.refund + gas
+ if newRefund < gas {
+ panic(evmtypes.ErrEngineFailure.Wrapf("gas refund counter overflow"))
+ }
+ d.refund = newRefund
+}
+
+// SubRefund removes gas from the refund counter.
+// This method will panic if the refund counter goes below zero
+func (d *cStateDb) SubRefund(gas uint64) {
+ if d.refund < gas {
+ panic(evmtypes.ErrEngineFailure.Wrapf("gas refund greater than remaining refund %d/%d", gas, d.refund))
+ }
+ d.refund -= gas
+}
+
+// GetRefund returns the current value of the refund counter.
+func (d *cStateDb) GetRefund() uint64 {
+ return d.refund
+}
+
+// GetCommittedState retrieves a value from the given account's committed storage trie.
+func (d *cStateDb) GetCommittedState(address common.Address, hash common.Hash) common.Hash {
+ accountAtCurrentCtx := d.accountKeeper.GetAccount(d.currentCtx, address.Bytes())
+ if accountAtCurrentCtx == nil {
+ // short-circuit for destroyed or not exists account
+ return common.Hash{}
+ }
+
+ accountAtOriginalCtx := d.accountKeeper.GetAccount(d.originalCtx, address.Bytes())
+ if accountAtOriginalCtx == nil {
+ // short-circuit for new account that not committed
+ return common.Hash{}
+ }
+
+ if accountAtCurrentCtx.GetAccountNumber() != accountAtOriginalCtx.GetAccountNumber() {
+ // short-circuit for remade account
+ return common.Hash{}
+ }
+
+ return d.evmKeeper.GetState(
+ d.originalCtx, /*get from original*/
+ address,
+ hash,
+ )
+}
+
+// GetState retrieves a value from the given account's storage trie.
+func (d *cStateDb) GetState(address common.Address, hash common.Hash) common.Hash {
+ return d.evmKeeper.GetState(d.currentCtx, address, hash)
+}
+
+func (d *cStateDb) SetState(address common.Address, key, value common.Hash) {
+ d.touched.Add(address)
+
+ d.createAccountIfNotExists(address)
+
+ d.evmKeeper.SetState(d.currentCtx, address, key, value.Bytes())
+}
+
+// GetTransientState gets transient storage for a given account.
+func (d *cStateDb) GetTransientState(addr common.Address, key common.Hash) common.Hash {
+ return d.transientStorage.Get(addr, key)
+}
+
+// SetTransientState sets transient storage for a given account.
+func (d *cStateDb) SetTransientState(addr common.Address, key, value common.Hash) {
+ d.transientStorage.Set(addr, key, value)
+}
+
+// Suicide marks the given account as self-destructed.
+// This clears the account balance.
+//
+// The account's state object is still available until the state is committed,
+// getStateObject will return a non-nil account after SelfDestruct.
+func (d *cStateDb) Suicide(address common.Address) bool {
+ d.touched.Add(address)
+
+ account := d.accountKeeper.GetAccount(d.currentCtx, address.Bytes())
+ if account == nil {
+ return false
+ }
+
+ // mark self-destructed
+ d.selfDestructed.Add(address)
+
+ // clear balance
+ currentBalance := d.bankKeeper.GetBalance(d.currentCtx, address.Bytes(), d.evmDenom).Amount
+ if !currentBalance.IsZero() {
+ d.SubBalance(address, currentBalance.BigInt())
+ }
+
+ return true
+}
+
+// HasSuicided returns true if the account was marked as self-destructed.
+func (d *cStateDb) HasSuicided(address common.Address) bool {
+ return d.selfDestructed.Has(address)
+}
+
+// Selfdestruct6780 sames as SelfDestruct but only operate if it was created within the same tx.
+// Note: this feature not yet available in go-ethereum v1.10.26.
+func (d *cStateDb) Selfdestruct6780(address common.Address) {
+ accountAtCurrentCtx := d.accountKeeper.GetAccount(d.currentCtx, address.Bytes())
+ if accountAtCurrentCtx == nil {
+ return
+ }
+
+ var createdWithinSameTx bool
+
+ accountAtOriginalCtx := d.accountKeeper.GetAccount(d.originalCtx, address.Bytes())
+ if accountAtOriginalCtx == nil {
+ createdWithinSameTx = true
+ } else if accountAtCurrentCtx.GetAccountNumber() != accountAtOriginalCtx.GetAccountNumber() {
+ createdWithinSameTx = true
+ }
+
+ if createdWithinSameTx {
+ d.Suicide(address)
+ }
+}
+
+// Exist reports whether the given account exists in state.
+// Notably this should also return true for self-destructed accounts.
+func (d *cStateDb) Exist(address common.Address) bool {
+ if d.selfDestructed.Has(address) {
+ return true
+ }
+
+ return d.accountKeeper.HasAccount(d.currentCtx, address.Bytes())
+}
+
+// Empty returns whether the given account is empty. Empty
+// is defined according to EIP161 (balance = nonce = code = 0).
+func (d *cStateDb) Empty(address common.Address) bool {
+ return d.evmKeeper.IsEmptyAccount(d.currentCtx, address)
+}
+
+// AddressInAccessList returns true if the given address is in the access list.
+func (d *cStateDb) AddressInAccessList(addr common.Address) bool {
+ return d.accessList.ContainsAddress(addr)
+}
+
+// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list.
+func (d *cStateDb) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) {
+ return d.accessList.Contains(addr, slot)
+}
+
+// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
+// even if the feature/fork is not active yet
+func (d *cStateDb) AddAddressToAccessList(addr common.Address) {
+ d.accessList.AddAddress(addr)
+}
+
+// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
+// even if the feature/fork is not active yet
+func (d *cStateDb) AddSlotToAccessList(addr common.Address, slot common.Hash) {
+ _, _ = d.accessList.AddSlot(addr, slot)
+}
+
+// Prepare handles the preparatory steps for executing a state transition with.
+// This method must be invoked before state transition.
+func (d *cStateDb) Prepare(rules ethparams.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses ethtypes.AccessList) {
+ // Legacy TODO UPGRADE check code changes for method StateDB::Prepare at https://github.com/ethereum/go-ethereum/blob/master/core/state/statedb.go
+ d.prepareByGoEthereum(rules, sender, coinbase, dest, precompiles, txAccesses)
+}
+
+// RevertToSnapshot reverts all state changes made since the given revision.
+func (d *cStateDb) RevertToSnapshot(id int) {
+ if id < 0 {
+ panic(evmtypes.ErrEngineFailure.Wrapf("invalid snapshot id: %d, below 0", id))
+ }
+
+ snapshotIdx := id + 1 // the first snapshot was created during state db initialization
+ snapshotState := d.snapshots[snapshotIdx]
+
+ if snapshotState.id != id {
+ panic(evmtypes.ErrEngineFailure.Wrapf("invalid snapshot id: %d, expected %d", snapshotState.id, id))
+ }
+
+ snapshotContext := d.snapshots[snapshotIdx-1] // this snapshot contains all change before snapshot was made
+ cacheCtx, writeFunc := snapshotContext.snapshotCtx.CacheContext()
+ snapshotState.snapshotCtx = cacheCtx
+ snapshotState.writeFunc = writeFunc
+
+ // revert all changes made after the reverted snapshot.
+ // NOTICE: always copy every backup fields from snapshot, do not direct assign.
+
+ d.currentCtx = cacheCtx
+ d.touched = snapshotState.touched.Copy()
+ d.refund = snapshotState.refund
+ d.selfDestructed = snapshotState.selfDestructed.Copy()
+ d.accessList = snapshotState.accessList.Copy()
+ d.logs = snapshotState.logs.Copy()
+ d.transientStorage = snapshotState.transientStorage.Clone()
+
+ // discard all snapshots after selected till the end
+ d.snapshots = append(d.snapshots[:snapshotIdx], snapshotState /*ensure changes to this record are applied*/)
+}
+
+// Snapshot returns an identifier for the current revision of the state.
+func (d *cStateDb) Snapshot() int {
+ nextSnapshot := newStateDbSnapshotFromStateDb(d, d.currentCtx)
+ nextSnapshot.id = len(d.snapshots) - 1
+
+ d.currentCtx = nextSnapshot.snapshotCtx
+ d.snapshots = append(d.snapshots, nextSnapshot)
+
+ return nextSnapshot.id
+}
+
+// AddLog adds a log, called by evm.
+//
+// WARNING: should maintain non-consensus fields externally.
+func (d *cStateDb) AddLog(log *ethtypes.Log) {
+ // TODO LOGIC fill non-consensus fields externally
+ d.logs = append(d.logs, log)
+}
+
+// GetTransactionLogs returns the logs added. The non-consensus fields should be filled externally.
+func (d *cStateDb) GetTransactionLogs() []*ethtypes.Log {
+ if len(d.logs) < 1 {
+ return []*ethtypes.Log{}
+ }
+ return d.logs[:]
+}
+
+// AddPreimage records a SHA3 preimage seen by the VM.
+// AddPreimage performs a no-op since the EnablePreimageRecording flag is disabled
+// on the vm.Config during state transitions. No store trie preimages are written
+// to the database.
+func (d *cStateDb) AddPreimage(_ common.Hash, _ []byte) {
+ // no-op (same as go-ethereum)
+}
+
+// CommitMultiStore commits branched cache multi-store to the original store, all state-transition will take effect.
+func (d *cStateDb) CommitMultiStore(deleteEmptyObjects bool) error {
+ if d.committed {
+ panic("called commit twice")
+ }
+
+ if preventCommit {
+ return fmt.Errorf("failed to commit state changes")
+ }
+
+ d.committed = true // prohibit further commit
+
+ for touchedAddress := range d.touched {
+ _, markedAsDestroy := d.selfDestructed[touchedAddress]
+ if markedAsDestroy || (deleteEmptyObjects && d.Empty(touchedAddress)) {
+ d.DestroyAccount(touchedAddress)
+ }
+ }
+
+ // When `writeFunc` is invoked, it will write the cache-multi-store branch to the parent cache-multi-store branch,
+ // so we need to invoke `writeFunc` from the most-recent snapshot to the earliest snapshot
+ // for the changes to be committed to the left-most cache-multi-store,
+ // then the left-most cache-multi-store commit to the original store which included into the original context.
+ for i := len(d.snapshots) - 1; i >= 0; i-- {
+ d.snapshots[i].WriteChanges()
+ }
+
+ return nil
+}
+
+// IntermediateRoot computes the current root hash of the state trie.
+// It is called in between transactions to get the root hash that
+// goes into transaction receipts.
+//
+// In the current implementation, it is not possible to compute the root hash right away,
+// and later there is logic to simulate the root hash, so we will return the empty root hash at this place.
+func (d *cStateDb) IntermediateRoot(deleteEmptyObjects bool) (common.Hash, error) {
+ if err := d.CommitMultiStore(deleteEmptyObjects); err != nil {
+ return common.Hash{}, err
+ }
+
+ return ethtypes.EmptyRootHash, nil
+}
+
+// computeCodeHash computes the code hash of the given code.
+// If the given code is empty, it returns `ethtypes.EmptyCodeHash`.
+func computeCodeHash(code []byte) common.Hash {
+ if len(code) == 0 {
+ return common.BytesToHash(evmtypes.EmptyCodeHash)
+ }
+ return ethcrypto.Keccak256Hash(code)
+}
diff --git a/x/evm/vm/state_db_access_list.go b/x/evm/vm/state_db_access_list.go
new file mode 100644
index 0000000000..5858a612a7
--- /dev/null
+++ b/x/evm/vm/state_db_access_list.go
@@ -0,0 +1,143 @@
+package vm
+
+import "github.com/ethereum/go-ethereum/common"
+
+// accessListI is for checking compatible of accessList (by go-ethereum) and AccessList2.
+// Here we have files:
+// 1. `state_db_access_list.go` which is our implementation.
+// 2. `state_db_access_list_geth.go` which is go-ethereum implementation, just keeping for compare compatible.
+//
+// Legacy TODO UPGRADE always copy accessList by go-ethereum implementation for testing new changes.
+// https://github.com/ethereum/go-ethereum/blob/master/core/state/access_list.go
+type accessListI interface {
+ ContainsAddress(address common.Address) bool
+ Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool)
+ AddAddress(address common.Address) bool
+ AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool)
+ DeleteSlot(address common.Address, slot common.Hash)
+ DeleteAddress(address common.Address)
+}
+
+var (
+ _ accessListI = (*accessList)(nil)
+ _ accessListI = (*AccessList2)(nil)
+)
+
+// AccessList2 has the same functionality as the accessList in go-ethereum.
+// But different implementation in order to perform a deep copy to match
+// with the implementation of Snapshot and RevertToSnapshot.
+type AccessList2 struct {
+ elements map[common.Address]map[common.Hash]bool
+}
+
+// newAccessList2 creates a new AccessList2.
+func newAccessList2() *AccessList2 {
+ return &AccessList2{
+ elements: make(map[common.Address]map[common.Hash]bool),
+ }
+}
+
+// ContainsAddress returns true if the address is in the access list.
+func (al *AccessList2) ContainsAddress(address common.Address) bool {
+ _, ok := al.elements[address]
+ return ok
+}
+
+// Contains checks if a slot within an account is present in the access list, returning
+// separate flags for the presence of the account and the slot respectively.
+func (al *AccessList2) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
+ slots, ok := al.elements[address]
+ if !ok {
+ return false, false
+ }
+ if len(slots) == 0 {
+ return true, false
+ }
+ _, slotPresent = slots[slot]
+ return true, slotPresent
+}
+
+// Copy creates an independent copy of an AccessList2.
+func (al *AccessList2) Copy() *AccessList2 {
+ elements := make(map[common.Address]map[common.Hash]bool, len(al.elements))
+ for address, existingSlots := range al.elements {
+ slots := make(map[common.Hash]bool, len(existingSlots))
+
+ for slot := range existingSlots {
+ slots[slot] = false // whatever value, not matter
+ }
+
+ if len(slots) == 0 {
+ elements[address] = nil
+ continue
+ }
+
+ elements[address] = slots
+ }
+
+ return &AccessList2{
+ elements: elements,
+ }
+}
+
+func (al *AccessList2) CloneElements() map[common.Address]map[common.Hash]bool {
+ return al.Copy().elements
+}
+
+// AddAddress adds an address to the access list, and returns 'true' if the operation
+// caused a change (addr was not previously in the list).
+func (al *AccessList2) AddAddress(address common.Address) bool {
+ if _, exists := al.elements[address]; exists {
+ return false
+ }
+ al.elements[address] = nil
+ return true
+}
+
+// AddSlot adds the specified (addr, slot) combo to the access list.
+// Return values are:
+// - address added
+// - slot added
+func (al *AccessList2) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) {
+ existingSlots, addressExisting := al.elements[address]
+ if !addressExisting {
+ al.elements[address] = map[common.Hash]bool{
+ slot: false, // whatever value, not matter
+ }
+ return true, true
+ }
+
+ if len(existingSlots) == 0 {
+ al.elements[address] = map[common.Hash]bool{
+ slot: false, // whatever value, not matter
+ }
+ return false, true
+ }
+
+ if _, slotExisting := existingSlots[slot]; !slotExisting {
+ existingSlots[slot] = false // whatever value, not matter
+ return false, true
+ }
+
+ return false, false
+}
+
+// DeleteSlot removes an (address, slot)-tuple from the access list.
+func (al *AccessList2) DeleteSlot(address common.Address, slot common.Hash) {
+ existingSlots, addressExisting := al.elements[address]
+ if !addressExisting {
+ panic("reverting slot change, address not present in list")
+ }
+
+ delete(existingSlots, slot)
+
+ if len(existingSlots) == 0 {
+ // cleanup
+ al.elements[address] = nil
+ }
+}
+
+// DeleteAddress removes an address from the access list.
+func (al *AccessList2) DeleteAddress(address common.Address) {
+ delete(al.elements, address)
+}
diff --git a/x/evm/statedb/access_list.go b/x/evm/vm/state_db_access_list_geth.go
similarity index 89%
rename from x/evm/statedb/access_list.go
rename to x/evm/vm/state_db_access_list_geth.go
index 4513a91641..13f9fe61e9 100644
--- a/x/evm/statedb/access_list.go
+++ b/x/evm/vm/state_db_access_list_geth.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package statedb
+package vm
import (
"github.com/ethereum/go-ethereum/common"
@@ -54,6 +54,23 @@ func newAccessList() *accessList {
}
}
+// Copy creates an independent copy of an accessList.
+func (a *accessList) Copy() *accessList {
+ cp := newAccessList()
+ for k, v := range a.addresses {
+ cp.addresses[k] = v
+ }
+ cp.slots = make([]map[common.Hash]struct{}, len(a.slots))
+ for i, slotMap := range a.slots {
+ newSlotmap := make(map[common.Hash]struct{}, len(slotMap))
+ for k := range slotMap {
+ newSlotmap[k] = struct{}{}
+ }
+ cp.slots[i] = newSlotmap
+ }
+ return cp
+}
+
// AddAddress adds an address to the access list, and returns 'true' if the operation
// caused a change (addr was not previously in the list).
func (al *accessList) AddAddress(address common.Address) bool {
@@ -95,6 +112,7 @@ func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrCha
// operations.
func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) {
idx, addrOk := al.addresses[address]
+ // There are two ways this can fail
if !addrOk {
panic("reverting slot change, address not present in list")
}
diff --git a/x/evm/vm/state_db_access_list_test.go b/x/evm/vm/state_db_access_list_test.go
new file mode 100644
index 0000000000..0051ce0d6b
--- /dev/null
+++ b/x/evm/vm/state_db_access_list_test.go
@@ -0,0 +1,153 @@
+package vm
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_AccessList(t *testing.T) {
+ al2 := newAccessList2()
+ testAccessList(t, al2)
+
+ alGeth := newAccessList()
+ testAccessList(t, alGeth)
+
+ t.Run("Copy AccessList2", func(t *testing.T) {
+ alSrc := newAccessList2()
+
+ fillRandomDataToSrc := func() {
+ for a := 10; a < 20; a++ {
+ addr := GenerateAddress()
+ alSrc.AddAddress(addr)
+
+ if a%2 == 0 {
+ for s := 0; s < a-8; s++ {
+ alSrc.AddSlot(addr, GenerateHash())
+ }
+ }
+ }
+ }
+
+ fillRandomDataToSrc()
+
+ originalLen := len(alSrc.elements)
+ originalSumLen := originalLen
+ for _, v := range alSrc.elements {
+ originalSumLen += len(v)
+ }
+
+ alCopy := alSrc.Copy()
+ require.True(t, reflect.DeepEqual(alSrc.elements, alCopy.elements))
+
+ fillRandomDataToSrc()
+ require.False(t, reflect.DeepEqual(alSrc.elements, alCopy.elements))
+
+ laterLen := len(alSrc.elements)
+ laterSumLen := laterLen
+ for _, v := range alSrc.elements {
+ laterSumLen += len(v)
+ }
+
+ require.Less(t, originalSumLen, laterSumLen)
+ require.Less(t, originalLen, laterLen)
+
+ copyLen := len(alCopy.elements)
+ copySumLen := copyLen
+ for _, v := range alCopy.elements {
+ copySumLen += len(v)
+ }
+
+ require.Equal(t, originalSumLen, copySumLen)
+ require.Equal(t, originalLen, copyLen)
+ })
+}
+
+func testAccessList(t *testing.T, al accessListI) {
+ require.NotNil(t, al)
+
+ addr1 := GenerateAddress()
+ addr2 := GenerateAddress()
+
+ require.False(t, al.ContainsAddress(addr1))
+ require.False(t, al.ContainsAddress(addr2))
+
+ al.AddAddress(addr1)
+ require.True(t, al.ContainsAddress(addr1))
+ require.False(t, al.ContainsAddress(addr2))
+
+ al.AddAddress(addr2)
+ require.True(t, al.ContainsAddress(addr2))
+
+ slot1 := GenerateHash()
+ slot2 := GenerateHash()
+
+ existsAddr, existsSlot := al.Contains(addr1, slot1)
+ require.True(t, existsAddr)
+ require.False(t, existsSlot)
+
+ addrChange, slotChange := al.AddSlot(addr1, slot1)
+ require.False(t, addrChange)
+ require.True(t, slotChange)
+
+ addrChange, slotChange = al.AddSlot(addr1, slot1)
+ require.False(t, addrChange)
+ require.False(t, slotChange)
+
+ existsAddr, existsSlot = al.Contains(addr1, slot1)
+ require.True(t, existsAddr)
+ require.True(t, existsSlot)
+
+ existsAddr, existsSlot = al.Contains(addr1, slot2)
+ require.True(t, existsAddr)
+ require.False(t, existsSlot)
+
+ existsAddr, existsSlot = al.Contains(GenerateAddress(), slot1)
+ require.False(t, existsAddr)
+ require.False(t, existsSlot)
+
+ addr3 := GenerateAddress()
+ require.False(t, al.ContainsAddress(addr3))
+ require.True(t, al.AddAddress(addr3))
+ require.True(t, al.ContainsAddress(addr3))
+ require.False(t, al.AddAddress(addr3))
+
+ require.NotPanics(t, func() {
+ al.DeleteAddress(GenerateAddress())
+ }, "should not panic even tho address is not in the access list")
+
+ require.Panics(t, func() {
+ al.DeleteSlot(GenerateAddress(), GenerateHash())
+ }, "should panic if address is not in the access list")
+
+ al.DeleteAddress(addr1)
+ require.False(t, al.ContainsAddress(addr1))
+
+ addrChange, slotChange = al.AddSlot(addr1, slot1)
+ require.True(t, addrChange)
+ require.True(t, slotChange)
+
+ addrChange, slotChange = al.AddSlot(addr1, slot2)
+ require.False(t, addrChange)
+ require.True(t, slotChange)
+
+ require.NotPanics(t, func() {
+ al.DeleteSlot(addr1, GenerateHash())
+ }, "should not panic even slot does not exists")
+
+ require.NotPanics(t, func() {
+ al.DeleteSlot(addr1, slot1)
+ })
+ if localAl, ok := al.(*AccessList2); ok {
+ require.Len(t, localAl.elements[addr1], 1)
+ }
+
+ require.NotPanics(t, func() {
+ al.DeleteSlot(addr1, slot2)
+ })
+ if localAl, ok := al.(*AccessList2); ok {
+ require.Len(t, localAl.elements[addr1], 0)
+ require.Nil(t, localAl.elements[addr1], "map should be erased")
+ }
+}
diff --git a/x/evm/vm/state_db_account_tracker.go b/x/evm/vm/state_db_account_tracker.go
new file mode 100644
index 0000000000..c92e2f0508
--- /dev/null
+++ b/x/evm/vm/state_db_account_tracker.go
@@ -0,0 +1,30 @@
+package vm
+
+import "github.com/ethereum/go-ethereum/common"
+
+type AccountTracker map[common.Address]bool
+
+func newAccountTracker() AccountTracker {
+ return make(AccountTracker)
+}
+
+func (t AccountTracker) Add(addr common.Address) {
+ t[addr] = false // whatever value, not matter
+}
+
+func (t AccountTracker) Has(addr common.Address) bool {
+ _, found := t[addr]
+ return found
+}
+
+func (t AccountTracker) Delete(addr common.Address) {
+ delete(t, addr)
+}
+
+func (t AccountTracker) Copy() AccountTracker {
+ tracker := make(AccountTracker)
+ for k, v := range t {
+ tracker[k] = v
+ }
+ return tracker
+}
diff --git a/x/evm/vm/state_db_account_tracker_test.go b/x/evm/vm/state_db_account_tracker_test.go
new file mode 100644
index 0000000000..cc2bdfe0d4
--- /dev/null
+++ b/x/evm/vm/state_db_account_tracker_test.go
@@ -0,0 +1,52 @@
+package vm
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func Test_AccountTracker(t *testing.T) {
+ originalTracker := newAccountTracker()
+ require.NotNil(t, originalTracker)
+ require.Empty(t, originalTracker)
+
+ addr1 := GenerateAddress()
+ addr2 := GenerateAddress()
+ addr3 := GenerateAddress()
+
+ originalTracker.Add(addr1)
+ originalTracker.Add(addr2)
+ require.True(t, originalTracker.Has(addr1))
+ require.True(t, originalTracker.Has(addr2))
+ require.False(t, originalTracker.Has(addr3))
+
+ copiedTracker := originalTracker.Copy()
+
+ addr4 := GenerateAddress()
+ copiedTracker.Add(addr4)
+ require.True(t, copiedTracker.Has(addr1))
+ require.True(t, copiedTracker.Has(addr2))
+ require.False(t, copiedTracker.Has(addr3))
+ require.True(t, copiedTracker.Has(addr4))
+
+ // change address 1 in copied tracker
+ copiedTracker.Delete(addr1)
+
+ // assert original value on the original tracker
+ require.True(t, originalTracker.Has(addr1))
+ require.True(t, originalTracker.Has(addr2))
+ require.False(t, originalTracker.Has(addr3))
+ require.False(t, originalTracker.Has(addr4))
+
+ // update on original tracker and expect not to effect copied tracker
+ originalTracker.Delete(addr1)
+ originalTracker.Delete(addr2)
+ originalTracker.Add(addr3)
+ originalTracker.Add(addr4)
+
+ require.False(t, copiedTracker.Has(addr1))
+ require.True(t, copiedTracker.Has(addr2))
+ require.False(t, copiedTracker.Has(addr3))
+ require.True(t, copiedTracker.Has(addr4))
+}
diff --git a/x/evm/vm/state_db_geth.go b/x/evm/vm/state_db_geth.go
new file mode 100644
index 0000000000..d324eea22f
--- /dev/null
+++ b/x/evm/vm/state_db_geth.go
@@ -0,0 +1,68 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// Package state provides a caching layer atop the Ethereum state trie.
+
+package vm
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ ethtypes "github.com/ethereum/go-ethereum/core/types"
+ ethparams "github.com/ethereum/go-ethereum/params"
+)
+
+// prepareByGoEthereum is copied from go-ethereum.
+//
+// geth::Prepare handles the preparatory steps for executing a state transition with.
+// This method must be invoked before state transition.
+//
+// Berlin fork:
+// - Add sender to access list (2929)
+// - Add destination to access list (2929)
+// - Add precompiles to access list (2929)
+// - Add the contents of the optional tx access list (2930)
+//
+// Potential EIPs:
+// - Reset access list (Berlin)
+// - Add coinbase to access list (EIP-3651)
+// - Reset transient storage (EIP-1153)
+func (d *cStateDb) prepareByGoEthereum(rules ethparams.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list ethtypes.AccessList) {
+ if rules.IsBerlin {
+ // Clear out any leftover from previous executions
+ al := newAccessList2()
+ d.accessList = al
+
+ al.AddAddress(sender)
+ if dst != nil {
+ al.AddAddress(*dst)
+ // If it's a create-tx, the destination will be added inside evm.create
+ }
+ for _, addr := range precompiles {
+ al.AddAddress(addr)
+ }
+ for _, el := range list {
+ al.AddAddress(el.Address)
+ for _, key := range el.StorageKeys {
+ al.AddSlot(el.Address, key)
+ }
+ }
+ if rules.IsShanghai { // EIP-3651: warm coinbase
+ al.AddAddress(coinbase)
+ }
+ }
+ // Reset transient storage at the beginning of transaction execution
+ d.transientStorage = newTransientStorage()
+}
diff --git a/x/evm/vm/state_db_it_test.go b/x/evm/vm/state_db_it_test.go
new file mode 100644
index 0000000000..5b43b13267
--- /dev/null
+++ b/x/evm/vm/state_db_it_test.go
@@ -0,0 +1,1867 @@
+package vm_test
+
+//goland:noinspection SpellCheckingInspection
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "math/big"
+ mathrand "math/rand"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/crypto"
+
+ sdkmath "cosmossdk.io/math"
+ storetypes "cosmossdk.io/store/types"
+
+ "github.com/EscanBE/evermint/v12/integration_test_util"
+ itutiltypes "github.com/EscanBE/evermint/v12/integration_test_util/types"
+ evmtypes "github.com/EscanBE/evermint/v12/x/evm/types"
+ evmvm "github.com/EscanBE/evermint/v12/x/evm/vm"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
+ "github.com/ethereum/go-ethereum/common"
+ ethtypes "github.com/ethereum/go-ethereum/core/types"
+ corevm "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/stretchr/testify/suite"
+)
+
+//goland:noinspection GoSnakeCaseUsage,SpellCheckingInspection
+type StateDbIntegrationTestSuite struct {
+ suite.Suite
+ CITS *integration_test_util.ChainIntegrationTestSuite
+ StateDB corevm.StateDB
+ CStateDB evmvm.CStateDB
+}
+
+func (suite *StateDbIntegrationTestSuite) App() itutiltypes.ChainApp {
+ return suite.CITS.ChainApp
+}
+
+func (suite *StateDbIntegrationTestSuite) Ctx() sdk.Context {
+ return suite.CITS.CurrentContext.
+ WithKVGasConfig(storetypes.GasConfig{}).
+ WithTransientKVGasConfig(storetypes.GasConfig{})
+}
+
+func (suite *StateDbIntegrationTestSuite) Commit() {
+ suite.CITS.Commit()
+}
+
+func TestStateDbIntegrationTestSuite(t *testing.T) {
+ suite.Run(t, new(StateDbIntegrationTestSuite))
+}
+
+func (suite *StateDbIntegrationTestSuite) SetupSuite() {
+}
+
+func (suite *StateDbIntegrationTestSuite) SetupTest() {
+ if suite.CITS != nil {
+ suite.TearDownTest()
+ }
+
+ suite.CITS = integration_test_util.CreateChainIntegrationTestSuite(suite.T(), suite.Require())
+ suite.StateDB = suite.newStateDB()
+ suite.CStateDB = suite.StateDB.(evmvm.CStateDB)
+}
+
+func (suite *StateDbIntegrationTestSuite) TearDownTest() {
+ suite.CITS.Cleanup()
+}
+
+func (suite *StateDbIntegrationTestSuite) TearDownSuite() {
+}
+
+func (suite *StateDbIntegrationTestSuite) SkipIfDisabledTendermint() {
+ if !suite.CITS.HasCometBFT() {
+ suite.T().Skip("CometBFT is disabled, some methods can not be used, skip")
+ }
+}
+
+func (suite *StateDbIntegrationTestSuite) newStateDB() corevm.StateDB {
+ return evmvm.NewStateDB(
+ suite.Ctx(),
+ suite.App().EvmKeeper(),
+ *suite.App().AccountKeeper(),
+ suite.App().BankKeeper(),
+ )
+}
+
+// Begin test
+
+func (suite *StateDbIntegrationTestSuite) TestNewStateDB() {
+ suite.Run("context was branched", func() {
+ // context should be branched to prevent any change to the original provided context
+ // The state transition should only be effective after the StateDB is committed
+
+ account := suite.CITS.WalletAccounts.Number(1)
+ originalBalance := suite.CITS.QueryBalance(0, account.GetCosmosAddress().String()).Amount.Int64()
+ suite.Require().NotZero(originalBalance)
+
+ stateDB := suite.newStateDB()
+ suite.Require().NotNil(stateDB)
+
+ stateDB.AddBalance(account.GetEthAddress(), suite.CITS.NewBaseCoin(1).Amount.BigInt())
+
+ suite.Less(
+ originalBalance,
+ suite.CITS.ChainApp.BankKeeper().GetBalance(stateDB.(evmvm.CStateDB).ForTest_GetCurrentContext(), account.GetCosmosAddress(), suite.CStateDB.ForTest_GetEvmDenom()).Amount.Int64(),
+ "balance within StateDB context should be changed",
+ )
+
+ // DO NOT COMMIT
+
+ suite.Equal(
+ originalBalance, suite.CITS.QueryBalance(0, account.GetCosmosAddress().String()).Amount.Int64(),
+ "balance should not be changed because the commit multi-store within the StateDB is not committed",
+ )
+ })
+
+ suite.Len(suite.CStateDB.ForTest_GetSnapshots(), 1, "should have exactly one snapshot at the beginning")
+ suite.False(suite.CStateDB.ForTest_IsCommitted(), "committed flag should be false at the beginning")
+}
+
+func (suite *StateDbIntegrationTestSuite) TestCreateAccount() {
+ suite.Run("create non-existing account and modified should be kept", func() {
+ testAcc := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.StateDB.CreateAccount(testAcc.GetEthAddress())
+ suite.StateDB.AddBalance(testAcc.GetEthAddress(), big.NewInt(1000))
+
+ err := suite.CStateDB.CommitMultiStore(true)
+ suite.Require().NoError(err)
+
+ suite.CITS.WaitNextBlockOrCommit()
+
+ suite.True(
+ suite.App().AccountKeeper().HasAccount(suite.Ctx(), testAcc.GetCosmosAddress()),
+ )
+ })
+
+ suite.Run("create non-existing account but not modify will be removed", func() {
+ suite.SetupTest() // reset
+
+ emptyWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.StateDB.CreateAccount(emptyWallet.GetEthAddress())
+
+ err := suite.CStateDB.CommitMultiStore(true)
+ suite.Require().NoError(err)
+
+ suite.CITS.WaitNextBlockOrCommit()
+
+ suite.False(
+ suite.App().AccountKeeper().HasAccount(suite.Ctx(), emptyWallet.GetCosmosAddress()),
+ )
+ })
+
+ suite.Run("remake existing account", func() {
+ suite.SetupTest() // reset
+
+ existingWallet := suite.CITS.WalletAccounts.Number(1)
+
+ existingAccount := suite.App().AccountKeeper().GetAccount(suite.CStateDB.ForTest_GetCurrentContext(), existingWallet.GetCosmosAddress())
+
+ suite.StateDB.CreateAccount(existingWallet.GetEthAddress())
+
+ err := suite.CStateDB.CommitMultiStore(true)
+ suite.Require().NoError(err)
+
+ suite.CITS.WaitNextBlockOrCommit()
+
+ newAccount := suite.App().AccountKeeper().GetAccount(suite.Ctx(), existingWallet.GetCosmosAddress())
+ suite.Require().NotNil(newAccount)
+ suite.Less(existingAccount.GetAccountNumber(), newAccount.GetAccountNumber())
+ })
+
+ suite.Run("override existing account with balance", func() {
+ suite.SetupTest() // reset
+
+ existingWallet := suite.CITS.WalletAccounts.Number(1)
+
+ existingAccount := suite.App().AccountKeeper().GetAccount(suite.CStateDB.ForTest_GetCurrentContext(), existingWallet.GetCosmosAddress())
+
+ originalBalance := suite.App().BankKeeper().GetBalance(suite.Ctx(), existingAccount.GetAddress(), suite.CStateDB.ForTest_GetEvmDenom())
+ suite.Require().False(originalBalance.IsZero())
+
+ suite.StateDB.CreateAccount(existingWallet.GetEthAddress())
+
+ err := suite.CStateDB.CommitMultiStore(true)
+ suite.Require().NoError(err)
+
+ suite.CITS.WaitNextBlockOrCommit()
+
+ newAccount := suite.App().AccountKeeper().GetAccount(suite.Ctx(), existingWallet.GetCosmosAddress())
+ suite.Require().NotNil(newAccount)
+ suite.Less(existingAccount.GetAccountNumber(), newAccount.GetAccountNumber())
+
+ newBalance := suite.App().BankKeeper().GetBalance(suite.Ctx(), newAccount.GetAddress(), suite.CStateDB.ForTest_GetEvmDenom())
+ suite.Equal(originalBalance.Denom, newBalance.Denom, "balance should be brought to new account")
+ suite.Equal(originalBalance.Amount.String(), newBalance.Amount.String(), "balance should be brought to new account")
+ })
+
+ suite.Run("clear out previous account code and state", func() {
+ existingWallet1 := suite.CITS.WalletAccounts.Number(1)
+ existingWallet2 := suite.CITS.WalletAccounts.Number(2)
+
+ existingAccount1 := suite.App().AccountKeeper().GetAccount(suite.Ctx(), existingWallet1.GetCosmosAddress())
+ suite.Require().NotNil(existingAccount1)
+
+ existingAccount2 := suite.App().AccountKeeper().GetAccount(suite.Ctx(), existingWallet2.GetCosmosAddress())
+ suite.Require().NotNil(existingAccount2)
+
+ // prepareByGoEthereum data
+ codeHash, code := RandomContractCode()
+ hash1 := evmvm.GenerateHash()
+ hash2 := evmvm.GenerateHash()
+
+ suite.App().EvmKeeper().SetCode(suite.Ctx(), codeHash.Bytes(), code)
+ suite.App().EvmKeeper().SetCodeHash(suite.Ctx(), existingWallet1.GetEthAddress(), codeHash)
+ suite.App().EvmKeeper().SetState(suite.Ctx(), existingWallet1.GetEthAddress(), hash1, hash2.Bytes())
+ suite.App().EvmKeeper().SetCodeHash(suite.Ctx(), existingWallet2.GetEthAddress(), codeHash)
+ suite.App().EvmKeeper().SetState(suite.Ctx(), existingWallet2.GetEthAddress(), hash1, hash2.Bytes())
+
+ suite.Commit()
+
+ suite.Require().Equal(code, suite.App().EvmKeeper().GetCode(suite.Ctx(), codeHash))
+ suite.Require().Equal(codeHash, suite.App().EvmKeeper().GetCodeHash(suite.Ctx(), existingAccount1.GetAddress()))
+ suite.Require().Equal(hash2, suite.App().EvmKeeper().GetState(suite.Ctx(), existingWallet1.GetEthAddress(), hash1))
+ suite.Require().Equal(codeHash, suite.App().EvmKeeper().GetCodeHash(suite.Ctx(), existingAccount2.GetAddress()))
+ suite.Require().Equal(hash2, suite.App().EvmKeeper().GetState(suite.Ctx(), existingWallet2.GetEthAddress(), hash1))
+
+ suite.Commit()
+
+ // remake one account
+ stateDB := suite.newStateDB()
+ stateDB.CreateAccount(existingWallet1.GetEthAddress())
+
+ err := stateDB.(evmvm.CStateDB).CommitMultiStore(true)
+ suite.Require().NoError(err)
+ suite.Commit()
+
+ // ensure account data is wiped
+ codeHashPrevious := suite.App().EvmKeeper().GetCodeHash(suite.Ctx(), existingAccount1.GetAddress())
+ suite.Equalf(common.BytesToHash(evmtypes.EmptyCodeHash), codeHashPrevious, "code hash of account must be wiped (cur %s, origin %s)", codeHashPrevious, codeHash)
+ suite.Equal(code, suite.App().EvmKeeper().GetCode(suite.Ctx(), codeHash), "mapped code must be kept")
+ accStatePrevious := suite.App().EvmKeeper().GetState(suite.Ctx(), existingWallet1.GetEthAddress(), hash1)
+ suite.Equalf(common.Hash{}, accStatePrevious, "account state must be wiped (cur %s, origin %s)", accStatePrevious, hash2)
+
+ // ensure new account has empty data
+ newAccount := suite.App().AccountKeeper().GetAccount(suite.Ctx(), existingWallet1.GetCosmosAddress())
+ suite.Equal(common.BytesToHash(evmtypes.EmptyCodeHash), suite.App().EvmKeeper().GetCodeHash(suite.Ctx(), newAccount.GetAddress()), "code hash of new account must be empty")
+ suite.Equal(common.Hash{}, suite.App().EvmKeeper().GetState(suite.Ctx(), common.BytesToAddress(newAccount.GetAddress()), hash1), "new account state must be empty")
+
+ // ensure another account has state un-changed
+ suite.Require().Equal(codeHash.String(), suite.App().EvmKeeper().GetCodeHash(suite.Ctx(), existingAccount2.GetAddress()).String())
+ suite.Require().Equal(hash2.String(), suite.App().EvmKeeper().GetState(suite.Ctx(), existingWallet2.GetEthAddress(), hash1).String())
+ })
+}
+
+func (suite *StateDbIntegrationTestSuite) TestDestroyAccount() {
+ curCtx := suite.CStateDB.ForTest_GetCurrentContext()
+
+ moduleAccount := suite.App().AccountKeeper().GetModuleAccount(curCtx, authtypes.FeeCollectorName)
+ suite.Require().NotNil(moduleAccount)
+ suite.Panics(func() {
+ suite.CStateDB.DestroyAccount(common.BytesToAddress(moduleAccount.GetAddress()))
+ }, "should not destroy module account")
+
+ originalVestingCoin := suite.CITS.NewBaseCoin(1_000_000)
+ startVesting := time.Now().UTC().Unix() - 86400*10
+ endVesting := time.Now().UTC().Unix() + 86400*10
+
+ walletVesting := integration_test_util.NewTestAccount(suite.T(), nil)
+ vestingAccountNotExpired, err := vestingtypes.NewContinuousVestingAccount(
+ suite.App().AccountKeeper().NewAccountWithAddress(curCtx, walletVesting.GetCosmosAddress()).(*authtypes.BaseAccount),
+ sdk.NewCoins(originalVestingCoin),
+ startVesting,
+ endVesting,
+ )
+ suite.Require().NoError(err)
+ suite.App().AccountKeeper().SetAccount(curCtx, vestingAccountNotExpired)
+ vestingAccountINotExpired := suite.App().AccountKeeper().GetAccount(curCtx, walletVesting.GetCosmosAddress())
+
+ suite.Panics(func() {
+ suite.CStateDB.DestroyAccount(common.BytesToAddress(vestingAccountINotExpired.GetAddress()))
+ }, "should not destroy vesting account which still not expired")
+
+ endVesting = time.Now().Unix() - 1
+ vestingAccountExpired, err := vestingtypes.NewContinuousVestingAccount(
+ suite.App().AccountKeeper().NewAccountWithAddress(curCtx, walletVesting.GetCosmosAddress()).(*authtypes.BaseAccount),
+ sdk.NewCoins(originalVestingCoin),
+ startVesting,
+ endVesting,
+ )
+ suite.Require().NoError(err)
+ suite.App().AccountKeeper().SetAccount(curCtx, vestingAccountExpired)
+ vestingAccountIExpired := suite.App().AccountKeeper().GetAccount(curCtx, walletVesting.GetCosmosAddress())
+
+ suite.NotPanics(func() {
+ suite.CStateDB.DestroyAccount(common.BytesToAddress(vestingAccountIExpired.GetAddress()))
+ }, "able to destroy vesting account which expired")
+
+ existingWallet := suite.CITS.WalletAccounts.Number(1)
+ suite.Require().NotNil(
+ suite.App().AccountKeeper().GetAccount(curCtx, existingWallet.GetCosmosAddress()),
+ "must exists",
+ )
+ {
+ // add balance, code hash, and state
+ suite.StateDB.AddBalance(existingWallet.GetEthAddress(), big.NewInt(1))
+ suite.Require().False(suite.App().BankKeeper().GetAllBalances(curCtx, existingWallet.GetCosmosAddress()).IsZero())
+ suite.StateDB.SetCode(existingWallet.GetEthAddress(), []byte{1, 2, 3})
+ suite.StateDB.SetState(existingWallet.GetEthAddress(), common.BytesToHash([]byte{1, 2, 3}), common.BytesToHash([]byte{4, 5, 6}))
+ }
+
+ suite.NotPanics(func() {
+ suite.CStateDB.DestroyAccount(existingWallet.GetEthAddress())
+ }, "able to destroy normal account")
+
+ err = suite.CStateDB.CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+ suite.Commit()
+
+ suite.Nil(suite.App().AccountKeeper().GetAccount(suite.Ctx(), walletVesting.GetCosmosAddress()))
+ suite.Nil(suite.App().AccountKeeper().GetAccount(suite.Ctx(), existingWallet.GetCosmosAddress()))
+ suite.Require().Empty(suite.App().BankKeeper().GetAllBalances(curCtx, existingWallet.GetCosmosAddress()))
+ suite.Equal(common.Hash{}, suite.App().EvmKeeper().GetCodeHash(curCtx, existingWallet.GetEthAddress().Bytes()))
+ suite.Equal(common.Hash{}, suite.App().EvmKeeper().GetState(curCtx, existingWallet.GetEthAddress(), common.BytesToHash([]byte{1, 2, 3})))
+}
+
+func (suite *StateDbIntegrationTestSuite) TestAddSubGetBalance() {
+ receiver1 := integration_test_util.NewTestAccount(suite.T(), nil)
+ receiver2 := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(300))
+ suite.StateDB.AddBalance(receiver2.GetEthAddress(), big.NewInt(900))
+
+ suite.StateDB.SubBalance(receiver1.GetEthAddress(), big.NewInt(100))
+
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(300))
+
+ suite.StateDB.SubBalance(receiver1.GetEthAddress(), big.NewInt(150))
+
+ suite.Require().Equal(big.NewInt(350), suite.StateDB.GetBalance(receiver1.GetEthAddress()))
+ suite.Require().Equal(big.NewInt(900), suite.StateDB.GetBalance(receiver2.GetEthAddress()))
+
+ err := suite.CStateDB.CommitMultiStore(true)
+ suite.Require().NoError(err)
+
+ suite.CITS.WaitNextBlockOrCommit()
+
+ suite.Equal(int64(350), suite.CITS.QueryBalance(0, receiver1.GetCosmosAddress().String()).Amount.Int64())
+ suite.Equal(int64(900), suite.CITS.QueryBalance(0, receiver2.GetCosmosAddress().String()).Amount.Int64())
+}
+
+func (suite *StateDbIntegrationTestSuite) TestAddBalance() {
+ receiver1 := integration_test_util.NewTestAccount(suite.T(), nil)
+ receiver2 := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(300))
+ suite.StateDB.AddBalance(receiver2.GetEthAddress(), big.NewInt(900))
+
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(300))
+
+ err := suite.CStateDB.CommitMultiStore(true)
+ suite.Require().NoError(err)
+
+ suite.CITS.WaitNextBlockOrCommit()
+
+ suite.Equal(int64(600), suite.CITS.QueryBalance(0, receiver1.GetCosmosAddress().String()).Amount.Int64())
+ suite.Equal(int64(900), suite.CITS.QueryBalance(0, receiver2.GetCosmosAddress().String()).Amount.Int64())
+
+ suite.Run("add balance for non-existing account", func() {
+ suite.SetupTest() // reset
+
+ nonExistingAccount := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.StateDB.AddBalance(nonExistingAccount.GetEthAddress(), big.NewInt(300))
+
+ err := suite.CStateDB.CommitMultiStore(true)
+ suite.Require().NoError(err)
+
+ suite.CITS.WaitNextBlockOrCommit()
+
+ suite.Equal(int64(300), suite.CITS.QueryBalance(0, nonExistingAccount.GetCosmosAddress().String()).Amount.Int64())
+ })
+
+ suite.Run("after received balance, account should be created", func() {
+ suite.SetupTest() // reset
+
+ nonExistingAccount := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.StateDB.AddBalance(nonExistingAccount.GetEthAddress(), big.NewInt(300))
+
+ err := suite.CStateDB.CommitMultiStore(true)
+ suite.Require().NoError(err)
+
+ suite.CITS.WaitNextBlockOrCommit()
+
+ suite.Equal(int64(300), suite.CITS.QueryBalance(0, nonExistingAccount.GetCosmosAddress().String()).Amount.Int64())
+
+ suite.NotNil(suite.App().AccountKeeper().GetAccount(suite.Ctx(), nonExistingAccount.GetCosmosAddress()))
+ })
+
+ /* no longer happens because the input is now uint256
+ suite.Run("add negative value", func() {
+ suite.SetupTest() // reset
+ suite.Require().Panics(func() {
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(-1))
+ })
+ })
+
+ suite.Run("add negative value", func() {
+ suite.SetupTest() // reset
+ suite.Require().Panics(func() {
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(-100))
+ })
+ })
+ */
+}
+
+func (suite *StateDbIntegrationTestSuite) TestSubBalance() {
+ receiver1 := integration_test_util.NewTestAccount(suite.T(), nil)
+ receiver2 := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.CITS.MintCoin(receiver1, sdk.NewCoin(suite.CStateDB.ForTest_GetEvmDenom(), sdkmath.NewInt(1000)))
+ suite.CITS.MintCoin(receiver2, sdk.NewCoin(suite.CStateDB.ForTest_GetEvmDenom(), sdkmath.NewInt(1000)))
+
+ suite.Commit()
+
+ suite.StateDB.SubBalance(receiver1.GetEthAddress(), big.NewInt(100))
+ suite.StateDB.SubBalance(receiver2.GetEthAddress(), big.NewInt(100))
+
+ suite.StateDB.SubBalance(receiver1.GetEthAddress(), big.NewInt(150))
+
+ suite.Require().Equal(big.NewInt(750), suite.StateDB.GetBalance(receiver1.GetEthAddress()))
+ suite.Require().Equal(big.NewInt(900), suite.StateDB.GetBalance(receiver2.GetEthAddress()))
+
+ err := suite.CStateDB.CommitMultiStore(true)
+ suite.Require().NoError(err)
+
+ suite.CITS.WaitNextBlockOrCommit()
+
+ suite.Equal(int64(750), suite.CITS.QueryBalance(0, receiver1.GetCosmosAddress().String()).Amount.Int64())
+ suite.Equal(int64(900), suite.CITS.QueryBalance(0, receiver2.GetCosmosAddress().String()).Amount.Int64())
+
+ /* no longer happens because the input is now uint256
+ suite.Run("sub negative value", func() {
+ suite.SetupTest() // reset
+ suite.Require().Panics(func() {
+ suite.StateDB.SubBalance(receiver1.GetEthAddress(), big.NewInt(-1))
+ })
+ })
+
+ suite.Run("sub negative value", func() {
+ suite.SetupTest() // reset
+ suite.Require().Panics(func() {
+ suite.StateDB.SubBalance(receiver1.GetEthAddress(), big.NewInt(-100))
+ })
+ })
+ */
+
+ suite.Run("sub more than balance", func() {
+ suite.SetupTest() // reset
+
+ wallet := suite.CITS.WalletAccounts.Number(1)
+ originalBalance := suite.CITS.QueryBalance(0, wallet.GetCosmosAddress().String()).Amount.BigInt()
+ suite.Require().Greater(originalBalance.Uint64(), uint64(0))
+
+ suite.Require().Panics(func() {
+ suite.StateDB.SubBalance(wallet.GetEthAddress(), new(big.Int).Add(originalBalance, big.NewInt(1)))
+ })
+ })
+
+ suite.Run("sub exact balance", func() {
+ suite.SetupTest() // reset
+
+ wallet := suite.CITS.WalletAccounts.Number(2)
+ originalBalance := suite.CITS.QueryBalance(0, wallet.GetCosmosAddress().String()).Amount.BigInt()
+ suite.Require().Greater(originalBalance.Uint64(), uint64(0))
+
+ suite.Require().NotPanics(func() {
+ suite.StateDB.SubBalance(wallet.GetEthAddress(), originalBalance)
+ })
+
+ suite.Zero(suite.StateDB.GetBalance(wallet.GetEthAddress()).Uint64())
+ })
+
+ suite.Run("can not sub more than unlocked coins on vesting account", func() {
+ suite.SetupTest() // reset
+
+ wallet1 := integration_test_util.NewTestAccount(suite.T(), nil)
+ wallet2 := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ originalVestingCoin := suite.CITS.NewBaseCoin(1_000_000)
+ startVesting := time.Now().UTC().Unix() - 86400*10
+ endVesting := time.Now().UTC().Unix() + 86400*10
+
+ baseAccount1 := suite.App().AccountKeeper().NewAccountWithAddress(suite.Ctx(), wallet1.GetCosmosAddress())
+ vestingAccount1, err := vestingtypes.NewContinuousVestingAccount(
+ baseAccount1.(*authtypes.BaseAccount),
+ sdk.NewCoins(originalVestingCoin),
+ startVesting,
+ endVesting,
+ )
+ suite.Require().NoError(err)
+
+ baseAccount2 := suite.App().AccountKeeper().NewAccountWithAddress(suite.Ctx(), wallet2.GetCosmosAddress())
+ vestingAccount2, err := vestingtypes.NewContinuousVestingAccount(
+ baseAccount2.(*authtypes.BaseAccount),
+ sdk.NewCoins(originalVestingCoin),
+ startVesting,
+ endVesting,
+ )
+ suite.Require().NoError(err)
+
+ suite.App().AccountKeeper().SetAccount(suite.Ctx(), vestingAccount1)
+ suite.App().AccountKeeper().SetAccount(suite.Ctx(), vestingAccount2)
+
+ suite.CITS.MintCoin(wallet1, originalVestingCoin)
+ suite.CITS.MintCoin(wallet2, originalVestingCoin)
+
+ suite.Commit()
+ suite.Commit()
+
+ lockedCoins1 := vestingAccount1.LockedCoins(suite.Ctx().BlockTime())
+ suite.Require().True(lockedCoins1.IsAllPositive())
+ suite.Require().True(lockedCoins1[0].IsLT(originalVestingCoin))
+ suite.Require().Len(lockedCoins1, 1)
+ lockedCoins2 := vestingAccount2.LockedCoins(suite.Ctx().BlockTime())
+ suite.Require().True(lockedCoins2.IsAllPositive())
+ suite.Require().True(lockedCoins2[0].IsLT(originalVestingCoin))
+ suite.Require().Len(lockedCoins2, 1)
+
+ unlockedCoin1 := originalVestingCoin.Sub(lockedCoins1[0])
+ suite.Require().True(unlockedCoin1.IsPositive())
+ unlockedCoin2 := originalVestingCoin.Sub(lockedCoins2[0])
+ suite.Require().True(unlockedCoin2.IsPositive())
+
+ stateDB := suite.newStateDB()
+
+ // can sub entirely unlocked coins
+ suite.Require().NotPanics(func() {
+ stateDB.SubBalance(wallet1.GetEthAddress(), unlockedCoin1.Amount.BigInt())
+ })
+
+ // can not sub more than unlocked coins
+ suite.Require().Panics(func() {
+ stateDB.SubBalance(wallet2.GetEthAddress(), unlockedCoin2.Amount.AddRaw(1).BigInt())
+ })
+ })
+}
+
+func (suite *StateDbIntegrationTestSuite) TestAddSubBalanceWithRevertBehavior() {
+ receiver1 := integration_test_util.NewTestAccount(suite.T(), nil)
+ receiver2 := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(1000))
+ suite.StateDB.AddBalance(receiver2.GetEthAddress(), big.NewInt(1000))
+
+ suite.Require().Equal(big.NewInt(1000), suite.StateDB.GetBalance(receiver1.GetEthAddress()))
+ suite.Require().Equal(big.NewInt(1000), suite.StateDB.GetBalance(receiver2.GetEthAddress()))
+
+ sid0 := suite.StateDB.Snapshot()
+ suite.Require().Equal(0, sid0)
+
+ suite.StateDB.SubBalance(receiver1.GetEthAddress(), big.NewInt(500))
+ suite.StateDB.SubBalance(receiver2.GetEthAddress(), big.NewInt(500))
+
+ balance1AtCheckpoint := suite.StateDB.GetBalance(receiver1.GetEthAddress())
+ balance2AtCheckpoint := suite.StateDB.GetBalance(receiver2.GetEthAddress())
+ suite.Require().Equal(big.NewInt(500), balance1AtCheckpoint)
+ suite.Require().Equal(big.NewInt(500), balance2AtCheckpoint)
+
+ sidAtCheckpoint := suite.StateDB.Snapshot()
+ suite.Require().Equal(1, sidAtCheckpoint)
+
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(4500))
+ suite.StateDB.AddBalance(receiver2.GetEthAddress(), big.NewInt(4500))
+
+ suite.Require().Equal(big.NewInt(5000), suite.StateDB.GetBalance(receiver1.GetEthAddress()))
+ suite.Require().Equal(big.NewInt(5000), suite.StateDB.GetBalance(receiver2.GetEthAddress()))
+
+ sid2 := suite.StateDB.Snapshot()
+ suite.Require().Equal(2, sid2)
+
+ suite.StateDB.SubBalance(receiver1.GetEthAddress(), big.NewInt(2000))
+ suite.StateDB.SubBalance(receiver2.GetEthAddress(), big.NewInt(2000))
+
+ suite.Require().Equal(big.NewInt(3000), suite.StateDB.GetBalance(receiver1.GetEthAddress()))
+ suite.Require().Equal(big.NewInt(3000), suite.StateDB.GetBalance(receiver2.GetEthAddress()))
+
+ sid3 := suite.StateDB.Snapshot()
+ suite.Require().Equal(3, sid3)
+
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(7000))
+ suite.StateDB.AddBalance(receiver2.GetEthAddress(), big.NewInt(7000))
+
+ suite.Require().Equal(big.NewInt(10000), suite.StateDB.GetBalance(receiver1.GetEthAddress()))
+ suite.Require().Equal(big.NewInt(10000), suite.StateDB.GetBalance(receiver2.GetEthAddress()))
+
+ sid4 := suite.StateDB.Snapshot()
+ suite.Require().Equal(4, sid4)
+
+ suite.StateDB.RevertToSnapshot(sidAtCheckpoint) // revert state to checkpoint
+
+ // just for fun
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(8888))
+ suite.StateDB.AddBalance(receiver2.GetEthAddress(), big.NewInt(9999))
+
+ err := suite.CStateDB.CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+
+ suite.CITS.WaitNextBlockOrCommit()
+
+ suite.Equal(balance1AtCheckpoint.Uint64()+8888, suite.CITS.QueryBalance(0, receiver1.GetCosmosAddress().String()).Amount.Uint64())
+ suite.Equal(balance2AtCheckpoint.Uint64()+9999, suite.CITS.QueryBalance(0, receiver2.GetCosmosAddress().String()).Amount.Uint64())
+}
+
+func (suite *StateDbIntegrationTestSuite) TestGetBalance() {
+ wallet1 := suite.CITS.WalletAccounts.Number(1)
+ wallet2 := suite.CITS.WalletAccounts.Number(2)
+
+ suite.CITS.MintCoin(wallet1, suite.CITS.NewBaseCoin(100))
+
+ suite.Commit()
+
+ balance1 := suite.App().BankKeeper().GetBalance(suite.Ctx(), wallet1.GetCosmosAddress(), suite.CStateDB.ForTest_GetEvmDenom())
+ suite.Require().True(balance1.IsPositive())
+
+ balance2 := suite.App().BankKeeper().GetBalance(suite.Ctx(), wallet2.GetCosmosAddress(), suite.CStateDB.ForTest_GetEvmDenom())
+ suite.Require().True(balance2.IsPositive())
+
+ suite.Require().Equal(balance1.Amount.BigInt().String(), suite.StateDB.GetBalance(wallet1.GetEthAddress()).String())
+ suite.Require().Equal(balance2.Amount.BigInt().String(), suite.StateDB.GetBalance(wallet2.GetEthAddress()).String())
+}
+
+func (suite *StateDbIntegrationTestSuite) TestGetNonce() {
+ wallet := suite.CITS.WalletAccounts.Number(1)
+ nonExistsWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ const cosmosSequence = 99
+
+ // set Cosmos sequence
+ accountWallet := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet.GetCosmosAddress())
+ suite.Require().Equal(uint64(0), accountWallet.GetSequence())
+ err := accountWallet.SetSequence(cosmosSequence)
+ suite.Require().NoError(err)
+ suite.App().AccountKeeper().SetAccount(suite.Ctx(), accountWallet)
+
+ suite.Commit()
+
+ accountWallet = suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet.GetCosmosAddress())
+ suite.Require().Equal(uint64(cosmosSequence), accountWallet.GetSequence(), "sequence must be changed")
+ suite.Require().Equal(uint64(cosmosSequence), suite.App().EvmKeeper().GetNonce(suite.Ctx(), wallet.GetEthAddress()), "sequence must be changed")
+
+ stateDB := suite.newStateDB()
+ suite.Equal(uint64(cosmosSequence), stateDB.GetNonce(wallet.GetEthAddress()))
+ suite.Equal(uint64(0), stateDB.GetNonce(nonExistsWallet.GetEthAddress()))
+}
+
+func (suite *StateDbIntegrationTestSuite) TestSetNonce() {
+ wallet := suite.CITS.WalletAccounts.Number(1)
+ nonExistsWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.Commit()
+
+ const cosmosSequence1 = 99
+ const cosmosSequence2 = 88
+
+ stateDB := suite.newStateDB()
+
+ stateDB.SetNonce(wallet.GetEthAddress(), cosmosSequence1)
+ stateDB.SetNonce(nonExistsWallet.GetEthAddress(), cosmosSequence2)
+
+ err := stateDB.(evmvm.CStateDB).CommitMultiStore(true)
+ suite.Require().NoError(err)
+
+ suite.Commit()
+
+ accountWallet := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet.GetCosmosAddress())
+ suite.Equal(uint64(cosmosSequence1), accountWallet.GetSequence())
+ suite.Equal(uint64(cosmosSequence1), suite.App().EvmKeeper().GetNonce(suite.Ctx(), wallet.GetEthAddress()))
+
+ nonExistsAccount := suite.App().AccountKeeper().GetAccount(suite.Ctx(), nonExistsWallet.GetCosmosAddress())
+ suite.NotNil(nonExistsAccount, "account should be created")
+ suite.Equal(uint64(cosmosSequence2), nonExistsAccount.GetSequence())
+ suite.Equal(uint64(cosmosSequence2), suite.App().EvmKeeper().GetNonce(suite.Ctx(), nonExistsWallet.GetEthAddress()))
+}
+
+func (suite *StateDbIntegrationTestSuite) TestGetCodeHash() {
+ wallet := suite.CITS.WalletAccounts.Number(1)
+ nonExistsWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.Equal(common.BytesToHash(evmtypes.EmptyCodeHash), suite.StateDB.GetCodeHash(wallet.GetEthAddress()), "code hash of non-contract should be empty")
+ suite.Equal(common.Hash{}, suite.StateDB.GetCodeHash(nonExistsWallet.GetEthAddress()), "code hash of non-contract should be empty")
+
+ codeHash, code := RandomContractCode()
+ fmt.Println("generated contract code hash:", codeHash.Hex(), "content:", hex.EncodeToString(code))
+
+ contract := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet.GetCosmosAddress())
+
+ suite.App().EvmKeeper().SetCode(suite.Ctx(), codeHash.Bytes(), code)
+ suite.App().EvmKeeper().SetCodeHash(suite.Ctx(), common.BytesToAddress(contract.GetAddress()), codeHash)
+
+ suite.Commit()
+
+ stateDB := suite.newStateDB()
+
+ suite.Equal(codeHash, stateDB.GetCodeHash(common.BytesToAddress(contract.GetAddress().Bytes())), "code hash of contract should be changed")
+ suite.Equal(common.Hash{}, stateDB.GetCodeHash(nonExistsWallet.GetEthAddress()), "code hash of non-contract should be empty")
+}
+
+func (suite *StateDbIntegrationTestSuite) TestGetCode() {
+ wallet := suite.CITS.WalletAccounts.Number(1)
+ nonExistsWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.Empty(suite.StateDB.GetCode(wallet.GetEthAddress()), "code of non-contract should be empty")
+ suite.Empty(suite.StateDB.GetCode(nonExistsWallet.GetEthAddress()), "code of non-exists-account should be empty")
+
+ codeHash, code := RandomContractCode()
+
+ contract := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet.GetCosmosAddress())
+
+ suite.App().EvmKeeper().SetCode(suite.Ctx(), codeHash.Bytes(), code)
+ suite.App().EvmKeeper().SetCodeHash(suite.Ctx(), common.BytesToAddress(contract.GetAddress()), codeHash)
+
+ suite.Commit()
+
+ stateDB := suite.newStateDB()
+
+ suite.Equal(code, stateDB.GetCode(common.BytesToAddress(contract.GetAddress().Bytes())), "code of contract should be changed")
+ suite.Empty(stateDB.GetCode(nonExistsWallet.GetEthAddress()), "code of non-contract should be empty")
+}
+
+func (suite *StateDbIntegrationTestSuite) TestSetCode() {
+ wallet := suite.CITS.WalletAccounts.Number(1)
+ nonExistsWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.Empty(suite.StateDB.GetCode(wallet.GetEthAddress()), "code of non-contract should be empty")
+ suite.Empty(suite.StateDB.GetCode(nonExistsWallet.GetEthAddress()), "code of non-exists-account should be empty")
+
+ codeHash1, code1 := RandomContractCode()
+ codeHash2, code2 := RandomContractCode()
+
+ contract1 := suite.App().AccountKeeper().GetAccount(suite.CStateDB.ForTest_GetCurrentContext(), wallet.GetCosmosAddress())
+
+ suite.StateDB.SetCode(common.BytesToAddress(contract1.GetAddress().Bytes()), code1)
+ suite.StateDB.SetCode(nonExistsWallet.GetEthAddress(), code2)
+
+ contract2WasNotExistsBefore := suite.App().AccountKeeper().GetAccount(suite.CStateDB.ForTest_GetCurrentContext(), nonExistsWallet.GetCosmosAddress()) // contract created from void
+ suite.Require().NotNil(contract2WasNotExistsBefore, "contract should be created")
+
+ err := suite.CStateDB.CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+
+ suite.Commit()
+
+ suite.Equal(codeHash1, suite.App().EvmKeeper().GetCodeHash(suite.Ctx(), contract1.GetAddress()), "code hash mis-match")
+ suite.Equal(code1, suite.App().EvmKeeper().GetCode(suite.Ctx(), codeHash1), "code mis-match")
+
+ suite.Equal(codeHash2, suite.App().EvmKeeper().GetCodeHash(suite.Ctx(), contract2WasNotExistsBefore.GetAddress()), "code hash mis-match")
+ suite.Equal(code2, suite.App().EvmKeeper().GetCode(suite.Ctx(), codeHash2), "code mis-match")
+
+ suite.NotNil(suite.App().AccountKeeper().GetAccount(suite.Ctx(), contract2WasNotExistsBefore.GetAddress()), "contract should be created")
+}
+
+func (suite *StateDbIntegrationTestSuite) TestGetCodeSize() {
+ wallet := suite.CITS.WalletAccounts.Number(1)
+ nonExistsWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.Zero(suite.StateDB.GetCodeSize(wallet.GetEthAddress()), "code size of non-contract should be 0")
+ suite.Zero(suite.StateDB.GetCodeSize(nonExistsWallet.GetEthAddress()), "code size of non-exists account should be 0")
+
+ _, code1 := RandomContractCode()
+ suite.Require().NotEmpty(code1)
+ _, code2 := RandomContractCode()
+ suite.Require().NotEmpty(code2)
+
+ contract1 := wallet
+ contract2WasNotExistsBefore := nonExistsWallet // create contract from void
+
+ suite.StateDB.SetCode(contract1.GetEthAddress(), code1)
+ suite.StateDB.SetCode(contract2WasNotExistsBefore.GetEthAddress(), code2)
+
+ err := suite.CStateDB.CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+
+ suite.Commit()
+
+ stateDB := suite.newStateDB()
+ suite.Equal(len(code1), stateDB.GetCodeSize(contract1.GetEthAddress()))
+ suite.Equal(len(code2), stateDB.GetCodeSize(contract2WasNotExistsBefore.GetEthAddress()))
+}
+
+func (suite *StateDbIntegrationTestSuite) TestGetRefund() {
+ suite.CStateDB.AddRefund(1000)
+
+ suite.Equal(uint64(1000), suite.StateDB.GetRefund())
+
+ suite.CStateDB.AddRefund(1000)
+
+ suite.Equal(uint64(2000), suite.StateDB.GetRefund())
+
+ suite.StateDB.AddRefund(500)
+
+ suite.Equal(uint64(2500), suite.StateDB.GetRefund())
+}
+
+func (suite *StateDbIntegrationTestSuite) TestAddRefund() {
+ suite.CStateDB.AddRefund(1000)
+
+ suite.StateDB.AddRefund(500)
+ suite.StateDB.AddRefund(500)
+
+ suite.Equal(uint64(2000), suite.StateDB.GetRefund())
+}
+
+func (suite *StateDbIntegrationTestSuite) TestSubRefund() {
+ suite.CStateDB.AddRefund(2000)
+
+ suite.StateDB.SubRefund(500)
+ suite.StateDB.SubRefund(500)
+
+ suite.Equal(uint64(1000), suite.StateDB.GetRefund())
+}
+
+func (suite *StateDbIntegrationTestSuite) TestGetCommittedState() {
+ wallet1 := suite.CITS.WalletAccounts.Number(1)
+ wallet2 := suite.CITS.WalletAccounts.Number(2)
+ wallet3WithoutState := suite.CITS.WalletAccounts.Number(3)
+ wallet4 := suite.CITS.WalletAccounts.Number(4)
+
+ account1 := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet1.GetCosmosAddress())
+ suite.Require().NotNil(account1)
+ account2 := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet2.GetCosmosAddress())
+ suite.Require().NotNil(account2)
+ account3WithoutState := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet3WithoutState.GetCosmosAddress())
+ suite.Require().NotNil(account3WithoutState)
+ account4 := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet4.GetCosmosAddress())
+ suite.Require().NotNil(account4)
+
+ hash1 := evmvm.GenerateHash()
+ hash2 := evmvm.GenerateHash()
+
+ suite.App().EvmKeeper().SetState(suite.Ctx(), wallet1.GetEthAddress(), hash1, hash2.Bytes())
+ suite.App().EvmKeeper().SetState(suite.Ctx(), wallet2.GetEthAddress(), hash1, hash2.Bytes())
+ suite.App().EvmKeeper().SetState(suite.Ctx(), wallet4.GetEthAddress(), hash1, hash2.Bytes())
+
+ suite.Commit()
+
+ stateDB := suite.newStateDB()
+
+ suite.Equal(hash2, stateDB.GetCommittedState(wallet1.GetEthAddress(), hash1))
+ suite.Equal(hash2, stateDB.GetCommittedState(wallet2.GetEthAddress(), hash1))
+ suite.Equal(common.Hash{}, stateDB.GetCommittedState(wallet3WithoutState.GetEthAddress(), hash1))
+ suite.Equal(hash2, stateDB.GetCommittedState(wallet4.GetEthAddress(), hash1))
+
+ suite.Run("should returns state in original context, not current one", func() {
+ suite.App().EvmKeeper().SetState(stateDB.(evmvm.CStateDB).ForTest_GetCurrentContext(), wallet2.GetEthAddress(), hash1, evmvm.GenerateHash().Bytes())
+
+ suite.Equal(hash2, stateDB.GetCommittedState(wallet2.GetEthAddress(), hash1))
+ })
+
+ suite.Run("should returns empty state for deleted account no matter store data still exists", func() {
+ currentCtx := stateDB.(evmvm.CStateDB).ForTest_GetCurrentContext()
+
+ getCommittedState := func() common.Hash {
+ return stateDB.GetCommittedState(wallet2.GetEthAddress(), hash1)
+ }
+
+ suite.NotEqual(common.Hash{}, getCommittedState())
+
+ accountI2 := suite.App().AccountKeeper().GetAccount(currentCtx, wallet2.GetCosmosAddress())
+ suite.Require().NotNil(accountI2)
+ suite.App().AccountKeeper().RemoveAccount(currentCtx, accountI2)
+
+ suite.Equal(common.Hash{}, getCommittedState())
+ })
+
+ suite.Run("get state of new account of re-made", func() {
+ currentCtx := stateDB.(evmvm.CStateDB).ForTest_GetCurrentContext()
+
+ getCommittedState := func() common.Hash {
+ return stateDB.GetCommittedState(wallet4.GetEthAddress(), hash1)
+ }
+
+ suite.NotEqual(common.Hash{}, getCommittedState())
+
+ accountI4 := suite.App().AccountKeeper().GetAccount(currentCtx, wallet4.GetCosmosAddress())
+ suite.Require().NotNil(accountI4)
+
+ stateDB.CreateAccount(wallet4.GetEthAddress())
+ suite.Equal(common.Hash{}, getCommittedState(), "state of remade account should be empty")
+
+ // reload accountI to fetch new account number
+ accountI4 = suite.App().AccountKeeper().GetAccount(currentCtx, wallet4.GetCosmosAddress())
+ suite.Require().NotNil(accountI4)
+
+ suite.App().EvmKeeper().SetState(currentCtx, wallet4.GetEthAddress(), hash1, evmvm.GenerateHash().Bytes())
+ suite.Equal(common.Hash{}, getCommittedState(), "state of remade account should be empty regardless state in current un-persisted context")
+ })
+
+ suite.Run("get state of new account", func() {
+ currentCtx := stateDB.(evmvm.CStateDB).ForTest_GetCurrentContext()
+
+ nonExistsWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ getCommittedState := func() common.Hash {
+ return stateDB.GetCommittedState(nonExistsWallet.GetEthAddress(), hash1)
+ }
+
+ suite.Require().Equal(common.Hash{}, getCommittedState(), "state of non-existence account should be empty")
+
+ stateDB.CreateAccount(nonExistsWallet.GetEthAddress())
+ suite.Require().Equal(common.Hash{}, getCommittedState(), "state of new account should be empty")
+
+ accountI := suite.App().AccountKeeper().GetAccount(currentCtx, nonExistsWallet.GetCosmosAddress())
+ suite.Require().NotNil(accountI)
+
+ suite.App().EvmKeeper().SetState(currentCtx, nonExistsWallet.GetEthAddress(), hash1, evmvm.GenerateHash().Bytes())
+ suite.Equal(common.Hash{}, getCommittedState(), "state of new account should be empty regardless state in current un-persisted context")
+ })
+}
+
+func (suite *StateDbIntegrationTestSuite) TestGetState() {
+ wallet1 := suite.CITS.WalletAccounts.Number(1)
+ wallet2 := suite.CITS.WalletAccounts.Number(2)
+ wallet3WithoutState := suite.CITS.WalletAccounts.Number(3)
+ wallet4 := suite.CITS.WalletAccounts.Number(4)
+
+ account1 := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet1.GetCosmosAddress())
+ suite.Require().NotNil(account1)
+ account2 := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet2.GetCosmosAddress())
+ suite.Require().NotNil(account2)
+ account3WithoutState := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet3WithoutState.GetCosmosAddress())
+ suite.Require().NotNil(account3WithoutState)
+ account4 := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet4.GetCosmosAddress())
+ suite.Require().NotNil(account4)
+
+ hash1 := evmvm.GenerateHash()
+ hash2 := evmvm.GenerateHash()
+
+ suite.App().EvmKeeper().SetState(suite.Ctx(), wallet1.GetEthAddress(), hash1, hash2.Bytes())
+ suite.App().EvmKeeper().SetState(suite.Ctx(), wallet2.GetEthAddress(), hash1, hash2.Bytes())
+ suite.App().EvmKeeper().SetState(suite.Ctx(), wallet4.GetEthAddress(), hash1, hash2.Bytes())
+
+ suite.Commit()
+
+ stateDB := suite.newStateDB()
+
+ suite.Equal(hash2, stateDB.GetState(wallet1.GetEthAddress(), hash1))
+ suite.Equal(hash2, stateDB.GetState(wallet2.GetEthAddress(), hash1))
+ suite.Equal(common.Hash{}, stateDB.GetState(wallet3WithoutState.GetEthAddress(), hash1))
+ suite.Equal(hash2, stateDB.GetState(wallet4.GetEthAddress(), hash1))
+
+ suite.Run("should returns state of current context, not original one", func() {
+ newState := evmvm.GenerateHash()
+ suite.App().EvmKeeper().SetState(stateDB.(evmvm.CStateDB).ForTest_GetCurrentContext(), wallet2.GetEthAddress(), hash1, newState.Bytes())
+
+ suite.Equal(newState, stateDB.GetState(wallet2.GetEthAddress(), hash1))
+ suite.NotEqual(hash2, stateDB.GetState(wallet2.GetEthAddress(), hash1))
+ })
+
+ /* This test was disabled because this implementation only persist state by address so previous state will being maintained
+ suite.Run("should returns empty state for deleted account no matter store data still exists", func() {
+ currentCtx := stateDB.(xethvm.CStateDB).ForTest_GetCurrentContext()
+
+ getState := func() common.Hash {
+ return stateDB.GetState(wallet2.GetEthAddress(), hash1)
+ }
+
+ suite.NotEqual(common.Hash{}, getState())
+
+ accountI2 := suite.App().AccountKeeper().GetAccount(currentCtx, wallet2.GetCosmosAddress())
+ suite.Require().NotNil(accountI2)
+ suite.App().AccountKeeper().RemoveAccount(currentCtx, accountI2)
+
+ suite.Equal(common.Hash{}, getState())
+ })
+ */
+
+ suite.Run("get state of new account of re-made", func() {
+ currentCtx := stateDB.(evmvm.CStateDB).ForTest_GetCurrentContext()
+
+ getState := func() common.Hash {
+ return stateDB.GetState(wallet4.GetEthAddress(), hash1)
+ }
+
+ suite.NotEqual(common.Hash{}, getState())
+
+ accountI4 := suite.App().AccountKeeper().GetAccount(currentCtx, wallet4.GetCosmosAddress())
+ suite.Require().NotNil(accountI4)
+
+ stateDB.CreateAccount(wallet4.GetEthAddress())
+ suite.Equal(common.Hash{}, getState(), "state of remade account should be empty")
+
+ // reload accountI to fetch new account number
+ accountI4 = suite.App().AccountKeeper().GetAccount(currentCtx, wallet4.GetCosmosAddress())
+ suite.Require().NotNil(accountI4)
+
+ newState := evmvm.GenerateHash()
+ suite.App().EvmKeeper().SetState(currentCtx, wallet4.GetEthAddress(), hash1, newState.Bytes())
+ suite.Equal(newState, getState(), "state of remade account should be the dirty state")
+ })
+
+ suite.Run("get state of new account", func() {
+ currentCtx := stateDB.(evmvm.CStateDB).ForTest_GetCurrentContext()
+
+ nonExistsWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ getState := func() common.Hash {
+ return stateDB.GetState(nonExistsWallet.GetEthAddress(), hash1)
+ }
+
+ suite.Require().Equal(common.Hash{}, getState(), "state of non-existence account should be empty")
+
+ stateDB.CreateAccount(nonExistsWallet.GetEthAddress())
+ suite.Require().Equal(common.Hash{}, getState(), "state of new account should be empty")
+
+ accountI := suite.App().AccountKeeper().GetAccount(currentCtx, nonExistsWallet.GetCosmosAddress())
+ suite.Require().NotNil(accountI)
+
+ newState := evmvm.GenerateHash()
+ suite.App().EvmKeeper().SetState(currentCtx, nonExistsWallet.GetEthAddress(), hash1, newState.Bytes())
+ suite.Equal(newState, getState(), "state of new account should be the dirty state")
+ })
+}
+
+func (suite *StateDbIntegrationTestSuite) TestSetState() {
+ wallet1 := suite.CITS.WalletAccounts.Number(1)
+ wallet2 := suite.CITS.WalletAccounts.Number(2)
+ wallet3WithoutState := suite.CITS.WalletAccounts.Number(3)
+ wallet4 := suite.CITS.WalletAccounts.Number(4)
+
+ account1 := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet1.GetCosmosAddress())
+ suite.Require().NotNil(account1)
+ account2 := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet2.GetCosmosAddress())
+ suite.Require().NotNil(account2)
+ account3WithoutState := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet3WithoutState.GetCosmosAddress())
+ suite.Require().NotNil(account3WithoutState)
+ account4 := suite.App().AccountKeeper().GetAccount(suite.Ctx(), wallet4.GetCosmosAddress())
+ suite.Require().NotNil(account4)
+
+ hash1 := evmvm.GenerateHash()
+ hash2 := evmvm.GenerateHash()
+
+ suite.StateDB.SetState(wallet1.GetEthAddress(), hash1, hash2)
+ suite.StateDB.SetState(wallet2.GetEthAddress(), hash1, hash2)
+ suite.StateDB.SetState(wallet4.GetEthAddress(), hash1, hash2)
+
+ err := suite.CStateDB.CommitMultiStore(true)
+ suite.Require().NoError(err)
+
+ suite.Commit()
+
+ stateDB := suite.newStateDB()
+
+ suite.Equal(hash2, stateDB.GetState(wallet1.GetEthAddress(), hash1))
+ suite.Equal(hash2, stateDB.GetState(wallet2.GetEthAddress(), hash1))
+ suite.Equal(common.Hash{}, stateDB.GetState(wallet3WithoutState.GetEthAddress(), hash1))
+ suite.Equal(hash2, stateDB.GetState(wallet4.GetEthAddress(), hash1))
+
+ suite.Run("should change state of current context, not original one", func() {
+ newState := evmvm.GenerateHash()
+ stateDB.SetState(wallet2.GetEthAddress(), hash1, newState)
+
+ suite.Equal(newState, stateDB.GetState(wallet2.GetEthAddress(), hash1))
+ suite.NotEqual(hash2, stateDB.GetState(wallet2.GetEthAddress(), hash1))
+
+ suite.Equal(hash2, suite.App().EvmKeeper().GetState(stateDB.(evmvm.CStateDB).ForTest_GetOriginalContext(), wallet2.GetEthAddress(), hash1), "original state must be unchanged before commit")
+ })
+
+ suite.Run("should create new account for deleted account", func() {
+ currentCtx := stateDB.(evmvm.CStateDB).ForTest_GetCurrentContext()
+
+ getState := func() common.Hash {
+ return stateDB.GetState(wallet2.GetEthAddress(), hash1)
+ }
+
+ suite.NotEqual(common.Hash{}, getState())
+
+ accountI2 := suite.App().AccountKeeper().GetAccount(currentCtx, wallet2.GetCosmosAddress())
+ suite.Require().NotNil(accountI2)
+ suite.App().AccountKeeper().RemoveAccount(currentCtx, accountI2)
+
+ accountI2 = suite.App().AccountKeeper().GetAccount(currentCtx, wallet2.GetCosmosAddress())
+ suite.Require().Nil(accountI2)
+ // suite.Equal(common.Hash{}, getState())
+
+ newState := evmvm.GenerateHash()
+ stateDB.SetState(wallet2.GetEthAddress(), hash1, newState)
+ suite.Equal(newState, getState())
+
+ accountI2 = suite.App().AccountKeeper().GetAccount(currentCtx, wallet2.GetCosmosAddress())
+ suite.NotNil(accountI2, "account should be re-created")
+ })
+
+ suite.Run("state of new account of re-made", func() {
+ currentCtx := stateDB.(evmvm.CStateDB).ForTest_GetCurrentContext()
+
+ getState := func() common.Hash {
+ return stateDB.GetState(wallet4.GetEthAddress(), hash1)
+ }
+
+ suite.NotEqual(common.Hash{}, getState())
+
+ accountI4 := suite.App().AccountKeeper().GetAccount(currentCtx, wallet4.GetCosmosAddress())
+ suite.Require().NotNil(accountI4)
+
+ stateDB.CreateAccount(wallet4.GetEthAddress())
+ suite.Equal(common.Hash{}, getState(), "state of remade account should be empty")
+
+ // reload accountI to fetch new account number
+ accountI4 = suite.App().AccountKeeper().GetAccount(currentCtx, wallet4.GetCosmosAddress())
+ suite.Require().NotNil(accountI4)
+
+ newState := evmvm.GenerateHash()
+ stateDB.SetState(wallet4.GetEthAddress(), hash1, newState)
+ suite.Equal(newState, getState(), "state of remade account should be the dirty state")
+ })
+}
+
+func (suite *StateDbIntegrationTestSuite) TestGetSetTransientState() {
+ addr := evmvm.GenerateAddress()
+ key1 := evmvm.GenerateHash()
+ val1 := evmvm.GenerateHash()
+ key2 := evmvm.GenerateHash()
+ val2 := evmvm.GenerateHash()
+
+ addr2 := evmvm.GenerateAddress()
+
+ suite.CStateDB.SetTransientState(addr, key1, val1)
+ suite.CStateDB.SetTransientState(addr, key2, val2)
+ suite.CStateDB.SetTransientState(addr2, key1, val1)
+
+ suite.Equal(val1, suite.CStateDB.GetTransientState(addr, key1))
+ suite.Equal(val2, suite.CStateDB.GetTransientState(addr, key2))
+ suite.Equal(val1, suite.CStateDB.GetTransientState(addr2, key1))
+ suite.Equal(common.Hash{}, suite.CStateDB.GetTransientState(addr2, key2))
+}
+
+func (suite *StateDbIntegrationTestSuite) TestSelfDestruct() {
+ existingAccount1 := suite.CITS.WalletAccounts.Number(1)
+ existingAccount2 := suite.CITS.WalletAccounts.Number(2)
+ nonExistsAccount1 := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.Require().True(suite.App().AccountKeeper().HasAccount(suite.Ctx(), existingAccount1.GetCosmosAddress()))
+ suite.Require().False(suite.App().AccountKeeper().HasAccount(suite.Ctx(), nonExistsAccount1.GetCosmosAddress()))
+
+ suite.StateDB.(evmvm.CStateDB).Selfdestruct6780(existingAccount1.GetEthAddress())
+ suite.False(suite.CStateDB.HasSuicided(existingAccount1.GetEthAddress()), "Selfdestruct6780 should not marking self-destructed for existing account")
+ suite.Equal(
+ suite.CStateDB.HasSuicided(existingAccount1.GetEthAddress()),
+ suite.StateDB.HasSuicided(existingAccount1.GetEthAddress()),
+ )
+
+ suite.StateDB.(evmvm.CStateDB).Selfdestruct6780(nonExistsAccount1.GetEthAddress())
+ suite.False(suite.CStateDB.HasSuicided(nonExistsAccount1.GetEthAddress()), "Selfdestruct6780 should not marking self-destructed for non-exists account")
+ suite.Equal(
+ suite.CStateDB.HasSuicided(nonExistsAccount1.GetEthAddress()),
+ suite.StateDB.HasSuicided(nonExistsAccount1.GetEthAddress()),
+ )
+
+ err := suite.CStateDB.CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+ suite.Commit()
+
+ suite.Require().True(suite.App().AccountKeeper().HasAccount(suite.Ctx(), existingAccount1.GetCosmosAddress()), "existing account should not be deleted because it has not been marked as self-destructed")
+
+ stateDB := suite.newStateDB()
+ cbDB := stateDB.(evmvm.CStateDB)
+
+ stateDB.Suicide(existingAccount1.GetEthAddress())
+ suite.True(stateDB.HasSuicided(existingAccount1.GetEthAddress()), "Selfdestruct should marking self-destructed for existing account")
+ suite.Equal(
+ stateDB.HasSuicided(existingAccount1.GetEthAddress()),
+ stateDB.HasSuicided(existingAccount1.GetEthAddress()),
+ )
+
+ stateDB.Suicide(nonExistsAccount1.GetEthAddress())
+ suite.False(stateDB.HasSuicided(nonExistsAccount1.GetEthAddress()), "Selfdestruct should not marking self-destructed for non-exists account")
+ suite.Equal(
+ stateDB.HasSuicided(nonExistsAccount1.GetEthAddress()),
+ stateDB.HasSuicided(nonExistsAccount1.GetEthAddress()),
+ )
+
+ err = cbDB.CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+ suite.Commit()
+
+ suite.Require().False(suite.App().AccountKeeper().HasAccount(suite.Ctx(), existingAccount1.GetCosmosAddress()), "existing account should be deleted because it has been marked as self-destructed")
+
+ stateDB = suite.newStateDB()
+ cbDB = stateDB.(evmvm.CStateDB)
+ wasExistingAccountButDeletedBefore := existingAccount1
+
+ stateDB.CreateAccount(wasExistingAccountButDeletedBefore.GetEthAddress())
+ cbDB.Selfdestruct6780(wasExistingAccountButDeletedBefore.GetEthAddress())
+ suite.True(stateDB.HasSuicided(wasExistingAccountButDeletedBefore.GetEthAddress()), "Selfdestruct6780 should marking self-destructed because it has just been created within the same tx")
+ suite.Equal(
+ stateDB.HasSuicided(wasExistingAccountButDeletedBefore.GetEthAddress()),
+ stateDB.HasSuicided(wasExistingAccountButDeletedBefore.GetEthAddress()),
+ )
+
+ stateDB.CreateAccount(existingAccount2.GetEthAddress())
+ cbDB.Selfdestruct6780(existingAccount2.GetEthAddress())
+ suite.True(stateDB.HasSuicided(existingAccount2.GetEthAddress()), "Selfdestruct6780 should marking self-destructed because it has just been re-created within the same tx")
+ suite.Equal(
+ stateDB.HasSuicided(existingAccount2.GetEthAddress()),
+ stateDB.HasSuicided(existingAccount2.GetEthAddress()),
+ )
+
+ stateDB.CreateAccount(nonExistsAccount1.GetEthAddress())
+ cbDB.Selfdestruct6780(nonExistsAccount1.GetEthAddress())
+ suite.True(stateDB.HasSuicided(nonExistsAccount1.GetEthAddress()), "Selfdestruct6780 should marking self-destructed because it has just been created within the same tx")
+ suite.Equal(
+ stateDB.HasSuicided(nonExistsAccount1.GetEthAddress()),
+ stateDB.HasSuicided(nonExistsAccount1.GetEthAddress()),
+ )
+
+ err = cbDB.CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+ suite.Commit()
+
+ suite.False(suite.App().AccountKeeper().HasAccount(suite.Ctx(), wasExistingAccountButDeletedBefore.GetCosmosAddress()), "existing account should be deleted because it has been marked as self-destructed")
+ suite.False(suite.App().AccountKeeper().HasAccount(suite.Ctx(), existingAccount2.GetCosmosAddress()), "existing account should be deleted because it has been marked as self-destructed")
+ suite.False(suite.App().AccountKeeper().HasAccount(suite.Ctx(), nonExistsAccount1.GetCosmosAddress()), "should be deleted because it has been marked as self-destructed")
+
+ suite.Run("HasSuicided", func() {
+ stateDB := suite.newStateDB()
+
+ for i := 0; i < 10; i++ {
+ yes := i%2 == 0
+ addr := evmvm.GenerateAddress()
+ if yes {
+ stateDB.CreateAccount(addr)
+ stateDB.(evmvm.CStateDB).Selfdestruct6780(addr)
+ suite.True(stateDB.HasSuicided(addr))
+ stateDB.Suicide(addr)
+ suite.True(stateDB.HasSuicided(addr))
+ } else {
+ stateDB.(evmvm.CStateDB).Selfdestruct6780(addr)
+ suite.False(stateDB.HasSuicided(addr))
+ stateDB.Suicide(addr)
+ suite.False(stateDB.HasSuicided(addr))
+ }
+ }
+ })
+
+ suite.Run("State before and after self destruct", func() {
+ stateDB := suite.newStateDB()
+
+ wallet1 := suite.CITS.WalletAccounts.Number(1)
+ wallet2 := suite.CITS.WalletAccounts.Number(2)
+
+ key := evmvm.GenerateHash()
+ val := evmvm.GenerateHash()
+
+ codeHash, code := RandomContractCode()
+
+ stateDB.CreateAccount(wallet1.GetEthAddress())
+ stateDB.SetState(wallet1.GetEthAddress(), key, val)
+ stateDB.SetCode(wallet1.GetEthAddress(), code)
+
+ suite.Require().Equal(val, stateDB.GetState(wallet1.GetEthAddress(), key))
+ suite.Require().Equal(codeHash, stateDB.GetCodeHash(wallet1.GetEthAddress()))
+
+ err := stateDB.(evmvm.CStateDB).CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+ suite.Commit()
+
+ suite.Require().Equal(val, suite.App().EvmKeeper().GetState(suite.Ctx(), wallet1.GetEthAddress(), key))
+
+ stateDB = suite.newStateDB()
+
+ // assign for account that only available in current context
+ stateDB.CreateAccount(wallet2.GetEthAddress())
+ stateDB.SetState(wallet2.GetEthAddress(), key, val)
+ stateDB.SetCode(wallet2.GetEthAddress(), code)
+
+ suite.Require().Equal(val, stateDB.GetState(wallet2.GetEthAddress(), key))
+ suite.Require().Equal(codeHash, stateDB.GetCodeHash(wallet2.GetEthAddress()))
+
+ stateDB.Suicide(wallet1.GetEthAddress())
+ stateDB.Suicide(wallet2.GetEthAddress())
+
+ suite.Require().True(stateDB.HasSuicided(wallet1.GetEthAddress()))
+ suite.Require().True(stateDB.HasSuicided(wallet2.GetEthAddress()))
+
+ suite.Equal(val, stateDB.GetState(wallet1.GetEthAddress(), key), "state must be still accessible before commit")
+ suite.Equal(val, stateDB.GetState(wallet2.GetEthAddress(), key), "state must be still accessible before commit")
+
+ suite.Equal(codeHash, stateDB.GetCodeHash(wallet1.GetEthAddress()), "code hash must be still accessible before commit")
+ suite.Equal(codeHash, stateDB.GetCodeHash(wallet2.GetEthAddress()), "code hash must be still accessible before commit")
+
+ // backup account
+ cbDB := stateDB.(evmvm.CStateDB)
+ account1 := suite.App().AccountKeeper().GetAccount(cbDB.ForTest_GetCurrentContext(), wallet1.GetCosmosAddress())
+ suite.Require().NotNil(account1)
+ account2 := suite.App().AccountKeeper().GetAccount(cbDB.ForTest_GetCurrentContext(), wallet2.GetCosmosAddress())
+ suite.Require().NotNil(account2)
+
+ err = stateDB.(evmvm.CStateDB).CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+ suite.Commit()
+
+ suite.False(suite.App().AccountKeeper().HasAccount(suite.Ctx(), wallet1.GetCosmosAddress()), "existing account should be deleted because it has been marked as self-destructed")
+ suite.False(suite.App().AccountKeeper().HasAccount(suite.Ctx(), wallet2.GetCosmosAddress()), "existing account should be deleted because it has been marked as self-destructed")
+
+ // set back account for accessing code hash and state
+ suite.App().AccountKeeper().SetAccount(suite.Ctx(), account1)
+ suite.App().AccountKeeper().SetAccount(suite.Ctx(), account2)
+
+ suite.Commit()
+
+ suite.Equal(common.Hash{}, suite.App().EvmKeeper().GetState(suite.Ctx(), wallet1.GetEthAddress(), key), "state must be removed")
+ suite.Equal(common.Hash{}, suite.App().EvmKeeper().GetState(suite.Ctx(), wallet2.GetEthAddress(), key), "state must be removed")
+ suite.Equal(common.BytesToHash(evmtypes.EmptyCodeHash), suite.App().EvmKeeper().GetCodeHash(suite.Ctx(), account1.GetAddress()), "code hash must be removed")
+ suite.Equal(common.BytesToHash(evmtypes.EmptyCodeHash), suite.App().EvmKeeper().GetCodeHash(suite.Ctx(), account2.GetAddress()), "code hash must be removed")
+ })
+}
+
+func (suite *StateDbIntegrationTestSuite) TestExist() {
+ existingWallet1 := suite.CITS.WalletAccounts.Number(1)
+ existingWallet2 := suite.CITS.WalletAccounts.Number(2)
+ nonExistsWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.True(suite.StateDB.Exist(existingWallet1.GetEthAddress()))
+ suite.True(suite.StateDB.Exist(existingWallet2.GetEthAddress()))
+ suite.False(suite.StateDB.Exist(nonExistsWallet.GetEthAddress()))
+
+ suite.StateDB.CreateAccount(nonExistsWallet.GetEthAddress())
+ nonExistsWalletNowExists := nonExistsWallet
+ suite.True(suite.StateDB.Exist(nonExistsWalletNowExists.GetEthAddress()))
+
+ suite.StateDB.Suicide(existingWallet1.GetEthAddress())
+ suite.True(suite.StateDB.Exist(existingWallet1.GetEthAddress()), "existing account should be still exist because it has not been committed yet")
+
+ suite.StateDB.(evmvm.CStateDB).Selfdestruct6780(nonExistsWalletNowExists.GetEthAddress())
+ suite.True(suite.StateDB.Exist(nonExistsWalletNowExists.GetEthAddress()), "existing account should be still exist because it has not been committed yet")
+
+ err := suite.CStateDB.CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+ suite.Commit()
+
+ stateDB := suite.newStateDB()
+
+ suite.False(stateDB.Exist(existingWallet1.GetEthAddress()), "existing account should be deleted because it has been marked as self-destructed")
+ suite.True(stateDB.Exist(existingWallet2.GetEthAddress()))
+ suite.False(stateDB.Exist(nonExistsWalletNowExists.GetEthAddress()), "existing account should be deleted because it has been marked as self-destructed")
+}
+
+func (suite *StateDbIntegrationTestSuite) TestEmpty() {
+ nonExistsWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+
+ suite.True(suite.StateDB.Empty(nonExistsWallet.GetEthAddress()), "non exists account should be treated as empty")
+
+ suite.StateDB.CreateAccount(nonExistsWallet.GetEthAddress())
+ nonExistsWalletNowExists := nonExistsWallet
+ suite.True(suite.StateDB.Empty(nonExistsWalletNowExists.GetEthAddress()), "newly created account should be empty")
+
+ suite.StateDB.AddBalance(nonExistsWalletNowExists.GetEthAddress(), big.NewInt(1))
+ suite.False(suite.StateDB.Empty(nonExistsWalletNowExists.GetEthAddress()), "account with balance should not be empty")
+
+ suite.StateDB.SubBalance(nonExistsWalletNowExists.GetEthAddress(), big.NewInt(1))
+ suite.Require().True(suite.StateDB.Empty(nonExistsWalletNowExists.GetEthAddress()))
+
+ suite.StateDB.SetNonce(nonExistsWalletNowExists.GetEthAddress(), 1)
+ suite.False(suite.StateDB.Empty(nonExistsWalletNowExists.GetEthAddress()), "account with nonce should not be empty")
+
+ suite.StateDB.SetNonce(nonExistsWalletNowExists.GetEthAddress(), 0)
+ suite.Require().True(suite.StateDB.Empty(nonExistsWalletNowExists.GetEthAddress()))
+
+ suite.StateDB.SetCode(nonExistsWalletNowExists.GetEthAddress(), []byte{1})
+ suite.False(suite.StateDB.Empty(nonExistsWalletNowExists.GetEthAddress()), "account with code should not be empty")
+
+ suite.StateDB.SetCode(nonExistsWalletNowExists.GetEthAddress(), []byte{})
+ suite.Require().True(suite.StateDB.Empty(nonExistsWalletNowExists.GetEthAddress()))
+}
+
+func (suite *StateDbIntegrationTestSuite) TestAddressInAccessList() {
+ wallet1 := suite.CITS.WalletAccounts.Number(1)
+ wallet2 := suite.CITS.WalletAccounts.Number(2)
+
+ suite.False(suite.StateDB.AddressInAccessList(wallet1.GetEthAddress()))
+ suite.False(suite.StateDB.AddressInAccessList(wallet2.GetEthAddress()))
+
+ suite.CStateDB.AddAddressToAccessList(wallet1.GetEthAddress())
+ suite.True(suite.StateDB.AddressInAccessList(wallet1.GetEthAddress()))
+ suite.False(suite.StateDB.AddressInAccessList(wallet2.GetEthAddress()))
+}
+
+func (suite *StateDbIntegrationTestSuite) TestSlotInAccessList() {
+ wallet1 := suite.CITS.WalletAccounts.Number(1)
+ wallet2 := suite.CITS.WalletAccounts.Number(2)
+
+ slot1 := evmvm.GenerateHash()
+ slot2 := evmvm.GenerateHash()
+
+ suite.CStateDB.AddSlotToAccessList(wallet1.GetEthAddress(), slot1)
+ suite.CStateDB.AddSlotToAccessList(wallet1.GetEthAddress(), slot2)
+ suite.CStateDB.AddSlotToAccessList(wallet2.GetEthAddress(), slot1)
+
+ var addrExists, slotExists bool
+
+ addrExists, slotExists = suite.StateDB.SlotInAccessList(wallet1.GetEthAddress(), slot1)
+ suite.True(addrExists)
+ suite.True(slotExists)
+
+ addrExists, slotExists = suite.StateDB.SlotInAccessList(wallet1.GetEthAddress(), slot2)
+ suite.True(addrExists)
+ suite.True(slotExists)
+
+ addrExists, slotExists = suite.StateDB.SlotInAccessList(wallet2.GetEthAddress(), slot1)
+ suite.True(addrExists)
+ suite.True(slotExists)
+
+ addrExists, slotExists = suite.StateDB.SlotInAccessList(wallet2.GetEthAddress(), slot2)
+ suite.True(addrExists)
+ suite.False(slotExists)
+
+ addrExists, slotExists = suite.StateDB.SlotInAccessList(evmvm.GenerateAddress(), slot1)
+ suite.False(addrExists)
+ suite.False(slotExists)
+
+ addrExists, slotExists = suite.StateDB.SlotInAccessList(evmvm.GenerateAddress(), slot2)
+ suite.False(addrExists)
+ suite.False(slotExists)
+}
+
+func (suite *StateDbIntegrationTestSuite) TestAddAddressToAccessList() {
+ wallet1 := suite.CITS.WalletAccounts.Number(1)
+ wallet2 := suite.CITS.WalletAccounts.Number(2)
+
+ suite.StateDB.AddAddressToAccessList(wallet1.GetEthAddress())
+
+ suite.True(suite.CStateDB.AddressInAccessList(wallet1.GetEthAddress()))
+ suite.False(suite.CStateDB.AddressInAccessList(wallet2.GetEthAddress()))
+}
+
+func (suite *StateDbIntegrationTestSuite) TestAddSlotToAccessList() {
+ wallet1 := suite.CITS.WalletAccounts.Number(1)
+ wallet2 := suite.CITS.WalletAccounts.Number(2)
+
+ slot1 := evmvm.GenerateHash()
+ slot2 := evmvm.GenerateHash()
+
+ suite.StateDB.AddSlotToAccessList(wallet1.GetEthAddress(), slot1)
+ suite.StateDB.AddSlotToAccessList(wallet1.GetEthAddress(), slot2)
+ suite.StateDB.AddSlotToAccessList(wallet2.GetEthAddress(), slot1)
+
+ var addrExists, slotExists bool
+
+ addrExists, slotExists = suite.CStateDB.SlotInAccessList(wallet1.GetEthAddress(), slot1)
+ suite.True(addrExists)
+ suite.True(slotExists)
+
+ addrExists, slotExists = suite.CStateDB.SlotInAccessList(wallet1.GetEthAddress(), slot2)
+ suite.True(addrExists)
+ suite.True(slotExists)
+
+ addrExists, slotExists = suite.CStateDB.SlotInAccessList(wallet2.GetEthAddress(), slot1)
+ suite.True(addrExists)
+ suite.True(slotExists)
+
+ addrExists, slotExists = suite.CStateDB.SlotInAccessList(wallet2.GetEthAddress(), slot2)
+ suite.True(addrExists)
+ suite.False(slotExists)
+
+ addrExists, slotExists = suite.CStateDB.SlotInAccessList(evmvm.GenerateAddress(), slot1)
+ suite.False(addrExists)
+ suite.False(slotExists)
+
+ addrExists, slotExists = suite.CStateDB.SlotInAccessList(evmvm.GenerateAddress(), slot2)
+ suite.False(addrExists)
+ suite.False(slotExists)
+}
+
+func (suite *StateDbIntegrationTestSuite) TestPrepareAccessList() {
+ sender := evmvm.GenerateAddress()
+ // coinBase := evmvm.GenerateAddress()
+ dst := evmvm.GenerateAddress()
+ precompiles := []common.Address{evmvm.GenerateAddress(), evmvm.GenerateAddress()}
+ accessList := ethtypes.AccessList{
+ {
+ Address: evmvm.GenerateAddress(),
+ StorageKeys: []common.Hash{evmvm.GenerateHash(), evmvm.GenerateHash()},
+ },
+ {
+ Address: evmvm.GenerateAddress(),
+ StorageKeys: []common.Hash{evmvm.GenerateHash(), evmvm.GenerateHash()},
+ },
+ }
+ logs := evmvm.Logs{
+ {
+ Address: evmvm.GenerateAddress(),
+ Topics: nil,
+ Data: nil,
+ BlockNumber: 1,
+ TxHash: evmvm.GenerateHash(),
+ TxIndex: 1,
+ BlockHash: evmvm.GenerateHash(),
+ Index: 1,
+ Removed: false,
+ },
+ }
+
+ // dirty storages
+ var originalRefund uint64 = 5
+ suite.CStateDB.AddRefund(originalRefund)
+ suite.CStateDB.ForTest_AddAddressToSelfDestructedList(evmvm.GenerateAddress())
+ suite.CStateDB.AddSlotToAccessList(evmvm.GenerateAddress(), evmvm.GenerateHash())
+ suite.CStateDB.ForTest_SetLogs(logs)
+ suite.CStateDB.SetTransientState(evmvm.GenerateAddress(), evmvm.GenerateHash(), evmvm.GenerateHash())
+
+ // execute
+ suite.StateDB.PrepareAccessList(sender, &dst, precompiles, accessList)
+
+ // check
+ suite.Equal(originalRefund, suite.CStateDB.GetRefund(), "refund should be kept")
+ suite.NotEmpty(suite.CStateDB.ForTest_CloneSelfDestructed(), "self destructed should be kept")
+ suite.True(suite.StateDB.AddressInAccessList(sender))
+ suite.True(suite.StateDB.AddressInAccessList(dst))
+ for _, precompile := range precompiles {
+ suite.True(suite.StateDB.AddressInAccessList(precompile))
+ }
+ for _, access := range accessList {
+ suite.True(suite.StateDB.AddressInAccessList(access.Address))
+ for _, slot := range access.StorageKeys {
+ suite.True(suite.StateDB.SlotInAccessList(access.Address, slot))
+ }
+ }
+ suite.Equal(2 /*sender+dst*/ +1 /*coinbase*/ +len(precompiles)+len(accessList), len(suite.CStateDB.ForTest_CloneAccessList().CloneElements()))
+ suite.Equal([]*ethtypes.Log(logs), suite.CStateDB.GetTransactionLogs(), "logs should be kept")
+ suite.Zero(suite.CStateDB.ForTest_CountRecordsTransientStorage(), "transient storage should be cleared")
+}
+
+func (suite *StateDbIntegrationTestSuite) TestAddLog() {
+ for i := 0; i < 5; i++ {
+ suite.Require().Equal(i, len(suite.CStateDB.GetTransactionLogs()))
+ suite.StateDB.AddLog(ðtypes.Log{
+ Address: evmvm.GenerateAddress(),
+ Topics: nil,
+ Data: nil,
+ BlockNumber: uint64(i) + 1,
+ TxHash: evmvm.GenerateHash(),
+ TxIndex: uint(i),
+ BlockHash: evmvm.GenerateHash(),
+ Index: uint(i),
+ Removed: false,
+ })
+ suite.Require().Equal(i+1, len(suite.CStateDB.GetTransactionLogs()))
+ }
+}
+
+func (suite *StateDbIntegrationTestSuite) TestGetTransactionLogs() {
+ suite.Run("nil logs list returns empty list", func() {
+ suite.CStateDB.ForTest_SetLogs(nil)
+
+ output := suite.CStateDB.GetTransactionLogs()
+ suite.NotNil(output)
+ suite.Empty(output)
+ })
+
+ for i := 0; i < 5; i++ {
+ suite.Require().Len(suite.CStateDB.GetTransactionLogs(), i)
+ suite.CStateDB.AddLog(ðtypes.Log{
+ Address: evmvm.GenerateAddress(),
+ Topics: nil,
+ Data: nil,
+ BlockNumber: uint64(i) + 1,
+ TxHash: evmvm.GenerateHash(),
+ TxIndex: uint(i),
+ BlockHash: evmvm.GenerateHash(),
+ Index: uint(i),
+ Removed: false,
+ })
+ }
+
+ suite.Equal(len(suite.CStateDB.GetTransactionLogs()), len(suite.CStateDB.GetTransactionLogs()))
+}
+
+func (suite *StateDbIntegrationTestSuite) TestSnapshotAndRevert() {
+ receiver1 := integration_test_util.NewTestAccount(suite.T(), nil)
+ receiver2 := integration_test_util.NewTestAccount(suite.T(), nil)
+ nonExistsWalletBeforeCheckpoint := integration_test_util.NewTestAccount(suite.T(), nil)
+ existingWalletBeforeCheckpoint := suite.CITS.WalletAccounts.Number(1)
+ zeroNonceWalletBeforeCheckPoint := integration_test_util.NewTestAccount(suite.T(), nil)
+ emptyWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+ contract := integration_test_util.NewTestAccount(suite.T(), nil)
+ codeHash1, code1 := RandomContractCode()
+ stateKey := evmvm.GenerateHash()
+ stateVal := evmvm.GenerateHash()
+
+ const checkAtSnapshot = 5
+
+ var touchedAtCheckpoint evmvm.AccountTracker
+ var selfStructAtCheckpoint evmvm.AccountTracker
+ var accessListAtCheckpoint *evmvm.AccessList2
+ var transientStoreAtCheckpoint evmvm.TransientStorage
+ var logsAtCheckpoint evmvm.Logs
+
+ for r := 1; r < 10; r++ {
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(1000))
+ suite.StateDB.AddBalance(receiver2.GetEthAddress(), big.NewInt(1000))
+
+ suite.Require().Equal(big.NewInt(int64(1000*r)), suite.StateDB.GetBalance(receiver1.GetEthAddress()))
+ suite.Require().Equal(big.NewInt(int64(1000*r)), suite.StateDB.GetBalance(receiver2.GetEthAddress()))
+
+ suite.StateDB.SetNonce(zeroNonceWalletBeforeCheckPoint.GetEthAddress(), uint64(r))
+ suite.Require().Equal(uint64(r), suite.StateDB.GetNonce(zeroNonceWalletBeforeCheckPoint.GetEthAddress()))
+
+ suite.StateDB.AddRefund(1)
+ suite.Require().Equal(uint64(r), suite.CStateDB.GetRefund())
+
+ suite.CStateDB.ForTest_AddAddressToSelfDestructedList(evmvm.GenerateAddress())
+ suite.Require().Len(suite.CStateDB.ForTest_CloneSelfDestructed(), r)
+
+ suite.StateDB.AddAddressToAccessList(evmvm.GenerateAddress())
+ suite.Require().Len(suite.CStateDB.ForTest_CloneAccessList().CloneElements(), r)
+
+ lenTransientStoreBefore := suite.CStateDB.ForTest_CountRecordsTransientStorage()
+ suite.CStateDB.SetTransientState(evmvm.GenerateAddress(), evmvm.GenerateHash(), evmvm.GenerateHash())
+ suite.Require().Less(lenTransientStoreBefore, suite.CStateDB.ForTest_CountRecordsTransientStorage())
+
+ suite.StateDB.AddLog(ðtypes.Log{
+ Address: evmvm.GenerateAddress(),
+ TxHash: evmvm.GenerateHash(),
+ BlockHash: evmvm.GenerateHash(),
+ })
+ suite.Require().Len(suite.CStateDB.GetTransactionLogs(), r)
+
+ if r <= checkAtSnapshot {
+ suite.StateDB.SetCode(contract.GetEthAddress(), code1)
+ suite.Require().Equal(codeHash1, suite.StateDB.GetCodeHash(contract.GetEthAddress()))
+ suite.Require().Equal(code1, suite.StateDB.GetCode(contract.GetEthAddress()))
+
+ suite.StateDB.SetState(contract.GetEthAddress(), stateKey, stateVal)
+ suite.Require().Equal(stateVal, suite.StateDB.GetState(contract.GetEthAddress(), stateKey))
+
+ suite.CStateDB.SetTransientState(contract.GetEthAddress(), stateKey, stateVal)
+ suite.Require().Equal(stateVal, suite.CStateDB.GetTransientState(contract.GetEthAddress(), stateKey))
+
+ if r == checkAtSnapshot {
+ touchedAtCheckpoint = suite.CStateDB.ForTest_CloneTouched()
+ suite.Require().NotEmpty(touchedAtCheckpoint)
+
+ selfStructAtCheckpoint = suite.CStateDB.ForTest_CloneSelfDestructed()
+ suite.Require().Len(selfStructAtCheckpoint, r)
+
+ accessListAtCheckpoint = suite.CStateDB.ForTest_CloneAccessList()
+ suite.Require().Len(accessListAtCheckpoint.CloneElements(), r)
+
+ transientStoreAtCheckpoint = suite.CStateDB.ForTest_CloneTransientStorage()
+
+ logsAtCheckpoint = evmvm.Logs(suite.CStateDB.GetTransactionLogs()).Copy()
+ suite.Require().Len(logsAtCheckpoint, r)
+ }
+
+ suite.Require().True(suite.StateDB.Empty(emptyWallet.GetEthAddress()))
+ } else if r == checkAtSnapshot+1 {
+ suite.StateDB.CreateAccount(nonExistsWalletBeforeCheckpoint.GetEthAddress())
+ suite.Require().True(suite.StateDB.Exist(nonExistsWalletBeforeCheckpoint.GetEthAddress()))
+
+ existingAccountBeforeCheckpoint := suite.App().AccountKeeper().GetAccount(suite.CStateDB.ForTest_GetCurrentContext(), existingWalletBeforeCheckpoint.GetCosmosAddress())
+ suite.Require().NotNil(existingAccountBeforeCheckpoint)
+ suite.CStateDB.DestroyAccount(common.BytesToAddress(existingAccountBeforeCheckpoint.GetAddress()))
+
+ suite.StateDB.SetCode(contract.GetEthAddress(), []byte{})
+ suite.Require().Equal(common.BytesToHash(evmtypes.EmptyCodeHash), suite.StateDB.GetCodeHash(contract.GetEthAddress()))
+ suite.Require().Zero(suite.StateDB.GetCodeSize(contract.GetEthAddress()))
+
+ suite.StateDB.SetState(contract.GetEthAddress(), stateKey, evmvm.GenerateHash())
+ suite.Require().NotEqual(stateVal, suite.StateDB.GetState(contract.GetEthAddress(), stateKey))
+
+ suite.CStateDB.SetTransientState(contract.GetEthAddress(), stateKey, evmvm.GenerateHash())
+ suite.Require().NotEqual(stateVal, suite.CStateDB.GetTransientState(contract.GetEthAddress(), stateKey))
+
+ suite.StateDB.SetNonce(emptyWallet.GetEthAddress(), 1)
+ suite.Require().False(suite.StateDB.Empty(emptyWallet.GetEthAddress()))
+ }
+
+ // create snapshot
+ sid := suite.StateDB.Snapshot()
+ suite.Require().Equal(r-1, sid)
+ }
+
+ assertContentAfterRevert := func() {
+ snapshots := suite.CStateDB.ForTest_GetSnapshots()
+ suite.Require().Len(snapshots, checkAtSnapshot+1)
+ suite.Require().Equal(checkAtSnapshot-1, snapshots[len(snapshots)-1].GetID())
+
+ suite.Equal(big.NewInt(checkAtSnapshot*1000), suite.StateDB.GetBalance(receiver1.GetEthAddress()))
+ suite.Equal(big.NewInt(checkAtSnapshot*1000), suite.StateDB.GetBalance(receiver2.GetEthAddress()))
+
+ suite.False(suite.StateDB.Exist(nonExistsWalletBeforeCheckpoint.GetEthAddress()))
+ suite.True(suite.StateDB.Exist(existingWalletBeforeCheckpoint.GetEthAddress()))
+
+ suite.Equal(uint64(checkAtSnapshot), suite.StateDB.GetNonce(zeroNonceWalletBeforeCheckPoint.GetEthAddress()))
+
+ if suite.Equal(codeHash1, suite.StateDB.GetCodeHash(contract.GetEthAddress())) {
+ suite.Equal(code1, suite.StateDB.GetCode(contract.GetEthAddress()))
+ }
+
+ suite.Equal(uint64(checkAtSnapshot), suite.CStateDB.GetRefund())
+
+ suite.Equal(stateVal, suite.StateDB.GetState(contract.GetEthAddress(), stateKey))
+
+ suite.Equal(stateVal, suite.CStateDB.GetTransientState(contract.GetEthAddress(), stateKey))
+
+ if suite.Equal(len(touchedAtCheckpoint), len(suite.CStateDB.ForTest_CloneTouched())) {
+ suite.True(reflect.DeepEqual(touchedAtCheckpoint, suite.CStateDB.ForTest_CloneTouched()))
+ }
+
+ if suite.Equal(len(selfStructAtCheckpoint), len(suite.CStateDB.ForTest_CloneSelfDestructed())) {
+ suite.True(reflect.DeepEqual(selfStructAtCheckpoint, suite.CStateDB.ForTest_CloneSelfDestructed()))
+ }
+
+ if suite.Equal(len(accessListAtCheckpoint.CloneElements()), len(suite.CStateDB.ForTest_CloneAccessList().CloneElements())) {
+ suite.True(reflect.DeepEqual(accessListAtCheckpoint.CloneElements(), suite.CStateDB.ForTest_CloneAccessList().CloneElements()))
+ }
+
+ if suite.Equal(transientStoreAtCheckpoint.Size(), suite.CStateDB.ForTest_CountRecordsTransientStorage()) {
+ suite.True(reflect.DeepEqual(transientStoreAtCheckpoint, suite.CStateDB.ForTest_CloneTransientStorage()))
+ }
+
+ if suite.Equal(len(logsAtCheckpoint), len(suite.CStateDB.GetTransactionLogs())) {
+ suite.True(reflect.DeepEqual([]*ethtypes.Log(logsAtCheckpoint), suite.CStateDB.GetTransactionLogs()))
+ }
+
+ suite.True(suite.StateDB.Empty(emptyWallet.GetEthAddress()))
+ }
+
+ snapshotId := checkAtSnapshot - 1
+ suite.StateDB.RevertToSnapshot(snapshotId)
+ assertContentAfterRevert()
+
+ // do some change and revert then test again
+ for i := 0; i < 5; i++ {
+ suite.StateDB.AddBalance(receiver1.GetEthAddress(), big.NewInt(1000))
+ suite.StateDB.AddBalance(receiver2.GetEthAddress(), big.NewInt(1000))
+ suite.StateDB.SetNonce(zeroNonceWalletBeforeCheckPoint.GetEthAddress(), 9900+uint64(i))
+ suite.StateDB.AddRefund(1)
+ suite.CStateDB.ForTest_AddAddressToSelfDestructedList(evmvm.GenerateAddress())
+ suite.StateDB.AddAddressToAccessList(evmvm.GenerateAddress())
+ suite.CStateDB.SetTransientState(evmvm.GenerateAddress(), evmvm.GenerateHash(), evmvm.GenerateHash())
+ suite.StateDB.AddLog(ðtypes.Log{
+ Address: evmvm.GenerateAddress(),
+ TxHash: evmvm.GenerateHash(),
+ BlockHash: evmvm.GenerateHash(),
+ })
+ }
+ // end of change
+
+ suite.StateDB.RevertToSnapshot(snapshotId)
+ assertContentAfterRevert() // second test
+
+ // commit
+
+ err := suite.CStateDB.CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+ suite.Commit()
+
+ suite.Equal(int64(checkAtSnapshot*1000), suite.CITS.QueryBalance(0, receiver1.GetCosmosAddress().String()).Amount.Int64())
+ suite.Equal(int64(checkAtSnapshot*1000), suite.CITS.QueryBalance(0, receiver2.GetCosmosAddress().String()).Amount.Int64())
+
+ suite.False(suite.App().AccountKeeper().HasAccount(suite.Ctx(), nonExistsWalletBeforeCheckpoint.GetCosmosAddress()))
+ suite.True(suite.App().AccountKeeper().HasAccount(suite.Ctx(), existingWalletBeforeCheckpoint.GetCosmosAddress()))
+
+ suite.Equal(uint64(checkAtSnapshot), suite.App().EvmKeeper().GetNonce(suite.Ctx(), zeroNonceWalletBeforeCheckPoint.GetEthAddress()))
+
+ contractAccount := suite.App().AccountKeeper().GetAccount(suite.Ctx(), contract.GetCosmosAddress())
+ suite.Equal(codeHash1, suite.App().EvmKeeper().GetCodeHash(suite.Ctx(), contractAccount.GetAddress()))
+
+ suite.Equal(stateVal, suite.App().EvmKeeper().GetState(suite.Ctx(), contract.GetEthAddress(), stateKey))
+
+ suite.False(suite.App().AccountKeeper().HasAccount(suite.Ctx(), emptyWallet.GetCosmosAddress()))
+}
+
+func (suite *StateDbIntegrationTestSuite) TestAddPreimage() {
+ suite.Require().NotPanics(func() {
+ suite.StateDB.AddPreimage(evmvm.GenerateHash(), evmvm.GenerateHash().Bytes())
+ })
+}
+
+func (suite *StateDbIntegrationTestSuite) TestCommitMultiStore() {
+ existingWallet1 := suite.CITS.WalletAccounts.Number(1)
+ existingWallet2 := suite.CITS.WalletAccounts.Number(2)
+ nonExistsWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+ nonBalanceWallet := integration_test_util.NewTestAccount(suite.T(), nil)
+ existingWallet3 := suite.CITS.WalletAccounts.Number(3)
+
+ // transfer all non-EVM coins out to procedure case empty account
+ for _, coin := range suite.App().
+ BankKeeper().
+ GetAllBalances(suite.CStateDB.ForTest_GetCurrentContext(), existingWallet3.GetCosmosAddress()) {
+ if coin.GetDenom() == suite.CStateDB.ForTest_GetEvmDenom() {
+ continue
+ }
+
+ err := suite.App().BankKeeper().SendCoinsFromAccountToModule(suite.CStateDB.ForTest_GetCurrentContext(), existingWallet3.GetCosmosAddress(), evmtypes.ModuleName, sdk.NewCoins(coin))
+ suite.Require().NoError(err)
+ }
+
+ suite.Require().True(suite.StateDB.Exist(existingWallet1.GetEthAddress()))
+ suite.Require().True(suite.StateDB.Exist(existingWallet2.GetEthAddress()))
+ suite.Require().False(suite.StateDB.Exist(nonExistsWallet.GetEthAddress()))
+ suite.Require().Zero(suite.StateDB.GetBalance(nonBalanceWallet.GetEthAddress()).Sign())
+ suite.Require().True(suite.StateDB.Exist(existingWallet3.GetEthAddress()))
+
+ suite.StateDB.CreateAccount(nonExistsWallet.GetEthAddress())
+ nonExistsWalletNowExists := nonExistsWallet
+ suite.Require().True(suite.StateDB.Exist(nonExistsWalletNowExists.GetEthAddress()))
+
+ suite.StateDB.Suicide(existingWallet1.GetEthAddress())
+ suite.Require().True(suite.StateDB.Exist(existingWallet1.GetEthAddress()))
+
+ suite.StateDB.(evmvm.CStateDB).Selfdestruct6780(nonExistsWalletNowExists.GetEthAddress())
+ suite.Require().True(suite.StateDB.Exist(nonExistsWalletNowExists.GetEthAddress()))
+
+ suite.StateDB.AddBalance(nonBalanceWallet.GetEthAddress(), big.NewInt(1))
+
+ curBalance := suite.StateDB.GetBalance(existingWallet3.GetEthAddress())
+ suite.Require().NotZero(curBalance.Sign())
+ suite.StateDB.SubBalance(existingWallet3.GetEthAddress(), curBalance)
+
+ err := suite.CStateDB.CommitMultiStore(true) // commit cache multi-store within the StateDB
+ suite.Require().NoError(err)
+ suite.Commit()
+
+ suite.False(
+ suite.App().BankKeeper().GetBalance(suite.Ctx(), nonBalanceWallet.GetCosmosAddress(), suite.CStateDB.ForTest_GetEvmDenom()).IsZero(),
+ "changes should be committed",
+ )
+ suite.False(
+ suite.App().AccountKeeper().HasAccount(suite.Ctx(), existingWallet1.GetCosmosAddress()),
+ "destroy account should be committed",
+ )
+ suite.False(
+ suite.App().AccountKeeper().HasAccount(suite.Ctx(), existingWallet3.GetCosmosAddress()),
+ "touched to be empty account should be removed",
+ )
+ suite.Panics(func() {
+ suite.Require().True(suite.CStateDB.ForTest_IsCommitted()) // ensure flag is set
+
+ err := suite.CStateDB.CommitMultiStore(true)
+ suite.Require().NoError(err)
+ }, "can not commits twice")
+}
+
+func RandomContractCode() (codeHash common.Hash, code []byte) {
+ codeLen := mathrand.Uint32()%1000 + 100
+ buffer := make([]byte, codeLen)
+ _, err := rand.Read(buffer)
+ if err != nil {
+ panic(err)
+ }
+
+ code = buffer[:]
+ codeHash = common.BytesToHash(crypto.Keccak256(code))
+
+ return
+}
diff --git a/x/evm/vm/state_db_logs.go b/x/evm/vm/state_db_logs.go
new file mode 100644
index 0000000000..b8edc7d89c
--- /dev/null
+++ b/x/evm/vm/state_db_logs.go
@@ -0,0 +1,16 @@
+package vm
+
+import ethtypes "github.com/ethereum/go-ethereum/core/types"
+
+type Logs []*ethtypes.Log
+
+func (l Logs) Copy() Logs {
+ if l == nil {
+ return nil
+ }
+
+ copied := make(Logs, len(l))
+ copy(copied, l)
+
+ return copied
+}
diff --git a/x/evm/vm/state_db_logs_test.go b/x/evm/vm/state_db_logs_test.go
new file mode 100644
index 0000000000..5fffb11ea2
--- /dev/null
+++ b/x/evm/vm/state_db_logs_test.go
@@ -0,0 +1,53 @@
+package vm
+
+import (
+ "testing"
+
+ ethtypes "github.com/ethereum/go-ethereum/core/types"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_logs(t *testing.T) {
+ originalLogs := Logs{}
+ for i := 0; i < 5; i++ {
+ originalLogs = append(originalLogs, ðtypes.Log{
+ Address: GenerateAddress(),
+ Topics: nil,
+ Data: nil,
+ BlockNumber: uint64(i) + 1,
+ TxHash: GenerateHash(),
+ TxIndex: uint(i),
+ BlockHash: GenerateHash(),
+ Index: uint(i),
+ Removed: false,
+ })
+ }
+
+ originalLen := len(originalLogs)
+
+ copiedLogs := originalLogs.Copy()
+ require.Len(t, copiedLogs, originalLen)
+
+ const add = 5
+
+ for i := originalLen; i < originalLen+add; i++ {
+ copiedLogs = append(copiedLogs, ðtypes.Log{
+ Address: GenerateAddress(),
+ Topics: nil,
+ Data: nil,
+ BlockNumber: uint64(i) + 1,
+ TxHash: GenerateHash(),
+ TxIndex: uint(i),
+ BlockHash: GenerateHash(),
+ Index: uint(i),
+ Removed: false,
+ })
+ }
+
+ require.Len(t, originalLogs, originalLen)
+ require.Len(t, copiedLogs, originalLen+add)
+
+ copiedLogs = copiedLogs[:originalLen-1]
+ require.Len(t, copiedLogs, originalLen-1)
+ require.Len(t, originalLogs, originalLen)
+}
diff --git a/x/evm/vm/state_db_snapshot.go b/x/evm/vm/state_db_snapshot.go
new file mode 100644
index 0000000000..0e823af814
--- /dev/null
+++ b/x/evm/vm/state_db_snapshot.go
@@ -0,0 +1,48 @@
+package vm
+
+import (
+ sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+// RtStateDbSnapshot is a snapshot of the state of the entire state database at a particular point in time.
+//
+// CONTRACT: snapshot & revert must perform deep clone of the underlying state.
+type RtStateDbSnapshot struct {
+ id int
+
+ // context
+ snapshotCtx sdk.Context
+ writeFunc func()
+
+ // other states
+ touched AccountTracker
+ refund uint64
+ selfDestructed AccountTracker
+ accessList *AccessList2
+ logs Logs
+ transientStorage TransientStorage
+}
+
+func newStateDbSnapshotFromStateDb(stateDb *cStateDb, workingCtx sdk.Context) RtStateDbSnapshot {
+ cacheCtxForWorking, writeFuncForWorking := workingCtx.CacheContext()
+
+ return RtStateDbSnapshot{
+ snapshotCtx: cacheCtxForWorking,
+ writeFunc: writeFuncForWorking,
+ touched: stateDb.touched.Copy(),
+ refund: stateDb.refund,
+ selfDestructed: stateDb.selfDestructed.Copy(),
+ accessList: stateDb.accessList.Copy(),
+ logs: stateDb.logs.Copy(),
+ transientStorage: stateDb.transientStorage.Clone(),
+ }
+}
+
+func (s RtStateDbSnapshot) GetID() int {
+ return s.id
+}
+
+// WriteChanges commit any changes made to the snapshot cache-multi-store to the parent cache-multi-store.
+func (s RtStateDbSnapshot) WriteChanges() {
+ s.writeFunc()
+}
diff --git a/x/evm/vm/state_db_test_methods.go b/x/evm/vm/state_db_test_methods.go
new file mode 100644
index 0000000000..e2159e9c83
--- /dev/null
+++ b/x/evm/vm/state_db_test_methods.go
@@ -0,0 +1,102 @@
+package vm
+
+import (
+ "github.com/EscanBE/evermint/v12/utils"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// ForTest_AddAddressToSelfDestructedList is a test-only method that adds an address to the self-destructed list.
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_AddAddressToSelfDestructedList(addr common.Address) {
+ d.selfDestructed.Add(addr)
+}
+
+// ForTest_SetLogs is a test-only method that override the logs.
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_SetLogs(logs Logs) {
+ d.logs = logs
+}
+
+// ForTest_CountRecordsTransientStorage is a test-only method that returns the number of records in the transient storage.
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_CountRecordsTransientStorage() int {
+ return d.transientStorage.Size()
+}
+
+// ForTest_CloneTransientStorage is a test-only method that clone then returns a copy of the transient storage.
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_CloneTransientStorage() TransientStorage {
+ return d.transientStorage.Clone()
+}
+
+// ForTest_CloneAccessList is a test-only method that clone then returns a copy of the access list.
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_CloneAccessList() *AccessList2 {
+ return d.accessList.Copy()
+}
+
+// ForTest_CloneTouched is a test-only method that clone then returns a copy of the 'touched' list.
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_CloneTouched() AccountTracker {
+ return d.touched.Copy()
+}
+
+// ForTest_CloneSelfDestructed is a test-only method that clone then returns a copy of the 'selfDestructed' list.
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_CloneSelfDestructed() AccountTracker {
+ return d.selfDestructed.Copy()
+}
+
+// ForTest_GetSnapshots is a test-only method that expose the snapshot list
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_GetSnapshots() []RtStateDbSnapshot {
+ return d.snapshots[:]
+}
+
+// ForTest_ToggleStateDBPreventCommit toggles the flag to prevent committing state changes to the underlying storage.
+// This is used for testing purposes to simulate cases where Commit() is failed.
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_ToggleStateDBPreventCommit(prevent bool) {
+ if !utils.IsTestnet(d.currentCtx.ChainID()) {
+ panic("can only be called during testing")
+ }
+ preventCommit = prevent
+}
+
+// ForTest_GetOriginalContext is a test-only method that returns the original context.
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_GetOriginalContext() sdk.Context {
+ return d.originalCtx
+}
+
+// ForTest_GetCurrentContext is a test-only method that returns the current context.
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_GetCurrentContext() sdk.Context {
+ return d.currentCtx
+}
+
+// ForTest_GetEvmDenom is a test-only method that returns the EVM denomination.
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_GetEvmDenom() string {
+ return d.evmDenom
+}
+
+// ForTest_IsCommitted is a test-only method that returns true if the state was committed before
+//
+//goland:noinspection GoSnakeCaseUsage
+func (d *cStateDb) ForTest_IsCommitted() bool {
+ return d.committed
+}
diff --git a/x/evm/vm/state_db_transient_store.go b/x/evm/vm/state_db_transient_store.go
new file mode 100644
index 0000000000..b8e0502ba6
--- /dev/null
+++ b/x/evm/vm/state_db_transient_store.go
@@ -0,0 +1,24 @@
+package vm
+
+import "github.com/ethereum/go-ethereum/common"
+
+// TransientStorage is an interface for the transient storage.
+// We use this interface instead of export the un-exported type transientStorage
+// because we would like to avoid changing much of the existing code so that we can compare the code change easily.
+type TransientStorage interface {
+ Set(addr common.Address, key, value common.Hash)
+ Get(addr common.Address, key common.Hash) common.Hash
+ Clone() TransientStorage
+ Size() int
+}
+
+var _ TransientStorage = transientStorage{}
+
+// Clone is an alias of Copy. The Copy is defined by g-eth, returns the un-exported type transientStorage.
+func (t transientStorage) Clone() TransientStorage {
+ return t.Copy()
+}
+
+func (t transientStorage) Size() int {
+ return len(t)
+}
diff --git a/x/evm/vm/state_db_transient_store_geth.go b/x/evm/vm/state_db_transient_store_geth.go
new file mode 100644
index 0000000000..4dfa897881
--- /dev/null
+++ b/x/evm/vm/state_db_transient_store_geth.go
@@ -0,0 +1,56 @@
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package vm
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ ethstate "github.com/ethereum/go-ethereum/core/state"
+)
+
+// transientStorage is a representation of EIP-1153 "Transient Storage".
+type transientStorage map[common.Address]ethstate.Storage
+
+// newTransientStorage creates a new instance of a transientStorage.
+func newTransientStorage() transientStorage {
+ return make(transientStorage)
+}
+
+// Set sets the transient-storage `value` for `key` at the given `addr`.
+func (t transientStorage) Set(addr common.Address, key, value common.Hash) {
+ if _, ok := t[addr]; !ok {
+ t[addr] = make(ethstate.Storage)
+ }
+ t[addr][key] = value
+}
+
+// Get gets the transient storage for `key` at the given `addr`.
+func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash {
+ val, ok := t[addr]
+ if !ok {
+ return common.Hash{}
+ }
+ return val[key]
+}
+
+// Copy does a deep copy of the transientStorage
+func (t transientStorage) Copy() transientStorage {
+ storage := make(transientStorage)
+ for key, value := range t {
+ storage[key] = value.Copy()
+ }
+ return storage
+}
diff --git a/x/evm/vm/state_db_transient_store_test.go b/x/evm/vm/state_db_transient_store_test.go
new file mode 100644
index 0000000000..e389669768
--- /dev/null
+++ b/x/evm/vm/state_db_transient_store_test.go
@@ -0,0 +1,63 @@
+package vm
+
+import (
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_TransientStorage(t *testing.T) {
+ originalStore := newTransientStorage()
+ require.NotNil(t, originalStore)
+ require.Empty(t, originalStore)
+
+ addr1 := GenerateAddress()
+ key1 := GenerateHash()
+ val1 := GenerateHash()
+
+ require.Equal(t, common.Hash{}, originalStore.Get(addr1, key1), "non-exists address should returns empty hash")
+
+ originalStore.Set(addr1, key1, val1)
+ require.Equal(t, val1, originalStore.Get(addr1, key1))
+
+ key2 := GenerateHash()
+ val2 := GenerateHash()
+ originalStore.Set(addr1, key2, val2)
+ require.Equal(t, val2, originalStore.Get(addr1, key2))
+
+ copiedStore := originalStore.Copy()
+
+ key3 := GenerateHash()
+ val3 := GenerateHash()
+ copiedStore.Set(addr1, key3, val3)
+ require.Equal(t, val3, copiedStore.Get(addr1, key3))
+
+ addr2 := GenerateAddress()
+ key4 := GenerateHash()
+ val4 := GenerateHash()
+ copiedStore.Set(addr2, key4, val4)
+ require.Equal(t, val4, copiedStore.Get(addr2, key4))
+
+ // change address 1 storage in copied store
+ copiedStore.Set(addr1, key1, val2)
+ copiedStore.Set(addr1, key2, val3)
+ copiedStore.Set(addr1, key3, val4)
+ copiedStore.Set(addr1, key4, val1)
+
+ // assert original value on the original store
+ require.Equal(t, val1, originalStore.Get(addr1, key1), "original store should not be modified")
+ require.Equal(t, val2, originalStore.Get(addr1, key2), "original store should not be modified")
+ require.Equal(t, common.Hash{}, originalStore.Get(addr1, key3), "original store should not have this value")
+ require.Equal(t, common.Hash{}, originalStore.Get(addr1, key4), "original store should not have this value")
+
+ // update on original store and expect not to effect copied store
+ originalStore.Set(addr1, key1, val4)
+ originalStore.Set(addr1, key2, val4)
+ originalStore.Set(addr1, key3, val4)
+
+ require.Equal(t, val2, copiedStore.Get(addr1, key1), "copied store should reflect changes from original store")
+ require.Equal(t, val3, copiedStore.Get(addr1, key2), "copied store should reflect changes from original store")
+ require.Equal(t, val4, copiedStore.Get(addr1, key3), "copied store should reflect changes from original store")
+ require.Equal(t, val1, copiedStore.Get(addr1, key4), "copied store should reflect changes from original store")
+}
diff --git a/x/evm/vm/test_utils.go b/x/evm/vm/test_utils.go
new file mode 100644
index 0000000000..c75edd666a
--- /dev/null
+++ b/x/evm/vm/test_utils.go
@@ -0,0 +1,32 @@
+package vm
+
+import (
+ "github.com/EscanBE/evermint/v12/crypto/ethsecp256k1"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+// NewAddrKey generates an Ethereum address and its corresponding private key.
+func NewAddrKey() (common.Address, *ethsecp256k1.PrivKey) {
+ privkey, _ := ethsecp256k1.GenerateKey()
+ key, err := privkey.ToECDSA()
+ if err != nil {
+ return common.Address{}, nil
+ }
+
+ addr := crypto.PubkeyToAddress(key.PublicKey)
+
+ return addr, privkey
+}
+
+// GenerateAddress generates an Ethereum address.
+func GenerateAddress() common.Address {
+ addr, _ := NewAddrKey()
+ return addr
+}
+
+// GenerateHash generates an Ethereum hash.
+func GenerateHash() common.Hash {
+ _, pk := NewAddrKey()
+ return common.BytesToHash(pk.Bytes())
+}