From 12acbe4609fffd7d2e7fa61a2703560faaed82f8 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Fri, 3 Dec 2021 10:53:29 +0800 Subject: [PATCH] increase nonce for reverted contract deployment transaction Closes: #808 Solution: - Move nonce increaesment from ante handler into `ApplyMessage`. - Handle state revert correct. - More sophsticated unit test for transaction revert behaviour. --- app/ante/ante.go | 1 - app/ante/eth.go | 58 -------- app/ante/eth_test.go | 92 ------------ x/evm/handler_test.go | 241 +++++++++++++++++++++---------- x/evm/keeper/state_transition.go | 52 +++---- 5 files changed, 180 insertions(+), 264 deletions(-) diff --git a/app/ante/ante.go b/app/ante/ante.go index fbb59016ac..bd97ef64a1 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -63,7 +63,6 @@ func NewAnteHandler( NewEthNonceVerificationDecorator(ak), NewEthGasConsumeDecorator(evmKeeper), NewCanTransferDecorator(evmKeeper, feeMarketKeeper), - NewEthIncrementSenderSequenceDecorator(ak), // innermost AnteDecorator. ) default: diff --git a/app/ante/eth.go b/app/ante/eth.go index c51b53fb10..50d181a50f 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -385,64 +385,6 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate return next(ctx, tx, simulate) } -// EthIncrementSenderSequenceDecorator increments the sequence of the signers. -type EthIncrementSenderSequenceDecorator struct { - ak evmtypes.AccountKeeper -} - -// NewEthIncrementSenderSequenceDecorator creates a new EthIncrementSenderSequenceDecorator. -func NewEthIncrementSenderSequenceDecorator(ak evmtypes.AccountKeeper) EthIncrementSenderSequenceDecorator { - return EthIncrementSenderSequenceDecorator{ - ak: ak, - } -} - -// AnteHandle handles incrementing the sequence of the signer (i.e sender). If the transaction is a -// contract creation, the nonce will be incremented during the transaction execution and not within -// this AnteHandler decorator. -func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - for _, msg := range tx.GetMsgs() { - msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) - } - - txData, err := evmtypes.UnpackTxData(msgEthTx.Data) - if err != nil { - return ctx, sdkerrors.Wrap(err, "failed to unpack tx data") - } - - // NOTE: on contract creation, the nonce is incremented within the EVM Create function during tx execution - // and not previous to the state transition ¯\_(ツ)_/¯ - if txData.GetTo() == nil { - // contract creation, don't increment sequence on AnteHandler but on tx execution - // continue to the next item - continue - } - - // increment sequence of all signers - for _, addr := range msg.GetSigners() { - acc := issd.ak.GetAccount(ctx, addr) - - if acc == nil { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrUnknownAddress, - "account %s (%s) is nil", common.BytesToAddress(addr.Bytes()), addr, - ) - } - - if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { - return ctx, sdkerrors.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1) - } - - issd.ak.SetAccount(ctx, acc) - } - } - - // set the original gas meter - return next(ctx, tx, simulate) -} - // EthValidateBasicDecorator is adapted from ValidateBasicDecorator from cosmos-sdk, it ignores ErrNoSignatures type EthValidateBasicDecorator struct { evmKeeper EVMKeeper diff --git a/app/ante/eth_test.go b/app/ante/eth_test.go index 8df7b0740d..5ad53deed0 100644 --- a/app/ante/eth_test.go +++ b/app/ante/eth_test.go @@ -376,98 +376,6 @@ func (suite AnteTestSuite) TestCanTransferDecorator() { } } -func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() { - dec := ante.NewEthIncrementSenderSequenceDecorator(suite.app.AccountKeeper) - addr, privKey := tests.NewAddrKey() - - contract := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 0, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) - contract.From = addr.Hex() - - to := tests.GenerateAddress() - tx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 0, &to, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) - tx.From = addr.Hex() - - err := contract.Sign(suite.ethSigner, tests.NewSigner(privKey)) - suite.Require().NoError(err) - - err = tx.Sign(suite.ethSigner, tests.NewSigner(privKey)) - suite.Require().NoError(err) - - testCases := []struct { - name string - tx sdk.Tx - malleate func() - expPass bool - expPanic bool - }{ - { - "invalid transaction type", - &invalidTx{}, - func() {}, - false, false, - }, - { - "no signers", - evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &to, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil), - func() {}, - false, true, - }, - { - "account not set to store", - tx, - func() {}, - false, false, - }, - { - "success - create contract", - contract, - func() { - acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) - suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - }, - true, false, - }, - { - "success - call", - tx, - func() {}, - true, false, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - tc.malleate() - - if tc.expPanic { - suite.Require().Panics(func() { - _, _ = dec.AnteHandle(suite.ctx, tc.tx, false, nextFn) - }) - return - } - - _, err := dec.AnteHandle(suite.ctx, tc.tx, false, nextFn) - - if tc.expPass { - suite.Require().NoError(err) - msg := tc.tx.(*evmtypes.MsgEthereumTx) - - txData, err := evmtypes.UnpackTxData(msg.Data) - suite.Require().NoError(err) - - nonce := suite.app.EvmKeeper.GetNonce(addr) - if txData.GetTo() == nil { - suite.Require().Equal(txData.GetNonce(), nonce) - } else { - suite.Require().Equal(txData.GetNonce()+1, nonce) - } - } else { - suite.Require().Error(err) - } - }) - } -} - func (suite AnteTestSuite) TestEthSetupContextDecorator() { dec := ante.NewEthSetUpContextDecorator() tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index 1a62aeeac7..0e0b33cade 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -1,6 +1,7 @@ package evm_test import ( + "errors" "math/big" "testing" "time" @@ -165,6 +166,12 @@ func (suite *EvmTestSuite) SetupTest() { suite.DoSetupTest(suite.T()) } +func (suite *EvmTestSuite) SignTx(tx *types.MsgEthereumTx) { + tx.From = suite.from.String() + err := tx.Sign(suite.ethSigner, suite.signer) + suite.Require().NoError(err) +} + func TestEvmTestSuite(t *testing.T) { suite.Run(t, new(EvmTestSuite)) } @@ -182,11 +189,7 @@ func (suite *EvmTestSuite) TestHandleMsgEthereumTx() { func() { to := common.BytesToAddress(suite.to) tx = types.NewTx(suite.chainID, 0, &to, big.NewInt(100), 10_000_000, big.NewInt(10000), nil, nil, nil, nil) - tx.From = suite.from.String() - - // sign transaction - err := tx.Sign(suite.ethSigner, suite.signer) - suite.Require().NoError(err) + suite.SignTx(tx) }, true, }, @@ -194,10 +197,7 @@ func (suite *EvmTestSuite) TestHandleMsgEthereumTx() { "insufficient balance", func() { tx = types.NewTxContract(suite.chainID, 0, big.NewInt(100), 0, big.NewInt(10000), nil, nil, nil, nil) - tx.From = suite.from.Hex() - // sign transaction - err := tx.Sign(suite.ethSigner, suite.signer) - suite.Require().NoError(err) + suite.SignTx(tx) }, false, }, @@ -269,10 +269,7 @@ func (suite *EvmTestSuite) TestHandlerLogs() { bytecode := common.FromHex("0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029") tx := types.NewTx(suite.chainID, 1, nil, big.NewInt(0), gasLimit, gasPrice, nil, nil, bytecode, nil) - tx.From = suite.from.String() - - err := tx.Sign(suite.ethSigner, suite.signer) - suite.Require().NoError(err) + suite.SignTx(tx) result, err := suite.handler(suite.ctx, tx) suite.Require().NoError(err, "failed to handle eth tx msg") @@ -357,10 +354,7 @@ func (suite *EvmTestSuite) TestDeployAndCallContract() { bytecode := common.FromHex("0x608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167f342827c97908e5e2f71151c08502a66d44b6f758e3ac2f1de95f02eb95f0a73560405160405180910390a36102c4806100dc6000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c010000000000000000000000000000000000000000000000000000000090048063893d20e814610058578063a6f9dae1146100a2575b600080fd5b6100606100e6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100e4600480360360208110156100b857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061010f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146101d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f43616c6c6572206973206e6f74206f776e65720000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f342827c97908e5e2f71151c08502a66d44b6f758e3ac2f1de95f02eb95f0a73560405160405180910390a3806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505056fea265627a7a72315820f397f2733a89198bc7fed0764083694c5b828791f39ebcbc9e414bccef14b48064736f6c63430005100032") tx := types.NewTx(suite.chainID, 1, nil, big.NewInt(0), gasLimit, gasPrice, nil, nil, bytecode, nil) - tx.From = suite.from.String() - - err := tx.Sign(suite.ethSigner, suite.signer) - suite.Require().NoError(err) + suite.SignTx(tx) result, err := suite.handler(suite.ctx, tx) suite.Require().NoError(err, "failed to handle eth tx msg") @@ -379,10 +373,7 @@ func (suite *EvmTestSuite) TestDeployAndCallContract() { storeAddr := "0xa6f9dae10000000000000000000000006a82e4a67715c8412a9114fbd2cbaefbc8181424" bytecode = common.FromHex(storeAddr) tx = types.NewTx(suite.chainID, 2, &receiver, big.NewInt(0), gasLimit, gasPrice, nil, nil, bytecode, nil) - tx.From = suite.from.String() - - err = tx.Sign(suite.ethSigner, suite.signer) - suite.Require().NoError(err) + suite.SignTx(tx) _, err = suite.handler(suite.ctx, tx) suite.Require().NoError(err, "failed to handle eth tx msg") @@ -394,9 +385,7 @@ func (suite *EvmTestSuite) TestDeployAndCallContract() { // query - getOwner bytecode = common.FromHex("0x893d20e8") tx = types.NewTx(suite.chainID, 2, &receiver, big.NewInt(0), gasLimit, gasPrice, nil, nil, bytecode, nil) - tx.From = suite.from.String() - err = tx.Sign(suite.ethSigner, suite.signer) - suite.Require().NoError(err) + suite.SignTx(tx) _, err = suite.handler(suite.ctx, tx) suite.Require().NoError(err, "failed to handle eth tx msg") @@ -416,9 +405,7 @@ func (suite *EvmTestSuite) TestSendTransaction() { // send simple value transfer with gasLimit=21000 tx := types.NewTx(suite.chainID, 1, &common.Address{0x1}, big.NewInt(1), gasLimit, gasPrice, nil, nil, nil, nil) - tx.From = suite.from.String() - err := tx.Sign(suite.ethSigner, suite.signer) - suite.Require().NoError(err) + suite.SignTx(tx) result, err := suite.handler(suite.ctx, tx) suite.Require().NoError(err) @@ -487,10 +474,7 @@ func (suite *EvmTestSuite) TestOutOfGasWhenDeployContract() { bytecode := common.FromHex("0x608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167f342827c97908e5e2f71151c08502a66d44b6f758e3ac2f1de95f02eb95f0a73560405160405180910390a36102c4806100dc6000396000f3fe608060405234801561001057600080fd5b5060043610610053576000357c010000000000000000000000000000000000000000000000000000000090048063893d20e814610058578063a6f9dae1146100a2575b600080fd5b6100606100e6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100e4600480360360208110156100b857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061010f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146101d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f43616c6c6572206973206e6f74206f776e65720000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f342827c97908e5e2f71151c08502a66d44b6f758e3ac2f1de95f02eb95f0a73560405160405180910390a3806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505056fea265627a7a72315820f397f2733a89198bc7fed0764083694c5b828791f39ebcbc9e414bccef14b48064736f6c63430005100032") tx := types.NewTx(suite.chainID, 1, nil, big.NewInt(0), gasLimit, gasPrice, nil, nil, bytecode, nil) - tx.From = suite.from.String() - - err := tx.Sign(suite.ethSigner, suite.signer) - suite.Require().NoError(err) + suite.SignTx(tx) defer func() { if r := recover(); r != nil { @@ -511,10 +495,7 @@ func (suite *EvmTestSuite) TestErrorWhenDeployContract() { bytecode := common.FromHex("0xa6f9dae10000000000000000000000006a82e4a67715c8412a9114fbd2cbaefbc8181424") tx := types.NewTx(suite.chainID, 1, nil, big.NewInt(0), gasLimit, gasPrice, nil, nil, bytecode, nil) - tx.From = suite.from.String() - - err := tx.Sign(suite.ethSigner, suite.signer) - suite.Require().NoError(err) + suite.SignTx(tx) result, _ := suite.handler(suite.ctx, tx) var res types.MsgEthereumTxResponse @@ -529,7 +510,7 @@ func (suite *EvmTestSuite) TestErrorWhenDeployContract() { func (suite *EvmTestSuite) deployERC20Contract() common.Address { k := suite.app.EvmKeeper nonce := k.GetNonce(suite.from) - ctorArgs, err := types.ERC20Contract.ABI.Pack("", suite.from, big.NewInt(0)) + ctorArgs, err := types.ERC20Contract.ABI.Pack("", suite.from, big.NewInt(10000000000)) suite.Require().NoError(err) msg := ethtypes.NewMessage( suite.from, @@ -550,55 +531,148 @@ func (suite *EvmTestSuite) deployERC20Contract() common.Address { return crypto.CreateAddress(suite.from, nonce) } -// TestGasRefundWhenReverted check that when transaction reverted, gas refund should still work. -func (suite *EvmTestSuite) TestGasRefundWhenReverted() { - suite.SetupTest() - k := suite.app.EvmKeeper - - // the bug only reproduce when there are hooks - k.SetHooks(&DummyHook{}) - - // add some fund to pay gas fee - k.AddBalance(suite.from, big.NewInt(10000000000)) - - contract := suite.deployERC20Contract() - - // the call will fail because no balance - data, err := types.ERC20Contract.ABI.Pack("transfer", common.BigToAddress(big.NewInt(1)), big.NewInt(10)) - suite.Require().NoError(err) - - tx := types.NewTx( - suite.chainID, - k.GetNonce(suite.from), - &contract, - big.NewInt(0), - 41000, - big.NewInt(1), - nil, - nil, - data, - nil, - ) - tx.From = suite.from.String() - err = tx.Sign(suite.ethSigner, suite.signer) - suite.Require().NoError(err) +// TestERC20TransferReverted checks: +// - when transaction reverted, gas refund works. +// - when transaction reverted, nonce is still increased. +func (suite *EvmTestSuite) TestERC20TransferReverted() { + intrinsicGas := uint64(21572) + // test different hooks scenarios + testCases := []struct { + msg string + gasLimit uint64 + hooks types.EvmHooks + expErr string + }{ + { + "no hooks", + intrinsicGas, // enough for intrinsicGas, but not enough for execution + nil, + "out of gas", + }, + { + "success hooks", + intrinsicGas, // enough for intrinsicGas, but not enough for execution + &DummyHook{}, + "out of gas", + }, + { + "failure hooks", + 1000000, // enough gas limit, but hooks fails. + &FailureHook{}, + "mock error: failed to execute post processing", + }, + } - before := k.GetBalance(suite.from) + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() + k := suite.app.EvmKeeper + k.SetHooks(tc.hooks) + + // add some fund to pay gas fee + k.AddBalance(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) + tx := types.NewTx( + suite.chainID, + nonce, + &contract, + big.NewInt(0), + tc.gasLimit, + big.NewInt(1), + nil, + nil, + data, + nil, + ) + suite.SignTx(tx) + + before := k.GetBalance(suite.from) + + txData, err := types.UnpackTxData(tx.Data) + suite.Require().NoError(err) + _, err = k.DeductTxCostsFromUserBalance(suite.ctx, *tx, txData, "aphoton", true, true, true) + suite.Require().NoError(err) + + res, err := k.EthereumTx(sdk.WrapSDKContext(suite.ctx), tx) + suite.Require().NoError(err) + + suite.Require().True(res.Failed()) + suite.Require().Equal(tc.expErr, res.VmError) + + after := k.GetBalance(suite.from) + + if tc.expErr == "out of gas" { + suite.Require().Equal(tc.gasLimit, res.GasUsed) + } else { + suite.Require().Greater(tc.gasLimit, res.GasUsed) + } - txData, err := types.UnpackTxData(tx.Data) - suite.Require().NoError(err) - _, err = k.DeductTxCostsFromUserBalance(suite.ctx, *tx, txData, "aphoton", true, true, true) - suite.Require().NoError(err) + // check gas refund works: only deducted fee for gas used, rather than gas limit. + suite.Require().Equal(big.NewInt(int64(res.GasUsed)), new(big.Int).Sub(before, after)) - res, err := k.EthereumTx(sdk.WrapSDKContext(suite.ctx), tx) - suite.Require().NoError(err) - suite.Require().True(res.Failed()) + // transaction reverted, but nonce should be increased. + suite.Require().Equal(nonce+1, k.GetNonce(suite.from)) + }) + } +} - after := k.GetBalance(suite.from) +func (suite *EvmTestSuite) TestContractDeploymentRevert() { + intrinsicGas := uint64(134180) + testCases := []struct { + msg string + gasLimit uint64 + hooks types.EvmHooks + }{ + { + "no hooks", + intrinsicGas, + nil, + }, + { + "success hooks", + intrinsicGas, + &DummyHook{}, + }, + } - suite.Require().Equal(uint64(23861), res.GasUsed) - // check gas refund works - suite.Require().Equal(big.NewInt(23861), new(big.Int).Sub(before, after)) + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() + k := suite.app.EvmKeeper + + // test with different hooks scenarios + k.SetHooks(tc.hooks) + + nonce := k.GetNonce(suite.from) + ctorArgs, err := types.ERC20Contract.ABI.Pack("", suite.from, big.NewInt(0)) + suite.Require().NoError(err) + + tx := types.NewTx( + nil, + nonce, + nil, // to + nil, // amount + tc.gasLimit, + nil, nil, nil, + append(types.ERC20Contract.Bin, ctorArgs...), + nil, + ) + suite.SignTx(tx) + + rsp, err := k.EthereumTx(sdk.WrapSDKContext(suite.ctx), tx) + suite.Require().NoError(err) + suite.Require().True(rsp.Failed()) + + // transaction reverted, but nonce should be increased. + suite.Require().Equal(nonce+1, k.GetNonce(suite.from)) + }) + } } // DummyHook implements EvmHooks interface @@ -607,3 +681,10 @@ type DummyHook struct{} func (dh *DummyHook) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*ethtypes.Log) error { return nil } + +// FailureHook implements EvmHooks interface +type FailureHook struct{} + +func (dh *FailureHook) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*ethtypes.Log) error { + return errors.New("mock error") +} diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index f2938c0095..5eb16f633f 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -197,46 +197,14 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT // 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() - 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) - })() - } - res, err := k.ApplyMessageWithConfig(msg, nil, true, cfg) if err != nil { return nil, sdkerrors.Wrap(err, "failed to apply ethereum core message") } res.Hash = txHash.Hex() - logs := k.GetTxLogsTransient(txHash) - if !res.Failed() { - // Only call hooks if tx executed successfully. - if err = k.PostTxProcessing(txHash, logs); err != nil { - // If hooks return error, revert the whole tx. - res.VmError = types.ErrPostTxProcessing.Error() - k.Logger(k.Ctx()).Error("tx post processing failed", "error", err) - } else if commit != nil { - // PostTxProcessing is successful, commit the cache context - commit() - ctx.EventManager().EmitEvents(k.Ctx().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 { return nil, sdkerrors.Wrapf(err, "failed to refund gas leftover gas to sender %s", msg.From()) @@ -330,7 +298,7 @@ func (k *Keeper) ApplyMessageWithConfig(msg core.Message, tracer vm.Tracer, comm // Should check again even if it is checked on Ante Handler, because eth_call don't go through Ante Handler. if msg.Gas() < intrinsicGas { // eth_estimateGas will check for this exact error - return nil, sdkerrors.Wrap(core.ErrIntrinsicGas, "apply message") + return nil, sdkerrors.Wrapf(core.ErrIntrinsicGas, "not enough intrinsicGas: %d < %d", msg.Gas(), intrinsicGas) } leftoverGas := msg.Gas() - intrinsicGas @@ -345,7 +313,25 @@ func (k *Keeper) ApplyMessageWithConfig(msg core.Message, tracer vm.Tracer, comm if contractCreation { ret, _, leftoverGas, vmErr = evm.Create(sender, msg.Data(), leftoverGas, msg.Value()) } else { + // Increment the nonce for the next transaction + k.SetNonce(msg.From(), k.GetNonce(sender.Address())+1) + + // When hooks execution failed, we need to revert tx execution side effects, but not nonce increasement, + // but we can't do that for contract creation, because we can't seperate out the nonce increasement there. + var rev int + if k.hooks != nil { + rev = k.Snapshot() + } ret, leftoverGas, vmErr = evm.Call(sender, *msg.To(), msg.Data(), leftoverGas, msg.Value()) + if k.hooks != nil && vmErr == nil { + txHash := k.GetTxHashTransient() + logs := k.GetTxLogsTransient(txHash) + if err := k.hooks.PostTxProcessing(k.Ctx(), txHash, logs); err != nil { + vmErr = sdkerrors.Wrap(types.ErrPostTxProcessing, err.Error()) + // revert tx execution side-effects together. + k.RevertToSnapshot(rev) + } + } } refundQuotient := params.RefundQuotient