diff --git a/app/app.go b/app/app.go index bd596376..ff69c9db 100644 --- a/app/app.go +++ b/app/app.go @@ -221,8 +221,8 @@ type EthermintApp struct { cdc *codec.LegacyAmino appCodec codec.Codec interfaceRegistry types.InterfaceRegistry - msgSvcRouter *authmiddleware.MsgServiceRouter - legacyRouter sdk.Router + MsgSvcRouter *authmiddleware.MsgServiceRouter + LegacyRouter sdk.Router invCheckPeriod uint // keys to access the substores @@ -368,8 +368,8 @@ func NewEthermintApp( appCodec: appCodec, interfaceRegistry: interfaceRegistry, invCheckPeriod: invCheckPeriod, - legacyRouter: authmiddleware.NewLegacyRouter(), - msgSvcRouter: authmiddleware.NewMsgServiceRouter(interfaceRegistry), + LegacyRouter: authmiddleware.NewLegacyRouter(), + MsgSvcRouter: authmiddleware.NewMsgServiceRouter(interfaceRegistry), keys: keys, tkeys: tkeys, memKeys: memKeys, @@ -423,7 +423,7 @@ func NewEthermintApp( stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()), ) - app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.msgSvcRouter, app.AccountKeeper) + app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.MsgSvcRouter, app.AccountKeeper) tracer := cast.ToString(appOpts.Get(srvflags.EVMTracer)) @@ -479,7 +479,7 @@ func NewEthermintApp( govKeeper := govkeeper.NewKeeper( appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, - &stakingKeeper, govRouter, app.msgSvcRouter, govConfig, + &stakingKeeper, govRouter, app.MsgSvcRouter, govConfig, ) app.GovKeeper = *govKeeper.SetHooks( @@ -644,8 +644,8 @@ func NewEthermintApp( ) app.mm.RegisterInvariants(&app.CrisisKeeper) - app.mm.RegisterRoutes(app.legacyRouter, app.QueryRouter(), encodingConfig.Amino) - app.configurator = module.NewConfigurator(app.appCodec, app.msgSvcRouter, app.GRPCQueryRouter()) + app.mm.RegisterRoutes(app.LegacyRouter, app.QueryRouter(), encodingConfig.Amino) + app.configurator = module.NewConfigurator(app.appCodec, app.MsgSvcRouter, app.GRPCQueryRouter()) app.mm.RegisterServices(app.configurator) // add test gRPC service for testing gRPC queries in isolation @@ -685,8 +685,8 @@ func NewEthermintApp( options := middleware.HandlerOptions{ Codec: app.appCodec, Debug: app.Trace(), - LegacyRouter: app.legacyRouter, - MsgServiceRouter: app.msgSvcRouter, + LegacyRouter: app.LegacyRouter, + MsgServiceRouter: app.MsgSvcRouter, AccountKeeper: app.AccountKeeper, BankKeeper: app.BankKeeper, EvmKeeper: app.EvmKeeper, @@ -710,7 +710,9 @@ func (app *EthermintApp) setTxHandler(options middleware.HandlerOptions, txConfi indexEvents[e] = struct{}{} } - txHandler, err := middleware.NewMiddleware(indexEventsStr, options) + options.IndexEvents = indexEvents + + txHandler, err := middleware.NewMiddleware(options) if err != nil { panic(err) } diff --git a/app/middleware/eth.go b/app/middleware/eth.go index ebca5f6d..951b6842 100644 --- a/app/middleware/eth.go +++ b/app/middleware/eth.go @@ -171,7 +171,7 @@ func (vbd EthValidateBasicMiddleware) CheckTx(cx context.Context, req tx.Request // no need to validate basic on recheck tx, call next antehandler if ctx.IsReCheckTx() { - return vbd.CheckTx(ctx, req, checkReq) + return vbd.next.CheckTx(ctx, req, checkReq) } err := reqTx.ValidateBasic() diff --git a/app/middleware/eth_test.go b/app/middleware/eth_test.go new file mode 100644 index 00000000..3728de02 --- /dev/null +++ b/app/middleware/eth_test.go @@ -0,0 +1,519 @@ +package middleware_test + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/x/auth/middleware" + ante "github.com/tharsis/ethermint/app/middleware" + "github.com/tharsis/ethermint/server/config" + "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" +) + +func nextFn(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { + return ctx, nil +} + +func (suite MiddlewareTestSuite) TestEthSigVerificationDecorator() { + txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthSigVerificationMiddleware(suite.app.EvmKeeper)) + addr, privKey := tests.NewAddrKey() + + signedTx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) + signedTx.From = addr.Hex() + err := signedTx.Sign(suite.ethSigner, tests.NewSigner(privKey)) + suite.Require().NoError(err) + + testCases := []struct { + name string + tx sdk.Tx + reCheckTx bool + expPass bool + }{ + {"ReCheckTx", &invalidTx{}, true, false}, + {"invalid transaction type", &invalidTx{}, false, false}, + { + "invalid sender", + evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &addr, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil), + false, + false, + }, + {"successful signature verification", signedTx, false, true}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + _, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{}) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite MiddlewareTestSuite) TestNewEthAccountVerificationDecorator() { + txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthAccountVerificationMiddleware( + suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.EvmKeeper, + )) + + addr := tests.GenerateAddress() + + 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 + malleate func() + checkTx bool + expPass bool + }{ + {"not CheckTx", nil, func() {}, false, true}, + {"invalid transaction type", &invalidTx{}, func() {}, true, false}, + { + "sender not set to msg", + evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil), + func() {}, + true, + false, + }, + { + "sender not EOA", + tx, + func() { + // set not as an EOA + vmdb.SetCode(addr, []byte("1")) + }, + true, + false, + }, + { + "not enough balance to cover tx cost", + tx, + func() { + // reset back to EOA + vmdb.SetCode(addr, nil) + }, + true, + false, + }, + { + "success new account", + tx, + func() { + vmdb.AddBalance(addr, big.NewInt(1000000)) + }, + true, + true, + }, + { + "success existing account", + tx, + func() { + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + vmdb.AddBalance(addr, big.NewInt(1000000)) + }, + true, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + vmdb = suite.StateDB() + tc.malleate() + suite.Require().NoError(vmdb.Commit()) + + _, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx.WithIsCheckTx(tc.checkTx)), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{}) + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite MiddlewareTestSuite) TestEthNonceVerificationDecorator() { + suite.SetupTest() + txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthIncrementSenderSequenceMiddleware(suite.app.AccountKeeper)) + addr := tests.GenerateAddress() + + tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) + tx.From = addr.Hex() + + testCases := []struct { + name string + tx sdk.Tx + malleate func() + reCheckTx bool + expPass bool + }{ + {"ReCheckTx", &invalidTx{}, func() {}, true, false}, + {"invalid transaction type", &invalidTx{}, func() {}, false, false}, + {"sender account not found", tx, func() {}, false, false}, + { + "sender nonce missmatch", + tx, + func() { + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + }, + false, + false, + }, + { + "success", + tx, + func() { + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) + suite.Require().NoError(acc.SetSequence(1)) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + }, + false, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + _, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{}) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite MiddlewareTestSuite) TestEthGasConsumeDecorator() { + txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthGasConsumeMiddleware(suite.app.EvmKeeper, config.DefaultMaxTxGasWanted)) + + addr := tests.GenerateAddress() + + txGasLimit := uint64(1000) + tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), txGasLimit, big.NewInt(1), nil, nil, nil, nil) + tx.From = addr.Hex() + + tx2GasLimit := uint64(1000000) + tx2 := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), tx2GasLimit, 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 + gasLimit uint64 + malleate func() + expPass bool + expPanic bool + }{ + {"invalid transaction type", &invalidTx{}, 0, func() {}, false, false}, + { + "sender not found", + evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil), + 0, + func() {}, + false, false, + }, + { + "gas limit too low", + tx, + 0, + func() {}, + false, false, + }, + { + "not enough balance for fees", + tx2, + 0, + func() {}, + false, false, + }, + { + "not enough tx gas", + tx2, + 0, + func() { + vmdb.AddBalance(addr, big.NewInt(1000000)) + }, + false, true, + }, + { + "not enough block gas", + tx2, + 0, + func() { + vmdb.AddBalance(addr, big.NewInt(1000000)) + + suite.ctx = suite.ctx.WithBlockGasMeter(sdk.NewGasMeter(1)) + }, + false, true, + }, + { + "success", + tx2, + config.DefaultMaxTxGasWanted, // it's capped + func() { + vmdb.AddBalance(addr, big.NewInt(1000000)) + + suite.ctx = suite.ctx.WithBlockGasMeter(sdk.NewGasMeter(10000000000000000000)) + }, + true, false, + }, + } + + 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() { + _, _, _ = txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx.WithIsCheckTx(true).WithGasMeter(sdk.NewGasMeter(1))), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{}) + + }) + return + } + + _, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx.WithIsCheckTx(true).WithGasMeter(sdk.NewInfiniteGasMeter())), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{}) + + // ctx, err := t.AnteHandle(suite.ctx.WithIsCheckTx(true).WithGasMeter(sdk.NewInfiniteGasMeter()), tc.tx, false, nextFn) + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + // suite.Require().Equal(tc.gasLimit, ctx.GasMeter().Limit()) + }) + } +} + +func (suite MiddlewareTestSuite) TestCanTransferDecorator() { + // dec := ante.NewCanTransferMiddleware(suite.app.EvmKeeper) + txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewCanTransferMiddleware(suite.app.EvmKeeper)) + + addr, privKey := tests.NewAddrKey() + + suite.app.FeeMarketKeeper.SetBaseFee(suite.ctx, big.NewInt(100)) + + tx := evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 1, + big.NewInt(10), + 1000, + big.NewInt(150), + big.NewInt(200), + nil, + nil, + ðtypes.AccessList{}, + ) + tx2 := evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 1, + big.NewInt(10), + 1000, + big.NewInt(150), + big.NewInt(200), + nil, + nil, + ðtypes.AccessList{}, + ) + + tx.From = addr.Hex() + + err := tx.Sign(suite.ethSigner, tests.NewSigner(privKey)) + suite.Require().NoError(err) + + var vmdb *statedb.StateDB + + testCases := []struct { + name string + tx sdk.Tx + malleate func() + expPass bool + }{ + {"invalid transaction type", &invalidTx{}, func() {}, false}, + {"AsMessage failed", tx2, func() {}, false}, + { + "evm CanTransfer failed", + tx, + func() { + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + }, + false, + }, + { + "success", + tx, + func() { + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + vmdb.AddBalance(addr, big.NewInt(1000000)) + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + vmdb = suite.StateDB() + tc.malleate() + suite.Require().NoError(vmdb.Commit()) + + _, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx.WithIsCheckTx(true)), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{}) + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite MiddlewareTestSuite) TestEthIncrementSenderSequenceDecorator() { + txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthIncrementSenderSequenceMiddleware(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() + err := contract.Sign(suite.ethSigner, tests.NewSigner(privKey)) + suite.Require().NoError(err) + + 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 = tx.Sign(suite.ethSigner, tests.NewSigner(privKey)) + suite.Require().NoError(err) + + tx2 := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &to, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) + tx2.From = addr.Hex() + err = tx2.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, false, + }, + { + "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", + tx2, + func() {}, + true, false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + + if tc.expPanic { + suite.Require().Panics(func() { + _, _, _ = txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{}) + }) + return + } + + // _, err := dec.AnteHandle(suite.ctx, tc.tx, false, nextFn) + _, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{}) + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + + 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(suite.ctx, addr) + suite.Require().Equal(txData.GetNonce()+1, nonce) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite MiddlewareTestSuite) TestEthSetupContextDecorator() { + txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthSetUpContextMiddleware(suite.app.EvmKeeper)) + + tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) + + testCases := []struct { + name string + tx sdk.Tx + expPass bool + }{ + {"invalid transaction type - does not implement GasTx", &invalidTx{}, false}, + { + "success - transaction implement GasTx", + tx, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + _, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{}) + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/app/middleware/middleware.go b/app/middleware/middleware.go index 76eb7ccb..78425fbe 100644 --- a/app/middleware/middleware.go +++ b/app/middleware/middleware.go @@ -19,17 +19,17 @@ const ( type MD struct { ethMiddleware tx.Handler cosmosMiddleware tx.Handler - cosmoseip792 tx.Handler + cosmoseip712 tx.Handler } var _ tx.Handler = MD{} -func NewMiddleware(indexEventsStr []string, options HandlerOptions) (tx.Handler, error) { +func NewMiddleware(options HandlerOptions) (tx.Handler, error) { ethMiddleware, err := newEthAuthMiddleware(options) if err != nil { return nil, err } - cosmoseip792, err := newCosmosAnteHandlerEip712(options) + cosmoseip712, err := newCosmosAnteHandlerEip712(options) if err != nil { return nil, err } @@ -40,7 +40,7 @@ func NewMiddleware(indexEventsStr []string, options HandlerOptions) (tx.Handler, return MD{ ethMiddleware: ethMiddleware, cosmosMiddleware: cosmosMiddleware, - cosmoseip792: cosmoseip792, + cosmoseip712: cosmoseip712, }, nil } @@ -58,7 +58,7 @@ func (md MD) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestChe anteHandler = md.ethMiddleware case "/ethermint.types.v1.ExtensionOptionsWeb3Tx": // handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation - anteHandler = md.cosmoseip792 + anteHandler = md.cosmoseip712 default: return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf( sdkerrors.ErrUnknownExtensionOptions, @@ -93,7 +93,7 @@ func (md MD) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) anteHandler = md.ethMiddleware case "/ethermint.types.v1.ExtensionOptionsWeb3Tx": // handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation - anteHandler = md.cosmoseip792 + anteHandler = md.cosmoseip712 default: return tx.Response{}, sdkerrors.Wrapf( sdkerrors.ErrUnknownExtensionOptions, @@ -122,7 +122,7 @@ func (md MD) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error anteHandler = md.ethMiddleware case "/ethermint.types.v1.ExtensionOptionsWeb3Tx": // handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation - anteHandler = md.cosmoseip792 + anteHandler = md.cosmoseip712 default: return tx.Response{}, sdkerrors.Wrapf( sdkerrors.ErrUnknownExtensionOptions, diff --git a/app/middleware/middleware_test.go b/app/middleware/middleware_test.go new file mode 100644 index 00000000..05801bee --- /dev/null +++ b/app/middleware/middleware_test.go @@ -0,0 +1,691 @@ +package middleware_test + +import ( + "math/big" + "strings" + + "github.com/cosmos/cosmos-sdk/types/tx" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/ethereum/go-ethereum/core/types" + ethparams "github.com/ethereum/go-ethereum/params" + "github.com/tharsis/ethermint/tests" + evmtypes "github.com/tharsis/ethermint/x/evm/types" +) + +func (suite MiddlewareTestSuite) TestAnteHandler() { + suite.enableFeemarket = false + suite.SetupTest() // reset + + 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) + + suite.app.EvmKeeper.SetBalance(suite.ctx, addr, big.NewInt(10000000000)) + + suite.app.FeeMarketKeeper.SetBaseFee(suite.ctx, big.NewInt(100)) + + testCases := []struct { + name string + txFn func() sdk.Tx + checkTx bool + reCheckTx bool + expPass bool + }{ + { + "success - DeliverTx (contract)", + func() sdk.Tx { + signedContractTx := evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 1, + big.NewInt(10), + 100000, + big.NewInt(150), + big.NewInt(200), + nil, + nil, + nil, + ) + signedContractTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedContractTx, privKey, 1, false) + return tx + }, + false, false, true, + }, + { + "success - CheckTx (contract)", + func() sdk.Tx { + signedContractTx := evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 2, + big.NewInt(10), + 100000, + big.NewInt(150), + big.NewInt(200), + nil, + nil, + nil, + ) + signedContractTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedContractTx, privKey, 1, false) + return tx + }, + true, false, true, + }, + { + "success - ReCheckTx (contract)", + func() sdk.Tx { + signedContractTx := evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 3, + big.NewInt(10), + 100000, + big.NewInt(150), + big.NewInt(200), + nil, + nil, + nil, + ) + signedContractTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedContractTx, privKey, 1, false) + return tx + }, + false, true, true, + }, + { + "success - DeliverTx", + func() sdk.Tx { + signedTx := evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 4, + &to, + big.NewInt(10), + 100000, + big.NewInt(150), + big.NewInt(200), + nil, + nil, + nil, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, false) + return tx + }, + false, false, true, + }, + { + "success - CheckTx", + func() sdk.Tx { + signedTx := evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 5, + &to, + big.NewInt(10), + 100000, + big.NewInt(150), + big.NewInt(200), + nil, + nil, + nil, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, false) + return tx + }, + true, false, true, + }, + { + "success - ReCheckTx", + func() sdk.Tx { + signedTx := evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 6, + &to, + big.NewInt(10), + 100000, + big.NewInt(150), + big.NewInt(200), + nil, + nil, + nil, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, false) + return tx + }, false, true, true, + }, + { + "success - CheckTx (cosmos tx not signed)", + func() sdk.Tx { + signedTx := evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 7, + &to, + big.NewInt(10), + 100000, + big.NewInt(150), + big.NewInt(200), + nil, + nil, + nil, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, false) + return tx + }, false, true, true, + }, + { + "fail - CheckTx (cosmos tx is not valid)", + func() sdk.Tx { + signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 8, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) + signedTx.From = addr.Hex() + + txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false) + // bigger than MaxGasWanted + txBuilder.SetGasLimit(uint64(1 << 63)) + return txBuilder.GetTx() + }, true, false, false, + }, + { + "fail - CheckTx (memo too long)", + func() sdk.Tx { + signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 5, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) + signedTx.From = addr.Hex() + + txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false) + txBuilder.SetMemo(strings.Repeat("*", 257)) + return txBuilder.GetTx() + }, true, false, false, + }, + { + "fail - CheckTx (ExtensionOptionsEthereumTx not set)", + func() sdk.Tx { + signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 5, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) + signedTx.From = addr.Hex() + + txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false, true) + return txBuilder.GetTx() + }, true, false, false, + }, + // Based on EVMBackend.SendTransaction, for cosmos tx, forcing null for some fields except ExtensionOptions, Fee, MsgEthereumTx + // should be part of consensus + { + "fail - DeliverTx (cosmos tx signed)", + func() sdk.Tx { + nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) + suite.Require().NoError(err) + signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, true) + return tx + }, false, false, false, + }, + { + "fail - DeliverTx (cosmos tx with memo)", + func() sdk.Tx { + nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) + suite.Require().NoError(err) + signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) + signedTx.From = addr.Hex() + + txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false) + txBuilder.SetMemo("memo for cosmos tx not allowed") + return txBuilder.GetTx() + }, false, false, false, + }, + { + "fail - DeliverTx (cosmos tx with timeoutheight)", + func() sdk.Tx { + nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) + suite.Require().NoError(err) + signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) + signedTx.From = addr.Hex() + + txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false) + txBuilder.SetTimeoutHeight(10) + return txBuilder.GetTx() + }, false, false, false, + }, + { + "fail - DeliverTx (invalid fee amount)", + func() sdk.Tx { + nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) + suite.Require().NoError(err) + signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) + signedTx.From = addr.Hex() + + txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false) + + txData, err := evmtypes.UnpackTxData(signedTx.Data) + suite.Require().NoError(err) + + expFee := txData.Fee() + invalidFee := new(big.Int).Add(expFee, big.NewInt(1)) + invalidFeeAmount := sdk.Coins{sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewIntFromBigInt(invalidFee))} + txBuilder.SetFeeAmount(invalidFeeAmount) + return txBuilder.GetTx() + }, false, false, false, + }, + { + "fail - DeliverTx (invalid fee gaslimit)", + func() sdk.Tx { + nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) + suite.Require().NoError(err) + signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) + signedTx.From = addr.Hex() + + txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false) + + expGasLimit := signedTx.GetGas() + invalidGasLimit := expGasLimit + 1 + txBuilder.SetGasLimit(invalidGasLimit) + return txBuilder.GetTx() + }, false, false, false, + }, + { + "success - DeliverTx EIP712 signed Cosmos Tx with MsgSend", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9000-1", gas, amount) + return txBuilder.GetTx() + }, false, false, true, + }, + { + "success - DeliverTx EIP712 signed Cosmos Tx with DelegateMsg", + func() sdk.Tx { + from := acc.GetAddress() + coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000)) + amount := sdk.NewCoins(coinAmount) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgDelegate(from, privKey, "ethermint_9000-1", gas, amount) + return txBuilder.GetTx() + }, false, false, true, + }, + { + "fails - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9002-1", gas, amount) + return txBuilder.GetTx() + }, false, false, false, + }, + { + "fails - DeliverTx EIP712 signed Cosmos Tx with different gas fees", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) + txBuilder.SetGasLimit(uint64(300000)) + txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(30)))) + return txBuilder.GetTx() + }, false, false, false, + }, + { + "fails - DeliverTx EIP712 signed Cosmos Tx with empty signature", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) + sigsV2 := signing.SignatureV2{} + txBuilder.SetSignatures(sigsV2) + return txBuilder.GetTx() + }, false, false, false, + }, + { + "fails - DeliverTx EIP712 signed Cosmos Tx with invalid sequence", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) + nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) + suite.Require().NoError(err) + sigsV2 := signing.SignatureV2{ + PubKey: privKey.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + }, + Sequence: nonce - 1, + } + txBuilder.SetSignatures(sigsV2) + return txBuilder.GetTx() + }, false, false, false, + }, + { + "fails - DeliverTx EIP712 signed Cosmos Tx with invalid signMode", + func() sdk.Tx { + from := acc.GetAddress() + amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000))) + gas := uint64(200000) + txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) + nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) + suite.Require().NoError(err) + sigsV2 := signing.SignatureV2{ + PubKey: privKey.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_UNSPECIFIED, + }, + Sequence: nonce, + } + txBuilder.SetSignatures(sigsV2) + return txBuilder.GetTx() + }, false, false, false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.ctx = suite.ctx.WithIsCheckTx(tc.checkTx).WithIsReCheckTx(tc.reCheckTx) + + // expConsumed := params.TxGasContractCreation + params.TxGas + // _, err := suite.anteHandler(suite.ctx, tc.txFn(), false) + _, _, err := suite.anteHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.txFn()}, tx.RequestCheckTx{}) + // suite.Require().Equal(consumed, ctx.GasMeter().GasConsumed()) + + if tc.expPass { + suite.Require().NoError(err) + // suite.Require().Equal(int(expConsumed), int(suite.ctx.GasMeter().GasConsumed())) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite MiddlewareTestSuite) TestAnteHandlerWithDynamicTxFee() { + addr, privKey := tests.NewAddrKey() + to := tests.GenerateAddress() + + testCases := []struct { + name string + txFn func() sdk.Tx + enableLondonHF bool + checkTx bool + reCheckTx bool + expPass bool + }{ + { + "success - DeliverTx (contract)", + func() sdk.Tx { + signedContractTx := + evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 1, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedContractTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedContractTx, privKey, 1, false) + return tx + }, + true, + false, false, true, + }, + { + "success - CheckTx (contract)", + func() sdk.Tx { + signedContractTx := + evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 1, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedContractTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedContractTx, privKey, 1, false) + return tx + }, + true, + true, false, true, + }, + { + "success - ReCheckTx (contract)", + func() sdk.Tx { + signedContractTx := + evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 1, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedContractTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedContractTx, privKey, 1, false) + return tx + }, + true, + false, true, true, + }, + { + "success - DeliverTx", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 1, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, false) + return tx + }, + true, + false, false, true, + }, + { + "success - CheckTx", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 1, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, false) + return tx + }, + true, + true, false, true, + }, + { + "success - ReCheckTx", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 1, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, false) + return tx + }, + true, + false, true, true, + }, + { + "success - CheckTx (cosmos tx not signed)", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 1, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedTx, privKey, 1, false) + return tx + }, + true, + false, true, true, + }, + { + "fail - CheckTx (cosmos tx is not valid)", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 1, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false) + // bigger than MaxGasWanted + txBuilder.SetGasLimit(uint64(1 << 63)) + return txBuilder.GetTx() + }, + true, + true, false, false, + }, + { + "fail - CheckTx (memo too long)", + func() sdk.Tx { + signedTx := + evmtypes.NewTx( + suite.app.EvmKeeper.ChainID(), + 1, + &to, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedTx.From = addr.Hex() + + txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false) + txBuilder.SetMemo(strings.Repeat("*", 257)) + return txBuilder.GetTx() + }, + true, + true, false, false, + }, + { + "fail - DynamicFeeTx without london hark fork", + func() sdk.Tx { + signedContractTx := + evmtypes.NewTxContract( + suite.app.EvmKeeper.ChainID(), + 1, + big.NewInt(10), + 100000, + nil, + big.NewInt(ethparams.InitialBaseFee+1), + big.NewInt(1), + nil, + &types.AccessList{}, + ) + signedContractTx.From = addr.Hex() + + tx := suite.CreateTestTx(signedContractTx, privKey, 1, false) + return tx + }, + false, + false, false, false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.enableFeemarket = true + suite.enableLondonHF = tc.enableLondonHF + suite.SetupTest() // reset + + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr.Bytes()) + suite.Require().NoError(acc.SetSequence(1)) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + + suite.ctx = suite.ctx.WithIsCheckTx(tc.checkTx).WithIsReCheckTx(tc.reCheckTx) + suite.app.EvmKeeper.SetBalance(suite.ctx, addr, big.NewInt((ethparams.InitialBaseFee+10)*100000)) + // _, err := suite.anteHandler(suite.ctx, tc.txFn(), false) + _, _, err := suite.anteHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.txFn()}, tx.RequestCheckTx{}) + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } + suite.enableFeemarket = false + suite.enableLondonHF = true +} diff --git a/app/middleware/sigs_test.go b/app/middleware/sigs_test.go new file mode 100644 index 00000000..d6b24b80 --- /dev/null +++ b/app/middleware/sigs_test.go @@ -0,0 +1,46 @@ +package middleware_test + +import ( + "math/big" + + "github.com/tharsis/ethermint/tests" + "github.com/tharsis/ethermint/x/evm/statedb" + evmtypes "github.com/tharsis/ethermint/x/evm/types" +) + +func (suite MiddlewareTestSuite) TestSignatures() { + suite.enableFeemarket = false + suite.SetupTest() // reset + + addr, privKey := tests.NewAddrKey() + to := tests.GenerateAddress() + + acc := statedb.NewEmptyAccount() + acc.Nonce = 1 + acc.Balance = 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() + + // CreateTestTx will sign the msgEthereumTx but not sign the cosmos tx since we have signCosmosTx as false + tx := suite.CreateTestTx(msgEthereumTx, privKey, 1, false) + sigs, err := tx.GetSignaturesV2() + suite.Require().NoError(err) + + // signatures of cosmos tx should be empty + suite.Require().Equal(len(sigs), 0) + + txData, err := evmtypes.UnpackTxData(msgEthereumTx.Data) + suite.Require().NoError(err) + + msgV, msgR, msgS := txData.GetRawSignatureValues() + + ethTx := msgEthereumTx.AsTransaction() + ethV, ethR, ethS := ethTx.RawSignatureValues() + + // The signatures of MsgehtereumTx should be the same with the corresponding eth tx + suite.Require().Equal(msgV, ethV) + suite.Require().Equal(msgR, ethR) + suite.Require().Equal(msgS, ethS) +} diff --git a/app/middleware/utils_test.go b/app/middleware/utils_test.go new file mode 100644 index 00000000..ca90436b --- /dev/null +++ b/app/middleware/utils_test.go @@ -0,0 +1,322 @@ +package middleware_test + +import ( + context "context" + "math" + "testing" + "time" + + clientTx "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" + types2 "github.com/cosmos/cosmos-sdk/x/bank/types" + types3 "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/spf13/cast" + ante "github.com/tharsis/ethermint/app/middleware" + "github.com/tharsis/ethermint/ethereum/eip712" + "github.com/tharsis/ethermint/server/config" + "github.com/tharsis/ethermint/types" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/client" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tharsis/ethermint/app" + "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" +) + +// customTxHandler is a test middleware that will run a custom function. +type customTxHandler struct { + fn func(context.Context, tx.Request) (tx.Response, error) +} + +var _ tx.Handler = customTxHandler{} + +func (h customTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) { + return h.fn(ctx, req) +} +func (h customTxHandler) CheckTx(ctx context.Context, req tx.Request, _ tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) { + res, err := h.fn(ctx, req) + return res, tx.ResponseCheckTx{}, err +} +func (h customTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) { + return h.fn(ctx, req) +} + +// noopTxHandler is a test middleware that returns an empty response. +var noopTxHandler = customTxHandler{func(_ context.Context, _ tx.Request) (tx.Response, error) { + return tx.Response{}, nil +}} + +type MiddlewareTestSuite struct { + suite.Suite + + ctx sdk.Context + app *app.EthermintApp + clientCtx client.Context + anteHandler tx.Handler + ethSigner ethtypes.Signer + enableFeemarket bool + enableLondonHF bool +} + +func (suite *MiddlewareTestSuite) StateDB() *statedb.StateDB { + return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()))) +} + +func (suite *MiddlewareTestSuite) SetupTest() { + checkTx := false + + suite.app = app.Setup(suite.T(), checkTx, func(app *app.EthermintApp, genesis simapp.GenesisState) simapp.GenesisState { + if suite.enableFeemarket { + // setup feemarketGenesis params + feemarketGenesis := feemarkettypes.DefaultGenesisState() + feemarketGenesis.Params.EnableHeight = 1 + feemarketGenesis.Params.NoBaseFee = false + // Verify feeMarket genesis + err := feemarketGenesis.Validate() + suite.Require().NoError(err) + genesis[feemarkettypes.ModuleName] = app.AppCodec().MustMarshalJSON(feemarketGenesis) + } + if !suite.enableLondonHF { + evmGenesis := evmtypes.DefaultGenesisState() + maxInt := sdk.NewInt(math.MaxInt64) + evmGenesis.Params.ChainConfig.LondonBlock = &maxInt + evmGenesis.Params.ChainConfig.ArrowGlacierBlock = &maxInt + evmGenesis.Params.ChainConfig.MergeForkBlock = &maxInt + genesis[evmtypes.ModuleName] = app.AppCodec().MustMarshalJSON(evmGenesis) + } + return genesis + }) + + suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 2, ChainID: "ethermint_9000-1", Time: time.Now().UTC()}) + suite.ctx = suite.ctx.WithMinGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(evmtypes.DefaultEVMDenom, sdk.OneInt()))) + suite.ctx = suite.ctx.WithBlockGasMeter(sdk.NewGasMeter(1000000000000000000)) + suite.app.EvmKeeper.WithChainID(suite.ctx) + + infCtx := suite.ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + suite.app.AccountKeeper.SetParams(infCtx, authtypes.DefaultParams()) + + encodingConfig := encoding.MakeConfig(app.ModuleBasics) + // We're using TestMsg amino encoding in some tests, so register it here. + encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) + + suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig) + maxGasWanted := cast.ToUint64(config.DefaultMaxTxGasWanted) + + options := ante.HandlerOptions{ + TxDecoder: suite.clientCtx.TxConfig.TxDecoder(), + AccountKeeper: suite.app.AccountKeeper, + BankKeeper: suite.app.BankKeeper, + EvmKeeper: suite.app.EvmKeeper, + FeegrantKeeper: suite.app.FeeGrantKeeper, + Codec: suite.app.AppCodec(), + Debug: suite.app.Trace(), + LegacyRouter: suite.app.LegacyRouter, + MsgServiceRouter: suite.app.MsgSvcRouter, + FeeMarketKeeper: suite.app.FeeMarketKeeper, + SignModeHandler: suite.clientCtx.TxConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + MaxTxGasWanted: maxGasWanted, + } + + suite.Require().NoError(options.Validate()) + middleware, err := ante.NewMiddleware(options) + suite.Require().NoError(err) + suite.anteHandler = middleware + suite.ethSigner = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) +} + +func TestMiddlewareTestSuite(t *testing.T) { + suite.Run(t, &MiddlewareTestSuite{ + enableLondonHF: true, + }) +} + +// CreateTestTx is a helper function to create a tx given multiple inputs. +func (suite *MiddlewareTestSuite) CreateTestTx( + msg *evmtypes.MsgEthereumTx, priv cryptotypes.PrivKey, accNum uint64, signCosmosTx bool, + unsetExtensionOptions ...bool, +) authsigning.Tx { + return suite.CreateTestTxBuilder(msg, priv, accNum, signCosmosTx).GetTx() +} + +// CreateTestTxBuilder is a helper function to create a tx builder given multiple inputs. +func (suite *MiddlewareTestSuite) CreateTestTxBuilder( + msg *evmtypes.MsgEthereumTx, priv cryptotypes.PrivKey, accNum uint64, signCosmosTx bool, + unsetExtensionOptions ...bool, +) client.TxBuilder { + var option *codectypes.Any + var err error + if len(unsetExtensionOptions) == 0 { + option, err = codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) + suite.Require().NoError(err) + } + + txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() + builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder) + suite.Require().True(ok) + + if len(unsetExtensionOptions) == 0 { + builder.SetExtensionOptions(option) + } + + err = msg.Sign(suite.ethSigner, tests.NewSigner(priv)) + suite.Require().NoError(err) + + err = builder.SetMsgs(msg) + suite.Require().NoError(err) + + txData, err := evmtypes.UnpackTxData(msg.Data) + suite.Require().NoError(err) + + fees := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewIntFromBigInt(txData.Fee()))) + builder.SetFeeAmount(fees) + builder.SetGasLimit(msg.GetGas()) + + if signCosmosTx { + // First round: we gather all the signer infos. We use the "set empty + // signature" hack to do that. + sigV2 := signing.SignatureV2{ + PubKey: priv.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: txData.GetNonce(), + } + + sigsV2 := []signing.SignatureV2{sigV2} + + err = txBuilder.SetSignatures(sigsV2...) + suite.Require().NoError(err) + + // Second round: all signer infos are set, so each signer can sign. + + signerData := authsigning.SignerData{ + ChainID: suite.ctx.ChainID(), + AccountNumber: accNum, + Sequence: txData.GetNonce(), + } + sigV2, err = clientTx.SignWithPrivKey( + suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData, + txBuilder, priv, suite.clientCtx.TxConfig, txData.GetNonce(), + ) + suite.Require().NoError(err) + + sigsV2 = []signing.SignatureV2{sigV2} + + err = txBuilder.SetSignatures(sigsV2...) + suite.Require().NoError(err) + } + + return txBuilder +} + +func (suite *MiddlewareTestSuite) CreateTestEIP712TxBuilderMsgSend(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder { + // Build MsgSend + recipient := sdk.AccAddress(common.Address{}.Bytes()) + msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(1)))) + return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSend) +} + +func (suite *MiddlewareTestSuite) CreateTestEIP712TxBuilderMsgDelegate(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder { + // Build MsgSend + valEthAddr := tests.GenerateAddress() + valAddr := sdk.ValAddress(valEthAddr.Bytes()) + msgSend := types3.NewMsgDelegate(from, valAddr, sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000))) + return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSend) +} + +func (suite *MiddlewareTestSuite) CreateTestEIP712CosmosTxBuilder( + from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, msg sdk.Msg, +) client.TxBuilder { + var err error + + nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, from) + suite.Require().NoError(err) + + pc, err := types.ParseChainID(chainId) + suite.Require().NoError(err) + ethChainId := pc.Uint64() + + // GenerateTypedData TypedData + var ethermintCodec codec.ProtoCodecMarshaler + fee := legacytx.NewStdFee(gas, gasAmount) + accNumber := suite.app.AccountKeeper.GetAccount(suite.ctx, from).GetAccountNumber() + + data := legacytx.StdSignBytes(chainId, accNumber, nonce, 0, fee, []sdk.Msg{msg}, "", nil) + typedData, err := eip712.WrapTxToTypedData(ethermintCodec, ethChainId, msg, data, &eip712.FeeDelegationOptions{ + FeePayer: from, + }) + suite.Require().NoError(err) + + sigHash, err := eip712.ComputeTypedDataHash(typedData) + suite.Require().NoError(err) + + // Sign typedData + keyringSigner := tests.NewSigner(priv) + signature, pubKey, err := keyringSigner.SignByAddress(from, sigHash) + suite.Require().NoError(err) + signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + + // Add ExtensionOptionsWeb3Tx extension + var option *codectypes.Any + option, err = codectypes.NewAnyWithValue(&types.ExtensionOptionsWeb3Tx{ + FeePayer: from.String(), + TypedDataChainID: ethChainId, + FeePayerSig: signature, + }) + suite.Require().NoError(err) + + suite.clientCtx.TxConfig.SignModeHandler() + txBuilder := suite.clientCtx.TxConfig.NewTxBuilder() + builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder) + suite.Require().True(ok) + + builder.SetExtensionOptions(option) + builder.SetFeeAmount(gasAmount) + builder.SetGasLimit(gas) + + sigsV2 := signing.SignatureV2{ + PubKey: pubKey, + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + }, + Sequence: nonce, + } + + err = builder.SetSignatures(sigsV2) + suite.Require().NoError(err) + + err = builder.SetMsgs(msg) + suite.Require().NoError(err) + + return builder +} + +var _ sdk.Tx = &invalidTx{} + +type invalidTx struct{} + +func (invalidTx) GetMsgs() []sdk.Msg { return []sdk.Msg{nil} } +func (invalidTx) ValidateBasic() error { return nil } diff --git a/encoding/codec/codec.go b/encoding/codec/codec.go index 957d00a1..591f0318 100644 --- a/encoding/codec/codec.go +++ b/encoding/codec/codec.go @@ -4,6 +4,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/std" + sdk "github.com/cosmos/cosmos-sdk/types" cryptocodec "github.com/tharsis/ethermint/crypto/codec" @@ -19,6 +20,7 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { // RegisterInterfaces registers Interfaces from types, crypto, and SDK std. func RegisterInterfaces(interfaceRegistry codectypes.InterfaceRegistry) { + sdk.RegisterInterfaces(interfaceRegistry) std.RegisterInterfaces(interfaceRegistry) cryptocodec.RegisterInterfaces(interfaceRegistry) ethermint.RegisterInterfaces(interfaceRegistry) diff --git a/types/codec.go b/types/codec.go index ccf4455f..0db2b542 100644 --- a/types/codec.go +++ b/types/codec.go @@ -19,7 +19,7 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { &EthAccount{}, ) registry.RegisterInterface( - "ethermint.v1.ExtensionOptionsWeb3Tx", + "ethermint.types.v1.ExtensionOptionsWeb3Tx", (*ExtensionOptionsWeb3TxI)(nil), &ExtensionOptionsWeb3Tx{}, )