diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b64ab2e..5a762732 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -52,6 +52,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (evm) Reject invalid `MsgEthereumTx` wrapping tx
* (evm) Fix `SelfDestruct` opcode by deleting account code and state.
* (feemarket) [tharsis#855](https://github.com/tharsis/ethermint/pull/855) consistent `BaseFee` check logic.
+* (evm) [tharsis#729](https://github.com/tharsis/ethermint/pull/729) Refactor EVM StateDB implementation.
### Improvements
diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go
index 77bc3bc8..5ca53093 100644
--- a/app/ante/ante_test.go
+++ b/app/ante/ante_test.go
@@ -23,7 +23,7 @@ func (suite AnteTestSuite) TestAnteHandler() {
suite.Require().NoError(acc.SetSequence(1))
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
- suite.app.EvmKeeper.AddBalance(addr, big.NewInt(10000000000))
+ suite.app.EvmKeeper.SetBalance(suite.ctx, addr, big.NewInt(10000000000))
suite.app.FeeMarketKeeper.SetBaseFee(suite.ctx, big.NewInt(100))
@@ -577,7 +577,7 @@ func (suite AnteTestSuite) TestAnteHandlerWithDynamicTxFee() {
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
suite.ctx = suite.ctx.WithIsCheckTx(tc.checkTx).WithIsReCheckTx(tc.reCheckTx)
- suite.app.EvmKeeper.AddBalance(addr, big.NewInt((ethparams.InitialBaseFee+10)*100000))
+ suite.app.EvmKeeper.SetBalance(suite.ctx, addr, big.NewInt((ethparams.InitialBaseFee+10)*100000))
_, err := suite.anteHandler(suite.ctx, tc.txFn(), false)
if tc.expPass {
suite.Require().NoError(err)
diff --git a/app/ante/eth.go b/app/ante/eth.go
index 75ea445f..4e8a0420 100644
--- a/app/ante/eth.go
+++ b/app/ante/eth.go
@@ -10,6 +10,7 @@ import (
ethermint "github.com/tharsis/ethermint/types"
evmkeeper "github.com/tharsis/ethermint/x/evm/keeper"
+ "github.com/tharsis/ethermint/x/evm/statedb"
evmtypes "github.com/tharsis/ethermint/x/evm/types"
"github.com/ethereum/go-ethereum/common"
@@ -95,9 +96,6 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx
return next(ctx, tx, simulate)
}
- avd.evmKeeper.WithContext(ctx)
- evmDenom := avd.evmKeeper.GetParams(ctx).EvmDenom
-
for i, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
@@ -117,25 +115,25 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx
// check whether the sender address is EOA
fromAddr := common.BytesToAddress(from)
- codeHash := avd.evmKeeper.GetCodeHash(fromAddr)
- if codeHash != common.BytesToHash(evmtypes.EmptyCodeHash) {
+ acct, err := avd.evmKeeper.GetAccount(ctx, fromAddr)
+ if err != nil {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType,
- "the sender is not EOA: address <%v>, codeHash <%s>", fromAddr, codeHash)
+ "the sender is not EthAccount: address %s", fromAddr)
}
-
- acc := avd.ak.GetAccount(ctx, from)
- if acc == nil {
- acc = avd.ak.NewAccountWithAddress(ctx, from)
+ if acct == nil {
+ acc := avd.ak.NewAccountWithAddress(ctx, from)
avd.ak.SetAccount(ctx, acc)
+ acct = statedb.NewEmptyAccount()
+ } else if acct.IsContract() {
+ return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType,
+ "the sender is not EOA: address %s, codeHash <%s>", fromAddr, acct.CodeHash)
}
- if err := evmkeeper.CheckSenderBalance(ctx, avd.bankKeeper, from, txData, evmDenom); err != nil {
+ if err := evmkeeper.CheckSenderBalance(sdk.NewIntFromBigInt(acct.Balance), txData); err != nil {
return ctx, sdkerrors.Wrap(err, "failed to check sender balance")
}
}
- // recover the original gas meter
- avd.evmKeeper.WithContext(ctx)
return next(ctx, tx, simulate)
}
@@ -221,9 +219,6 @@ func NewEthGasConsumeDecorator(
// - user doesn't have enough balance to deduct the transaction fees (gas_limit * gas_price)
// - transaction or block gas meter runs out of gas
func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
- // reset the refund gas value in the keeper for the current transaction
- egcd.evmKeeper.ResetRefundTransient(ctx)
-
params := egcd.evmKeeper.GetParams(ctx)
ethCfg := params.ChainConfig.EthereumConfig(egcd.evmKeeper.ChainID())
@@ -278,8 +273,6 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
}
// we know that we have enough gas on the pool to cover the intrinsic gas
- // set up the updated context to the evm Keeper
- egcd.evmKeeper.WithContext(ctx)
return next(ctx, tx, simulate)
}
@@ -301,8 +294,6 @@ func NewCanTransferDecorator(evmKeeper EVMKeeper, fmk evmtypes.FeeMarketKeeper)
// AnteHandle creates an EVM from the message and calls the BlockContext CanTransfer function to
// see if the address can execute the transaction.
func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
- ctd.evmKeeper.WithContext(ctx)
-
params := ctd.evmKeeper.GetParams(ctx)
ethCfg := params.ChainConfig.EthereumConfig(ctd.evmKeeper.ChainID())
signer := ethtypes.MakeSigner(ethCfg, big.NewInt(ctx.BlockHeight()))
@@ -330,11 +321,12 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
CoinBase: common.Address{},
BaseFee: baseFee,
}
- evm := ctd.evmKeeper.NewEVM(coreMsg, cfg, evmtypes.NewNoOpTracer())
+ stateDB := statedb.New(ctx, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes())))
+ evm := ctd.evmKeeper.NewEVM(ctx, coreMsg, cfg, evmtypes.NewNoOpTracer(), stateDB)
// check that caller has enough balance to cover asset transfer for **topmost** call
// NOTE: here the gas consumed is from the context with the infinite gas meter
- if coreMsg.Value().Sign() > 0 && !evm.Context.CanTransfer(ctd.evmKeeper, coreMsg.From(), coreMsg.Value()) {
+ if coreMsg.Value().Sign() > 0 && !evm.Context.CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFunds,
"failed to transfer %s from address %s using the EVM block context transfer function",
@@ -360,7 +352,6 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
}
}
- // set the original gas meter
return next(ctx, tx, simulate)
}
@@ -422,8 +413,6 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu
return next(ctx, tx, simulate)
}
- vbd.evmKeeper.WithContext(ctx)
-
err := tx.ValidateBasic()
// ErrNoSignatures is fine with eth tx
if err != nil && !errors.Is(err, sdkerrors.ErrNoSignatures) {
diff --git a/app/ante/eth_test.go b/app/ante/eth_test.go
index 69f10d82..f8f75fbc 100644
--- a/app/ante/eth_test.go
+++ b/app/ante/eth_test.go
@@ -7,6 +7,7 @@ import (
"github.com/tharsis/ethermint/app/ante"
"github.com/tharsis/ethermint/tests"
+ "github.com/tharsis/ethermint/x/evm/statedb"
evmtypes "github.com/tharsis/ethermint/x/evm/types"
ethtypes "github.com/ethereum/go-ethereum/core/types"
@@ -65,6 +66,8 @@ func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() {
tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil)
tx.From = addr.Hex()
+ var vmdb *statedb.StateDB
+
testCases := []struct {
name string
tx sdk.Tx
@@ -86,7 +89,7 @@ func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() {
tx,
func() {
// set not as an EOA
- suite.app.EvmKeeper.SetCode(addr, []byte("1"))
+ vmdb.SetCode(addr, []byte("1"))
},
true,
false,
@@ -96,7 +99,7 @@ func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() {
tx,
func() {
// reset back to EOA
- suite.app.EvmKeeper.SetCode(addr, nil)
+ vmdb.SetCode(addr, nil)
},
true,
false,
@@ -105,7 +108,7 @@ func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() {
"success new account",
tx,
func() {
- suite.app.EvmKeeper.AddBalance(addr, big.NewInt(1000000))
+ vmdb.AddBalance(addr, big.NewInt(1000000))
},
true,
true,
@@ -117,7 +120,7 @@ func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes())
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
- suite.app.EvmKeeper.AddBalance(addr, big.NewInt(1000000))
+ vmdb.AddBalance(addr, big.NewInt(1000000))
},
true,
true,
@@ -126,7 +129,10 @@ func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() {
for _, tc := range testCases {
suite.Run(tc.name, func() {
+ vmdb = suite.StateDB()
tc.malleate()
+ suite.Require().NoError(vmdb.Commit())
+
_, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(tc.checkTx), tc.tx, false, nextFn)
if tc.expPass {
@@ -204,6 +210,8 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
tx2 := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000000, big.NewInt(1), nil, nil, nil, ðtypes.AccessList{{Address: addr, StorageKeys: nil}})
tx2.From = addr.Hex()
+ var vmdb *statedb.StateDB
+
testCases := []struct {
name string
tx sdk.Tx
@@ -221,29 +229,20 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
{
"gas limit too low",
tx,
- func() {
- acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes())
- suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
- },
+ func() {},
false, false,
},
{
"not enough balance for fees",
tx2,
- func() {
- acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes())
- suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
- },
+ func() {},
false, false,
},
{
"not enough tx gas",
tx2,
func() {
- acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes())
- suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
-
- suite.app.EvmKeeper.AddBalance(addr, big.NewInt(1000000))
+ vmdb.AddBalance(addr, big.NewInt(1000000))
},
false, true,
},
@@ -251,10 +250,7 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
"not enough block gas",
tx2,
func() {
- acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes())
- suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
-
- suite.app.EvmKeeper.AddBalance(addr, big.NewInt(1000000))
+ vmdb.AddBalance(addr, big.NewInt(1000000))
suite.ctx = suite.ctx.WithBlockGasMeter(sdk.NewGasMeter(1))
},
@@ -264,10 +260,7 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
"success",
tx2,
func() {
- acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes())
- suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
-
- suite.app.EvmKeeper.AddBalance(addr, big.NewInt(1000000))
+ vmdb.AddBalance(addr, big.NewInt(1000000))
suite.ctx = suite.ctx.WithBlockGasMeter(sdk.NewGasMeter(10000000000000000000))
},
@@ -277,7 +270,9 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
for _, tc := range testCases {
suite.Run(tc.name, func() {
+ vmdb = suite.StateDB()
tc.malleate()
+ suite.Require().NoError(vmdb.Commit())
if tc.expPanic {
suite.Require().Panics(func() {
@@ -331,6 +326,8 @@ func (suite AnteTestSuite) TestCanTransferDecorator() {
err := tx.Sign(suite.ethSigner, tests.NewSigner(privKey))
suite.Require().NoError(err)
+ var vmdb *statedb.StateDB
+
testCases := []struct {
name string
tx sdk.Tx
@@ -355,7 +352,7 @@ func (suite AnteTestSuite) TestCanTransferDecorator() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes())
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
- suite.app.EvmKeeper.AddBalance(addr, big.NewInt(1000000))
+ vmdb.AddBalance(addr, big.NewInt(1000000))
},
true,
},
@@ -363,7 +360,9 @@ func (suite AnteTestSuite) TestCanTransferDecorator() {
for _, tc := range testCases {
suite.Run(tc.name, func() {
+ vmdb = suite.StateDB()
tc.malleate()
+ suite.Require().NoError(vmdb.Commit())
_, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true), tc.tx, false, nextFn)
@@ -458,7 +457,7 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() {
txData, err := evmtypes.UnpackTxData(msg.Data)
suite.Require().NoError(err)
- nonce := suite.app.EvmKeeper.GetNonce(addr)
+ nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, addr)
suite.Require().Equal(txData.GetNonce()+1, nonce)
} else {
suite.Require().Error(err)
diff --git a/app/ante/interfaces.go b/app/ante/interfaces.go
index 08bf9c72..f2ab4b7f 100644
--- a/app/ante/interfaces.go
+++ b/app/ante/interfaces.go
@@ -5,23 +5,20 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
tx "github.com/cosmos/cosmos-sdk/types/tx"
- "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
+ "github.com/tharsis/ethermint/x/evm/statedb"
evmtypes "github.com/tharsis/ethermint/x/evm/types"
)
// EVMKeeper defines the expected keeper interface used on the Eth AnteHandler
type EVMKeeper interface {
- vm.StateDB
+ statedb.Keeper
ChainID() *big.Int
GetParams(ctx sdk.Context) evmtypes.Params
- WithContext(ctx sdk.Context)
- ResetRefundTransient(ctx sdk.Context)
- NewEVM(msg core.Message, cfg *evmtypes.EVMConfig, tracer vm.Tracer) *vm.EVM
- GetCodeHash(addr common.Address) common.Hash
+ NewEVM(ctx sdk.Context, msg core.Message, cfg *evmtypes.EVMConfig, tracer vm.Tracer, stateDB vm.StateDB) *vm.EVM
DeductTxCostsFromUserBalance(
ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, homestead, istanbul, london bool,
) (sdk.Coins, error)
diff --git a/app/ante/sigs_test.go b/app/ante/sigs_test.go
index 5c685746..f07d0986 100644
--- a/app/ante/sigs_test.go
+++ b/app/ante/sigs_test.go
@@ -4,6 +4,7 @@ import (
"math/big"
"github.com/tharsis/ethermint/tests"
+ "github.com/tharsis/ethermint/x/evm/statedb"
evmtypes "github.com/tharsis/ethermint/x/evm/types"
)
@@ -14,11 +15,11 @@ func (suite AnteTestSuite) TestSignatures() {
addr, privKey := tests.NewAddrKey()
to := tests.GenerateAddress()
- acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes())
- suite.Require().NoError(acc.SetSequence(1))
- suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
+ acc := statedb.NewEmptyAccount()
+ acc.Nonce = 1
+ acc.Balance = big.NewInt(10000000000)
- suite.app.EvmKeeper.AddBalance(addr, big.NewInt(10000000000))
+ suite.app.EvmKeeper.SetAccount(suite.ctx, addr, *acc)
msgEthereumTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil)
msgEthereumTx.From = addr.Hex()
diff --git a/app/ante/utils_test.go b/app/ante/utils_test.go
index 3a94e8a2..4a474cd1 100644
--- a/app/ante/utils_test.go
+++ b/app/ante/utils_test.go
@@ -5,6 +5,7 @@ import (
"testing"
"time"
+ "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/suite"
@@ -25,6 +26,7 @@ import (
ante "github.com/tharsis/ethermint/app/ante"
"github.com/tharsis/ethermint/encoding"
"github.com/tharsis/ethermint/tests"
+ "github.com/tharsis/ethermint/x/evm/statedb"
evmtypes "github.com/tharsis/ethermint/x/evm/types"
feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types"
@@ -43,6 +45,10 @@ type AnteTestSuite struct {
enableLondonHF bool
}
+func (suite *AnteTestSuite) StateDB() *statedb.StateDB {
+ return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes())))
+}
+
func (suite *AnteTestSuite) SetupTest() {
checkTx := false
diff --git a/tests/importer/importer_test.go b/tests/importer/importer_test.go
index f7d74436..e141f40a 100644
--- a/tests/importer/importer_test.go
+++ b/tests/importer/importer_test.go
@@ -17,6 +17,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
evmkeeper "github.com/tharsis/ethermint/x/evm/keeper"
+ "github.com/tharsis/ethermint/x/evm/statedb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
@@ -139,23 +140,23 @@ func (suite *ImporterTestSuite) TestImportBlocks() {
})
ctx := suite.app.NewContext(false, tmheader)
ctx = ctx.WithBlockHeight(tmheader.Height)
- suite.app.EvmKeeper.WithContext(ctx)
+ vmdb := statedb.New(ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes())))
if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 {
- applyDAOHardFork(suite.app.EvmKeeper)
+ applyDAOHardFork(vmdb)
}
for _, tx := range block.Transactions() {
receipt, gas, err := applyTransaction(
- chainConfig, chainContext, nil, gp, suite.app.EvmKeeper, header, tx, usedGas, vmConfig,
+ ctx, chainConfig, chainContext, nil, gp, suite.app.EvmKeeper, vmdb, header, tx, usedGas, vmConfig,
)
suite.Require().NoError(err, "failed to apply tx at block %d; tx: %X; gas %d; receipt:%v", block.NumberU64(), tx.Hash(), gas, receipt)
suite.Require().NotNil(receipt)
}
// apply mining rewards
- accumulateRewards(chainConfig, suite.app.EvmKeeper, header, block.Uncles())
+ accumulateRewards(chainConfig, vmdb, header, block.Uncles())
// simulate BaseApp EndBlocker commitment
endBR := types.RequestEndBlock{Height: tmheader.Height}
@@ -173,7 +174,7 @@ func (suite *ImporterTestSuite) TestImportBlocks() {
// reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewards(
- config *ethparams.ChainConfig, evmKeeper *evmkeeper.Keeper,
+ config *ethparams.ChainConfig, vmdb ethvm.StateDB,
header *ethtypes.Header, uncles []*ethtypes.Header,
) {
// select the correct block reward based on chain progression
@@ -191,12 +192,12 @@ func accumulateRewards(
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, rewardBig8)
- evmKeeper.AddBalance(uncle.Coinbase, r)
+ vmdb.AddBalance(uncle.Coinbase, r)
r.Div(blockReward, rewardBig32)
reward.Add(reward, r)
}
- evmKeeper.AddBalance(header.Coinbase, reward)
+ vmdb.AddBalance(header.Coinbase, reward)
}
// ApplyDAOHardFork modifies the state database according to the DAO hard-fork
@@ -205,15 +206,15 @@ func accumulateRewards(
// Code is pulled from go-ethereum 1.9 because the StateDB interface does not include the
// SetBalance function implementation
// Ref: https://github.com/ethereum/go-ethereum/blob/52f2461774bcb8cdd310f86b4bc501df5b783852/consensus/misc/dao.go#L74
-func applyDAOHardFork(evmKeeper *evmkeeper.Keeper) {
+func applyDAOHardFork(vmdb ethvm.StateDB) {
// Retrieve the contract to refund balances into
- if !evmKeeper.Exist(ethparams.DAORefundContract) {
- evmKeeper.CreateAccount(ethparams.DAORefundContract)
+ if !vmdb.Exist(ethparams.DAORefundContract) {
+ vmdb.CreateAccount(ethparams.DAORefundContract)
}
// Move every DAO account and extra-balance account funds into the refund contract
for _, addr := range ethparams.DAODrainList() {
- evmKeeper.AddBalance(ethparams.DAORefundContract, evmKeeper.GetBalance(addr))
+ vmdb.AddBalance(ethparams.DAORefundContract, vmdb.GetBalance(addr))
}
}
@@ -224,8 +225,8 @@ func applyDAOHardFork(evmKeeper *evmkeeper.Keeper) {
// Function is also pulled from go-ethereum 1.9 because of the incompatible usage
// Ref: https://github.com/ethereum/go-ethereum/blob/52f2461774bcb8cdd310f86b4bc501df5b783852/core/state_processor.go#L88
func applyTransaction(
- config *ethparams.ChainConfig, bc ethcore.ChainContext, author *common.Address,
- gp *ethcore.GasPool, evmKeeper *evmkeeper.Keeper, header *ethtypes.Header,
+ ctx sdk.Context, config *ethparams.ChainConfig, bc ethcore.ChainContext, author *common.Address,
+ gp *ethcore.GasPool, evmKeeper *evmkeeper.Keeper, vmdb *statedb.StateDB, header *ethtypes.Header,
tx *ethtypes.Transaction, usedGas *uint64, cfg ethvm.Config,
) (*ethtypes.Receipt, uint64, error) {
msg, err := tx.AsMessage(ethtypes.MakeSigner(config, header.Number), sdk.ZeroInt().BigInt())
@@ -239,7 +240,7 @@ func applyTransaction(
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
- vmenv := ethvm.NewEVM(blockCtx, txCtx, evmKeeper, config, cfg)
+ vmenv := ethvm.NewEVM(blockCtx, txCtx, vmdb, config, cfg)
// Apply the transaction to the current state (included in the env)
execResult, err := ethcore.ApplyMessage(vmenv, msg, gp)
@@ -263,11 +264,11 @@ func applyTransaction(
}
// Set the receipt logs and create a bloom for filtering
- receipt.Logs = evmKeeper.GetTxLogsTransient(tx.Hash())
+ receipt.Logs = vmdb.Logs()
receipt.Bloom = ethtypes.CreateBloom(ethtypes.Receipts{receipt})
receipt.BlockHash = header.Hash()
receipt.BlockNumber = header.Number
- receipt.TransactionIndex = uint(evmKeeper.GetTxIndexTransient())
+ receipt.TransactionIndex = uint(evmKeeper.GetTxIndexTransient(ctx))
return receipt, execResult.UsedGas, err
}
diff --git a/x/evm/genesis.go b/x/evm/genesis.go
index a54fd794..557e3d6f 100644
--- a/x/evm/genesis.go
+++ b/x/evm/genesis.go
@@ -22,7 +22,6 @@ func InitGenesis(
accountKeeper types.AccountKeeper,
data types.GenesisState,
) []abci.ValidatorUpdate {
- k.WithContext(ctx)
k.WithChainID(ctx)
k.SetParams(ctx, data.Params)
@@ -55,10 +54,10 @@ func InitGenesis(
if !bytes.Equal(common.HexToHash(ethAcct.CodeHash).Bytes(), codeHash.Bytes()) {
panic("code don't match codeHash")
}
- k.SetCode(address, code)
+ k.SetCode(ctx, codeHash.Bytes(), code)
for _, storage := range account.Storage {
- k.SetState(address, common.HexToHash(storage.Key), common.HexToHash(storage.Value))
+ k.SetState(ctx, address, common.HexToHash(storage.Key), common.HexToHash(storage.Value).Bytes())
}
}
@@ -67,8 +66,6 @@ func InitGenesis(
// ExportGenesis exports genesis state of the EVM module
func ExportGenesis(ctx sdk.Context, k *keeper.Keeper, ak types.AccountKeeper) *types.GenesisState {
- k.WithContext(ctx)
-
var ethGenAccounts []types.GenesisAccount
ak.IterateAccounts(ctx, func(account authtypes.AccountI) bool {
ethAccount, ok := account.(*ethermint.EthAccount)
@@ -79,14 +76,11 @@ func ExportGenesis(ctx sdk.Context, k *keeper.Keeper, ak types.AccountKeeper) *t
addr := ethAccount.EthAddress()
- storage, err := k.GetAccountStorage(ctx, addr)
- if err != nil {
- panic(err)
- }
+ storage := k.GetAccountStorage(ctx, addr)
genAccount := types.GenesisAccount{
Address: addr.String(),
- Code: common.Bytes2Hex(k.GetCode(addr)),
+ Code: common.Bytes2Hex(k.GetCode(ctx, ethAccount.GetCodeHash())),
Storage: storage,
}
diff --git a/x/evm/genesis_test.go b/x/evm/genesis_test.go
index e4506552..9a35390b 100644
--- a/x/evm/genesis_test.go
+++ b/x/evm/genesis_test.go
@@ -8,6 +8,7 @@ import (
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/tharsis/ethermint/crypto/ethsecp256k1"
"github.com/tharsis/ethermint/x/evm"
+ "github.com/tharsis/ethermint/x/evm/statedb"
"github.com/tharsis/ethermint/x/evm/types"
)
@@ -17,6 +18,8 @@ func (suite *EvmTestSuite) TestInitGenesis() {
address := common.HexToAddress(privkey.PubKey().Address().String())
+ var vmdb *statedb.StateDB
+
testCases := []struct {
name string
malleate func()
@@ -32,11 +35,7 @@ func (suite *EvmTestSuite) TestInitGenesis() {
{
"valid account",
func() {
- acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
- suite.Require().NotNil(acc)
-
- suite.app.EvmKeeper.AddBalance(address, big.NewInt(1))
- suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
+ vmdb.AddBalance(address, big.NewInt(1))
},
&types.GenesisState{
Params: types.DefaultParams(),
@@ -102,8 +101,10 @@ func (suite *EvmTestSuite) TestInitGenesis() {
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset values
+ vmdb = suite.StateDB()
tc.malleate()
+ vmdb.Commit()
if tc.expPanic {
suite.Require().Panics(
diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go
index b8f96ac2..e80791be 100644
--- a/x/evm/handler_test.go
+++ b/x/evm/handler_test.go
@@ -33,6 +33,7 @@ import (
"github.com/tharsis/ethermint/tests"
ethermint "github.com/tharsis/ethermint/types"
"github.com/tharsis/ethermint/x/evm"
+ "github.com/tharsis/ethermint/x/evm/statedb"
"github.com/tharsis/ethermint/x/evm/types"
"github.com/tendermint/tendermint/crypto/tmhash"
@@ -137,7 +138,6 @@ func (suite *EvmTestSuite) DoSetupTest(t require.TestingT) {
ConsensusHash: tmhash.Sum([]byte("consensus")),
LastResultsHash: tmhash.Sum([]byte("last_result")),
})
- suite.app.EvmKeeper.WithContext(suite.ctx)
queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry())
types.RegisterQueryServer(queryHelper, suite.app.EvmKeeper)
@@ -173,6 +173,10 @@ func (suite *EvmTestSuite) SignTx(tx *types.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().Bytes())))
+}
+
func TestEvmTestSuite(t *testing.T) {
suite.Run(t, new(EvmTestSuite))
}
@@ -230,7 +234,6 @@ func (suite *EvmTestSuite) TestHandleMsgEthereumTx() {
suite.SetupTest() // reset
//nolint
tc.malleate()
- suite.app.EvmKeeper.Snapshot()
res, err := suite.handler(suite.ctx, tx)
//nolint
@@ -282,16 +285,6 @@ func (suite *EvmTestSuite) TestHandlerLogs() {
suite.Require().Equal(len(txResponse.Logs), 1)
suite.Require().Equal(len(txResponse.Logs[0].Topics), 2)
-
- tlogs := types.LogsToEthereum(txResponse.Logs)
- for _, log := range tlogs {
- suite.app.EvmKeeper.AddLogTransient(log)
- }
- suite.Require().NoError(err)
-
- logs := suite.app.EvmKeeper.GetTxLogsTransient(tlogs[0].TxHash)
-
- suite.Require().Equal(logs, tlogs)
}
func (suite *EvmTestSuite) TestDeployAndCallContract() {
@@ -510,7 +503,7 @@ func (suite *EvmTestSuite) TestErrorWhenDeployContract() {
func (suite *EvmTestSuite) deployERC20Contract() common.Address {
k := suite.app.EvmKeeper
- nonce := k.GetNonce(suite.from)
+ nonce := k.GetNonce(suite.ctx, suite.from)
ctorArgs, err := types.ERC20Contract.ABI.Pack("", suite.from, big.NewInt(10000000000))
suite.Require().NoError(err)
msg := ethtypes.NewMessage(
@@ -526,7 +519,7 @@ func (suite *EvmTestSuite) deployERC20Contract() common.Address {
nil,
true,
)
- rsp, err := k.ApplyMessage(msg, nil, true)
+ rsp, err := k.ApplyMessage(suite.ctx, msg, nil, true)
suite.Require().NoError(err)
suite.Require().False(rsp.Failed())
return crypto.CreateAddress(suite.from, nonce)
@@ -571,14 +564,14 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() {
k.SetHooks(tc.hooks)
// add some fund to pay gas fee
- k.AddBalance(suite.from, big.NewInt(10000000000))
+ k.SetBalance(suite.ctx, suite.from, big.NewInt(10000000000))
contract := suite.deployERC20Contract()
data, err := types.ERC20Contract.ABI.Pack("transfer", suite.from, big.NewInt(10))
suite.Require().NoError(err)
- nonce := k.GetNonce(suite.from)
+ nonce := k.GetNonce(suite.ctx, suite.from)
tx := types.NewTx(
suite.chainID,
nonce,
@@ -593,7 +586,7 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() {
)
suite.SignTx(tx)
- before := k.GetBalance(suite.from)
+ before := k.GetBalance(suite.ctx, suite.from)
txData, err := types.UnpackTxData(tx.Data)
suite.Require().NoError(err)
@@ -606,7 +599,7 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() {
suite.Require().True(res.Failed())
suite.Require().Equal(tc.expErr, res.VmError)
- after := k.GetBalance(suite.from)
+ after := k.GetBalance(suite.ctx, suite.from)
if tc.expErr == "out of gas" {
suite.Require().Equal(tc.gasLimit, res.GasUsed)
@@ -618,7 +611,8 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() {
suite.Require().Equal(big.NewInt(int64(res.GasUsed)), new(big.Int).Sub(before, after))
// nonce should not be increased.
- suite.Require().Equal(nonce, k.GetNonce(suite.from))
+ nonce2 := k.GetNonce(suite.ctx, suite.from)
+ suite.Require().Equal(nonce, nonce2)
})
}
}
@@ -650,7 +644,7 @@ func (suite *EvmTestSuite) TestContractDeploymentRevert() {
// test with different hooks scenarios
k.SetHooks(tc.hooks)
- nonce := k.GetNonce(suite.from)
+ nonce := k.GetNonce(suite.ctx, suite.from)
ctorArgs, err := types.ERC20Contract.ABI.Pack("", suite.from, big.NewInt(0))
suite.Require().NoError(err)
@@ -667,14 +661,17 @@ func (suite *EvmTestSuite) TestContractDeploymentRevert() {
suite.SignTx(tx)
// simulate nonce increment in ante handler
- k.SetNonce(suite.from, nonce+1)
+ db := suite.StateDB()
+ db.SetNonce(suite.from, nonce+1)
+ suite.Require().NoError(db.Commit())
rsp, err := k.EthereumTx(sdk.WrapSDKContext(suite.ctx), tx)
suite.Require().NoError(err)
suite.Require().True(rsp.Failed())
// nonce don't change
- suite.Require().Equal(nonce+1, k.GetNonce(suite.from))
+ nonce2 := k.GetNonce(suite.ctx, suite.from)
+ suite.Require().Equal(nonce+1, nonce2)
})
}
}
diff --git a/x/evm/keeper/abci.go b/x/evm/keeper/abci.go
index 939680c6..e619b626 100644
--- a/x/evm/keeper/abci.go
+++ b/x/evm/keeper/abci.go
@@ -10,7 +10,6 @@ import (
// BeginBlock sets the sdk Context and EIP155 chain id to the Keeper.
func (k *Keeper) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
- k.WithContext(ctx)
k.WithChainID(ctx)
}
@@ -20,12 +19,9 @@ func (k *Keeper) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
func (k *Keeper) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate {
// Gas costs are handled within msg handler so costs should be ignored
infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
- k.WithContext(infCtx)
- bloom := ethtypes.BytesToBloom(k.GetBlockBloomTransient().Bytes())
+ bloom := ethtypes.BytesToBloom(k.GetBlockBloomTransient(infCtx).Bytes())
k.EmitBlockBloomEvent(infCtx, bloom)
- k.WithContext(ctx)
-
return []abci.ValidatorUpdate{}
}
diff --git a/x/evm/keeper/benchmark_test.go b/x/evm/keeper/benchmark_test.go
index c3126815..c4461f86 100644
--- a/x/evm/keeper/benchmark_test.go
+++ b/x/evm/keeper/benchmark_test.go
@@ -12,7 +12,6 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethermint "github.com/tharsis/ethermint/types"
- "github.com/tharsis/ethermint/x/evm/keeper"
"github.com/tharsis/ethermint/x/evm/types"
)
@@ -81,7 +80,7 @@ func BenchmarkTokenTransfer(b *testing.B) {
DoBenchmark(b, func(suite *KeeperTestSuite, contract common.Address) *types.MsgEthereumTx {
input, err := types.ERC20Contract.ABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000))
require.NoError(b, err)
- nonce := suite.app.EvmKeeper.GetNonce(suite.address)
+ nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
return types.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &contract, big.NewInt(0), 410000, big.NewInt(1), nil, nil, input, nil)
})
}
@@ -90,7 +89,7 @@ func BenchmarkEmitLogs(b *testing.B) {
DoBenchmark(b, func(suite *KeeperTestSuite, contract common.Address) *types.MsgEthereumTx {
input, err := types.ERC20Contract.ABI.Pack("benchmarkLogs", big.NewInt(1000))
require.NoError(b, err)
- nonce := suite.app.EvmKeeper.GetNonce(suite.address)
+ nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
return types.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &contract, big.NewInt(0), 4100000, big.NewInt(1), nil, nil, input, nil)
})
}
@@ -99,7 +98,7 @@ func BenchmarkTokenTransferFrom(b *testing.B) {
DoBenchmark(b, func(suite *KeeperTestSuite, contract common.Address) *types.MsgEthereumTx {
input, err := types.ERC20Contract.ABI.Pack("transferFrom", suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(0))
require.NoError(b, err)
- nonce := suite.app.EvmKeeper.GetNonce(suite.address)
+ nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
return types.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &contract, big.NewInt(0), 410000, big.NewInt(1), nil, nil, input, nil)
})
}
@@ -108,7 +107,7 @@ func BenchmarkTokenMint(b *testing.B) {
DoBenchmark(b, func(suite *KeeperTestSuite, contract common.Address) *types.MsgEthereumTx {
input, err := types.ERC20Contract.ABI.Pack("mint", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000))
require.NoError(b, err)
- nonce := suite.app.EvmKeeper.GetNonce(suite.address)
+ nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
return types.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &contract, big.NewInt(0), 410000, big.NewInt(1), nil, nil, input, nil)
})
}
@@ -118,7 +117,7 @@ func BenchmarkMessageCall(b *testing.B) {
input, err := types.TestMessageCall.ABI.Pack("benchmarkMessageCall", big.NewInt(10000))
require.NoError(b, err)
- nonce := suite.app.EvmKeeper.GetNonce(suite.address)
+ nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
msg := types.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &contract, big.NewInt(0), 25000000, big.NewInt(1), nil, nil, input, nil)
msg.From = suite.address.Hex()
@@ -143,40 +142,3 @@ func BenchmarkMessageCall(b *testing.B) {
require.False(b, rsp.Failed())
}
}
-
-func DoBenchmarkDeepContextStack(b *testing.B, depth int) {
- begin := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
- end := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
-
- suite := KeeperTestSuite{}
- suite.DoSetupTest(b)
-
- transientKey := suite.app.GetTKey(types.TransientKey)
-
- var stack keeper.ContextStack
- stack.Reset(suite.ctx)
-
- for i := 0; i < depth; i++ {
- stack.Snapshot()
-
- store := stack.CurrentContext().TransientStore(transientKey)
- store.Set(begin, []byte("value"))
- }
-
- store := stack.CurrentContext().TransientStore(transientKey)
- for i := 0; i < b.N; i++ {
- store.Iterator(begin, end)
- }
-}
-
-func BenchmarkDeepContextStack1(b *testing.B) {
- DoBenchmarkDeepContextStack(b, 1)
-}
-
-func BenchmarkDeepContextStack10(b *testing.B) {
- DoBenchmarkDeepContextStack(b, 10)
-}
-
-func BenchmarkDeepContextStack13(b *testing.B) {
- DoBenchmarkDeepContextStack(b, 13)
-}
diff --git a/x/evm/keeper/context_stack.go b/x/evm/keeper/context_stack.go
deleted file mode 100644
index f832bd1e..00000000
--- a/x/evm/keeper/context_stack.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package keeper
-
-import (
- "fmt"
-
- sdk "github.com/cosmos/cosmos-sdk/types"
-)
-
-// cachedContext is a pair of cache context and its corresponding commit method.
-// They are obtained from the return value of `context.CacheContext()`.
-type cachedContext struct {
- ctx sdk.Context
- commit func()
-}
-
-// ContextStack manages the initial context and a stack of cached contexts,
-// to support the `StateDB.Snapshot` and `StateDB.RevertToSnapshot` methods.
-type ContextStack struct {
- // Context of the initial state before transaction execution.
- // It's the context used by `StateDB.CommitedState`.
- initialCtx sdk.Context
- cachedContexts []cachedContext
-}
-
-// CurrentContext returns the top context of cached stack,
-// if the stack is empty, returns the initial context.
-func (cs *ContextStack) CurrentContext() sdk.Context {
- l := len(cs.cachedContexts)
- if l == 0 {
- return cs.initialCtx
- }
- return cs.cachedContexts[l-1].ctx
-}
-
-// Reset sets the initial context and clear the cache context stack.
-func (cs *ContextStack) Reset(ctx sdk.Context) {
- cs.initialCtx = ctx
- if len(cs.cachedContexts) > 0 {
- cs.cachedContexts = []cachedContext{}
- }
-}
-
-// IsEmpty returns true if the cache context stack is empty.
-func (cs *ContextStack) IsEmpty() bool {
- return len(cs.cachedContexts) == 0
-}
-
-// Commit commits all the cached contexts from top to bottom in order and clears the stack by setting an empty slice of cache contexts.
-func (cs *ContextStack) Commit() {
- // commit in order from top to bottom
- for i := len(cs.cachedContexts) - 1; i >= 0; i-- {
- // keep all the cosmos events
- cs.initialCtx.EventManager().EmitEvents(cs.cachedContexts[i].ctx.EventManager().Events())
- if cs.cachedContexts[i].commit == nil {
- panic(fmt.Sprintf("commit function at index %d should not be nil", i))
- } else {
- cs.cachedContexts[i].commit()
- }
- }
- cs.cachedContexts = []cachedContext{}
-}
-
-// CommitToRevision commit the cache after the target revision,
-// to improve efficiency of db operations.
-func (cs *ContextStack) CommitToRevision(target int) error {
- if target < 0 || target >= len(cs.cachedContexts) {
- return fmt.Errorf("snapshot index %d out of bound [%d..%d)", target, 0, len(cs.cachedContexts))
- }
-
- targetCtx := cs.cachedContexts[target].ctx
- // commit in order from top to bottom
- for i := len(cs.cachedContexts) - 1; i > target; i-- {
- // keep all the cosmos events
- targetCtx.EventManager().EmitEvents(cs.cachedContexts[i].ctx.EventManager().Events())
- if cs.cachedContexts[i].commit == nil {
- return fmt.Errorf("commit function at index %d should not be nil", i)
- }
- cs.cachedContexts[i].commit()
- }
- cs.cachedContexts = cs.cachedContexts[0 : target+1]
-
- return nil
-}
-
-// Snapshot pushes a new cached context to the stack,
-// and returns the index of it.
-func (cs *ContextStack) Snapshot() int {
- i := len(cs.cachedContexts)
- ctx, commit := cs.CurrentContext().CacheContext()
- cs.cachedContexts = append(cs.cachedContexts, cachedContext{ctx: ctx, commit: commit})
- return i
-}
-
-// RevertToSnapshot pops all the cached contexts after the target index (inclusive).
-// the target should be snapshot index returned by `Snapshot`.
-// This function panics if the index is out of bounds.
-func (cs *ContextStack) RevertToSnapshot(target int) {
- if target < 0 || target >= len(cs.cachedContexts) {
- panic(fmt.Errorf("snapshot index %d out of bound [%d..%d)", target, 0, len(cs.cachedContexts)))
- }
- cs.cachedContexts = cs.cachedContexts[:target]
-}
-
-// RevertAll discards all the cache contexts.
-func (cs *ContextStack) RevertAll() {
- if len(cs.cachedContexts) > 0 {
- cs.RevertToSnapshot(0)
- }
-}
diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go
index 0eaebbc1..32a78ea5 100644
--- a/x/evm/keeper/grpc_query.go
+++ b/x/evm/keeper/grpc_query.go
@@ -23,6 +23,7 @@ import (
ethparams "github.com/ethereum/go-ethereum/params"
ethermint "github.com/tharsis/ethermint/types"
+ "github.com/tharsis/ethermint/x/evm/statedb"
"github.com/tharsis/ethermint/x/evm/types"
)
@@ -47,12 +48,14 @@ func (k Keeper) Account(c context.Context, req *types.QueryAccountRequest) (*typ
addr := common.HexToAddress(req.Address)
ctx := sdk.UnwrapSDKContext(c)
- k.WithContext(ctx)
-
+ acct, err := k.GetAccountOrEmpty(ctx, addr)
+ if err != nil {
+ return nil, status.Error(codes.InvalidArgument, err.Error())
+ }
return &types.QueryAccountResponse{
- Balance: k.GetBalance(addr).String(),
- CodeHash: k.GetCodeHash(addr).Hex(),
- Nonce: k.GetNonce(addr),
+ Balance: acct.Balance.String(),
+ CodeHash: common.BytesToHash(acct.CodeHash).Hex(),
+ Nonce: acct.Nonce,
}, nil
}
@@ -68,7 +71,6 @@ func (k Keeper) CosmosAccount(c context.Context, req *types.QueryCosmosAccountRe
}
ctx := sdk.UnwrapSDKContext(c)
- k.WithContext(ctx)
ethAddr := common.HexToAddress(req.Address)
cosmosAddr := sdk.AccAddress(ethAddr.Bytes())
@@ -99,7 +101,6 @@ func (k Keeper) ValidatorAccount(c context.Context, req *types.QueryValidatorAcc
}
ctx := sdk.UnwrapSDKContext(c)
- k.WithContext(ctx)
validator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
if !found {
@@ -135,9 +136,8 @@ func (k Keeper) Balance(c context.Context, req *types.QueryBalanceRequest) (*typ
}
ctx := sdk.UnwrapSDKContext(c)
- k.WithContext(ctx)
- balanceInt := k.GetBalance(common.HexToAddress(req.Address))
+ balanceInt := k.GetBalance(ctx, common.HexToAddress(req.Address))
return &types.QueryBalanceResponse{
Balance: balanceInt.String(),
@@ -158,12 +158,11 @@ func (k Keeper) Storage(c context.Context, req *types.QueryStorageRequest) (*typ
}
ctx := sdk.UnwrapSDKContext(c)
- k.WithContext(ctx)
address := common.HexToAddress(req.Address)
key := common.HexToHash(req.Key)
- state := k.GetState(address, key)
+ state := k.GetState(ctx, address, key)
stateHex := state.Hex()
return &types.QueryStorageResponse{
@@ -185,10 +184,17 @@ func (k Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.Que
}
ctx := sdk.UnwrapSDKContext(c)
- k.WithContext(ctx)
address := common.HexToAddress(req.Address)
- code := k.GetCode(address)
+ acct, err := k.GetAccountWithoutBalance(ctx, address)
+ if err != nil {
+ return nil, status.Error(codes.InvalidArgument, err.Error())
+ }
+
+ var code []byte
+ if acct != nil && acct.IsContract() {
+ code = k.GetCode(ctx, common.BytesToHash(acct.CodeHash))
+ }
return &types.QueryCodeResponse{
Code: code,
@@ -212,7 +218,6 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.Ms
}
ctx := sdk.UnwrapSDKContext(c)
- k.WithContext(ctx)
var args types.TransactionArgs
err := json.Unmarshal(req.Args, &args)
@@ -226,7 +231,7 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.Ms
}
// ApplyMessageWithConfig expect correct nonce set in msg
- nonce := k.GetNonce(args.GetFrom())
+ nonce := k.GetNonce(ctx, args.GetFrom())
args.Nonce = (*hexutil.Uint64)(&nonce)
msg, err := args.ToMessage(req.GasCap, cfg.BaseFee)
@@ -234,7 +239,10 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.Ms
return nil, status.Error(codes.InvalidArgument, err.Error())
}
- res, err := k.ApplyMessageWithConfig(msg, nil, false, cfg)
+ txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))
+
+ // pass false to not commit StateDB
+ res, err := k.ApplyMessageWithConfig(ctx, msg, nil, false, cfg, txConfig)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
@@ -249,7 +257,6 @@ func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*type
}
ctx := sdk.UnwrapSDKContext(c)
- k.WithContext(ctx)
if req.GasCap < ethparams.TxGas {
return nil, status.Error(codes.InvalidArgument, "gas cap cannot be lower than 21,000")
@@ -295,23 +302,22 @@ func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*type
}
// ApplyMessageWithConfig expect correct nonce set in msg
- nonce := k.GetNonce(args.GetFrom())
+ nonce := k.GetNonce(ctx, args.GetFrom())
args.Nonce = (*hexutil.Uint64)(&nonce)
+ txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))
+
// Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) (vmerror bool, rsp *types.MsgEthereumTxResponse, err error) {
args.Gas = (*hexutil.Uint64)(&gas)
- // Reset to the initial context
- k.WithContext(ctx)
-
msg, err := args.ToMessage(req.GasCap, cfg.BaseFee)
if err != nil {
return false, nil, err
}
- rsp, err = k.ApplyMessageWithConfig(msg, nil, false, cfg)
-
+ // pass false to not commit StateDB
+ rsp, err = k.ApplyMessageWithConfig(ctx, msg, nil, false, cfg, txConfig)
if err != nil {
if errors.Is(err, core.ErrIntrinsicGas) {
return true, nil, nil // Special case, raise gas limit
@@ -363,30 +369,33 @@ func (k Keeper) TraceTx(c context.Context, req *types.QueryTraceTxRequest) (*typ
ctx = ctx.WithBlockHeight(req.BlockNumber)
ctx = ctx.WithBlockTime(req.BlockTime)
ctx = ctx.WithHeaderHash(common.Hex2Bytes(req.BlockHash))
- k.WithContext(ctx)
cfg, err := k.EVMConfig(ctx)
if err != nil {
- return nil, status.Error(codes.Internal, "failed to load evm config")
+ return nil, status.Errorf(codes.Internal, "failed to load evm config: %s", err.Error())
}
signer := ethtypes.MakeSigner(cfg.ChainConfig, big.NewInt(ctx.BlockHeight()))
+ txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))
for i, tx := range req.Predecessors {
ethTx := tx.AsTransaction()
msg, err := ethTx.AsMessage(signer, cfg.BaseFee)
if err != nil {
continue
}
- k.SetTxHashTransient(ethTx.Hash())
- k.SetTxIndexTransient(uint64(i))
-
- if _, err := k.ApplyMessageWithConfig(msg, types.NewNoOpTracer(), true, cfg); err != nil {
+ txConfig.TxHash = ethTx.Hash()
+ txConfig.TxIndex = uint(i)
+ rsp, err := k.ApplyMessageWithConfig(ctx, msg, types.NewNoOpTracer(), true, cfg, txConfig)
+ if err != nil {
continue
}
+ txConfig.LogIndex += uint(len(rsp.Logs))
}
tx := req.Msg.AsTransaction()
- result, err := k.traceTx(ctx, cfg, signer, req.TxIndex, tx, req.TraceConfig, false)
+ txConfig.TxHash = tx.Hash()
+ txConfig.TxIndex++
+ result, _, err := k.traceTx(ctx, cfg, txConfig, signer, tx, req.TraceConfig, false)
if err != nil {
// error will be returned with detail status from traceTx
return nil, err
@@ -418,7 +427,6 @@ func (k Keeper) TraceBlock(c context.Context, req *types.QueryTraceBlockRequest)
ctx = ctx.WithBlockHeight(req.BlockNumber)
ctx = ctx.WithBlockTime(req.BlockTime)
ctx = ctx.WithHeaderHash(common.Hex2Bytes(req.BlockHash))
- k.WithContext(ctx)
cfg, err := k.EVMConfig(ctx)
if err != nil {
@@ -428,14 +436,18 @@ func (k Keeper) TraceBlock(c context.Context, req *types.QueryTraceBlockRequest)
txsLength := len(req.Txs)
results := make([]*types.TxTraceResult, 0, txsLength)
+ txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))
for i, tx := range req.Txs {
result := types.TxTraceResult{}
ethTx := tx.AsTransaction()
- traceResult, err := k.traceTx(ctx, cfg, signer, uint64(i), ethTx, req.TraceConfig, true)
+ txConfig.TxHash = ethTx.Hash()
+ txConfig.TxIndex = uint(i)
+ traceResult, logIndex, err := k.traceTx(ctx, cfg, txConfig, signer, ethTx, req.TraceConfig, true)
if err != nil {
result.Error = err.Error()
continue
}
+ txConfig.LogIndex = logIndex
result.Result = traceResult
results = append(results, &result)
}
@@ -450,15 +462,16 @@ func (k Keeper) TraceBlock(c context.Context, req *types.QueryTraceBlockRequest)
}, nil
}
+// traceTx do trace on one transaction, it returns a tuple: (traceResult, nextLogIndex, error).
func (k *Keeper) traceTx(
ctx sdk.Context,
cfg *types.EVMConfig,
+ txConfig statedb.TxConfig,
signer ethtypes.Signer,
- txIndex uint64,
tx *ethtypes.Transaction,
traceConfig *types.TraceConfig,
commitMessage bool,
-) (*interface{}, error) {
+) (*interface{}, uint, error) {
// Assemble the structured logger or the JavaScript tracer
var (
tracer vm.Tracer
@@ -468,11 +481,9 @@ func (k *Keeper) traceTx(
msg, err := tx.AsMessage(signer, cfg.BaseFee)
if err != nil {
- return nil, status.Error(codes.Internal, err.Error())
+ return nil, 0, status.Error(codes.Internal, err.Error())
}
- txHash := tx.Hash()
-
if traceConfig != nil && traceConfig.Overrides != nil {
overrides = traceConfig.Overrides.EthereumConfig(cfg.ChainConfig.ChainID)
}
@@ -485,19 +496,19 @@ func (k *Keeper) traceTx(
if traceConfig.Timeout != "" {
timeout, err = time.ParseDuration(traceConfig.Timeout)
if err != nil {
- return nil, status.Errorf(codes.InvalidArgument, "timeout value: %s", err.Error())
+ return nil, 0, status.Errorf(codes.InvalidArgument, "timeout value: %s", err.Error())
}
}
tCtx := &tracers.Context{
- BlockHash: k.GetHashFn()(uint64(ctx.BlockHeight())),
- TxIndex: int(txIndex),
- TxHash: txHash,
+ BlockHash: txConfig.BlockHash,
+ TxIndex: int(txConfig.TxIndex),
+ TxHash: txConfig.TxHash,
}
// Construct the JavaScript tracer to execute with
if tracer, err = tracers.New(traceConfig.Tracer, tCtx); err != nil {
- return nil, status.Error(codes.Internal, err.Error())
+ return nil, 0, status.Error(codes.Internal, err.Error())
}
// Handle timeouts and RPC cancellations
@@ -526,12 +537,9 @@ func (k *Keeper) traceTx(
tracer = types.NewTracer(types.TracerStruct, msg, cfg.ChainConfig, ctx.BlockHeight())
}
- k.SetTxHashTransient(txHash)
- k.SetTxIndexTransient(txIndex)
-
- res, err := k.ApplyMessageWithConfig(msg, tracer, commitMessage, cfg)
+ res, err := k.ApplyMessageWithConfig(ctx, msg, tracer, commitMessage, cfg, txConfig)
if err != nil {
- return nil, status.Error(codes.Internal, err.Error())
+ return nil, 0, status.Error(codes.Internal, err.Error())
}
var result interface{}
@@ -549,12 +557,12 @@ func (k *Keeper) traceTx(
case *tracers.Tracer:
result, err = tracer.GetResult()
if err != nil {
- return nil, status.Error(codes.Internal, err.Error())
+ return nil, 0, status.Error(codes.Internal, err.Error())
}
default:
- return nil, status.Errorf(codes.InvalidArgument, "invalid tracer type %T", tracer)
+ return nil, 0, status.Errorf(codes.InvalidArgument, "invalid tracer type %T", tracer)
}
- return &result, nil
+ return &result, txConfig.LogIndex + uint(len(res.Logs)), nil
}
diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go
index 582c8f74..1f495d04 100644
--- a/x/evm/keeper/grpc_query_test.go
+++ b/x/evm/keeper/grpc_query_test.go
@@ -7,7 +7,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
+ "github.com/tharsis/ethermint/x/evm/statedb"
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -232,12 +234,12 @@ func (suite *KeeperTestSuite) TestQueryStorage() {
testCases := []struct {
msg string
- malleate func()
+ malleate func(vm.StateDB)
expPass bool
}{
{
"invalid address",
- func() {
+ func(vm.StateDB) {
req = &types.QueryStorageRequest{
Address: invalidAddress,
}
@@ -246,11 +248,11 @@ func (suite *KeeperTestSuite) TestQueryStorage() {
},
{
"success",
- func() {
+ func(vmdb vm.StateDB) {
key := common.BytesToHash([]byte("key"))
value := common.BytesToHash([]byte("value"))
expValue = value.String()
- suite.app.EvmKeeper.SetState(suite.address, key, value)
+ vmdb.SetState(suite.address, key, value)
req = &types.QueryStorageRequest{
Address: suite.address.String(),
Key: key.String(),
@@ -264,7 +266,10 @@ func (suite *KeeperTestSuite) TestQueryStorage() {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
- tc.malleate()
+ vmdb := suite.StateDB()
+ tc.malleate(vmdb)
+ suite.Require().NoError(vmdb.Commit())
+
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.Storage(ctx, req)
@@ -288,12 +293,12 @@ func (suite *KeeperTestSuite) TestQueryCode() {
testCases := []struct {
msg string
- malleate func()
+ malleate func(vm.StateDB)
expPass bool
}{
{
"invalid address",
- func() {
+ func(vm.StateDB) {
req = &types.QueryCodeRequest{
Address: invalidAddress,
}
@@ -304,9 +309,9 @@ func (suite *KeeperTestSuite) TestQueryCode() {
},
{
"success",
- func() {
+ func(vmdb vm.StateDB) {
expCode = []byte("code")
- suite.app.EvmKeeper.SetCode(suite.address, expCode)
+ vmdb.SetCode(suite.address, expCode)
req = &types.QueryCodeRequest{
Address: suite.address.String(),
@@ -320,7 +325,10 @@ func (suite *KeeperTestSuite) TestQueryCode() {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
- tc.malleate()
+ vmdb := suite.StateDB()
+ tc.malleate(vmdb)
+ suite.Require().NoError(vmdb.Commit())
+
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.Code(ctx, req)
@@ -338,26 +346,25 @@ func (suite *KeeperTestSuite) TestQueryCode() {
func (suite *KeeperTestSuite) TestQueryTxLogs() {
var (
- txHash common.Hash
expLogs []*types.Log
)
+ txHash := common.BytesToHash([]byte("tx_hash"))
+ txIndex := uint(1)
+ logIndex := uint(1)
testCases := []struct {
msg string
- malleate func()
+ malleate func(vm.StateDB)
}{
{
"empty logs",
- func() {
- txHash = common.BytesToHash([]byte("hash"))
+ func(vm.StateDB) {
expLogs = nil
},
},
{
"success",
- func() {
- txHash = common.BytesToHash([]byte("tx_hash"))
-
+ func(vmdb vm.StateDB) {
expLogs = []*types.Log{
{
Address: suite.address.String(),
@@ -365,17 +372,15 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() {
Data: []byte("data"),
BlockNumber: 1,
TxHash: txHash.String(),
- TxIndex: 1,
+ TxIndex: uint64(txIndex),
BlockHash: common.BytesToHash(suite.ctx.HeaderHash()).Hex(),
- Index: 0,
+ Index: uint64(logIndex),
Removed: false,
},
}
- suite.app.EvmKeeper.SetTxHashTransient(txHash)
- suite.app.EvmKeeper.IncreaseTxIndexTransient()
for _, log := range types.LogsToEthereum(expLogs) {
- suite.app.EvmKeeper.AddLog(log)
+ vmdb.AddLog(log)
}
},
},
@@ -385,8 +390,11 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
- tc.malleate()
- logs := suite.app.EvmKeeper.GetTxLogsTransient(txHash)
+ vmdb := statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()), txHash, txIndex, logIndex))
+ tc.malleate(vmdb)
+ suite.Require().NoError(vmdb.Commit())
+
+ logs := vmdb.Logs()
suite.Require().Equal(expLogs, types.NewLogsFromEth(logs))
})
}
@@ -680,8 +688,11 @@ func (suite *KeeperTestSuite) TestTraceTx() {
malleate: func() {
txIndex = 1
traceConfig = nil
+
// increase nonce to avoid address collision
- suite.app.EvmKeeper.SetNonce(suite.address, suite.app.EvmKeeper.GetNonce(suite.address)+1)
+ vmdb := suite.StateDB()
+ vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1)
+ suite.Require().NoError(vmdb.Commit())
contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()
@@ -807,8 +818,12 @@ func (suite *KeeperTestSuite) TestTraceBlock() {
msg: "tracer with multiple transactions",
malleate: func() {
traceConfig = nil
+
// increase nonce to avoid address collision
- suite.app.EvmKeeper.SetNonce(suite.address, suite.app.EvmKeeper.GetNonce(suite.address)+1)
+ vmdb := suite.StateDB()
+ vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1)
+ suite.Require().NoError(vmdb.Commit())
+
contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()
// create multiple transactions in the same block
@@ -867,7 +882,7 @@ func (suite *KeeperTestSuite) TestNonceInQuery() {
priv, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
address := common.BytesToAddress(priv.PubKey().Address().Bytes())
- suite.Require().Equal(uint64(0), suite.app.EvmKeeper.GetNonce(address))
+ suite.Require().Equal(uint64(0), suite.app.EvmKeeper.GetNonce(suite.ctx, address))
supply := sdk.NewIntWithDecimal(1000, 18).BigInt()
// accupy nonce 0
diff --git a/x/evm/keeper/hooks_test.go b/x/evm/keeper/hooks_test.go
index 1f512f39..3dc5a83e 100644
--- a/x/evm/keeper/hooks_test.go
+++ b/x/evm/keeper/hooks_test.go
@@ -9,6 +9,7 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/tharsis/ethermint/x/evm/keeper"
+ "github.com/tharsis/ethermint/x/evm/statedb"
"github.com/tharsis/ethermint/x/evm/types"
)
@@ -62,18 +63,25 @@ func (suite *KeeperTestSuite) TestEvmHooks() {
suite.app.EvmKeeper.SetHooks(keeper.NewMultiEvmHooks(hook))
k := suite.app.EvmKeeper
+ ctx := suite.ctx
txHash := common.BigToHash(big.NewInt(1))
- k.SetTxHashTransient(txHash)
- k.AddLog(ðtypes.Log{
+ vmdb := statedb.New(ctx, k, statedb.NewTxConfig(
+ common.BytesToHash(ctx.HeaderHash().Bytes()),
+ txHash,
+ 0,
+ 0,
+ ))
+
+ vmdb.AddLog(ðtypes.Log{
Topics: []common.Hash{},
Address: suite.address,
})
- logs := k.GetTxLogsTransient(txHash)
+ logs := vmdb.Logs()
receipt := ðtypes.Receipt{
TxHash: txHash,
Logs: logs,
}
- result := k.PostTxProcessing(common.Address{}, nil, receipt)
+ result := k.PostTxProcessing(ctx, common.Address{}, nil, receipt)
tc.expFunc(hook, result)
}
diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go
index 077c7689..85fabac8 100644
--- a/x/evm/keeper/keeper.go
+++ b/x/evm/keeper/keeper.go
@@ -1,21 +1,22 @@
package keeper
import (
+ "errors"
"math/big"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
- sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
paramtypes "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"
+ "github.com/ethereum/go-ethereum/params"
"github.com/tendermint/tendermint/libs/log"
- "github.com/ethereum/go-ethereum/params"
ethermint "github.com/tharsis/ethermint/types"
+ "github.com/tharsis/ethermint/x/evm/statedb"
"github.com/tharsis/ethermint/x/evm/types"
)
@@ -44,12 +45,6 @@ type Keeper struct {
// fetch EIP1559 base fee and parameters
feeMarketKeeper types.FeeMarketKeeper
- // Manage the initial context and cache context stack for accessing the store,
- // emit events and log info.
- // It is kept as a field to make is accessible by the StateDb
- // functions. Resets on every transaction/block.
- ctxStack ContextStack
-
// chain ID number obtained from the context's chain id
eip155ChainID *big.Int
@@ -58,9 +53,6 @@ type Keeper struct {
// EVM Hooks for tx post-processing
hooks types.EvmHooks
-
- // error from previous state operation
- stateErr error
}
// NewKeeper generates new evm module keeper
@@ -92,35 +84,14 @@ func NewKeeper(
storeKey: storeKey,
transientKey: transientKey,
tracer: tracer,
- stateErr: nil,
}
}
-// Ctx returns the current context from the context stack.
-func (k Keeper) Ctx() sdk.Context {
- return k.ctxStack.CurrentContext()
-}
-
-// CommitCachedContexts commit all the cache contexts created by `StateDB.Snapshot`.
-func (k *Keeper) CommitCachedContexts() {
- k.ctxStack.Commit()
-}
-
-// CachedContextsEmpty returns true if there's no cache contexts.
-func (k *Keeper) CachedContextsEmpty() bool {
- return k.ctxStack.IsEmpty()
-}
-
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", types.ModuleName)
}
-// WithContext clears the context stack, and set the initial context.
-func (k *Keeper) WithContext(ctx sdk.Context) {
- k.ctxStack.Reset(ctx)
-}
-
// WithChainID sets the chain id to the local variable in the keeper
func (k *Keeper) WithChainID(ctx sdk.Context) {
chainID, err := ethermint.ParseChainID(ctx.ChainID())
@@ -156,9 +127,9 @@ func (k Keeper) EmitBlockBloomEvent(ctx sdk.Context, bloom ethtypes.Bloom) {
}
// GetBlockBloomTransient returns bloom bytes for the current block height
-func (k Keeper) GetBlockBloomTransient() *big.Int {
- store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientBloom)
- heightBz := sdk.Uint64ToBigEndian(uint64(k.Ctx().BlockHeight()))
+func (k Keeper) GetBlockBloomTransient(ctx sdk.Context) *big.Int {
+ store := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientBloom)
+ heightBz := sdk.Uint64ToBigEndian(uint64(ctx.BlockHeight()))
bz := store.Get(heightBz)
if len(bz) == 0 {
return big.NewInt(0)
@@ -169,9 +140,9 @@ func (k Keeper) GetBlockBloomTransient() *big.Int {
// SetBlockBloomTransient sets the given bloom bytes to the transient store. This value is reset on
// every block.
-func (k Keeper) SetBlockBloomTransient(bloom *big.Int) {
- store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientBloom)
- heightBz := sdk.Uint64ToBigEndian(uint64(k.Ctx().BlockHeight()))
+func (k Keeper) SetBlockBloomTransient(ctx sdk.Context, bloom *big.Int) {
+ store := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientBloom)
+ heightBz := sdk.Uint64ToBigEndian(uint64(ctx.BlockHeight()))
store.Set(heightBz, bloom.Bytes())
}
@@ -179,32 +150,15 @@ func (k Keeper) SetBlockBloomTransient(bloom *big.Int) {
// Tx
// ----------------------------------------------------------------------------
-// GetTxHashTransient returns the hash of current processing transaction
-func (k Keeper) GetTxHashTransient() common.Hash {
- store := k.Ctx().TransientStore(k.transientKey)
- bz := store.Get(types.KeyPrefixTransientTxHash)
- if len(bz) == 0 {
- return common.Hash{}
- }
-
- return common.BytesToHash(bz)
-}
-
-// SetTxHashTransient set the hash of processing transaction
-func (k Keeper) SetTxHashTransient(hash common.Hash) {
- store := k.Ctx().TransientStore(k.transientKey)
- store.Set(types.KeyPrefixTransientTxHash, hash.Bytes())
-}
-
// SetTxIndexTransient set the index of processing transaction
-func (k Keeper) SetTxIndexTransient(index uint64) {
- store := k.Ctx().TransientStore(k.transientKey)
+func (k Keeper) SetTxIndexTransient(ctx sdk.Context, index uint64) {
+ store := ctx.TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientTxIndex, sdk.Uint64ToBigEndian(index))
}
// GetTxIndexTransient returns EVM transaction index on the current block.
-func (k Keeper) GetTxIndexTransient() uint64 {
- store := k.Ctx().TransientStore(k.transientKey)
+func (k Keeper) GetTxIndexTransient(ctx sdk.Context) uint64 {
+ store := ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientTxIndex)
if len(bz) == 0 {
return 0
@@ -213,61 +167,13 @@ func (k Keeper) GetTxIndexTransient() uint64 {
return sdk.BigEndianToUint64(bz)
}
-// IncreaseTxIndexTransient fetches the current EVM tx index from the transient store, increases its
-// value by one and then sets the new index back to the transient store.
-func (k Keeper) IncreaseTxIndexTransient() {
- txIndex := k.GetTxIndexTransient()
- k.SetTxIndexTransient(txIndex + 1)
-}
-
-// ResetRefundTransient resets the available refund amount to 0
-func (k Keeper) ResetRefundTransient(ctx sdk.Context) {
- store := ctx.TransientStore(k.transientKey)
- store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(0))
-}
-
// ----------------------------------------------------------------------------
// Log
// ----------------------------------------------------------------------------
-// GetTxLogsTransient returns the current logs for a given transaction hash from the KVStore.
-// This function returns an empty, non-nil slice if no logs are found.
-func (k Keeper) GetTxLogsTransient(txHash common.Hash) []*ethtypes.Log {
- store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientTxLogs)
-
- // We store the logs with key equal to txHash.Bytes() | sdk.Uint64ToBigEndian(uint64(log.Index)),
- // therefore, we set the end boundary(excluded) to txHash.Bytes() | uint64.Max -> []byte
- end := txHash.Bytes()
- end = append(end, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}...)
-
- iter := store.Iterator(txHash.Bytes(), end)
- defer iter.Close()
-
- logs := []*ethtypes.Log{}
- for ; iter.Valid(); iter.Next() {
- var log types.Log
- k.cdc.MustUnmarshal(iter.Value(), &log)
- logs = append(logs, log.ToEthereum())
- }
-
- return logs
-}
-
-// SetLog sets the log for a transaction in the KVStore.
-func (k Keeper) AddLogTransient(log *ethtypes.Log) {
- store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientTxLogs)
-
- key := log.TxHash.Bytes()
- key = append(key, sdk.Uint64ToBigEndian(uint64(log.Index))...)
-
- txIndexLog := types.NewLogFromEth(log)
- bz := k.cdc.MustMarshal(txIndexLog)
- store.Set(key, bz)
-}
-
// GetLogSizeTransient returns EVM log index on the current block.
-func (k Keeper) GetLogSizeTransient() uint64 {
- store := k.Ctx().TransientStore(k.transientKey)
+func (k Keeper) GetLogSizeTransient(ctx sdk.Context) uint64 {
+ store := ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientLogSize)
if len(bz) == 0 {
return 0
@@ -276,12 +182,11 @@ func (k Keeper) GetLogSizeTransient() uint64 {
return sdk.BigEndianToUint64(bz)
}
-// IncreaseLogSizeTransient fetches the current EVM log index from the transient store, increases its
+// SetLogSizeTransient fetches the current EVM log index from the transient store, increases its
// value by one and then sets the new index back to the transient store.
-func (k Keeper) IncreaseLogSizeTransient() {
- logSize := k.GetLogSizeTransient()
- store := k.Ctx().TransientStore(k.transientKey)
- store.Set(types.KeyPrefixTransientLogSize, sdk.Uint64ToBigEndian(logSize+1))
+func (k Keeper) SetLogSizeTransient(ctx sdk.Context, logSize uint64) {
+ store := ctx.TransientStore(k.transientKey)
+ store.Set(types.KeyPrefixTransientLogSize, sdk.Uint64ToBigEndian(logSize))
}
// ----------------------------------------------------------------------------
@@ -289,70 +194,23 @@ func (k Keeper) IncreaseLogSizeTransient() {
// ----------------------------------------------------------------------------
// GetAccountStorage return state storage associated with an account
-func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (types.Storage, error) {
+func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) types.Storage {
storage := types.Storage{}
- err := k.ForEachStorage(address, func(key, value common.Hash) bool {
+ k.ForEachStorage(ctx, address, func(key, value common.Hash) bool {
storage = append(storage, types.NewState(key, value))
return true
})
- if err != nil {
- return types.Storage{}, err
- }
- return storage, nil
+ return storage
}
// ----------------------------------------------------------------------------
// Account
// ----------------------------------------------------------------------------
-func (k Keeper) DeleteState(addr common.Address, key common.Hash) {
- store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.AddressStoragePrefix(addr))
- store.Delete(key.Bytes())
-}
-
-// DeleteAccountStorage clears all the storage state associated with the given address.
-func (k Keeper) DeleteAccountStorage(addr common.Address) {
- _ = k.ForEachStorage(addr, func(key, _ common.Hash) bool {
- k.DeleteState(addr, key)
- return true
- })
-}
-
-// DeleteCode removes the contract code byte array from the store associated with
-// the given address and empties CodeHash on account.
-func (k Keeper) DeleteCode(addr common.Address) {
- k.SetCode(addr, nil)
-}
-
-// ClearBalance subtracts the EVM all the balance denomination from the address
-// balance while also updating the total supply.
-func (k Keeper) ClearBalance(addr sdk.AccAddress) (prevBalance sdk.Coin, err error) {
- params := k.GetParams(k.Ctx())
-
- prevBalance = k.bankKeeper.GetBalance(k.Ctx(), addr, params.EvmDenom)
- if prevBalance.IsPositive() {
- if err := k.bankKeeper.SendCoinsFromAccountToModule(k.Ctx(), addr, types.ModuleName, sdk.Coins{prevBalance}); err != nil {
- return sdk.Coin{}, sdkerrors.Wrap(err, "failed to transfer to module account")
- }
-
- if err := k.bankKeeper.BurnCoins(k.Ctx(), types.ModuleName, sdk.Coins{prevBalance}); err != nil {
- return sdk.Coin{}, sdkerrors.Wrap(err, "failed to burn coins from evm module account")
- }
- }
-
- return prevBalance, nil
-}
-
-// ResetAccount removes the code, storage state, but keep all the native tokens stored
-// with the given address.
-func (k Keeper) ResetAccount(addr common.Address) {
- k.DeleteCode(addr)
- k.DeleteAccountStorage(addr)
-}
-
// SetHooks sets the hooks for the EVM module
+// It should be called only once during initialization, it panic if called more than once.
func (k *Keeper) SetHooks(eh types.EvmHooks) *Keeper {
if k.hooks != nil {
panic("cannot set evm hooks twice")
@@ -363,16 +221,76 @@ func (k *Keeper) SetHooks(eh types.EvmHooks) *Keeper {
}
// PostTxProcessing delegate the call to the hooks. If no hook has been registered, this function returns with a `nil` error
-func (k *Keeper) PostTxProcessing(from common.Address, to *common.Address, receipt *ethtypes.Receipt) error {
+func (k *Keeper) PostTxProcessing(ctx sdk.Context, from common.Address, to *common.Address, receipt *ethtypes.Receipt) error {
if k.hooks == nil {
return nil
}
- return k.hooks.PostTxProcessing(k.Ctx(), from, to, receipt)
+ return k.hooks.PostTxProcessing(ctx, from, to, receipt)
}
// Tracer return a default vm.Tracer based on current keeper state
-func (k Keeper) Tracer(msg core.Message, ethCfg *params.ChainConfig) vm.Tracer {
- return types.NewTracer(k.tracer, msg, ethCfg, k.Ctx().BlockHeight())
+func (k Keeper) Tracer(ctx sdk.Context, msg core.Message, ethCfg *params.ChainConfig) vm.Tracer {
+ return types.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, error) {
+ cosmosAddr := sdk.AccAddress(addr.Bytes())
+ acct := k.accountKeeper.GetAccount(ctx, cosmosAddr)
+ if acct == nil {
+ return nil, nil
+ }
+
+ ethAcct, ok := acct.(*ethermint.EthAccount)
+ if !ok {
+ return nil, errors.New("not EthAccount")
+ }
+
+ return &statedb.Account{
+ Nonce: ethAcct.Sequence,
+ CodeHash: common.FromHex(ethAcct.CodeHash),
+ }, nil
+}
+
+// GetAccountOrEmpty returns empty account if not exist, returns error if it's not `EthAccount`
+func (k *Keeper) GetAccountOrEmpty(ctx sdk.Context, addr common.Address) (statedb.Account, error) {
+ acct, err := k.GetAccount(ctx, addr)
+ if err != nil {
+ return statedb.Account{}, err
+ }
+ if acct == nil {
+ // empty account
+ return statedb.Account{
+ Balance: new(big.Int),
+ CodeHash: types.EmptyCodeHash,
+ }, nil
+ }
+ return *acct, nil
+}
+
+// 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)
+ if acct == nil {
+ return 0
+ }
+
+ ethAcct, ok := acct.(*ethermint.EthAccount)
+ if !ok {
+ return 0
+ }
+
+ return ethAcct.Sequence
+}
+
+// GetBalance load account's balance of gas token
+func (k *Keeper) GetBalance(ctx sdk.Context, addr common.Address) *big.Int {
+ cosmosAddr := sdk.AccAddress(addr.Bytes())
+ params := k.GetParams(ctx)
+ coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom)
+ return coin.Amount.BigInt()
}
// BaseFee returns current base fee, return values:
diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go
index 10501632..ed208e49 100644
--- a/x/evm/keeper/keeper_test.go
+++ b/x/evm/keeper/keeper_test.go
@@ -29,6 +29,7 @@ import (
"github.com/tharsis/ethermint/server/config"
"github.com/tharsis/ethermint/tests"
ethermint "github.com/tharsis/ethermint/types"
+ "github.com/tharsis/ethermint/x/evm/statedb"
"github.com/tharsis/ethermint/x/evm/types"
"github.com/ethereum/go-ethereum/common"
@@ -153,7 +154,6 @@ func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) {
ConsensusHash: tmhash.Sum([]byte("consensus")),
LastResultsHash: tmhash.Sum([]byte("last_result")),
})
- suite.app.EvmKeeper.WithContext(suite.ctx)
queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry())
types.RegisterQueryServer(queryHelper, suite.app.EvmKeeper)
@@ -201,13 +201,16 @@ func (suite *KeeperTestSuite) Commit() {
// update ctx
suite.ctx = suite.app.BaseApp.NewContext(false, header)
- suite.app.EvmKeeper.WithContext(suite.ctx)
queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry())
types.RegisterQueryServer(queryHelper, suite.app.EvmKeeper)
suite.queryClient = types.NewQueryClient(queryHelper)
}
+func (suite *KeeperTestSuite) StateDB() *statedb.StateDB {
+ return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes())))
+}
+
// DeployTestContract deploy a test erc20 contract and returns the contract address
func (suite *KeeperTestSuite) DeployTestContract(t require.TestingT, owner common.Address, supply *big.Int) common.Address {
ctx := sdk.WrapSDKContext(suite.ctx)
@@ -216,7 +219,7 @@ func (suite *KeeperTestSuite) DeployTestContract(t require.TestingT, owner commo
ctorArgs, err := types.ERC20Contract.ABI.Pack("", owner, supply)
require.NoError(t, err)
- nonce := suite.app.EvmKeeper.GetNonce(suite.address)
+ nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
data := append(types.ERC20Contract.Bin, ctorArgs...)
args, err := json.Marshal(&types.TransactionArgs{
@@ -280,7 +283,7 @@ func (suite *KeeperTestSuite) TransferERC20Token(t require.TestingT, contractAdd
})
require.NoError(t, err)
- nonce := suite.app.EvmKeeper.GetNonce(suite.address)
+ nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
var ercTransferTx *types.MsgEthereumTx
if suite.enableFeemarket {
@@ -337,7 +340,7 @@ func (suite *KeeperTestSuite) DeployTestMessageCall(t require.TestingT) common.A
})
require.NoError(t, err)
- nonce := suite.app.EvmKeeper.GetNonce(suite.address)
+ nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
var erc20DeployTx *types.MsgEthereumTx
if suite.enableFeemarket {
diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go
index bc052cd7..f3cfe68e 100644
--- a/x/evm/keeper/msg_server.go
+++ b/x/evm/keeper/msg_server.go
@@ -23,13 +23,12 @@ var _ types.MsgServer = &Keeper{}
// parameter.
func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*types.MsgEthereumTxResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
- k.WithContext(ctx)
sender := msg.From
tx := msg.AsTransaction()
- txIndex := k.GetTxIndexTransient()
+ txIndex := k.GetTxIndexTransient(ctx)
- response, err := k.ApplyTransaction(tx)
+ response, err := k.ApplyTransaction(ctx, tx)
if err != nil {
return nil, sdkerrors.Wrap(err, "failed to apply transaction")
}
diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go
index be86fcfa..1560b616 100644
--- a/x/evm/keeper/state_transition.go
+++ b/x/evm/keeper/state_transition.go
@@ -12,6 +12,7 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
ethermint "github.com/tharsis/ethermint/types"
+ "github.com/tharsis/ethermint/x/evm/statedb"
"github.com/tharsis/ethermint/x/evm/types"
"github.com/ethereum/go-ethereum/common"
@@ -22,6 +23,18 @@ import (
"github.com/ethereum/go-ethereum/params"
)
+// GasToRefund calculates the amount of gas the state machine should refund to the sender. It is
+// capped by the refund quotient value.
+// Note: do not pass 0 to refundQuotient
+func GasToRefund(availableRefund, gasConsumed, refundQuotient uint64) uint64 {
+ // Apply refund counter
+ refund := gasConsumed / refundQuotient
+ if refund > availableRefund {
+ return availableRefund
+ }
+ return refund
+}
+
// EVMConfig creates the EVMConfig based on current state
func (k *Keeper) EVMConfig(ctx sdk.Context) (*types.EVMConfig, error) {
params := k.GetParams(ctx)
@@ -42,39 +55,51 @@ func (k *Keeper) EVMConfig(ctx sdk.Context) (*types.EVMConfig, error) {
}, nil
}
+// TxConfig load `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.GetTxIndexTransient(ctx)), // TxIndex
+ uint(k.GetLogSizeTransient(ctx)), // LogIndex
+ )
+}
+
// NewEVM generates a go-ethereum VM from the provided Message fields and the chain parameters
// (ChainConfig and module Params). It additionally sets the validator operator address as the
// coinbase address to make it available for the COINBASE opcode, even though there is no
// beneficiary of the coinbase transaction (since we're not mining).
func (k *Keeper) NewEVM(
+ ctx sdk.Context,
msg core.Message,
cfg *types.EVMConfig,
tracer vm.Tracer,
+ stateDB vm.StateDB,
) *vm.EVM {
blockCtx := vm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
- GetHash: k.GetHashFn(),
+ GetHash: k.GetHashFn(ctx),
Coinbase: cfg.CoinBase,
- GasLimit: ethermint.BlockGasLimit(k.Ctx()),
- BlockNumber: big.NewInt(k.Ctx().BlockHeight()),
- Time: big.NewInt(k.Ctx().BlockHeader().Time.Unix()),
+ GasLimit: ethermint.BlockGasLimit(ctx),
+ BlockNumber: big.NewInt(ctx.BlockHeight()),
+ Time: big.NewInt(ctx.BlockHeader().Time.Unix()),
Difficulty: big.NewInt(0), // unused. Only required in PoW context
BaseFee: cfg.BaseFee,
}
txCtx := core.NewEVMTxContext(msg)
if tracer == nil {
- tracer = k.Tracer(msg, cfg.ChainConfig)
+ tracer = k.Tracer(ctx, msg, cfg.ChainConfig)
}
- vmConfig := k.VMConfig(cfg.Params, tracer)
- return vm.NewEVM(blockCtx, txCtx, k, cfg.ChainConfig, vmConfig)
+ vmConfig := k.VMConfig(ctx, msg, cfg.Params, tracer)
+ return vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig)
}
// 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(params types.Params, tracer vm.Tracer) vm.Config {
- fmParams := k.feeMarketKeeper.GetParams(k.Ctx())
+func (k Keeper) VMConfig(ctx sdk.Context, msg core.Message, params types.Params, tracer vm.Tracer) vm.Config {
+ fmParams := k.feeMarketKeeper.GetParams(ctx)
var debug bool
if _, ok := tracer.(types.NoOpTracer); !ok {
@@ -94,10 +119,8 @@ func (k Keeper) VMConfig(params types.Params, tracer vm.Tracer) vm.Config {
// 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
// 3. The requested height is from a height greater than the latest one
-func (k Keeper) GetHashFn() vm.GetHashFunc {
+func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
return func(height uint64) common.Hash {
- ctx := k.Ctx()
-
h, err := ethermint.SafeInt64(height)
if err != nil {
k.Logger(ctx).Error("failed to cast height to int64", "error", err)
@@ -165,21 +188,17 @@ func (k Keeper) GetHashFn() vm.GetHashFunc {
// returning.
//
// For relevant discussion see: https://github.com/cosmos/cosmos-sdk/discussions/9072
-func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumTxResponse, error) {
+func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*types.MsgEthereumTxResponse, error) {
var (
bloom *big.Int
bloomReceipt ethtypes.Bloom
)
- ctx := k.Ctx()
-
- // ensure keeper state error is cleared
- defer k.ClearStateError()
-
cfg, err := k.EVMConfig(ctx)
if err != nil {
return nil, sdkerrors.Wrap(err, "failed to load evm config")
}
+ txConfig := k.TxConfig(ctx, tx.Hash())
// get the signer according to the chain rules from the config and block height
signer := ethtypes.MakeSigner(cfg.ChainConfig, big.NewInt(ctx.BlockHeight()))
@@ -188,38 +207,28 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
return nil, sdkerrors.Wrap(err, "failed to return ethereum transaction as core message")
}
- txHash := tx.Hash()
-
- // set the transaction hash and index to the impermanent (transient) block state so that it's also
- // available on the StateDB functions (eg: AddLog)
- k.SetTxHashTransient(txHash)
-
// snapshot to contain the tx processing and post processing in same scope
var commit func()
+ tmpCtx := ctx
if k.hooks != nil {
// Create a cache context to revert state when tx hooks fails,
// the cache context is only committed when both tx and hooks executed successfully.
// Didn't use `Snapshot` because the context stack has exponential complexity on certain operations,
// thus restricted to be used only inside `ApplyMessage`.
- var cacheCtx sdk.Context
- cacheCtx, commit = ctx.CacheContext()
- k.WithContext(cacheCtx)
- defer (func() {
- k.WithContext(ctx)
- })()
+ tmpCtx, commit = ctx.CacheContext()
}
- res, err := k.ApplyMessageWithConfig(msg, nil, true, cfg)
+ // pass true to commit the StateDB
+ res, err := k.ApplyMessageWithConfig(tmpCtx, msg, nil, true, cfg, txConfig)
if err != nil {
return nil, sdkerrors.Wrap(err, "failed to apply ethereum core message")
}
- res.Hash = txHash.Hex()
- logs := k.GetTxLogsTransient(txHash)
+ logs := types.LogsToEthereum(res.Logs)
// Compute block bloom filter
if len(logs) > 0 {
- bloom = k.GetBlockBloomTransient()
+ bloom = k.GetBlockBloomTransient(ctx)
bloom.Or(bloom, big.NewInt(0).SetBytes(ethtypes.LogsBloom(logs)))
bloomReceipt = ethtypes.BytesToBloom(bloom.Bytes())
}
@@ -244,45 +253,42 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
CumulativeGasUsed: cumulativeGasUsed,
Bloom: bloomReceipt,
Logs: logs,
- TxHash: txHash,
+ TxHash: txConfig.TxHash,
ContractAddress: contractAddr,
GasUsed: res.GasUsed,
- BlockHash: common.BytesToHash(ctx.HeaderHash()),
+ BlockHash: txConfig.BlockHash,
BlockNumber: big.NewInt(ctx.BlockHeight()),
- TransactionIndex: uint(k.GetTxIndexTransient()),
+ TransactionIndex: txConfig.TxIndex,
}
// Only call hooks if tx executed successfully.
- if err = k.PostTxProcessing(msg.From(), tx.To(), receipt); err != nil {
+ if err = k.PostTxProcessing(tmpCtx, msg.From(), tx.To(), receipt); err != nil {
// If hooks return error, revert the whole tx.
res.VmError = types.ErrPostTxProcessing.Error()
k.Logger(ctx).Error("tx post processing failed", "error", err)
} else if commit != nil {
- // PostTxProcessing is successful, commit the cache context
+ // PostTxProcessing is successful, commit the tmpCtx
commit()
- ctx.EventManager().EmitEvents(k.Ctx().EventManager().Events())
+ ctx.EventManager().EmitEvents(tmpCtx.EventManager().Events())
}
}
- // change to original context
- k.WithContext(ctx)
-
- // refund gas according to Ethereum gas accounting rules.
- if err := k.RefundGas(msg, msg.Gas()-res.GasUsed, cfg.Params.EvmDenom); err != nil {
+ // refund gas in order to match the Ethereum gas consumption instead of the default SDK one.
+ if err = k.RefundGas(ctx, msg, msg.Gas()-res.GasUsed, cfg.Params.EvmDenom); err != nil {
return nil, sdkerrors.Wrapf(err, "failed to refund gas leftover gas to sender %s", msg.From())
}
if len(logs) > 0 {
- res.Logs = types.NewLogsFromEth(logs)
-
// Update transient block bloom filter
- k.SetBlockBloomTransient(bloom)
+ k.SetBlockBloomTransient(ctx, bloom)
+
+ k.SetLogSizeTransient(ctx, uint64(txConfig.LogIndex)+uint64(len(logs)))
}
- k.IncreaseTxIndexTransient()
+ k.SetTxIndexTransient(ctx, uint64(txConfig.TxIndex)+1)
// update the gas used after refund
- k.ResetGasMeterAndConsumeGas(res.GasUsed)
+ k.ResetGasMeterAndConsumeGas(ctx, res.GasUsed)
return res, nil
}
@@ -292,8 +298,7 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
//
// Reverted state
//
-// The snapshot and rollback are supported by the `ContextStack`, which should be only used inside `ApplyMessage`,
-// because some operations has exponential computational complexity with deep stack.
+// The snapshot and rollback are supported by the `statedb.StateDB`.
//
// Different Callers
//
@@ -324,22 +329,13 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
//
// Commit parameter
//
-// If commit is true, the cache context stack will be committed, otherwise discarded.
-func (k *Keeper) ApplyMessageWithConfig(msg core.Message, tracer vm.Tracer, commit bool, cfg *types.EVMConfig) (*types.MsgEthereumTxResponse, error) {
+// If commit is true, the `StateDB` will be committed, otherwise discarded.
+func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, msg core.Message, tracer vm.Tracer, commit bool, cfg *types.EVMConfig, txConfig statedb.TxConfig) (*types.MsgEthereumTxResponse, error) {
var (
ret []byte // return bytes from evm execution
vmErr error // vm errors do not effect consensus and are therefore not assigned to err
)
- if !k.ctxStack.IsEmpty() {
- panic("context stack shouldn't be dirty before apply message")
- }
-
- evm := k.NewEVM(msg, cfg, tracer)
-
- // ensure keeper state error is cleared
- defer k.ClearStateError()
-
// return error if contract creation or call are disabled through governance
if !cfg.Params.EnableCreate && msg.To() == nil {
return nil, sdkerrors.Wrap(types.ErrCreateDisabled, "failed to create new contract")
@@ -347,11 +343,14 @@ func (k *Keeper) ApplyMessageWithConfig(msg core.Message, tracer vm.Tracer, comm
return nil, sdkerrors.Wrap(types.ErrCallDisabled, "failed to call contract")
}
+ stateDB := statedb.New(ctx, k, txConfig)
+ evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB)
+
sender := vm.AccountRef(msg.From())
contractCreation := msg.To() == nil
isLondon := cfg.ChainConfig.IsLondon(evm.Context.BlockNumber)
- intrinsicGas, err := k.GetEthIntrinsicGas(msg, cfg.ChainConfig, contractCreation)
+ intrinsicGas, err := k.GetEthIntrinsicGas(ctx, msg, cfg.ChainConfig, contractCreation)
if err != nil {
// should have already been checked on Ante Handler
return nil, sdkerrors.Wrap(err, "intrinsic gas failed")
@@ -363,21 +362,19 @@ func (k *Keeper) ApplyMessageWithConfig(msg core.Message, tracer vm.Tracer, comm
}
leftoverGas := msg.Gas() - intrinsicGas
- // Clear access list before executing the contract
- k.ClearAccessList()
// access list preparaion is moved from ante handler to here, because it's needed when `ApplyMessage` is called
// under contexts where ante handlers are not run, for example `eth_call` and `eth_estimateGas`.
- if rules := cfg.ChainConfig.Rules(big.NewInt(k.Ctx().BlockHeight())); rules.IsBerlin {
- k.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
+ if rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight())); rules.IsBerlin {
+ stateDB.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
}
if contractCreation {
// take over the nonce management from evm:
// - reset sender's nonce to msg.Nonce() before calling evm.
// - increase sender's nonce by one no matter the result.
- k.SetNonce(sender.Address(), msg.Nonce())
+ stateDB.SetNonce(sender.Address(), msg.Nonce())
ret, _, leftoverGas, vmErr = evm.Create(sender, msg.Data(), leftoverGas, msg.Value())
- k.SetNonce(sender.Address(), msg.Nonce()+1)
+ stateDB.SetNonce(sender.Address(), msg.Nonce()+1)
} else {
ret, leftoverGas, vmErr = evm.Call(sender, *msg.To(), msg.Data(), leftoverGas, msg.Value())
}
@@ -394,7 +391,7 @@ func (k *Keeper) ApplyMessageWithConfig(msg core.Message, tracer vm.Tracer, comm
return nil, sdkerrors.Wrap(types.ErrGasOverflow, "apply message")
}
gasUsed := msg.Gas() - leftoverGas
- refund := k.GasToRefund(gasUsed, refundQuotient)
+ refund := GasToRefund(stateDB.GetRefund(), gasUsed, refundQuotient)
if refund > gasUsed {
return nil, sdkerrors.Wrap(types.ErrGasOverflow, "apply message")
}
@@ -406,57 +403,46 @@ func (k *Keeper) ApplyMessageWithConfig(msg core.Message, tracer vm.Tracer, comm
vmError = vmErr.Error()
}
- // The context stack is designed specifically for `StateDB` interface, it should only be used in `ApplyMessage`,
- // after return, the stack should be clean, the cached states are either committed or discarded.
+ // The dirty states in `StateDB` is either committed or discarded after return
if commit {
- k.CommitCachedContexts()
- } else {
- k.ctxStack.RevertAll()
+ if err := stateDB.Commit(); err != nil {
+ return nil, sdkerrors.Wrap(err, "failed to commit stateDB")
+ }
}
return &types.MsgEthereumTxResponse{
GasUsed: gasUsed,
VmError: vmError,
Ret: ret,
+ Logs: types.NewLogsFromEth(stateDB.Logs()),
+ Hash: txConfig.TxHash.Hex(),
}, nil
}
// ApplyMessage calls ApplyMessageWithConfig with default EVMConfig
-func (k *Keeper) ApplyMessage(msg core.Message, tracer vm.Tracer, commit bool) (*types.MsgEthereumTxResponse, error) {
- cfg, err := k.EVMConfig(k.Ctx())
+func (k *Keeper) ApplyMessage(ctx sdk.Context, msg core.Message, tracer vm.Tracer, commit bool) (*types.MsgEthereumTxResponse, error) {
+ cfg, err := k.EVMConfig(ctx)
if err != nil {
return nil, sdkerrors.Wrap(err, "failed to load evm config")
}
- return k.ApplyMessageWithConfig(msg, tracer, commit, cfg)
+ txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))
+ return k.ApplyMessageWithConfig(ctx, msg, tracer, commit, cfg, txConfig)
}
// GetEthIntrinsicGas returns the intrinsic gas cost for the transaction
-func (k *Keeper) GetEthIntrinsicGas(msg core.Message, cfg *params.ChainConfig, isContractCreation bool) (uint64, error) {
- height := big.NewInt(k.Ctx().BlockHeight())
+func (k *Keeper) GetEthIntrinsicGas(ctx sdk.Context, msg core.Message, cfg *params.ChainConfig, isContractCreation bool) (uint64, error) {
+ height := big.NewInt(ctx.BlockHeight())
homestead := cfg.IsHomestead(height)
istanbul := cfg.IsIstanbul(height)
return core.IntrinsicGas(msg.Data(), msg.AccessList(), isContractCreation, homestead, istanbul)
}
-// GasToRefund calculates the amount of gas the state machine should refund to the sender. It is
-// capped by the refund quotient value.
-// Note: do not pass 0 to refundQuotient
-func (k *Keeper) GasToRefund(gasConsumed, refundQuotient uint64) uint64 {
- // Apply refund counter
- refund := gasConsumed / refundQuotient
- availableRefund := k.GetRefund()
- if refund > availableRefund {
- return availableRefund
- }
- return refund
-}
-
// RefundGas transfers the leftover gas to the sender of the message, caped to half of the total gas
// consumed in the transaction. Additionally, the function sets the total gas consumed to the value
// returned by the EVM execution, thus ignoring the previous intrinsic gas consumed during in the
// AnteHandler.
-func (k *Keeper) RefundGas(msg core.Message, leftoverGas uint64, denom string) error {
+func (k *Keeper) RefundGas(ctx sdk.Context, msg core.Message, leftoverGas uint64, denom string) error {
// Return EVM tokens for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(leftoverGas), msg.GasPrice())
@@ -470,7 +456,7 @@ func (k *Keeper) RefundGas(msg core.Message, leftoverGas uint64, denom string) e
// refund to sender from the fee collector module account, which is the escrow account in charge of collecting tx fees
- err := k.bankKeeper.SendCoinsFromModuleToAccount(k.Ctx(), authtypes.FeeCollectorName, msg.From().Bytes(), refundedCoins)
+ err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, authtypes.FeeCollectorName, msg.From().Bytes(), refundedCoins)
if err != nil {
err = sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "fee collector account failed to refund fees: %s", err.Error())
return sdkerrors.Wrapf(err, "failed to refund %d leftover gas (%s)", leftoverGas, refundedCoins.String())
@@ -484,9 +470,8 @@ func (k *Keeper) RefundGas(msg core.Message, leftoverGas uint64, denom string) e
// ResetGasMeterAndConsumeGas reset first the gas meter consumed value to zero and set it back to the new value
// 'gasUsed'
-func (k *Keeper) ResetGasMeterAndConsumeGas(gasUsed uint64) {
+func (k *Keeper) ResetGasMeterAndConsumeGas(ctx sdk.Context, gasUsed uint64) {
// reset the gas count
- ctx := k.Ctx()
ctx.GasMeter().RefundGas(ctx.GasMeter().GasConsumed(), "reset the gas count")
ctx.GasMeter().ConsumeGas(gasUsed, "apply evm transaction")
}
diff --git a/x/evm/keeper/state_transition_benchmark_test.go b/x/evm/keeper/state_transition_benchmark_test.go
index a013cc9f..9b4f912d 100644
--- a/x/evm/keeper/state_transition_benchmark_test.go
+++ b/x/evm/keeper/state_transition_benchmark_test.go
@@ -152,7 +152,7 @@ func BenchmarkApplyTransaction(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
tx, err := newSignedEthTx(templateAccessListTx,
- suite.app.EvmKeeper.GetNonce(suite.address),
+ suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address),
sdk.AccAddress(suite.address.Bytes()),
suite.signer,
ethSigner,
@@ -160,7 +160,7 @@ func BenchmarkApplyTransaction(b *testing.B) {
require.NoError(b, err)
b.StartTimer()
- resp, err := suite.app.EvmKeeper.ApplyTransaction(tx)
+ resp, err := suite.app.EvmKeeper.ApplyTransaction(suite.ctx, tx)
b.StopTimer()
require.NoError(b, err)
@@ -179,7 +179,7 @@ func BenchmarkApplyTransactionWithLegacyTx(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
tx, err := newSignedEthTx(templateLegacyTx,
- suite.app.EvmKeeper.GetNonce(suite.address),
+ suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address),
sdk.AccAddress(suite.address.Bytes()),
suite.signer,
ethSigner,
@@ -187,7 +187,7 @@ func BenchmarkApplyTransactionWithLegacyTx(b *testing.B) {
require.NoError(b, err)
b.StartTimer()
- resp, err := suite.app.EvmKeeper.ApplyTransaction(tx)
+ resp, err := suite.app.EvmKeeper.ApplyTransaction(suite.ctx, tx)
b.StopTimer()
require.NoError(b, err)
@@ -206,7 +206,7 @@ func BenchmarkApplyTransactionWithDynamicFeeTx(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
tx, err := newSignedEthTx(templateDynamicFeeTx,
- suite.app.EvmKeeper.GetNonce(suite.address),
+ suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address),
sdk.AccAddress(suite.address.Bytes()),
suite.signer,
ethSigner,
@@ -214,7 +214,7 @@ func BenchmarkApplyTransactionWithDynamicFeeTx(b *testing.B) {
require.NoError(b, err)
b.StartTimer()
- resp, err := suite.app.EvmKeeper.ApplyTransaction(tx)
+ resp, err := suite.app.EvmKeeper.ApplyTransaction(suite.ctx, tx)
b.StopTimer()
require.NoError(b, err)
@@ -236,7 +236,7 @@ func BenchmarkApplyMessage(b *testing.B) {
b.StopTimer()
m, err := newNativeMessage(
- suite.app.EvmKeeper.GetNonce(suite.address),
+ suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address),
suite.ctx.BlockHeight(),
suite.address,
ethCfg,
@@ -249,7 +249,7 @@ func BenchmarkApplyMessage(b *testing.B) {
require.NoError(b, err)
b.StartTimer()
- resp, err := suite.app.EvmKeeper.ApplyMessage(m, nil, true)
+ resp, err := suite.app.EvmKeeper.ApplyMessage(suite.ctx, m, nil, true)
b.StopTimer()
require.NoError(b, err)
@@ -271,7 +271,7 @@ func BenchmarkApplyMessageWithLegacyTx(b *testing.B) {
b.StopTimer()
m, err := newNativeMessage(
- suite.app.EvmKeeper.GetNonce(suite.address),
+ suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address),
suite.ctx.BlockHeight(),
suite.address,
ethCfg,
@@ -284,7 +284,7 @@ func BenchmarkApplyMessageWithLegacyTx(b *testing.B) {
require.NoError(b, err)
b.StartTimer()
- resp, err := suite.app.EvmKeeper.ApplyMessage(m, nil, true)
+ resp, err := suite.app.EvmKeeper.ApplyMessage(suite.ctx, m, nil, true)
b.StopTimer()
require.NoError(b, err)
@@ -306,7 +306,7 @@ func BenchmarkApplyMessageWithDynamicFeeTx(b *testing.B) {
b.StopTimer()
m, err := newNativeMessage(
- suite.app.EvmKeeper.GetNonce(suite.address),
+ suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address),
suite.ctx.BlockHeight(),
suite.address,
ethCfg,
@@ -319,7 +319,7 @@ func BenchmarkApplyMessageWithDynamicFeeTx(b *testing.B) {
require.NoError(b, err)
b.StartTimer()
- resp, err := suite.app.EvmKeeper.ApplyMessage(m, nil, true)
+ resp, err := suite.app.EvmKeeper.ApplyMessage(suite.ctx, m, nil, true)
b.StopTimer()
require.NoError(b, err)
diff --git a/x/evm/keeper/state_transition_test.go b/x/evm/keeper/state_transition_test.go
index cbaa5101..c2586ba4 100644
--- a/x/evm/keeper/state_transition_test.go
+++ b/x/evm/keeper/state_transition_test.go
@@ -15,6 +15,7 @@ import (
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/tharsis/ethermint/tests"
+ "github.com/tharsis/ethermint/x/evm/keeper"
"github.com/tharsis/ethermint/x/evm/types"
)
@@ -34,7 +35,6 @@ func (suite *KeeperTestSuite) TestGetHashFn() {
uint64(suite.ctx.BlockHeight()),
func() {
suite.ctx = suite.ctx.WithHeaderHash(tmhash.Sum([]byte("header")))
- suite.app.EvmKeeper.WithContext(suite.ctx)
},
common.BytesToHash(tmhash.Sum([]byte("header"))),
},
@@ -45,7 +45,6 @@ func (suite *KeeperTestSuite) TestGetHashFn() {
header := tmproto.Header{}
header.Height = suite.ctx.BlockHeight()
suite.ctx = suite.ctx.WithBlockHeader(header)
- suite.app.EvmKeeper.WithContext(suite.ctx)
},
common.Hash{},
},
@@ -54,7 +53,6 @@ func (suite *KeeperTestSuite) TestGetHashFn() {
uint64(suite.ctx.BlockHeight()),
func() {
suite.ctx = suite.ctx.WithBlockHeader(header)
- suite.app.EvmKeeper.WithContext(suite.ctx)
},
common.BytesToHash(hash),
},
@@ -63,7 +61,6 @@ func (suite *KeeperTestSuite) TestGetHashFn() {
1,
func() {
suite.ctx = suite.ctx.WithBlockHeight(10)
- suite.app.EvmKeeper.WithContext(suite.ctx)
},
common.Hash{},
},
@@ -73,7 +70,6 @@ func (suite *KeeperTestSuite) TestGetHashFn() {
func() {
suite.app.StakingKeeper.SetHistoricalInfo(suite.ctx, 1, &stakingtypes.HistoricalInfo{})
suite.ctx = suite.ctx.WithBlockHeight(10)
- suite.app.EvmKeeper.WithContext(suite.ctx)
},
common.Hash{},
},
@@ -86,7 +82,6 @@ func (suite *KeeperTestSuite) TestGetHashFn() {
}
suite.app.StakingKeeper.SetHistoricalInfo(suite.ctx, 1, histInfo)
suite.ctx = suite.ctx.WithBlockHeight(10)
- suite.app.EvmKeeper.WithContext(suite.ctx)
},
common.BytesToHash(hash),
},
@@ -104,7 +99,7 @@ func (suite *KeeperTestSuite) TestGetHashFn() {
tc.malleate()
- hash := suite.app.EvmKeeper.GetHashFn()(tc.height)
+ hash := suite.app.EvmKeeper.GetHashFn(suite.ctx)(tc.height)
suite.Require().Equal(tc.expHash, hash)
})
}
@@ -124,7 +119,6 @@ func (suite *KeeperTestSuite) TestGetCoinbaseAddress() {
header := suite.ctx.BlockHeader()
header.ProposerAddress = []byte{}
suite.ctx = suite.ctx.WithBlockHeader(header)
- suite.app.EvmKeeper.WithContext(suite.ctx)
},
false,
},
@@ -152,7 +146,6 @@ func (suite *KeeperTestSuite) TestGetCoinbaseAddress() {
_, found := suite.app.StakingKeeper.GetValidatorByConsAddr(suite.ctx, valConsAddr.Bytes())
suite.Require().True(found)
- suite.app.EvmKeeper.WithContext(suite.ctx)
suite.Require().NotEmpty(suite.ctx.BlockHeader().ProposerAddress)
},
true,
@@ -276,10 +269,10 @@ func (suite *KeeperTestSuite) TestGetEthIntrinsicGas() {
signer := ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID())
suite.ctx = suite.ctx.WithBlockHeight(tc.height)
- suite.app.EvmKeeper.WithContext(suite.ctx)
+ nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
m, err := newNativeMessage(
- suite.app.EvmKeeper.GetNonce(suite.address),
+ nonce,
suite.ctx.BlockHeight(),
suite.address,
ethCfg,
@@ -291,7 +284,7 @@ func (suite *KeeperTestSuite) TestGetEthIntrinsicGas() {
)
suite.Require().NoError(err)
- gas, err := suite.app.EvmKeeper.GetEthIntrinsicGas(m, ethCfg, tc.isContractCreation)
+ gas, err := suite.app.EvmKeeper.GetEthIntrinsicGas(suite.ctx, m, ethCfg, tc.isContractCreation)
if tc.noError {
suite.Require().NoError(err)
} else {
@@ -345,15 +338,16 @@ func (suite *KeeperTestSuite) TestGasToRefund() {
suite.Run(fmt.Sprintf("Case %s", tc.name), func() {
suite.mintFeeCollector = true
suite.SetupTest() // reset
- suite.app.EvmKeeper.AddRefund(10)
+ vmdb := suite.StateDB()
+ vmdb.AddRefund(10)
if tc.expPanic {
panicF := func() {
- suite.app.EvmKeeper.GasToRefund(tc.gasconsumed, tc.refundQuotient)
+ keeper.GasToRefund(vmdb.GetRefund(), tc.gasconsumed, tc.refundQuotient)
}
suite.Require().Panics(panicF)
} else {
- gr := suite.app.EvmKeeper.GasToRefund(tc.gasconsumed, tc.refundQuotient)
+ gr := keeper.GasToRefund(vmdb.GetRefund(), tc.gasconsumed, tc.refundQuotient)
suite.Require().Equal(tc.expGasRefund, gr)
}
})
@@ -407,9 +401,10 @@ func (suite *KeeperTestSuite) TestRefundGas() {
keeperParams := suite.app.EvmKeeper.GetParams(suite.ctx)
ethCfg := keeperParams.ChainConfig.EthereumConfig(suite.app.EvmKeeper.ChainID())
signer := ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID())
+ vmdb := suite.StateDB()
m, err := newNativeMessage(
- suite.app.EvmKeeper.GetNonce(suite.address),
+ vmdb.GetNonce(suite.address),
suite.ctx.BlockHeight(),
suite.address,
ethCfg,
@@ -421,16 +416,16 @@ func (suite *KeeperTestSuite) TestRefundGas() {
)
suite.Require().NoError(err)
- suite.app.EvmKeeper.AddRefund(params.TxGas)
+ vmdb.AddRefund(params.TxGas)
if tc.leftoverGas > m.Gas() {
return
}
gasUsed := m.Gas() - tc.leftoverGas
- refund := suite.app.EvmKeeper.GasToRefund(gasUsed, tc.refundQuotient)
+ refund := keeper.GasToRefund(vmdb.GetRefund(), gasUsed, tc.refundQuotient)
suite.Require().Equal(tc.expGasRefund, refund)
- err = suite.app.EvmKeeper.RefundGas(m, refund, "aphoton")
+ err = suite.app.EvmKeeper.RefundGas(suite.ctx, m, refund, "aphoton")
if tc.noError {
suite.Require().NoError(err)
} else {
@@ -487,10 +482,8 @@ func (suite *KeeperTestSuite) TestResetGasMeterAndConsumeGas() {
panicF := func() {
gm := sdk.NewGasMeter(10)
gm.ConsumeGas(tc.gasConsumed, "")
- suite.ctx = suite.ctx.WithGasMeter(gm)
- suite.app.EvmKeeper.WithContext(suite.ctx)
-
- suite.app.EvmKeeper.ResetGasMeterAndConsumeGas(tc.gasUsed)
+ ctx := suite.ctx.WithGasMeter(gm)
+ suite.app.EvmKeeper.ResetGasMeterAndConsumeGas(ctx, tc.gasUsed)
}
if tc.expPanic {
@@ -516,5 +509,6 @@ func (suite *KeeperTestSuite) TestEVMConfig() {
func (suite *KeeperTestSuite) TestContractDeployment() {
suite.SetupTest()
contractAddress := suite.DeployTestContract(suite.T(), suite.address, big.NewInt(10000000000000))
- suite.Require().Greater(suite.app.EvmKeeper.GetCodeSize(contractAddress), 0)
+ db := suite.StateDB()
+ suite.Require().Greater(db.GetCodeSize(contractAddress), 0)
}
diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go
index 4adb50ec..ae908057 100644
--- a/x/evm/keeper/statedb.go
+++ b/x/evm/keeper/statedb.go
@@ -2,460 +2,38 @@ package keeper
import (
"bytes"
+ "errors"
"fmt"
"math/big"
- "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/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
-
+ "github.com/ethereum/go-ethereum/common"
ethermint "github.com/tharsis/ethermint/types"
+ "github.com/tharsis/ethermint/x/evm/statedb"
"github.com/tharsis/ethermint/x/evm/types"
)
-var _ vm.StateDB = &Keeper{}
+var _ statedb.Keeper = &Keeper{}
// ----------------------------------------------------------------------------
-// Account
+// statedb.Keeper implementation
// ----------------------------------------------------------------------------
-// CreateAccount creates a new EthAccount instance from the provided address and
-// sets the value to store. If an account with the given address already exists,
-// this function also resets any preexisting code and storage associated with that
-// address.
-func (k *Keeper) CreateAccount(addr common.Address) {
- if k.HasStateError() {
- return
+// GetAccount returns nil if account is not exist, returns error if it's not `EthAccount`
+func (k *Keeper) GetAccount(ctx sdk.Context, addr common.Address) (*statedb.Account, error) {
+ acct, err := k.GetAccountWithoutBalance(ctx, addr)
+ if acct == nil || err != nil {
+ return acct, err
}
- cosmosAddr := sdk.AccAddress(addr.Bytes())
- ctx := k.Ctx()
- account := k.accountKeeper.GetAccount(ctx, cosmosAddr)
- log := ""
- if account == nil {
- log = "account created"
- } else {
- log = "account overwritten"
- k.ResetAccount(addr)
- }
-
- account = k.accountKeeper.NewAccountWithAddress(ctx, cosmosAddr)
- k.accountKeeper.SetAccount(ctx, account)
-
- k.Logger(ctx).Debug(
- log,
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- )
+ acct.Balance = k.GetBalance(ctx, addr)
+ return acct, nil
}
-// ----------------------------------------------------------------------------
-// Balance
-// ----------------------------------------------------------------------------
-
-// AddBalance adds the given amount to the address balance coin by minting new
-// coins and transferring them to the address. The coin denomination is obtained
-// from the module parameters.
-func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) {
- if k.HasStateError() {
- return
- }
-
- ctx := k.Ctx()
-
- if amount.Sign() != 1 {
- k.Logger(ctx).Debug(
- "ignored non-positive amount addition",
- "ethereum-address", addr.Hex(),
- "amount", amount.Int64(),
- )
- return
- }
-
- cosmosAddr := sdk.AccAddress(addr.Bytes())
- params := k.GetParams(ctx)
-
- // Coin denom and amount already validated
- coins := sdk.Coins{
- {
- Denom: params.EvmDenom,
- Amount: sdk.NewIntFromBigInt(amount),
- },
- }
-
- if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil {
- k.Logger(ctx).Error(
- "failed to mint coins when adding balance",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- "error", err,
- )
- k.stateErr = err
- return
- }
-
- if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, cosmosAddr, coins); err != nil {
- k.Logger(ctx).Error(
- "failed to send from module to account when adding balance",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- "error", err,
- )
- k.stateErr = err
- return
- }
-
- k.Logger(ctx).Debug(
- "balance addition",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- )
-}
-
-// SubBalance subtracts the given amount from the address balance by transferring the
-// coins to an escrow account and then burning them. The coin denomination is obtained
-// from the module parameters. This function performs a no-op if the amount is negative
-// or the user doesn't have enough funds for the transfer.
-func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) {
- if k.HasStateError() {
- return
- }
-
- ctx := k.Ctx()
-
- if amount.Sign() != 1 {
- k.Logger(ctx).Debug(
- "ignored non-positive amount addition",
- "ethereum-address", addr.Hex(),
- "amount", amount.Int64(),
- )
- return
- }
-
- cosmosAddr := sdk.AccAddress(addr.Bytes())
-
- params := k.GetParams(ctx)
-
- // Coin denom and amount already validated
- coins := sdk.Coins{
- {
- Denom: params.EvmDenom,
- Amount: sdk.NewIntFromBigInt(amount),
- },
- }
-
- if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, cosmosAddr, types.ModuleName, coins); err != nil {
- k.Logger(ctx).Debug(
- "failed to send from account to module when subtracting balance",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- "error", err,
- )
-
- return
- }
-
- if err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, coins); err != nil {
- k.Logger(ctx).Error(
- "failed to burn coins when subtracting balance",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- "error", err,
- )
- k.stateErr = err
- return
- }
-
- k.Logger(ctx).Debug(
- "balance subtraction",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- )
-}
-
-// GetBalance returns the EVM denomination balance of the provided address. The
-// denomination is obtained from the module parameters.
-func (k *Keeper) GetBalance(addr common.Address) *big.Int {
- if k.HasStateError() {
- return big.NewInt(0)
- }
-
- ctx := k.Ctx()
-
- cosmosAddr := sdk.AccAddress(addr.Bytes())
- params := k.GetParams(ctx)
- balance := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom)
-
- return balance.Amount.BigInt()
-}
-
-// ----------------------------------------------------------------------------
-// Nonce
-// ----------------------------------------------------------------------------
-
-// GetNonce retrieves the account with the given address and returns the tx
-// sequence (i.e nonce). The function performs a no-op if the account is not found.
-func (k *Keeper) GetNonce(addr common.Address) uint64 {
- if k.HasStateError() {
- return 0
- }
-
- ctx := k.Ctx()
-
- cosmosAddr := sdk.AccAddress(addr.Bytes())
- nonce, err := k.accountKeeper.GetSequence(ctx, cosmosAddr)
- if err != nil {
- k.Logger(ctx).Error(
- "account not found",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- "error", err,
- )
- // since adding state error here will break some logic in the go-ethereum (unwanted panic), no
- // state error will be store here
- // Refer panic: https://github.com/ethereum/go-ethereum/blob/991384a7f6719e1125ca0be7fb27d0c4d1c5d2d3/core/vm/operations_acl.go#L66
- }
-
- return nonce
-}
-
-// SetNonce sets the given nonce as the sequence of the address' account. If the
-// account doesn't exist, a new one will be created from the address.
-func (k *Keeper) SetNonce(addr common.Address, nonce uint64) {
- if k.HasStateError() {
- return
- }
-
- ctx := k.Ctx()
-
- cosmosAddr := sdk.AccAddress(addr.Bytes())
- account := k.accountKeeper.GetAccount(ctx, cosmosAddr)
- if account == nil {
- k.Logger(ctx).Debug(
- "account not found",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- )
-
- // create address if it doesn't exist
- account = k.accountKeeper.NewAccountWithAddress(ctx, cosmosAddr)
- }
-
- if err := account.SetSequence(nonce); err != nil {
- k.Logger(ctx).Error(
- "failed to set nonce",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- "nonce", nonce,
- "error", err,
- )
- k.stateErr = err
- return
- }
-
- k.accountKeeper.SetAccount(ctx, account)
-
- k.Logger(ctx).Debug(
- "nonce set",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- "nonce", nonce,
- )
-}
-
-// ----------------------------------------------------------------------------
-// Code
-// ----------------------------------------------------------------------------
-
-// GetCodeHash fetches the account from the store and returns its code hash. If the account doesn't
-// exist or is not an EthAccount type, GetCodeHash returns the empty code hash value.
-func (k *Keeper) GetCodeHash(addr common.Address) common.Hash {
- if k.HasStateError() {
- return common.Hash{}
- }
-
- ctx := k.Ctx()
- cosmosAddr := sdk.AccAddress(addr.Bytes())
-
- account := k.accountKeeper.GetAccount(ctx, cosmosAddr)
- if account == nil {
- return common.BytesToHash(types.EmptyCodeHash)
- }
-
- ethAccount, isEthAccount := account.(*ethermint.EthAccount)
- if !isEthAccount {
- return common.BytesToHash(types.EmptyCodeHash)
- }
-
- return common.HexToHash(ethAccount.CodeHash)
-}
-
-// GetCode returns the code byte array associated with the given address.
-// If the code hash from the account is empty, this function returns nil.
-func (k *Keeper) GetCode(addr common.Address) []byte {
- if k.HasStateError() {
- return nil
- }
-
- ctx := k.Ctx()
- hash := k.GetCodeHash(addr)
-
- if bytes.Equal(hash.Bytes(), common.BytesToHash(types.EmptyCodeHash).Bytes()) {
- return nil
- }
-
- store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixCode)
- code := store.Get(hash.Bytes())
-
- if len(code) == 0 {
- k.Logger(ctx).Debug(
- "code not found",
- "ethereum-address", addr.Hex(),
- "code-hash", hash.Hex(),
- )
- }
-
- return code
-}
-
-// SetCode stores the code byte array to the application KVStore and sets the
-// code hash to the given account. The code is deleted from the store if it is empty.
-func (k *Keeper) SetCode(addr common.Address, code []byte) {
- if k.HasStateError() {
- return
- }
-
- ctx := k.Ctx()
-
- if bytes.Equal(code, types.EmptyCodeHash) {
- k.Logger(ctx).Debug("passed in EmptyCodeHash, but expected empty code")
- }
- hash := crypto.Keccak256Hash(code)
-
- // update account code hash
- account := k.accountKeeper.GetAccount(ctx, addr.Bytes())
- if account == nil {
- account = k.accountKeeper.NewAccountWithAddress(ctx, addr.Bytes())
- k.accountKeeper.SetAccount(ctx, account)
- }
-
- ethAccount, isEthAccount := account.(*ethermint.EthAccount)
- if !isEthAccount {
- k.Logger(ctx).Error(
- "invalid account type",
- "ethereum-address", addr.Hex(),
- "code-hash", hash.Hex(),
- )
- k.stateErr = fmt.Errorf("invalid account type, ethereum-address %v, code-hash %v", addr.Hex(), hash.Hex())
- return
- }
-
- ethAccount.CodeHash = hash.Hex()
- k.accountKeeper.SetAccount(ctx, ethAccount)
-
- store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixCode)
-
- action := "updated"
-
- // store or delete code
- if len(code) == 0 {
- store.Delete(hash.Bytes())
- action = "deleted"
- } else {
- store.Set(hash.Bytes(), code)
- }
-
- k.Logger(ctx).Debug(
- fmt.Sprintf("code %s", action),
- "ethereum-address", addr.Hex(),
- "code-hash", hash.Hex(),
- )
-}
-
-// GetCodeSize returns the size of the contract code associated with this object,
-// or zero if none.
-func (k *Keeper) GetCodeSize(addr common.Address) int {
- if k.HasStateError() {
- return 0
- }
-
- code := k.GetCode(addr)
- return len(code)
-}
-
-// ----------------------------------------------------------------------------
-// Refund
-// ----------------------------------------------------------------------------
-
-// NOTE: gas refunded needs to be tracked and stored in a separate variable in
-// order to add it subtract/add it from/to the gas used value after the EVM
-// execution has finalized. The refund value is cleared on every transaction and
-// at the end of every block.
-
-// AddRefund adds the given amount of gas to the refund transient value.
-func (k *Keeper) AddRefund(gas uint64) {
- if k.HasStateError() {
- return
- }
-
- ctx := k.Ctx()
- refund := k.GetRefund()
-
- refund += gas
-
- store := ctx.TransientStore(k.transientKey)
- store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund))
-}
-
-// SubRefund subtracts the given amount of gas from the transient refund value. This function
-// will panic if gas amount is greater than the stored refund.
-func (k *Keeper) SubRefund(gas uint64) {
- if k.HasStateError() {
- return
- }
-
- ctx := k.Ctx()
- refund := k.GetRefund()
-
- if gas > refund {
- // TODO: (@fedekunze) set to 0?? Geth panics here
- panic("refund counter below zero")
- }
-
- refund -= gas
-
- store := ctx.TransientStore(k.transientKey)
- store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund))
-}
-
-// GetRefund returns the amount of gas available for return after the tx execution
-// finalizes. This value is reset to 0 on every transaction.
-func (k *Keeper) GetRefund() uint64 {
- if k.HasStateError() {
- return 0
- }
-
- ctx := k.Ctx()
- store := ctx.TransientStore(k.transientKey)
-
- bz := store.Get(types.KeyPrefixTransientRefund)
- if len(bz) == 0 {
- return 0
- }
-
- return sdk.BigEndianToUint64(bz)
-}
-
-// ----------------------------------------------------------------------------
-// State
-// ----------------------------------------------------------------------------
-
-func doGetState(ctx sdk.Context, storeKey sdk.StoreKey, addr common.Address, key common.Hash) common.Hash {
- store := prefix.NewStore(ctx.KVStore(storeKey), types.AddressStoragePrefix(addr))
+// GetState loads contract state from database, implements `statedb.Keeper` interface.
+func (k *Keeper) GetState(ctx sdk.Context, addr common.Address, key common.Hash) common.Hash {
+ store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr))
value := store.Get(key.Bytes())
if len(value) == 0 {
@@ -465,355 +43,14 @@ func doGetState(ctx sdk.Context, storeKey sdk.StoreKey, addr common.Address, key
return common.BytesToHash(value)
}
-// GetCommittedState returns the value set in store for the given key hash. If the key is not registered
-// this function returns the empty hash.
-func (k *Keeper) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
- if k.HasStateError() {
- return common.Hash{}
- }
-
- return doGetState(k.ctxStack.initialCtx, k.storeKey, addr, hash)
+// 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), types.KeyPrefixCode)
+ return store.Get(codeHash.Bytes())
}
-// GetState returns the committed state for the given key hash, as all changes are committed directly
-// to the KVStore.
-func (k *Keeper) GetState(addr common.Address, hash common.Hash) common.Hash {
- if k.HasStateError() {
- return common.Hash{}
- }
-
- ctx := k.Ctx()
- return doGetState(ctx, k.storeKey, addr, hash)
-}
-
-// SetState sets the given hashes (key, value) to the KVStore. If the value hash is empty, this
-// function deletes the key from the store.
-func (k *Keeper) SetState(addr common.Address, key, value common.Hash) {
- if k.HasStateError() {
- return
- }
-
- ctx := k.Ctx()
- store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr))
-
- action := "updated"
- if ethermint.IsEmptyHash(value.Hex()) {
- store.Delete(key.Bytes())
- action = "deleted"
- } else {
- store.Set(key.Bytes(), value.Bytes())
- }
-
- k.Logger(ctx).Debug(
- fmt.Sprintf("state %s", action),
- "ethereum-address", addr.Hex(),
- "key", key.Hex(),
- )
-}
-
-// ----------------------------------------------------------------------------
-// Suicide
-// ----------------------------------------------------------------------------
-
-// Suicide marks the given account as suicided and clears the account balance of
-// the EVM tokens.
-func (k *Keeper) Suicide(addr common.Address) bool {
- if k.HasStateError() {
- return false
- }
-
- ctx := k.Ctx()
-
- prev := k.HasSuicided(addr)
- if prev {
- return true
- }
-
- cosmosAddr := sdk.AccAddress(addr.Bytes())
-
- _, err := k.ClearBalance(cosmosAddr)
- if err != nil {
- k.Logger(ctx).Error(
- "failed to subtract balance on suicide",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- "error", err,
- )
- k.stateErr = err
- return false
- }
-
- k.setSuicided(ctx, addr)
-
- // delete account code and state
- k.ResetAccount(addr)
-
- k.Logger(ctx).Debug(
- "account suicided",
- "ethereum-address", addr.Hex(),
- "cosmos-address", cosmosAddr.String(),
- )
-
- return true
-}
-
-// setSuicided sets a single byte to the transient store and marks the address as suicided
-func (k Keeper) setSuicided(ctx sdk.Context, addr common.Address) {
- store := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided)
- store.Set(addr.Bytes(), []byte{1})
-}
-
-// HasSuicided queries the transient store to check if the account has been marked as suicided in the
-// current block. Accounts that are suicided will be returned as non-nil during queries and "cleared"
-// after the block has been committed.
-func (k *Keeper) HasSuicided(addr common.Address) bool {
- if k.HasStateError() {
- return false
- }
-
- ctx := k.Ctx()
- store := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided)
- return store.Has(addr.Bytes())
-}
-
-// ----------------------------------------------------------------------------
-// Account Exist / Empty
-// ----------------------------------------------------------------------------
-
-// Exist returns true if the given account exists in store or if it has been
-// marked as suicided in the transient store.
-func (k *Keeper) Exist(addr common.Address) bool {
- if k.HasStateError() {
- return false
- }
-
- ctx := k.Ctx()
- // return true if the account has suicided
- if k.HasSuicided(addr) {
- return true
- }
-
- cosmosAddr := sdk.AccAddress(addr.Bytes())
- account := k.accountKeeper.GetAccount(ctx, cosmosAddr)
- return account != nil
-}
-
-// Empty returns true if the address meets the following conditions:
-// - nonce is 0
-// - balance amount for evm denom is 0
-// - account code hash is empty
-//
-// Non-ethereum accounts are considered not empty
-func (k *Keeper) Empty(addr common.Address) bool {
- if k.HasStateError() {
- return false
- }
-
- ctx := k.Ctx()
- nonce := uint64(0)
- codeHash := types.EmptyCodeHash
-
- cosmosAddr := sdk.AccAddress(addr.Bytes())
- account := k.accountKeeper.GetAccount(ctx, cosmosAddr)
-
- if account != nil {
- nonce = account.GetSequence()
- ethAccount, isEthAccount := account.(*ethermint.EthAccount)
- if !isEthAccount {
- return false
- }
-
- codeHash = common.HexToHash(ethAccount.CodeHash).Bytes()
- }
-
- balance := k.GetBalance(addr)
- hasZeroBalance := balance.Sign() == 0
- hasEmptyCodeHash := bytes.Equal(codeHash, types.EmptyCodeHash)
-
- return hasZeroBalance && nonce == 0 && hasEmptyCodeHash
-}
-
-// ----------------------------------------------------------------------------
-// Access List
-// ----------------------------------------------------------------------------
-
-// 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 (k *Keeper) PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses ethtypes.AccessList) {
- if k.HasStateError() {
- return
- }
-
- k.AddAddressToAccessList(sender)
- if dest != nil {
- k.AddAddressToAccessList(*dest)
- // If it's a create-tx, the destination will be added inside evm.create
- }
- for _, addr := range precompiles {
- k.AddAddressToAccessList(addr)
- }
- for _, tuple := range txAccesses {
- k.AddAddressToAccessList(tuple.Address)
- for _, key := range tuple.StorageKeys {
- k.AddSlotToAccessList(tuple.Address, key)
- }
- }
-}
-
-// AddressInAccessList returns true if the address is registered on the transient store.
-func (k *Keeper) AddressInAccessList(addr common.Address) bool {
- if k.HasStateError() {
- return false
- }
-
- ctx := k.Ctx()
- ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress)
- return ts.Has(addr.Bytes())
-}
-
-// SlotInAccessList checks if the address and the slots are registered in the transient store
-func (k *Keeper) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk, slotOk bool) {
- if k.HasStateError() {
- return false, false
- }
-
- addressOk = k.AddressInAccessList(addr)
- slotOk = k.addressSlotInAccessList(addr, slot)
- return addressOk, slotOk
-}
-
-// addressSlotInAccessList returns true if the address's slot is registered on the transient store.
-func (k *Keeper) addressSlotInAccessList(addr common.Address, slot common.Hash) bool {
- ctx := k.Ctx()
- ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot)
- key := append(addr.Bytes(), slot.Bytes()...)
- return ts.Has(key)
-}
-
-// AddAddressToAccessList adds the given address to the access list. If the address is already
-// in the access list, this function performs a no-op.
-func (k *Keeper) AddAddressToAccessList(addr common.Address) {
- if k.HasStateError() {
- return
- }
-
- if k.AddressInAccessList(addr) {
- return
- }
-
- ctx := k.Ctx()
- ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress)
- ts.Set(addr.Bytes(), []byte{0x1})
-}
-
-// AddSlotToAccessList adds the given (address, slot) to the access list. If the address and slot are
-// already in the access list, this function performs a no-op.
-func (k *Keeper) AddSlotToAccessList(addr common.Address, slot common.Hash) {
- if k.HasStateError() {
- return
- }
-
- k.AddAddressToAccessList(addr)
- if k.addressSlotInAccessList(addr, slot) {
- return
- }
-
- ctx := k.Ctx()
- ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot)
- key := append(addr.Bytes(), slot.Bytes()...)
- ts.Set(key, []byte{0x1})
-}
-
-// ClearAccessList clear current access list
-func (k *Keeper) ClearAccessList() {
- ctx := k.Ctx()
- ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot)
- itr := ts.Iterator(nil, nil)
- for ; itr.Valid(); itr.Next() {
- ts.Delete(itr.Key())
- }
-}
-
-// ----------------------------------------------------------------------------
-// Snapshotting
-// ----------------------------------------------------------------------------
-
-// Snapshot return the index in the cached context stack
-func (k *Keeper) Snapshot() int {
- if k.HasStateError() {
- return 0
- }
-
- return k.ctxStack.Snapshot()
-}
-
-// RevertToSnapshot pop all the cached contexts after(including) the snapshot
-func (k *Keeper) RevertToSnapshot(target int) {
- if k.HasStateError() {
- return
- }
-
- k.ctxStack.RevertToSnapshot(target)
-}
-
-// ----------------------------------------------------------------------------
-// Log
-// ----------------------------------------------------------------------------
-
-// AddLog appends the given ethereum Log to the list of Logs associated with the transaction hash kept in the current
-// context. This function also fills in the tx hash, block hash, tx index and log index fields before setting the log
-// to store.
-func (k *Keeper) AddLog(log *ethtypes.Log) {
- if k.HasStateError() {
- return
- }
-
- ctx := k.Ctx()
-
- log.BlockHash = common.BytesToHash(ctx.HeaderHash())
- log.TxIndex = uint(k.GetTxIndexTransient())
- log.TxHash = k.GetTxHashTransient()
-
- log.Index = uint(k.GetLogSizeTransient())
- k.IncreaseLogSizeTransient()
- k.AddLogTransient(log)
-
- k.Logger(ctx).Debug(
- "log added",
- "tx-hash-ethereum", log.TxHash.Hex(),
- "log-index", int(log.Index),
- )
-}
-
-// ----------------------------------------------------------------------------
-// Trie
-// ----------------------------------------------------------------------------
-
-// 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 (k *Keeper) AddPreimage(_ common.Hash, _ []byte) {}
-
-// ----------------------------------------------------------------------------
-// Iterator
-// ----------------------------------------------------------------------------
-
-// ForEachStorage uses the store iterator to iterate over all the state keys and perform a callback
-// function on each of them.
-// The callback should return `true` to continue, return `false` to break early.
-func (k *Keeper) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
- if k.HasStateError() {
- return k.stateErr
- }
-
- ctx := k.Ctx()
+// ForEachStorage iterate contract storage, callback return false to break early
+func (k *Keeper) ForEachStorage(ctx sdk.Context, addr common.Address, cb func(key, value common.Hash) bool) {
store := ctx.KVStore(k.storeKey)
prefix := types.AddressStoragePrefix(addr)
@@ -821,26 +58,155 @@ func (k *Keeper) ForEachStorage(addr common.Address, cb func(key, value common.H
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
-
- // TODO: check if the key prefix needs to be trimmed
key := common.BytesToHash(iterator.Key())
value := common.BytesToHash(iterator.Value())
// check if iteration stops
if !cb(key, value) {
- return nil
+ return
}
}
+}
+// 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, sdk.NewIntFromBigInt(delta)))
+ if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil {
+ return err
+ }
+ if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, cosmosAddr, coins); err != nil {
+ return err
+ }
+ case -1:
+ // burn
+ coins := sdk.NewCoins(sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(new(big.Int).Neg(delta))))
+ if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, cosmosAddr, types.ModuleName, coins); err != nil {
+ return err
+ }
+ if err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, coins); err != nil {
+ return err
+ }
+ default:
+ // not changed
+ }
return nil
}
-// HasStateError return the previous error for any state operations
-func (k *Keeper) HasStateError() bool {
- return k.stateErr != 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)
+ }
+ ethAcct, ok := acct.(*ethermint.EthAccount)
+ if !ok {
+ return errors.New("not EthAccount")
+ }
+ if err := ethAcct.SetSequence(account.Nonce); err != nil {
+ return err
+ }
+ ethAcct.CodeHash = common.BytesToHash(account.CodeHash).Hex()
+ k.accountKeeper.SetAccount(ctx, ethAcct)
+
+ err := k.SetBalance(ctx, addr, account.Balance)
+ k.Logger(ctx).Debug(
+ "account updated",
+ "ethereum-address", addr.Hex(),
+ "nonce", account.Nonce,
+ "codeHash", common.BytesToHash(account.CodeHash).Hex(),
+ "balance", account.Balance,
+ )
+ return err
}
-// ClearStateError reset the previous state operation error to nil
-func (k *Keeper) ClearStateError() {
- k.stateErr = 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), types.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(),
+ )
+}
+
+// SetCode set contract code, delete if code is empty.
+func (k *Keeper) SetCode(ctx sdk.Context, codeHash, code []byte) {
+ store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixCode)
+
+ // store or delete code
+ action := "updated"
+ if len(code) == 0 {
+ store.Delete(codeHash)
+ action = "deleted"
+ } else {
+ store.Set(codeHash, code)
+ }
+ k.Logger(ctx).Debug(
+ fmt.Sprintf("code %s", action),
+ "code-hash", common.BytesToHash(codeHash).Hex(),
+ )
+}
+
+// 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
+ }
+
+ ethAcct, ok := acct.(*ethermint.EthAccount)
+ if !ok {
+ return errors.New("not EthAccount")
+ }
+
+ // clear balance
+ if err := k.SetBalance(ctx, addr, new(big.Int)); err != nil {
+ return err
+ }
+
+ // remove code
+ codeHash := common.HexToHash(ethAcct.CodeHash).Bytes()
+ if !bytes.Equal(codeHash, types.EmptyCodeHash) {
+ k.SetCode(ctx, codeHash, nil)
+ }
+
+ // clear storage
+ k.ForEachStorage(ctx, addr, func(key, value 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
}
diff --git a/x/evm/keeper/statedb_benchmark_test.go b/x/evm/keeper/statedb_benchmark_test.go
index baaa27a4..834a8161 100644
--- a/x/evm/keeper/statedb_benchmark_test.go
+++ b/x/evm/keeper/statedb_benchmark_test.go
@@ -16,6 +16,7 @@ import (
func BenchmarkCreateAccountNew(b *testing.B) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)
+ vmdb := suite.StateDB()
b.ResetTimer()
b.ReportAllocs()
@@ -24,25 +25,27 @@ func BenchmarkCreateAccountNew(b *testing.B) {
b.StopTimer()
addr := tests.GenerateAddress()
b.StartTimer()
- suite.app.EvmKeeper.CreateAccount(addr)
+ vmdb.CreateAccount(addr)
}
}
func BenchmarkCreateAccountExisting(b *testing.B) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)
+ vmdb := suite.StateDB()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- suite.app.EvmKeeper.CreateAccount(suite.address)
+ vmdb.CreateAccount(suite.address)
}
}
func BenchmarkAddBalance(b *testing.B) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)
+ vmdb := suite.StateDB()
amt := big.NewInt(10)
@@ -50,13 +53,14 @@ func BenchmarkAddBalance(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- suite.app.EvmKeeper.AddBalance(suite.address, amt)
+ vmdb.AddBalance(suite.address, amt)
}
}
func BenchmarkSetCode(b *testing.B) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)
+ vmdb := suite.StateDB()
hash := crypto.Keccak256Hash([]byte("code")).Bytes()
@@ -64,13 +68,14 @@ func BenchmarkSetCode(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- suite.app.EvmKeeper.SetCode(suite.address, hash)
+ vmdb.SetCode(suite.address, hash)
}
}
func BenchmarkSetState(b *testing.B) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)
+ vmdb := suite.StateDB()
hash := crypto.Keccak256Hash([]byte("topic")).Bytes()
@@ -78,13 +83,14 @@ func BenchmarkSetState(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- suite.app.EvmKeeper.SetCode(suite.address, hash)
+ vmdb.SetCode(suite.address, hash)
}
}
func BenchmarkAddLog(b *testing.B) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)
+ vmdb := suite.StateDB()
topic := crypto.Keccak256Hash([]byte("topic"))
txHash := crypto.Keccak256Hash([]byte("tx_hash"))
@@ -94,7 +100,7 @@ func BenchmarkAddLog(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- suite.app.EvmKeeper.AddLog(ðtypes.Log{
+ vmdb.AddLog(ðtypes.Log{
Address: suite.address,
Topics: []common.Hash{topic},
Data: []byte("data"),
@@ -111,18 +117,19 @@ func BenchmarkAddLog(b *testing.B) {
func BenchmarkSnapshot(b *testing.B) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)
+ vmdb := suite.StateDB()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- target := suite.app.EvmKeeper.Snapshot()
+ target := vmdb.Snapshot()
require.Equal(b, i, target)
}
for i := b.N - 1; i >= 0; i-- {
require.NotPanics(b, func() {
- suite.app.EvmKeeper.RevertToSnapshot(i)
+ vmdb.RevertToSnapshot(i)
})
}
}
@@ -130,6 +137,7 @@ func BenchmarkSnapshot(b *testing.B) {
func BenchmarkSubBalance(b *testing.B) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)
+ vmdb := suite.StateDB()
amt := big.NewInt(10)
@@ -137,46 +145,49 @@ func BenchmarkSubBalance(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- suite.app.EvmKeeper.SubBalance(suite.address, amt)
+ vmdb.SubBalance(suite.address, amt)
}
}
func BenchmarkSetNonce(b *testing.B) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)
+ vmdb := suite.StateDB()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- suite.app.EvmKeeper.SetNonce(suite.address, 1)
+ vmdb.SetNonce(suite.address, 1)
}
}
func BenchmarkAddRefund(b *testing.B) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)
+ vmdb := suite.StateDB()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- suite.app.EvmKeeper.AddRefund(1)
+ vmdb.AddRefund(1)
}
}
func BenchmarkSuicide(b *testing.B) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)
+ vmdb := suite.StateDB()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
b.StopTimer()
addr := tests.GenerateAddress()
- suite.app.EvmKeeper.CreateAccount(addr)
+ vmdb.CreateAccount(addr)
b.StartTimer()
- suite.app.EvmKeeper.Suicide(addr)
+ vmdb.Suicide(addr)
}
}
diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go
index 837234e8..b7550099 100644
--- a/x/evm/keeper/statedb_test.go
+++ b/x/evm/keeper/statedb_test.go
@@ -13,8 +13,10 @@ import (
"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/tharsis/ethermint/tests"
+ "github.com/tharsis/ethermint/x/evm/statedb"
"github.com/tharsis/ethermint/x/evm/types"
)
@@ -22,39 +24,38 @@ func (suite *KeeperTestSuite) TestCreateAccount() {
testCases := []struct {
name string
addr common.Address
- malleate func(common.Address)
- callback func(common.Address)
+ malleate func(vm.StateDB, common.Address)
+ callback func(vm.StateDB, common.Address)
}{
{
"reset account (keep balance)",
suite.address,
- func(addr common.Address) {
- suite.app.EvmKeeper.AddBalance(addr, big.NewInt(100))
- suite.Require().NotZero(suite.app.EvmKeeper.GetBalance(addr).Int64())
+ func(vmdb vm.StateDB, addr common.Address) {
+ vmdb.AddBalance(addr, big.NewInt(100))
+ suite.Require().NotZero(vmdb.GetBalance(addr).Int64())
},
- func(addr common.Address) {
- suite.Require().Equal(suite.app.EvmKeeper.GetBalance(addr).Int64(), int64(100))
+ func(vmdb vm.StateDB, addr common.Address) {
+ suite.Require().Equal(vmdb.GetBalance(addr).Int64(), int64(100))
},
},
{
"create account",
tests.GenerateAddress(),
- func(addr common.Address) {
- acc := suite.app.AccountKeeper.GetAccount(suite.ctx, addr.Bytes())
- suite.Require().Nil(acc)
+ func(vmdb vm.StateDB, addr common.Address) {
+ suite.Require().False(vmdb.Exist(addr))
},
- func(addr common.Address) {
- acc := suite.app.AccountKeeper.GetAccount(suite.ctx, addr.Bytes())
- suite.Require().NotNil(acc)
+ func(vmdb vm.StateDB, addr common.Address) {
+ suite.Require().True(vmdb.Exist(addr))
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
- tc.malleate(tc.addr)
- suite.app.EvmKeeper.CreateAccount(tc.addr)
- tc.callback(tc.addr)
+ vmdb := suite.StateDB()
+ tc.malleate(vmdb, tc.addr)
+ vmdb.CreateAccount(tc.addr)
+ tc.callback(vmdb, tc.addr)
})
}
}
@@ -78,15 +79,16 @@ func (suite *KeeperTestSuite) TestAddBalance() {
{
"negative amount",
big.NewInt(-1),
- true,
+ false, // seems to be consistent with go-ethereum's implementation
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
- prev := suite.app.EvmKeeper.GetBalance(suite.address)
- suite.app.EvmKeeper.AddBalance(suite.address, tc.amount)
- post := suite.app.EvmKeeper.GetBalance(suite.address)
+ vmdb := suite.StateDB()
+ prev := vmdb.GetBalance(suite.address)
+ vmdb.AddBalance(suite.address, tc.amount)
+ post := vmdb.GetBalance(suite.address)
if tc.isNoOp {
suite.Require().Equal(prev.Int64(), post.Int64())
@@ -101,44 +103,45 @@ func (suite *KeeperTestSuite) TestSubBalance() {
testCases := []struct {
name string
amount *big.Int
- malleate func()
+ malleate func(vm.StateDB)
isNoOp bool
}{
{
"positive amount, below zero",
big.NewInt(100),
- func() {},
- true,
+ func(vm.StateDB) {},
+ false,
},
{
- "positive amount, below zero",
+ "positive amount, above zero",
big.NewInt(50),
- func() {
- suite.app.EvmKeeper.AddBalance(suite.address, big.NewInt(100))
+ func(vmdb vm.StateDB) {
+ vmdb.AddBalance(suite.address, big.NewInt(100))
},
false,
},
{
"zero amount",
big.NewInt(0),
- func() {},
+ func(vm.StateDB) {},
true,
},
{
"negative amount",
big.NewInt(-1),
- func() {},
- true,
+ func(vm.StateDB) {},
+ false,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
- tc.malleate()
+ vmdb := suite.StateDB()
+ tc.malleate(vmdb)
- prev := suite.app.EvmKeeper.GetBalance(suite.address)
- suite.app.EvmKeeper.SubBalance(suite.address, tc.amount)
- post := suite.app.EvmKeeper.GetBalance(suite.address)
+ prev := vmdb.GetBalance(suite.address)
+ vmdb.SubBalance(suite.address, tc.amount)
+ post := vmdb.GetBalance(suite.address)
if tc.isNoOp {
suite.Require().Equal(prev.Int64(), post.Int64())
@@ -154,29 +157,30 @@ func (suite *KeeperTestSuite) TestGetNonce() {
name string
address common.Address
expectedNonce uint64
- malleate func()
+ malleate func(vm.StateDB)
}{
{
"account not found",
tests.GenerateAddress(),
0,
- func() {},
+ func(vm.StateDB) {},
},
{
"existing account",
suite.address,
1,
- func() {
- suite.app.EvmKeeper.SetNonce(suite.address, 1)
+ func(vmdb vm.StateDB) {
+ vmdb.SetNonce(suite.address, 1)
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
- tc.malleate()
+ vmdb := suite.StateDB()
+ tc.malleate(vmdb)
- nonce := suite.app.EvmKeeper.GetNonce(tc.address)
+ nonce := vmdb.GetNonce(tc.address)
suite.Require().Equal(tc.expectedNonce, nonce)
})
}
@@ -205,8 +209,9 @@ func (suite *KeeperTestSuite) TestSetNonce() {
for _, tc := range testCases {
suite.Run(tc.name, func() {
- suite.app.EvmKeeper.SetNonce(tc.address, tc.nonce)
- nonce := suite.app.EvmKeeper.GetNonce(tc.address)
+ vmdb := suite.StateDB()
+ vmdb.SetNonce(tc.address, tc.nonce)
+ nonce := vmdb.GetNonce(tc.address)
suite.Require().Equal(tc.nonce, nonce)
})
}
@@ -221,35 +226,36 @@ func (suite *KeeperTestSuite) TestGetCodeHash() {
name string
address common.Address
expHash common.Hash
- malleate func()
+ malleate func(vm.StateDB)
}{
{
"account not found",
tests.GenerateAddress(),
- common.BytesToHash(types.EmptyCodeHash),
- func() {},
+ common.Hash{},
+ func(vm.StateDB) {},
},
{
- "account not EthAccount type",
+ "account not EthAccount type, error",
addr,
- common.BytesToHash(types.EmptyCodeHash),
- func() {},
+ common.Hash{},
+ func(vm.StateDB) {},
},
{
"existing account",
suite.address,
crypto.Keccak256Hash([]byte("codeHash")),
- func() {
- suite.app.EvmKeeper.SetCode(suite.address, []byte("codeHash"))
+ func(vmdb vm.StateDB) {
+ vmdb.SetCode(suite.address, []byte("codeHash"))
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
- tc.malleate()
+ vmdb := suite.StateDB()
+ tc.malleate(vmdb)
- hash := suite.app.EvmKeeper.GetCodeHash(tc.address)
+ hash := vmdb.GetCodeHash(tc.address)
suite.Require().Equal(tc.expHash, hash)
})
}
@@ -294,9 +300,10 @@ func (suite *KeeperTestSuite) TestSetCode() {
for _, tc := range testCases {
suite.Run(tc.name, func() {
- prev := suite.app.EvmKeeper.GetCode(tc.address)
- suite.app.EvmKeeper.SetCode(tc.address, tc.code)
- post := suite.app.EvmKeeper.GetCode(tc.address)
+ vmdb := suite.StateDB()
+ prev := vmdb.GetCode(tc.address)
+ vmdb.SetCode(tc.address, tc.code)
+ post := vmdb.GetCode(tc.address)
if tc.isNoOp {
suite.Require().Equal(prev, post)
@@ -304,9 +311,7 @@ func (suite *KeeperTestSuite) TestSetCode() {
suite.Require().Equal(tc.code, post)
}
- suite.Require().Equal(len(post), suite.app.EvmKeeper.GetCodeSize(tc.address))
-
- suite.app.EvmKeeper.ClearStateError()
+ suite.Require().Equal(len(post), vmdb.GetCodeSize(tc.address))
})
}
}
@@ -314,21 +319,21 @@ func (suite *KeeperTestSuite) TestSetCode() {
func (suite *KeeperTestSuite) TestRefund() {
testCases := []struct {
name string
- malleate func()
+ malleate func(vm.StateDB)
expRefund uint64
expPanic bool
}{
{
"success - add and subtract refund",
- func() {
- suite.app.EvmKeeper.AddRefund(11)
+ func(vmdb vm.StateDB) {
+ vmdb.AddRefund(11)
},
1,
false,
},
{
"fail - subtract amount > current refund",
- func() {
+ func(vm.StateDB) {
},
0,
true,
@@ -337,18 +342,15 @@ func (suite *KeeperTestSuite) TestRefund() {
for _, tc := range testCases {
suite.Run(tc.name, func() {
- tc.malleate()
+ vmdb := suite.StateDB()
+ tc.malleate(vmdb)
if tc.expPanic {
- suite.Require().Panics(func() { suite.app.EvmKeeper.SubRefund(10) })
+ suite.Require().Panics(func() { vmdb.SubRefund(10) })
} else {
- suite.app.EvmKeeper.SubRefund(10)
- suite.Require().Equal(tc.expRefund, suite.app.EvmKeeper.GetRefund())
+ vmdb.SubRefund(10)
+ suite.Require().Equal(tc.expRefund, vmdb.GetRefund())
}
-
- // clear and reset refund from store
- suite.app.EvmKeeper.ResetRefundTransient(suite.ctx)
- suite.Require().Zero(suite.app.EvmKeeper.GetRefund())
})
}
}
@@ -372,8 +374,9 @@ func (suite *KeeperTestSuite) TestState() {
for _, tc := range testCases {
suite.Run(tc.name, func() {
- suite.app.EvmKeeper.SetState(suite.address, tc.key, tc.value)
- value := suite.app.EvmKeeper.GetState(suite.address, tc.key)
+ vmdb := suite.StateDB()
+ vmdb.SetState(suite.address, tc.key, tc.value)
+ value := vmdb.GetState(suite.address, tc.key)
suite.Require().Equal(tc.value, value)
})
}
@@ -386,99 +389,120 @@ func (suite *KeeperTestSuite) TestCommittedState() {
value1 := common.BytesToHash([]byte("value1"))
value2 := common.BytesToHash([]byte("value2"))
- suite.app.EvmKeeper.SetState(suite.address, key, value1)
+ vmdb := suite.StateDB()
+ vmdb.SetState(suite.address, key, value1)
+ vmdb.Commit()
- suite.app.EvmKeeper.Snapshot()
-
- suite.app.EvmKeeper.SetState(suite.address, key, value2)
- tmp := suite.app.EvmKeeper.GetState(suite.address, key)
+ vmdb = suite.StateDB()
+ vmdb.SetState(suite.address, key, value2)
+ tmp := vmdb.GetState(suite.address, key)
suite.Require().Equal(value2, tmp)
- tmp = suite.app.EvmKeeper.GetCommittedState(suite.address, key)
+ tmp = vmdb.GetCommittedState(suite.address, key)
suite.Require().Equal(value1, tmp)
+ vmdb.Commit()
- suite.app.EvmKeeper.CommitCachedContexts()
-
- tmp = suite.app.EvmKeeper.GetCommittedState(suite.address, key)
+ vmdb = suite.StateDB()
+ tmp = vmdb.GetCommittedState(suite.address, key)
suite.Require().Equal(value2, tmp)
}
func (suite *KeeperTestSuite) TestSuicide() {
code := []byte("code")
+ db := suite.StateDB()
// Add code to account
- suite.app.EvmKeeper.SetCode(suite.address, code)
- suite.Require().Equal(code, suite.app.EvmKeeper.GetCode(suite.address))
+ db.SetCode(suite.address, code)
+ suite.Require().Equal(code, db.GetCode(suite.address))
// Add state to account
for i := 0; i < 5; i++ {
- suite.app.EvmKeeper.SetState(suite.address, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))
+ 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())
+ db = suite.StateDB()
+
// Call Suicide
- suite.Require().Equal(true, suite.app.EvmKeeper.Suicide(suite.address))
+ suite.Require().Equal(true, db.Suicide(suite.address))
// Check suicided is marked
- suite.Require().Equal(true, suite.app.EvmKeeper.HasSuicided(suite.address))
+ suite.Require().Equal(true, db.HasSuicided(suite.address))
+
+ suite.Require().NoError(db.Commit())
+ db = suite.StateDB()
+
// Check code is deleted
- suite.Require().Nil(suite.app.EvmKeeper.GetCode(suite.address))
+ suite.Require().Nil(db.GetCode(suite.address))
// Check state is deleted
var storage types.Storage
- err := suite.app.EvmKeeper.ForEachStorage(suite.address, func(key, value common.Hash) bool {
+ suite.app.EvmKeeper.ForEachStorage(suite.ctx, suite.address, func(key, value common.Hash) bool {
storage = append(storage, types.NewState(key, value))
return true
})
- suite.Require().NoError(err)
suite.Require().Equal(0, len(storage))
- // Check CodeHash is emptied
- suite.Require().Equal(common.BytesToHash(types.EmptyCodeHash).Bytes(), suite.app.EvmKeeper.GetCodeHash(suite.address).Bytes())
+ // Check account is deleted
+ suite.Require().Equal(common.Hash{}, db.GetCodeHash(suite.address))
}
func (suite *KeeperTestSuite) TestExist() {
testCases := []struct {
name string
address common.Address
- malleate func()
+ malleate func(vm.StateDB)
exists bool
}{
- {"success, account exists", suite.address, func() {}, true},
- {"success, has suicided", suite.address, func() {
- suite.app.EvmKeeper.Suicide(suite.address)
+ {"success, account exists", suite.address, func(vm.StateDB) {}, true},
+ {"success, has suicided", suite.address, func(vmdb vm.StateDB) {
+ vmdb.Suicide(suite.address)
}, true},
- {"success, account doesn't exist", tests.GenerateAddress(), func() {}, false},
+ {"success, account doesn't exist", tests.GenerateAddress(), func(vm.StateDB) {}, false},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
- tc.malleate()
+ vmdb := suite.StateDB()
+ tc.malleate(vmdb)
- suite.Require().Equal(tc.exists, suite.app.EvmKeeper.Exist(tc.address))
+ suite.Require().Equal(tc.exists, vmdb.Exist(tc.address))
})
}
}
func (suite *KeeperTestSuite) TestEmpty() {
+ suite.SetupTest()
addr := tests.GenerateAddress()
baseAcc := &authtypes.BaseAccount{Address: sdk.AccAddress(addr.Bytes()).String()}
suite.app.AccountKeeper.SetAccount(suite.ctx, baseAcc)
+ acct, err := suite.app.EvmKeeper.GetAccount(suite.ctx, suite.address)
+ suite.Require().NoError(err)
+ fmt.Println("default address", acct)
+
testCases := []struct {
name string
address common.Address
- malleate func()
+ malleate func(vm.StateDB)
empty bool
+ expErr bool
}{
- {"empty, account exists", suite.address, func() {}, true},
- {"not empty, non ethereum account", addr, func() {}, false},
- {"not empty, positive balance", suite.address, func() {
- suite.app.EvmKeeper.AddBalance(suite.address, big.NewInt(100))
- }, false},
- {"empty, account doesn't exist", tests.GenerateAddress(), func() {}, true},
+ {"empty, account exists", suite.address, func(vm.StateDB) {}, true, false},
+ {"error, non ethereum account", addr, func(vm.StateDB) {}, true, true},
+ {"not empty, positive balance", suite.address, func(vmdb vm.StateDB) {
+ vmdb.AddBalance(suite.address, big.NewInt(100))
+ }, false, false},
+ {"empty, account doesn't exist", tests.GenerateAddress(), func(vm.StateDB) {}, true, false},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
- tc.malleate()
+ vmdb := suite.StateDB()
+ tc.malleate(vmdb)
- suite.Require().Equal(tc.empty, suite.app.EvmKeeper.Empty(tc.address))
+ suite.Require().Equal(tc.empty, vmdb.Empty(tc.address))
+ if tc.expErr {
+ suite.Require().Error(vmdb.Error())
+ } else {
+ suite.Require().NoError(vmdb.Error())
+ }
})
}
}
@@ -490,53 +514,52 @@ func (suite *KeeperTestSuite) TestSnapshot() {
testCases := []struct {
name string
- malleate func()
+ malleate func(vm.StateDB)
}{
- {"simple revert", func() {
- revision := suite.app.EvmKeeper.Snapshot()
+ {"simple revert", func(vmdb vm.StateDB) {
+ revision := vmdb.Snapshot()
suite.Require().Zero(revision)
- suite.app.EvmKeeper.SetState(suite.address, key, value1)
- suite.Require().Equal(value1, suite.app.EvmKeeper.GetState(suite.address, key))
+ vmdb.SetState(suite.address, key, value1)
+ suite.Require().Equal(value1, vmdb.GetState(suite.address, key))
- suite.app.EvmKeeper.RevertToSnapshot(revision)
+ vmdb.RevertToSnapshot(revision)
// reverted
- suite.Require().Equal(common.Hash{}, suite.app.EvmKeeper.GetState(suite.address, key))
+ suite.Require().Equal(common.Hash{}, vmdb.GetState(suite.address, key))
}},
- {"nested snapshot/revert", func() {
- revision1 := suite.app.EvmKeeper.Snapshot()
+ {"nested snapshot/revert", func(vmdb vm.StateDB) {
+ revision1 := vmdb.Snapshot()
suite.Require().Zero(revision1)
- suite.app.EvmKeeper.SetState(suite.address, key, value1)
+ vmdb.SetState(suite.address, key, value1)
- revision2 := suite.app.EvmKeeper.Snapshot()
+ revision2 := vmdb.Snapshot()
- suite.app.EvmKeeper.SetState(suite.address, key, value2)
- suite.Require().Equal(value2, suite.app.EvmKeeper.GetState(suite.address, key))
+ vmdb.SetState(suite.address, key, value2)
+ suite.Require().Equal(value2, vmdb.GetState(suite.address, key))
- suite.app.EvmKeeper.RevertToSnapshot(revision2)
- suite.Require().Equal(value1, suite.app.EvmKeeper.GetState(suite.address, key))
+ vmdb.RevertToSnapshot(revision2)
+ suite.Require().Equal(value1, vmdb.GetState(suite.address, key))
- suite.app.EvmKeeper.RevertToSnapshot(revision1)
- suite.Require().Equal(common.Hash{}, suite.app.EvmKeeper.GetState(suite.address, key))
+ vmdb.RevertToSnapshot(revision1)
+ suite.Require().Equal(common.Hash{}, vmdb.GetState(suite.address, key))
}},
- {"jump revert", func() {
- revision1 := suite.app.EvmKeeper.Snapshot()
- suite.app.EvmKeeper.SetState(suite.address, key, value1)
- suite.app.EvmKeeper.Snapshot()
- suite.app.EvmKeeper.SetState(suite.address, key, value2)
- suite.app.EvmKeeper.RevertToSnapshot(revision1)
- suite.Require().Equal(common.Hash{}, suite.app.EvmKeeper.GetState(suite.address, key))
+ {"jump revert", func(vmdb vm.StateDB) {
+ revision1 := vmdb.Snapshot()
+ vmdb.SetState(suite.address, key, value1)
+ vmdb.Snapshot()
+ vmdb.SetState(suite.address, key, value2)
+ vmdb.RevertToSnapshot(revision1)
+ suite.Require().Equal(common.Hash{}, vmdb.GetState(suite.address, key))
}},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest()
- tc.malleate()
- // the test case should finish in clean state
- suite.Require().True(suite.app.EvmKeeper.CachedContextsEmpty())
+ vmdb := suite.StateDB()
+ tc.malleate(vmdb)
})
}
}
@@ -574,7 +597,6 @@ func (suite *KeeperTestSuite) TestAddLog() {
tx2 := suite.CreateTestTx(msg2, privKey)
msg2, _ = tx2.GetMsgs()[0].(*types.MsgEthereumTx)
- txHash2 := msg2.AsTransaction().Hash()
msg3 := types.NewTx(big.NewInt(1), 0, &suite.address, big.NewInt(1), 100000, nil, big.NewInt(1), big.NewInt(1), []byte("test"), nil)
msg3.From = addr.Hex()
@@ -588,92 +610,55 @@ func (suite *KeeperTestSuite) TestAddLog() {
tx4 := suite.CreateTestTx(msg4, privKey)
msg4, _ = tx4.GetMsgs()[0].(*types.MsgEthereumTx)
- txHash4 := msg4.AsTransaction().Hash()
testCases := []struct {
name string
hash common.Hash
log, expLog *ethtypes.Log // pre and post populating log fields
- malleate func()
+ malleate func(vm.StateDB)
}{
{
"tx hash from message",
txHash,
ðtypes.Log{
Address: addr,
+ Topics: make([]common.Hash, 0),
},
ðtypes.Log{
Address: addr,
TxHash: txHash,
Topics: make([]common.Hash, 0),
},
- func() {},
- },
- {
- "log index keep increasing in new tx",
- txHash2,
- ðtypes.Log{
- Address: addr,
- },
- ðtypes.Log{
- Address: addr,
- TxHash: txHash2,
- TxIndex: 1,
- Index: 1,
- Topics: make([]common.Hash, 0),
- },
- func() {
- suite.app.EvmKeeper.SetTxHashTransient(txHash)
- suite.app.EvmKeeper.AddLog(ðtypes.Log{
- Address: addr,
- })
- suite.app.EvmKeeper.IncreaseTxIndexTransient()
- },
+ func(vm.StateDB) {},
},
{
"dynamicfee tx hash from message",
txHash3,
ðtypes.Log{
Address: addr,
+ Topics: make([]common.Hash, 0),
},
ðtypes.Log{
Address: addr,
TxHash: txHash3,
Topics: make([]common.Hash, 0),
},
- func() {},
- },
- {
- "log index keep increasing in new dynamicfee tx",
- txHash4,
- ðtypes.Log{
- Address: addr,
- },
- ðtypes.Log{
- Address: addr,
- TxHash: txHash4,
- TxIndex: 1,
- Index: 1,
- Topics: make([]common.Hash, 0),
- },
- func() {
- suite.app.EvmKeeper.SetTxHashTransient(txHash)
- suite.app.EvmKeeper.AddLog(ðtypes.Log{
- Address: addr,
- })
- suite.app.EvmKeeper.IncreaseTxIndexTransient()
- },
+ func(vm.StateDB) {},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest()
- tc.malleate()
+ vmdb := statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewTxConfig(
+ common.BytesToHash(suite.ctx.HeaderHash().Bytes()),
+ tc.hash,
+ 0, 0,
+ ))
+ tc.malleate(vmdb)
- suite.app.EvmKeeper.SetTxHashTransient(tc.hash)
- suite.app.EvmKeeper.AddLog(tc.log)
- logs := suite.app.EvmKeeper.GetTxLogsTransient(tc.hash)
+ vmdb.AddLog(tc.log)
+ logs := vmdb.Logs()
suite.Require().Equal(1, len(logs))
suite.Require().Equal(tc.expLog, logs[0])
})
@@ -688,18 +673,19 @@ func (suite *KeeperTestSuite) TestPrepareAccessList() {
{Address: tests.GenerateAddress(), StorageKeys: []common.Hash{common.BytesToHash([]byte("key1"))}},
}
- suite.app.EvmKeeper.PrepareAccessList(suite.address, &dest, precompiles, accesses)
+ vmdb := suite.StateDB()
+ vmdb.PrepareAccessList(suite.address, &dest, precompiles, accesses)
- suite.Require().True(suite.app.EvmKeeper.AddressInAccessList(suite.address))
- suite.Require().True(suite.app.EvmKeeper.AddressInAccessList(dest))
+ suite.Require().True(vmdb.AddressInAccessList(suite.address))
+ suite.Require().True(vmdb.AddressInAccessList(dest))
for _, precompile := range precompiles {
- suite.Require().True(suite.app.EvmKeeper.AddressInAccessList(precompile))
+ suite.Require().True(vmdb.AddressInAccessList(precompile))
}
for _, access := range accesses {
for _, key := range access.StorageKeys {
- addrOK, slotOK := suite.app.EvmKeeper.SlotInAccessList(access.Address, key)
+ addrOK, slotOK := vmdb.SlotInAccessList(access.Address, key)
suite.Require().True(addrOK, access.Address.Hex())
suite.Require().True(slotOK, key.Hex())
}
@@ -717,8 +703,9 @@ func (suite *KeeperTestSuite) TestAddAddressToAccessList() {
for _, tc := range testCases {
suite.Run(tc.name, func() {
- suite.app.EvmKeeper.AddAddressToAccessList(tc.addr)
- addrOk := suite.app.EvmKeeper.AddressInAccessList(tc.addr)
+ vmdb := suite.StateDB()
+ vmdb.AddAddressToAccessList(tc.addr)
+ addrOk := vmdb.AddressInAccessList(tc.addr)
suite.Require().True(addrOk, tc.addr.Hex())
})
}
@@ -738,28 +725,30 @@ func (suite *KeeperTestSuite) AddSlotToAccessList() {
for _, tc := range testCases {
suite.Run(tc.name, func() {
- suite.app.EvmKeeper.AddSlotToAccessList(tc.addr, tc.slot)
- addrOk, slotOk := suite.app.EvmKeeper.SlotInAccessList(tc.addr, tc.slot)
+ vmdb := suite.StateDB()
+ vmdb.AddSlotToAccessList(tc.addr, tc.slot)
+ addrOk, slotOk := vmdb.SlotInAccessList(tc.addr, tc.slot)
suite.Require().True(addrOk, tc.addr.Hex())
suite.Require().True(slotOk, tc.slot.Hex())
})
}
}
-func (suite *KeeperTestSuite) TestForEachStorage() {
+// FIXME skip for now
+func (suite *KeeperTestSuite) _TestForEachStorage() {
var storage types.Storage
testCase := []struct {
name string
- malleate func()
+ malleate func(vm.StateDB)
callback func(key, value common.Hash) (stop bool)
expValues []common.Hash
}{
{
"aggregate state",
- func() {
+ func(vmdb vm.StateDB) {
for i := 0; i < 5; i++ {
- suite.app.EvmKeeper.SetState(suite.address, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))
+ vmdb.SetState(suite.address, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), common.BytesToHash([]byte(fmt.Sprintf("value%d", i))))
}
},
func(key, value common.Hash) bool {
@@ -776,9 +765,9 @@ func (suite *KeeperTestSuite) TestForEachStorage() {
},
{
"filter state",
- func() {
- suite.app.EvmKeeper.SetState(suite.address, common.BytesToHash([]byte("key")), common.BytesToHash([]byte("value")))
- suite.app.EvmKeeper.SetState(suite.address, common.BytesToHash([]byte("filterkey")), common.BytesToHash([]byte("filtervalue")))
+ func(vmdb vm.StateDB) {
+ vmdb.SetState(suite.address, common.BytesToHash([]byte("key")), common.BytesToHash([]byte("value")))
+ vmdb.SetState(suite.address, common.BytesToHash([]byte("filterkey")), common.BytesToHash([]byte("filtervalue")))
},
func(key, value common.Hash) bool {
if value == common.BytesToHash([]byte("filtervalue")) {
@@ -796,9 +785,10 @@ func (suite *KeeperTestSuite) TestForEachStorage() {
for _, tc := range testCase {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
- tc.malleate()
+ vmdb := suite.StateDB()
+ tc.malleate(vmdb)
- err := suite.app.EvmKeeper.ForEachStorage(suite.address, tc.callback)
+ err := vmdb.ForEachStorage(suite.address, tc.callback)
suite.Require().NoError(err)
suite.Require().Equal(len(tc.expValues), len(storage), fmt.Sprintf("Expected values:\n%v\nStorage Values\n%v", tc.expValues, storage))
diff --git a/x/evm/keeper/utils.go b/x/evm/keeper/utils.go
index 75303632..3846e03a 100644
--- a/x/evm/keeper/utils.go
+++ b/x/evm/keeper/utils.go
@@ -82,26 +82,22 @@ func (k Keeper) DeductTxCostsFromUserBalance(
// 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(
- ctx sdk.Context,
- bankKeeper evmtypes.BankKeeper,
- sender sdk.AccAddress,
+ balance sdk.Int,
txData evmtypes.TxData,
- denom string,
) error {
- balance := bankKeeper.GetBalance(ctx, sender, denom)
cost := txData.Cost()
if cost.Sign() < 0 {
return sdkerrors.Wrapf(
sdkerrors.ErrInvalidCoins,
- "tx cost (%s%s) is negative and invalid", cost, denom,
+ "tx cost (%s) is negative and invalid", cost,
)
}
- if balance.IsNegative() || balance.Amount.BigInt().Cmp(cost) < 0 {
+ if balance.IsNegative() || balance.BigInt().Cmp(cost) < 0 {
return sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFunds,
- "sender balance < tx cost (%s < %s%s)", balance, txData.Cost(), denom,
+ "sender balance < tx cost (%s < %s)", balance, txData.Cost(),
)
}
return nil
diff --git a/x/evm/keeper/utils_test.go b/x/evm/keeper/utils_test.go
index 0f7a2e35..e2c4f5b3 100644
--- a/x/evm/keeper/utils_test.go
+++ b/x/evm/keeper/utils_test.go
@@ -202,9 +202,11 @@ func (suite *KeeperTestSuite) TestCheckSenderBalance() {
},
}
- suite.app.EvmKeeper.AddBalance(suite.address, hundredInt.BigInt())
- balance := suite.app.EvmKeeper.GetBalance(suite.address)
+ vmdb := suite.StateDB()
+ vmdb.AddBalance(suite.address, hundredInt.BigInt())
+ balance := vmdb.GetBalance(suite.address)
suite.Require().Equal(balance, hundredInt.BigInt())
+ vmdb.Commit()
for i, tc := range testCases {
suite.Run(tc.name, func() {
@@ -233,12 +235,11 @@ func (suite *KeeperTestSuite) TestCheckSenderBalance() {
txData, _ := evmtypes.UnpackTxData(tx.Data)
- err := evmkeeper.CheckSenderBalance(
- suite.app.EvmKeeper.Ctx(),
- suite.app.BankKeeper,
- suite.address[:],
+ acct, err := suite.app.EvmKeeper.GetAccountOrEmpty(suite.ctx, suite.address)
+ suite.Require().NoError(err)
+ err = evmkeeper.CheckSenderBalance(
+ sdk.NewIntFromBigInt(acct.Balance),
txData,
- evmtypes.DefaultEVMDenom,
)
if tc.expectPass {
@@ -367,6 +368,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
suite.Run(tc.name, func() {
suite.enableFeemarket = tc.enableFeemarket
suite.SetupTest()
+ vmdb := suite.StateDB()
var amount, gasPrice, gasFeeCap, gasTipCap *big.Int
if tc.cost != nil {
@@ -382,18 +384,19 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
} else {
gasTipCap = tc.gasTipCap
}
- suite.app.EvmKeeper.AddBalance(suite.address, initBalance.BigInt())
- balance := suite.app.EvmKeeper.GetBalance(suite.address)
+ 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()
}
- suite.app.EvmKeeper.AddBalance(suite.address, hundredInt.BigInt())
- balance := suite.app.EvmKeeper.GetBalance(suite.address)
+ vmdb.AddBalance(suite.address, hundredInt.BigInt())
+ balance := vmdb.GetBalance(suite.address)
suite.Require().Equal(balance, hundredInt.BigInt())
}
+ vmdb.Commit()
tx := evmtypes.NewTx(zeroInt.BigInt(), 1, &suite.address, amount, tc.gasLimit, gasPrice, gasFeeCap, gasTipCap, nil, tc.accessList)
tx.From = suite.address.String()
@@ -401,7 +404,7 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
txData, _ := evmtypes.UnpackTxData(tx.Data)
fees, err := suite.app.EvmKeeper.DeductTxCostsFromUserBalance(
- suite.app.EvmKeeper.Ctx(),
+ suite.ctx,
*tx,
txData,
evmtypes.DefaultEVMDenom,
diff --git a/x/evm/statedb/access_list.go b/x/evm/statedb/access_list.go
new file mode 100644
index 00000000..99342819
--- /dev/null
+++ b/x/evm/statedb/access_list.go
@@ -0,0 +1,135 @@
+// Copyright 2020 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 (
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type accessList struct {
+ addresses map[common.Address]int
+ slots []map[common.Hash]struct{}
+}
+
+// ContainsAddress returns true if the address is in the access list.
+func (al *accessList) ContainsAddress(address common.Address) bool {
+ _, ok := al.addresses[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 *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
+ idx, ok := al.addresses[address]
+ if !ok {
+ // no such address (and hence zero slots)
+ return false, false
+ }
+ if idx == -1 {
+ // address yes, but no slots
+ return true, false
+ }
+ _, slotPresent = al.slots[idx][slot]
+ return true, slotPresent
+}
+
+// newAccessList creates a new accessList.
+func newAccessList() *accessList {
+ return &accessList{
+ addresses: make(map[common.Address]int),
+ }
+}
+
+// Copy creates an independent copy of an accessList.
+func (al *accessList) Copy() *accessList {
+ cp := newAccessList()
+ for k, v := range al.addresses {
+ cp.addresses[k] = v
+ }
+ cp.slots = make([]map[common.Hash]struct{}, len(al.slots))
+ for i, slotMap := range al.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 {
+ if _, present := al.addresses[address]; present {
+ return false
+ }
+ al.addresses[address] = -1
+ return true
+}
+
+// AddSlot adds the specified (addr, slot) combo to the access list.
+// Return values are:
+// - address added
+// - slot added
+// For any 'true' value returned, a corresponding journal entry must be made.
+func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) {
+ idx, addrPresent := al.addresses[address]
+ if !addrPresent || idx == -1 {
+ // Address not present, or addr present but no slots there
+ al.addresses[address] = len(al.slots)
+ slotmap := map[common.Hash]struct{}{slot: {}}
+ al.slots = append(al.slots, slotmap)
+ return !addrPresent, true
+ }
+ // There is already an (address,slot) mapping
+ slotmap := al.slots[idx]
+ if _, ok := slotmap[slot]; !ok {
+ slotmap[slot] = struct{}{}
+ // Journal add slot change
+ return false, true
+ }
+ // No changes required
+ return false, false
+}
+
+// DeleteSlot removes an (address, slot)-tuple from the access list.
+// This operation needs to be performed in the same order as the addition happened.
+// This method is meant to be used by the journal, which maintains ordering of
+// operations.
+func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) {
+ idx, addrOk := al.addresses[address]
+ if !addrOk {
+ panic("reverting slot change, address not present in list")
+ }
+ slotmap := al.slots[idx]
+ delete(slotmap, slot)
+ // If that was the last (first) slot, remove it
+ // Since additions and rollbacks are always performed in order,
+ // we can delete the item without worrying about screwing up later indices
+ if len(slotmap) == 0 {
+ al.slots = al.slots[:idx]
+ al.addresses[address] = -1
+ }
+}
+
+// DeleteAddress removes an address from the access list. This operation
+// needs to be performed in the same order as the addition happened.
+// This method is meant to be used by the journal, which maintains ordering of
+// operations.
+func (al *accessList) DeleteAddress(address common.Address) {
+ delete(al.addresses, address)
+}
diff --git a/x/evm/statedb/config.go b/x/evm/statedb/config.go
new file mode 100644
index 00000000..9932b7b5
--- /dev/null
+++ b/x/evm/statedb/config.go
@@ -0,0 +1,32 @@
+package statedb
+
+import "github.com/ethereum/go-ethereum/common"
+
+// 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
+}
+
+// NewTxConfig returns a TxConfig
+func NewTxConfig(bhash, thash common.Hash, txIndex, logIndex uint) TxConfig {
+ return TxConfig{
+ BlockHash: bhash,
+ TxHash: thash,
+ TxIndex: txIndex,
+ LogIndex: logIndex,
+ }
+}
+
+// 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,
+ }
+}
diff --git a/x/evm/statedb/interfaces.go b/x/evm/statedb/interfaces.go
new file mode 100644
index 00000000..5775ecdb
--- /dev/null
+++ b/x/evm/statedb/interfaces.go
@@ -0,0 +1,22 @@
+package statedb
+
+import (
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// Keeper provide underlying storage of StateDB
+type Keeper interface {
+ // Read methods
+ GetAccount(ctx sdk.Context, addr common.Address) (*Account, error)
+ 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
new file mode 100644
index 00000000..c3f692f9
--- /dev/null
+++ b/x/evm/statedb/journal.go
@@ -0,0 +1,243 @@
+// 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, len(j.dirties))
+ i := 0
+ for k := range j.dirties {
+ keys[i] = k
+ i++
+ }
+ 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
new file mode 100644
index 00000000..263653a4
--- /dev/null
+++ b/x/evm/statedb/mock_test.go
@@ -0,0 +1,84 @@
+package statedb_test
+
+import (
+ "errors"
+ "math/big"
+
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/tharsis/ethermint/x/evm/statedb"
+)
+
+var _ statedb.Keeper = &MockKeeper{}
+
+type MockKeeper struct {
+ errAddress common.Address
+
+ accounts map[common.Address]statedb.Account
+ states map[common.Address]statedb.Storage
+ codes map[common.Hash][]byte
+}
+
+func NewMockKeeper() *MockKeeper {
+ return &MockKeeper{
+ errAddress: common.BigToAddress(big.NewInt(1)),
+
+ accounts: make(map[common.Address]statedb.Account),
+ states: make(map[common.Address]statedb.Storage),
+ codes: make(map[common.Hash][]byte),
+ }
+}
+
+func (k MockKeeper) GetAccount(ctx sdk.Context, addr common.Address) (*statedb.Account, error) {
+ if addr == k.errAddress {
+ return nil, errors.New("mock db error")
+ }
+ acct, ok := k.accounts[addr]
+ if !ok {
+ return nil, nil
+ }
+ return &acct, nil
+}
+
+func (k MockKeeper) GetState(ctx sdk.Context, addr common.Address, key common.Hash) common.Hash {
+ return k.states[addr][key]
+}
+
+func (k MockKeeper) GetCode(ctx sdk.Context, codeHash common.Hash) []byte {
+ return k.codes[codeHash]
+}
+
+func (k MockKeeper) ForEachStorage(ctx sdk.Context, addr common.Address, cb func(key, value common.Hash) bool) {
+ for k, v := range k.states[addr] {
+ if !cb(k, v) {
+ return
+ }
+ }
+}
+
+func (k MockKeeper) SetAccount(ctx sdk.Context, addr common.Address, account statedb.Account) error {
+ k.accounts[addr] = account
+ return nil
+}
+
+func (k MockKeeper) SetState(ctx sdk.Context, addr common.Address, key common.Hash, value []byte) {
+ if len(value) == 0 {
+ delete(k.states[addr], key)
+ } else {
+ k.states[addr][key] = common.BytesToHash(value)
+ }
+}
+
+func (k MockKeeper) SetCode(ctx sdk.Context, codeHash []byte, code []byte) {
+ k.codes[common.BytesToHash(codeHash)] = code
+}
+
+func (k MockKeeper) DeleteAccount(ctx sdk.Context, addr common.Address) error {
+ old := k.accounts[addr]
+ delete(k.accounts, addr)
+ delete(k.states, addr)
+ if len(old.CodeHash) > 0 {
+ delete(k.codes, common.BytesToHash(old.CodeHash))
+ }
+ return nil
+}
diff --git a/x/evm/statedb/state_object.go b/x/evm/statedb/state_object.go
new file mode 100644
index 00000000..f107e5a3
--- /dev/null
+++ b/x/evm/statedb/state_object.go
@@ -0,0 +1,240 @@
+package statedb
+
+import (
+ "bytes"
+ "math/big"
+ "sort"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+var emptyCodeHash = crypto.Keccak256(nil)
+
+// 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: emptyCodeHash,
+ }
+}
+
+// IsContract returns if the account contains contract code.
+func (acct Account) IsContract() bool {
+ return !bytes.Equal(acct.CodeHash, emptyCodeHash)
+}
+
+// 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, len(s))
+ i := 0
+ for k := range s {
+ keys[i] = k
+ i++
+ }
+ 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 = 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 && bytes.Equal(s.account.CodeHash, emptyCodeHash)
+}
+
+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
+}
+
+// Return the gas back to the origin. Used by the Virtual machine or Closures
+func (s *stateObject) ReturnGas(gas *big.Int) {}
+
+//
+// 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 bytes.Equal(s.CodeHash(), emptyCodeHash) {
+ 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
new file mode 100644
index 00000000..1ec7b0a4
--- /dev/null
+++ b/x/evm/statedb/statedb.go
@@ -0,0 +1,498 @@
+package statedb
+
+import (
+ "fmt"
+ "math/big"
+ "sort"
+
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ "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{}
+
+// 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
+ dbErr error
+
+ // 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
+}
+
+// Context returns the embedded `sdk.Context`
+func (s *StateDB) Context() sdk.Context {
+ return s.ctx
+}
+
+// setError remembers the first non-nil error it is called with.
+func (s *StateDB) setError(err error) {
+ if s.dbErr == nil {
+ s.dbErr = err
+ }
+}
+
+// Error returns the database error recorded.
+func (s *StateDB) Error() error {
+ return s.dbErr
+}
+
+// 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
+}
+
+// TxIndex returns the current transaction index.
+func (s *StateDB) TxIndex() uint {
+ return s.txConfig.TxIndex
+}
+
+// BlockHash returns the current block hash.
+func (s *StateDB) BlockHash() common.Hash {
+ return s.txConfig.BlockHash
+}
+
+// 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(hash common.Hash, preimage []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, err := s.keeper.GetAccount(s.ctx, addr)
+ if err != nil {
+ s.setError(err)
+ return nil
+ }
+ 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 s.dbErr != nil {
+ return fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
+ }
+ 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 sdkerrors.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 sdkerrors.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
+}
diff --git a/x/evm/statedb/statedb_test.go b/x/evm/statedb/statedb_test.go
new file mode 100644
index 00000000..0cc0e85f
--- /dev/null
+++ b/x/evm/statedb/statedb_test.go
@@ -0,0 +1,291 @@
+package statedb_test
+
+import (
+ "math/big"
+ "testing"
+
+ 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/crypto"
+ "github.com/stretchr/testify/suite"
+ "github.com/tharsis/ethermint/x/evm/statedb"
+)
+
+type StateDBTestSuite struct {
+ suite.Suite
+}
+
+func (suite *StateDBTestSuite) TestAccounts() {
+ addrErr := common.BigToAddress(big.NewInt(1))
+ addr2 := common.BigToAddress(big.NewInt(2))
+ testTxConfig := statedb.NewTxConfig(
+ common.BigToHash(big.NewInt(10)), // tx hash
+ common.BigToHash(big.NewInt(11)), // block hash
+ 1, // txIndex
+ 1, // logSize
+ )
+
+ testCases := []struct {
+ msg string
+ test func(*statedb.StateDB)
+ }{
+ {
+ "success,empty account",
+ func(db *statedb.StateDB) {
+ suite.Require().Equal(true, db.Empty(addr2))
+ suite.Require().Equal(big.NewInt(0), db.GetBalance(addr2))
+ suite.Require().Equal([]byte(nil), db.GetCode(addr2))
+ suite.Require().Equal(uint64(0), db.GetNonce(addr2))
+ },
+ },
+ {
+ "success,GetBalance",
+ func(db *statedb.StateDB) {
+ db.AddBalance(addr2, big.NewInt(1))
+ suite.Require().Equal(big.NewInt(1), db.GetBalance(addr2))
+ },
+ },
+ {
+ "fail,GetBalance dbErr",
+ func(db *statedb.StateDB) {
+ suite.Require().Equal(big.NewInt(0), db.GetBalance(addrErr))
+ suite.Require().Error(db.Commit())
+ },
+ },
+ {
+ "success,change balance",
+ func(db *statedb.StateDB) {
+ db.AddBalance(addr2, big.NewInt(2))
+ suite.Require().Equal(big.NewInt(2), db.GetBalance(addr2))
+ db.SubBalance(addr2, big.NewInt(1))
+ suite.Require().Equal(big.NewInt(1), db.GetBalance(addr2))
+
+ suite.Require().NoError(db.Commit())
+
+ // create a clean StateDB, check the balance is committed
+ db = statedb.New(db.Context(), db.Keeper(), testTxConfig)
+ suite.Require().Equal(big.NewInt(1), db.GetBalance(addr2))
+ },
+ },
+ {
+ "success,SetState",
+ func(db *statedb.StateDB) {
+ key := common.BigToHash(big.NewInt(1))
+ value := common.BigToHash(big.NewInt(1))
+
+ suite.Require().Equal(common.Hash{}, db.GetState(addr2, key))
+ db.SetState(addr2, key, value)
+ suite.Require().Equal(value, db.GetState(addr2, key))
+ suite.Require().Equal(common.Hash{}, db.GetCommittedState(addr2, key))
+ },
+ },
+ {
+ "success,SetCode",
+ func(db *statedb.StateDB) {
+ code := []byte("hello world")
+ codeHash := crypto.Keccak256Hash(code)
+ db.SetCode(addr2, code)
+ suite.Require().Equal(code, db.GetCode(addr2))
+ suite.Require().Equal(codeHash, db.GetCodeHash(addr2))
+
+ suite.Require().NoError(db.Commit())
+
+ // create a clean StateDB, check the code is committed
+ db = statedb.New(db.Context(), db.Keeper(), testTxConfig)
+ suite.Require().Equal(code, db.GetCode(addr2))
+ suite.Require().Equal(codeHash, db.GetCodeHash(addr2))
+ },
+ },
+ {
+ "success,CreateAccount",
+ func(db *statedb.StateDB) {
+ // test balance carry over when overwritten
+ amount := big.NewInt(1)
+ code := []byte("hello world")
+ key := common.BigToHash(big.NewInt(1))
+ value := common.BigToHash(big.NewInt(1))
+
+ db.AddBalance(addr2, amount)
+ db.SetCode(addr2, code)
+ db.SetState(addr2, key, value)
+
+ rev := db.Snapshot()
+
+ db.CreateAccount(addr2)
+ suite.Require().Equal(amount, db.GetBalance(addr2))
+ suite.Require().Equal([]byte(nil), db.GetCode(addr2))
+ suite.Require().Equal(common.Hash{}, db.GetState(addr2, key))
+
+ db.RevertToSnapshot(rev)
+ suite.Require().Equal(amount, db.GetBalance(addr2))
+ suite.Require().Equal(code, db.GetCode(addr2))
+ suite.Require().Equal(value, db.GetState(addr2, key))
+
+ db.CreateAccount(addr2)
+ suite.Require().NoError(db.Commit())
+ db = statedb.New(db.Context(), db.Keeper(), testTxConfig)
+ suite.Require().Equal(amount, db.GetBalance(addr2))
+ suite.Require().Equal([]byte(nil), db.GetCode(addr2))
+ suite.Require().Equal(common.Hash{}, db.GetState(addr2, key))
+ },
+ },
+ {
+ "success,nested snapshot revert",
+ func(db *statedb.StateDB) {
+ key := common.BigToHash(big.NewInt(1))
+ value1 := common.BigToHash(big.NewInt(1))
+ value2 := common.BigToHash(big.NewInt(2))
+
+ rev1 := db.Snapshot()
+ db.SetState(addr2, key, value1)
+
+ rev2 := db.Snapshot()
+ db.SetState(addr2, key, value2)
+ suite.Require().Equal(value2, db.GetState(addr2, key))
+
+ db.RevertToSnapshot(rev2)
+ suite.Require().Equal(value1, db.GetState(addr2, key))
+
+ db.RevertToSnapshot(rev1)
+ suite.Require().Equal(common.Hash{}, db.GetState(addr2, key))
+ },
+ },
+ {
+ "success,nonce",
+ func(db *statedb.StateDB) {
+ suite.Require().Equal(uint64(0), db.GetNonce(addr2))
+ db.SetNonce(addr2, 1)
+ suite.Require().Equal(uint64(1), db.GetNonce(addr2))
+
+ suite.Require().NoError(db.Commit())
+
+ db = statedb.New(db.Context(), db.Keeper(), testTxConfig)
+ suite.Require().Equal(uint64(1), db.GetNonce(addr2))
+ },
+ },
+ {
+ "success,logs",
+ func(db *statedb.StateDB) {
+ data := []byte("hello world")
+ db.AddLog(ðtypes.Log{
+ Address: addr2,
+ Topics: []common.Hash{},
+ Data: data,
+ BlockNumber: 1,
+ })
+ suite.Require().Equal(1, len(db.Logs()))
+ expecedLog := ðtypes.Log{
+ Address: addr2,
+ Topics: []common.Hash{},
+ Data: data,
+ BlockNumber: 1,
+ BlockHash: common.BigToHash(big.NewInt(10)),
+ TxHash: common.BigToHash(big.NewInt(11)),
+ TxIndex: 1,
+ Index: 1,
+ }
+ suite.Require().Equal(expecedLog, db.Logs()[0])
+
+ rev := db.Snapshot()
+
+ db.AddLog(ðtypes.Log{
+ Address: addr2,
+ Topics: []common.Hash{},
+ Data: data,
+ BlockNumber: 1,
+ })
+ suite.Require().Equal(2, len(db.Logs()))
+ suite.Require().Equal(uint(2), db.Logs()[1].Index)
+
+ db.RevertToSnapshot(rev)
+ suite.Require().Equal(1, len(db.Logs()))
+ },
+ },
+ {
+ "success,refund",
+ func(db *statedb.StateDB) {
+ db.AddRefund(uint64(10))
+ suite.Require().Equal(uint64(10), db.GetRefund())
+
+ rev := db.Snapshot()
+
+ db.SubRefund(uint64(5))
+ suite.Require().Equal(uint64(5), db.GetRefund())
+
+ db.RevertToSnapshot(rev)
+ suite.Require().Equal(uint64(10), db.GetRefund())
+ },
+ },
+ {
+ "success,empty",
+ func(db *statedb.StateDB) {
+ suite.Require().False(db.Exist(addr2))
+ suite.Require().True(db.Empty(addr2))
+
+ db.AddBalance(addr2, big.NewInt(1))
+ suite.Require().True(db.Exist(addr2))
+ suite.Require().False(db.Empty(addr2))
+
+ db.SubBalance(addr2, big.NewInt(1))
+ suite.Require().True(db.Exist(addr2))
+ suite.Require().True(db.Empty(addr2))
+ },
+ },
+ {
+ "success,suicide commit",
+ func(db *statedb.StateDB) {
+ code := []byte("hello world")
+ db.SetCode(addr2, code)
+ db.AddBalance(addr2, big.NewInt(1))
+
+ suite.Require().True(db.Exist(addr2))
+ suite.Require().False(db.Empty(addr2))
+
+ db.Suicide(addr2)
+ suite.Require().True(db.HasSuicided(addr2))
+ suite.Require().True(db.Exist(addr2))
+ suite.Require().Equal(new(big.Int), db.GetBalance(addr2))
+
+ suite.Require().NoError(db.Commit())
+ db = statedb.New(db.Context(), db.Keeper(), testTxConfig)
+ suite.Require().True(db.Empty(addr2))
+ },
+ },
+ {
+ "success,suicide revert",
+ func(db *statedb.StateDB) {
+ code := []byte("hello world")
+ db.SetCode(addr2, code)
+ db.AddBalance(addr2, big.NewInt(1))
+
+ rev := db.Snapshot()
+
+ db.Suicide(addr2)
+ suite.Require().True(db.HasSuicided(addr2))
+
+ db.RevertToSnapshot(rev)
+
+ suite.Require().False(db.HasSuicided(addr2))
+ suite.Require().Equal(code, db.GetCode(addr2))
+ suite.Require().Equal(big.NewInt(1), db.GetBalance(addr2))
+ },
+ },
+ // TODO access lisForEachStorage
+ // https://github.com/tharsis/ethermint/issues/876
+ }
+ for _, tc := range testCases {
+ suite.Run(tc.msg, func() {
+ db := statedb.New(
+ sdk.Context{},
+ NewMockKeeper(),
+ testTxConfig,
+ )
+ tc.test(db)
+ })
+ }
+}
+
+func TestStateDBTestSuite(t *testing.T) {
+ suite.Run(t, &StateDBTestSuite{})
+}
diff --git a/x/evm/types/key.go b/x/evm/types/key.go
index 1b89c260..36e5a8fc 100644
--- a/x/evm/types/key.go
+++ b/x/evm/types/key.go
@@ -29,15 +29,9 @@ const (
// prefix bytes for the EVM transient store
const (
- prefixTransientSuicided = iota + 1
- prefixTransientBloom
+ prefixTransientBloom = iota + 1
prefixTransientTxIndex
- prefixTransientRefund
- prefixTransientAccessListAddress
- prefixTransientAccessListSlot
- prefixTransientTxHash
prefixTransientLogSize
- prefixTransientTxLogs
)
// KVStore key prefixes
@@ -48,15 +42,9 @@ var (
// Transient Store key prefixes
var (
- KeyPrefixTransientSuicided = []byte{prefixTransientSuicided}
- KeyPrefixTransientBloom = []byte{prefixTransientBloom}
- KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex}
- KeyPrefixTransientRefund = []byte{prefixTransientRefund}
- KeyPrefixTransientAccessListAddress = []byte{prefixTransientAccessListAddress}
- KeyPrefixTransientAccessListSlot = []byte{prefixTransientAccessListSlot}
- KeyPrefixTransientTxHash = []byte{prefixTransientTxHash}
- KeyPrefixTransientLogSize = []byte{prefixTransientLogSize}
- KeyPrefixTransientTxLogs = []byte{prefixTransientTxLogs}
+ KeyPrefixTransientBloom = []byte{prefixTransientBloom}
+ KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex}
+ KeyPrefixTransientLogSize = []byte{prefixTransientLogSize}
)
// AddressStoragePrefix returns a prefix to iterate over a given account storage.
diff --git a/x/feemarket/keeper/keeper_test.go b/x/feemarket/keeper/keeper_test.go
index 7a4a4fc7..94a1fd0a 100644
--- a/x/feemarket/keeper/keeper_test.go
+++ b/x/feemarket/keeper/keeper_test.go
@@ -90,7 +90,6 @@ func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) {
ConsensusHash: tmhash.Sum([]byte("consensus")),
LastResultsHash: tmhash.Sum([]byte("last_result")),
})
- suite.app.EvmKeeper.WithContext(suite.ctx)
queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry())
types.RegisterQueryServer(queryHelper, suite.app.FeeMarketKeeper)