diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 0000000000..9570d52ddc --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,41 @@ +name: Semgrep +on: + # Scan changed files in PRs, block on new issues only (existing issues ignored) + pull_request: {} + push: + branches: + - main + paths: + - .github/workflows/semgrep.yml + schedule: + - cron: '0 0 * * 0' +jobs: + semgrep: + name: Scan + runs-on: ubuntu-latest + if: (github.actor != 'dependabot[bot]') + steps: + - uses: actions/checkout@v2 + - name: Get Diff + uses: technote-space/get-diff-action@v6.0.1 + with: + PATTERNS: | + **/*.go + **/*.js + **/*.ts + **/*.sol + go.mod + go.sum + - uses: returntocorp/semgrep-action@v1 + with: + publishToken: ${{ secrets.SEMGREP_APP_TOKEN }} + # Upload findings to GitHub Advanced Security Dashboard [step 1/2] + # See also the next step. + generateSarif: "1" + if: "env.GIT_DIFF_FILTERED != ''" + # Upload findings to GitHub Advanced Security Dashboard [step 2/2] + - name: Upload SARIF file for GitHub Advanced Security Dashboard + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: semgrep.sarif + if: "env.GIT_DIFF_FILTERED != ''" diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 0000000000..cb655af10c --- /dev/null +++ b/.semgrepignore @@ -0,0 +1,29 @@ +# Ignore git items +.gitignore +.git/ +:include .gitignore + +# Common large paths +node_modules/ +build/ +dist/ +vendor/ +.env/ +.venv/ +.tox/ +*.min.js +*.pb.gw.go + +# Common test paths +test/ +tests/ +*_test.go + +# Semgrep rules folder +.semgrep + +# Semgrep-action log folder +.semgrep_logs/ + +# Documentation +client/docs/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 90e0e36ec2..25751ee594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,24 +35,26 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog -## [v0.10.0] - 2022-01-07 +## [v0.10.0] - 2022-01-27 ### API Breaking * (ante) [\#866](https://github.com/tharsis/ethermint/pull/866) `NewAnteHandler` constructor now receives a `HandlerOptions` field. * (evm) [\#849](https://github.com/tharsis/ethermint/pull/849) `PostTxProcessing` hook now takes an Ethereum tx `Receipt` and a `from` `Address` as arguments. +* (ante) [#916](https://github.com/tharsis/ethermint/pull/916) don't check min-gas-price for eth tx if london hardfork enabled and feemarket enabled. ### State Machine Breaking -* (evm) [tharsis#840](https://github.com/tharsis/ethermint/pull/840) Store empty topics as empty array rather than nil. -* (feemarket) [tharsis#822](https://github.com/tharsis/ethermint/pull/822) Update EIP1559 base fee in `BeginBlock`. -* (evm) [tharsis#817](https://github.com/tharsis/ethermint/pull/817) Use `effectiveGasPrice` in ante handler, add `effectiveGasPrice` to tx receipt. +* (deps) [tharis#912](https://github.com/tharsis/ethermint/pull/912) Bump Cosmos SDK version to [`v0.45.0`](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.45.0) +* (evm) [tharsis#840](https://github.com/tharsis/ethermint/pull/840) Store empty topics as empty array rather than nil. +* (feemarket) [tharsis#822](https://github.com/tharsis/ethermint/pull/822) Update EIP1559 base fee in `BeginBlock`. +* (evm) [tharsis#817](https://github.com/tharsis/ethermint/pull/817) Use `effectiveGasPrice` in ante handler, add `effectiveGasPrice` to tx receipt. * (evm) [tharsis#808](https://github.com/tharsis/ethermint/issues/808) increase nonce in ante handler for contract creation transaction. -* (evm) [tharsis#851](https://github.com/tharsis/ethermint/pull/851) fix contract address used in EVM, this issue is caused by [tharsis#808](https://github.com/tharsis/ethermint/issues/808). +* (evm) [tharsis#851](https://github.com/tharsis/ethermint/pull/851) fix contract address used in EVM, this issue is caused by [tharsis#808](https://github.com/tharsis/ethermint/issues/808). * (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. +* (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 @@ -60,39 +62,43 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (types) [tharsis#849](https://github.com/tharsis/ethermint/pull/849) Add `Type` function to distinguish EOAs from Contract accounts. * (evm) [tharsis#826](https://github.com/tharsis/ethermint/issues/826) Improve allocation of bytes of `tx.To` address. * (evm) [tharsis#827](https://github.com/tharsis/ethermint/issues/827) Speed up creation of event logs by using the slice insertion idiom with indices. -* (ante) [tharsis#819](https://github.com/tharsis/ethermint/pull/819) remove redundant ante handlers +* (ante) [tharsis#819](https://github.com/tharsis/ethermint/pull/819) remove redundant ante handlers * (app) [tharsis#873](https://github.com/tharsis/ethermint/pull/873) Validate code hash in GenesisAccount +* (evm) [tharsis#901](https://github.com/tharsis/ethermint/pull/901) Support multiple MsgEthereumTx in single tx. +* (config) [tharsis#908](https://github.com/tharsis/ethermint/pull/908) Add api.enable flag for Cosmos SDK Rest server +* (feemarket) [tharsis#919](https://github.com/tharsis/ethermint/pull/919) Initialize baseFee in default genesis state. ### Bug Fixes * (evm) [tharsis#884](https://github.com/tharsis/ethermint/pull/884) Support multiple account types on the EVM `StateDB`. * (rpc) [tharsis#831](https://github.com/tharsis/ethermint/pull/831) Fix BaseFee value when height is specified. * (evm) [tharsis#838](https://github.com/tharsis/ethermint/pull/838) Fix splitting of trace.Memory into 32 chunks. -* (rpc) [tharsis#860](https://github.com/tharsis/ethermint/pull/860) Fix `eth_getLogs` when specify blockHash without address/topics, and limit the response size. +* (rpc) [tharsis#860](https://github.com/tharsis/ethermint/pull/860) Fix `eth_getLogs` when specify blockHash without address/topics, and limit the response size. * (rpc) [tharsis#865](https://github.com/tharsis/ethermint/pull/865) Fix RPC Filter parameters being ignored -* (evm) [tharsis#871](https://github.com/tharsis/ethermint/pull/871) Set correct nonce in `EthCall` and `EstimateGas` grpc query. -* (rpc) [tharsis#878](https://github.com/tharsis/ethermint/pull/878) Workaround to make GetBlock RPC api report correct block gas used. +* (evm) [tharsis#871](https://github.com/tharsis/ethermint/pull/871) Set correct nonce in `EthCall` and `EstimateGas` grpc query. +* (rpc) [tharsis#878](https://github.com/tharsis/ethermint/pull/878) Workaround to make GetBlock RPC api report correct block gas used. +* (rpc) [tharsis#900](https://github.com/tharsis/ethermint/pull/900) newPendingTransactions filter return ethereum tx hash. ## [v0.9.0] - 2021-12-01 ### State Machine Breaking -* (evm) [tharsis#802](https://github.com/tharsis/ethermint/pull/802) Clear access list for each transaction +* (evm) [tharsis#802](https://github.com/tharsis/ethermint/pull/802) Clear access list for each transaction ### Improvements * (app) [tharsis#794](https://github.com/tharsis/ethermint/pull/794) Setup in-place store migrators. * (ci) [tharsis#784](https://github.com/tharsis/ethermint/pull/784) Enable automatic backport of PRs. -* (rpc) [tharsis#786](https://github.com/tharsis/ethermint/pull/786) Improve error message of `SendTransaction`/`SendRawTransaction` JSON-RPC APIs. -* (rpc) [tharsis#810](https://github.com/tharsis/ethermint/pull/810) Optimize tx index lookup in web3 rpc +* (rpc) [tharsis#786](https://github.com/tharsis/ethermint/pull/786) Improve error message of `SendTransaction`/`SendRawTransaction` JSON-RPC APIs. +* (rpc) [tharsis#810](https://github.com/tharsis/ethermint/pull/810) Optimize tx index lookup in web3 rpc ### Bug Fixes * (license) [tharsis#800](https://github.com/tharsis/ethermint/pull/800) Re-license project to [LGPLv3](https://choosealicense.com/licenses/lgpl-3.0/#) to comply with go-ethereum. * (evm) [tharsis#794](https://github.com/tharsis/ethermint/pull/794) Register EVM gRPC `Msg` server. * (rpc) [tharsis#781](https://github.com/tharsis/ethermint/pull/781) Fix get block invalid transactions filter. -* (rpc) [tharsis#782](https://github.com/tharsis/ethermint/pull/782) Fix wrong block gas limit returned by JSON-RPC. -* (evm) [tharsis#798](https://github.com/tharsis/ethermint/pull/798) Fix the semantic of `ForEachStorage` callback's return value +* (rpc) [tharsis#782](https://github.com/tharsis/ethermint/pull/782) Fix wrong block gas limit returned by JSON-RPC. +* (evm) [tharsis#798](https://github.com/tharsis/ethermint/pull/798) Fix the semantic of `ForEachStorage` callback's return value ## [v0.8.1] - 2021-11-23 @@ -115,7 +121,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (rpc, evm) [tharsis#673](https://github.com/tharsis/ethermint/pull/673) Use tendermint events to store fee market basefee. * (rpc) [tharsis#624](https://github.com/tharsis/ethermint/pull/624) Implement new JSON-RPC endpoints from latest geth version * (evm) [tharsis#662](https://github.com/tharsis/ethermint/pull/662) Disable basefee for non london blocks -* (cmd) [tharsis#712](https://github.com/tharsis/ethermint/pull/712) add tx cli to build evm transaction +* (cmd) [tharsis#712](https://github.com/tharsis/ethermint/pull/712) add tx cli to build evm transaction * (rpc) [tharsis#733](https://github.com/tharsis/ethermint/pull/733) add JSON_RPC endpoint `personal_unpair` * (rpc) [tharsis#734](https://github.com/tharsis/ethermint/pull/734) add JSON_RPC endpoint `eth_feeHistory` * (rpc) [tharsis#740](https://github.com/tharsis/ethermint/pull/740) add JSON_RPC endpoint `personal_initializeWallet` @@ -129,21 +135,21 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (app,cli) [tharsis#725](https://github.com/tharsis/ethermint/pull/725) Fix cli-config for `keys` command. * (rpc) [tharsis#727](https://github.com/tharsis/ethermint/pull/727) Decode raw transaction using RLP. * (rpc) [tharsis#661](https://github.com/tharsis/ethermint/pull/661) Fix OOM bug when creating too many filters using JSON-RPC. -* (evm) [tharsis#660](https://github.com/tharsis/ethermint/pull/660) Fix `nil` pointer panic in `ApplyNativeMessage`. +* (evm) [tharsis#660](https://github.com/tharsis/ethermint/pull/660) Fix `nil` pointer panic in `ApplyNativeMessage`. * (evm, test) [tharsis#649](https://github.com/tharsis/ethermint/pull/649) Test DynamicFeeTx. -* (evm) [tharsis#702](https://github.com/tharsis/ethermint/pull/702) Fix panic in web3 RPC handlers -* (rpc) [tharsis#720](https://github.com/tharsis/ethermint/pull/720) Fix `debug_traceTransaction` failure -* (rpc) [tharsis#741](https://github.com/tharsis/ethermint/pull/741) Fix `eth_getBlockByNumberAndHash` return with non eth txs +* (evm) [tharsis#702](https://github.com/tharsis/ethermint/pull/702) Fix panic in web3 RPC handlers +* (rpc) [tharsis#720](https://github.com/tharsis/ethermint/pull/720) Fix `debug_traceTransaction` failure +* (rpc) [tharsis#741](https://github.com/tharsis/ethermint/pull/741) Fix `eth_getBlockByNumberAndHash` return with non eth txs * (rpc) [tharsis#743](https://github.com/tharsis/ethermint/pull/743) Fix debug JSON RPC handler crash on non-existing block ### Improvements * (tests) [tharsis#704](https://github.com/tharsis/ethermint/pull/704) Introduce E2E testing framework for clients * (deps) [tharsis#737](https://github.com/tharsis/ethermint/pull/737) Bump ibc-go to [`v2.0.0`](https://github.com/cosmos/ibc-go/releases/tag/v2.0.0) -* (rpc) [tharsis#671](https://github.com/tharsis/ethermint/pull/671) Don't pass base fee externally for `EthCall`/`EthEstimateGas` apis. -* (evm) [tharsis#674](https://github.com/tharsis/ethermint/pull/674) Refactor `ApplyMessage`, remove +* (rpc) [tharsis#671](https://github.com/tharsis/ethermint/pull/671) Don't pass base fee externally for `EthCall`/`EthEstimateGas` apis. +* (evm) [tharsis#674](https://github.com/tharsis/ethermint/pull/674) Refactor `ApplyMessage`, remove `ApplyNativeMessage`. -* (rpc) [tharsis#714](https://github.com/tharsis/ethermint/pull/714) remove `MsgEthereumTx` support in `TxConfig` +* (rpc) [tharsis#714](https://github.com/tharsis/ethermint/pull/714) remove `MsgEthereumTx` support in `TxConfig` ## [v0.7.2] - 2021-10-24 @@ -189,8 +195,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### State Machine Breaking * (app) [tharsis#476](https://github.com/tharsis/ethermint/pull/476) Update Bech32 HRP to `ethm`. -* (evm) [tharsis#556](https://github.com/tharsis/ethermint/pull/556) Remove tx logs and block bloom from chain state -* (evm) [tharsis#590](https://github.com/tharsis/ethermint/pull/590) Contract storage key is not hashed anymore +* (evm) [tharsis#556](https://github.com/tharsis/ethermint/pull/556) Remove tx logs and block bloom from chain state +* (evm) [tharsis#590](https://github.com/tharsis/ethermint/pull/590) Contract storage key is not hashed anymore ### API Breaking @@ -199,7 +205,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features * (evm) [tharsis#469](https://github.com/tharsis/ethermint/pull/469) Support [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) -* (evm) [tharsis#417](https://github.com/tharsis/ethermint/pull/417) Add `EvmHooks` for tx post-processing +* (evm) [tharsis#417](https://github.com/tharsis/ethermint/pull/417) Add `EvmHooks` for tx post-processing * (rpc) [tharsis#506](https://github.com/tharsis/ethermint/pull/506) Support for `debug_traceTransaction` RPC endpoint * (rpc) [tharsis#555](https://github.com/tharsis/ethermint/pull/555) Support for `debug_traceBlockByNumber` RPC endpoint @@ -236,7 +242,7 @@ the Tracer type used to collect execution traces from the EVM transaction execut * (evm) [tharsis#68](https://github.com/tharsis/ethermint/issues/68) Replace block hash storage map to use staking `HistoricalInfo`. * (evm) [tharsis#276](https://github.com/tharsis/ethermint/pull/276) Vm errors don't result in cosmos tx failure, just different tx state and events. -* (evm) [tharsis#342](https://github.com/tharsis/ethermint/issues/342) Don't clear balance when resetting the account. +* (evm) [tharsis#342](https://github.com/tharsis/ethermint/issues/342) Don't clear balance when resetting the account. * (evm) [tharsis#334](https://github.com/tharsis/ethermint/pull/334) Log index changed to the index in block rather than tx. * (evm) [tharsis#399](https://github.com/tharsis/ethermint/pull/399) Exception in sub-message call reverts the call if it's not propagated. @@ -251,7 +257,7 @@ the Tracer type used to collect execution traces from the EVM transaction execut * The `ContractAddress`, `Bloom` have been removed from the `MsgEthereumTxResponse` and the response now contains the ethereum-formatted `Hash` in hex format. * (eth) [\#845](https://github.com/cosmos/ethermint/pull/845) The `eth` namespace must be included in the list of API's as default to run the rpc server without error. -* (evm) [#202](https://github.com/tharsis/ethermint/pull/202) Web3 api `SendTransaction`/`SendRawTransaction` returns ethereum compatible transaction hash, and query api `GetTransaction*` also accept that. +* (evm) [#202](https://github.com/tharsis/ethermint/pull/202) Web3 api `SendTransaction`/`SendRawTransaction` returns ethereum compatible transaction hash, and query api `GetTransaction*` also accept that. * (rpc) [tharsis#258](https://github.com/tharsis/ethermint/pull/258) Return empty `BloomFilter` instead of throwing an error when it cannot be found (`nil` or empty). * (rpc) [tharsis#277](https://github.com/tharsis/ethermint/pull/321) Fix `BloomFilter` response. @@ -270,7 +276,7 @@ the Tracer type used to collect execution traces from the EVM transaction execut * (rpc) [#124](https://github.com/tharsis/ethermint/issues/124) Implement `txpool_content`, `txpool_inspect` and `txpool_status` RPC methods * (rpc) [tharsis#112](https://github.com/tharsis/ethermint/pull/153) Fix `eth_coinbase` to return the ethereum address of the validator * (rpc) [tharsis#176](https://github.com/tharsis/ethermint/issues/176) Support fetching pending nonce -* (rpc) [tharsis#272](https://github.com/tharsis/ethermint/pull/272) do binary search to estimate gas accurately +* (rpc) [tharsis#272](https://github.com/tharsis/ethermint/pull/272) do binary search to estimate gas accurately * (rpc) [#313](https://github.com/tharsis/ethermint/pull/313) Implement internal debug namespace (Not including logger functions nor traces). * (rpc) [#349](https://github.com/tharsis/ethermint/pull/349) Implement configurable JSON-RPC APIs to manage enabled namespaces. * (rpc) [#377](https://github.com/tharsis/ethermint/pull/377) Implement `miner_` namespace. `miner_setEtherbase` and `miner_setGasPrice` are working as intended. All the other calls are not applicable and return `unsupported`. diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go index 5ca5309309..00b6a02ff6 100644 --- a/app/ante/ante_test.go +++ b/app/ante/ante_test.go @@ -146,7 +146,7 @@ func (suite AnteTestSuite) TestAnteHandler() { func() sdk.Tx { signedTx := evmtypes.NewTx( suite.app.EvmKeeper.ChainID(), - 3, + 6, &to, big.NewInt(10), 100000, @@ -167,7 +167,7 @@ func (suite AnteTestSuite) TestAnteHandler() { func() sdk.Tx { signedTx := evmtypes.NewTx( suite.app.EvmKeeper.ChainID(), - 4, + 7, &to, big.NewInt(10), 100000, @@ -186,7 +186,7 @@ func (suite AnteTestSuite) TestAnteHandler() { { "fail - CheckTx (cosmos tx is not valid)", func() sdk.Tx { - signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 4, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) + 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) diff --git a/app/ante/eth.go b/app/ante/eth.go index 09d0bd67f7..4ed65a644b 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -35,10 +35,6 @@ func NewEthSigVerificationDecorator(ek EVMKeeper) EthSigVerificationDecorator { // Failure in RecheckTx will prevent tx to be included into block, especially when CheckTx succeed, in which case user // won't see the error message. func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - if tx == nil || len(tx.GetMsgs()) != 1 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx") - } - chainID := esvd.evmKeeper.ChainID() params := esvd.evmKeeper.GetParams(ctx) @@ -47,26 +43,27 @@ func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, s blockNum := big.NewInt(ctx.BlockHeight()) signer := ethtypes.MakeSigner(ethCfg, blockNum) - msg := tx.GetMsgs()[0] - msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) - } + for _, msg := range tx.GetMsgs() { + msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } - sender, err := signer.Sender(msgEthTx.AsTransaction()) - if err != nil { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrorInvalidSigner, - "couldn't retrieve sender address ('%s') from the ethereum transaction: %s", - msgEthTx.From, - err.Error(), - ) - } + sender, err := signer.Sender(msgEthTx.AsTransaction()) + if err != nil { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrorInvalidSigner, + "couldn't retrieve sender address ('%s') from the ethereum transaction: %s", + msgEthTx.From, + err.Error(), + ) + } - // set up the sender to the transaction field if not already - msgEthTx.From = sender.Hex() + // set up the sender to the transaction field if not already + msgEthTx.From = sender.Hex() + } - return next(ctx, msgEthTx, simulate) + return next(ctx, tx, simulate) } // EthAccountVerificationDecorator validates an account balance checks @@ -99,7 +96,7 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx for i, 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)) + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) } txData, err := evmtypes.UnpackTxData(msgEthTx.Data) @@ -134,58 +131,6 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx return next(ctx, tx, simulate) } -// EthNonceVerificationDecorator checks that the account nonce from the transaction matches -// the sender account sequence. -type EthNonceVerificationDecorator struct { - ak evmtypes.AccountKeeper -} - -// NewEthNonceVerificationDecorator creates a new EthNonceVerificationDecorator -func NewEthNonceVerificationDecorator(ak evmtypes.AccountKeeper) EthNonceVerificationDecorator { - return EthNonceVerificationDecorator{ - ak: ak, - } -} - -// AnteHandle validates that the transaction nonces are valid and equivalent to the sender account’s -// current nonce. -func (nvd EthNonceVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - // no need to check the nonce on ReCheckTx - if ctx.IsReCheckTx() { - return next(ctx, tx, simulate) - } - - 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)) - } - - // sender address should be in the tx cache from the previous AnteHandle call - seq, err := nvd.ak.GetSequence(ctx, msgEthTx.GetFrom()) - if err != nil { - return ctx, sdkerrors.Wrapf(err, "sequence not found for address %s", msgEthTx.From) - } - - txData, err := evmtypes.UnpackTxData(msgEthTx.Data) - if err != nil { - return ctx, sdkerrors.Wrap(err, "failed to unpack tx data") - } - - // if multiple transactions are submitted in succession with increasing nonces, - // all will be rejected except the first, since the first needs to be included in a block - // before the sequence increments - if txData.GetNonce() != seq { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrInvalidSequence, - "invalid nonce; got %d, expected %d", txData.GetNonce(), seq, - ) - } - } - - return next(ctx, tx, simulate) -} - // EthGasConsumeDecorator validates enough intrinsic gas for the transaction and // gas consumption. type EthGasConsumeDecorator struct { @@ -231,7 +176,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula 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)) + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) } txData, err := evmtypes.UnpackTxData(msgEthTx.Data) @@ -276,15 +221,13 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula // CanTransferDecorator checks if the sender is allowed to transfer funds according to the EVM block // context rules. type CanTransferDecorator struct { - evmKeeper EVMKeeper - feemarketKeeper evmtypes.FeeMarketKeeper + evmKeeper EVMKeeper } // NewCanTransferDecorator creates a new CanTransferDecorator instance. -func NewCanTransferDecorator(evmKeeper EVMKeeper, fmk evmtypes.FeeMarketKeeper) CanTransferDecorator { +func NewCanTransferDecorator(evmKeeper EVMKeeper) CanTransferDecorator { return CanTransferDecorator{ - evmKeeper: evmKeeper, - feemarketKeeper: fmk, + evmKeeper: evmKeeper, } } @@ -298,7 +241,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate 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)) + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) } baseFee := ctd.evmKeeper.BaseFee(ctx, ethCfg) @@ -341,7 +284,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate } if coreMsg.GasFeeCap().Cmp(baseFee) < 0 { return ctx, sdkerrors.Wrapf( - evmtypes.ErrInvalidBaseFee, + sdkerrors.ErrInsufficientFee, "max fee per gas less than block base fee (%s < %s)", coreMsg.GasFeeCap(), baseFee, ) @@ -369,23 +312,40 @@ func NewEthIncrementSenderSequenceDecorator(ak evmtypes.AccountKeeper) EthIncrem // 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() { - // increment sequence of all signers - for _, addr := range msg.GetSigners() { - acc := issd.ak.GetAccount(ctx, addr) + msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } - if acc == nil { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrUnknownAddress, - "account %s (%s) is nil", common.BytesToAddress(addr.Bytes()), addr, - ) - } + txData, err := evmtypes.UnpackTxData(msgEthTx.Data) + if err != nil { + return ctx, sdkerrors.Wrap(err, "failed to unpack tx data") + } - if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { - return ctx, sdkerrors.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1) - } + // increase sequence of sender + acc := issd.ak.GetAccount(ctx, msgEthTx.GetFrom()) + if acc == nil { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrUnknownAddress, + "account %s is nil", common.BytesToAddress(msgEthTx.GetFrom().Bytes()), + ) + } + nonce := acc.GetSequence() + + // we merged the nonce verification to nonce increment, so when tx includes multiple messages + // with same sender, they'll be accepted. + if txData.GetNonce() != nonce { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrInvalidSequence, + "invalid nonce; got %d, expected %d", txData.GetNonce(), nonce, + ) + } - issd.ak.SetAccount(ctx, acc) + if err := acc.SetSequence(nonce + 1); err != nil { + return ctx, sdkerrors.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1) } + + issd.ak.SetAccount(ctx, acc) } return next(ctx, tx, simulate) @@ -430,30 +390,31 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1") } - if len(protoTx.GetMsgs()) != 1 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx") - } - msg := protoTx.GetMsgs()[0] - msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) - } - ethGasLimit := msgEthTx.GetGas() + txFee := sdk.Coins{} + txGasLimit := uint64(0) - txData, err := evmtypes.UnpackTxData(msgEthTx.Data) - if err != nil { - return ctx, sdkerrors.Wrap(err, "failed to unpack MsgEthereumTx Data") - } + for _, msg := range protoTx.GetMsgs() { + msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } + txGasLimit += msgEthTx.GetGas() - params := vbd.evmKeeper.GetParams(ctx) - chainID := vbd.evmKeeper.ChainID() - ethCfg := params.ChainConfig.EthereumConfig(chainID) - baseFee := vbd.evmKeeper.BaseFee(ctx, ethCfg) - if baseFee == nil && txData.TxType() == ethtypes.DynamicFeeTxType { - return ctx, sdkerrors.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported") - } + txData, err := evmtypes.UnpackTxData(msgEthTx.Data) + if err != nil { + return ctx, sdkerrors.Wrap(err, "failed to unpack MsgEthereumTx Data") + } + + params := vbd.evmKeeper.GetParams(ctx) + chainID := vbd.evmKeeper.ChainID() + ethCfg := params.ChainConfig.EthereumConfig(chainID) + baseFee := vbd.evmKeeper.BaseFee(ctx, ethCfg) + if baseFee == nil && txData.TxType() == ethtypes.DynamicFeeTxType { + return ctx, sdkerrors.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported") + } - ethFeeAmount := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee()))} + txFee = txFee.Add(sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee()))) + } authInfo := protoTx.AuthInfo if len(authInfo.SignerInfos) > 0 { @@ -464,12 +425,12 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty") } - if !authInfo.Fee.Amount.IsEqual(ethFeeAmount) { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid eth tx AuthInfo Fee Amount") + if !authInfo.Fee.Amount.IsEqual(txFee) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee) } - if authInfo.Fee.GasLimit != ethGasLimit { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid eth tx AuthInfo Fee GasLimit") + if authInfo.Fee.GasLimit != txGasLimit { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit) } sigs := protoTx.Signatures @@ -483,10 +444,14 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu // EthSetupContextDecorator is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption // by setting the gas meter to infinite -type EthSetupContextDecorator struct{} +type EthSetupContextDecorator struct { + evmKeeper EVMKeeper +} -func NewEthSetUpContextDecorator() EthSetupContextDecorator { - return EthSetupContextDecorator{} +func NewEthSetUpContextDecorator(evmKeeper EVMKeeper) EthSetupContextDecorator { + return EthSetupContextDecorator{ + evmKeeper: evmKeeper, + } } func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { @@ -497,6 +462,9 @@ func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul } newCtx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + // Reset transient gas used to prepare the execution of current cosmos tx. + // Transient gas-used is necessary to sum the gas-used of cosmos tx, when it contains multiple eth msgs. + esc.evmKeeper.ResetTransientGasUsed(ctx) return next(newCtx, tx, simulate) } @@ -507,47 +475,39 @@ func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul // If fee is high enough or not CheckTx, then call next AnteHandler // CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator type EthMempoolFeeDecorator struct { - feemarketKeeper evmtypes.FeeMarketKeeper - evmKeeper EVMKeeper + evmKeeper EVMKeeper } -func NewEthMempoolFeeDecorator(ek EVMKeeper, fmk evmtypes.FeeMarketKeeper) EthMempoolFeeDecorator { +func NewEthMempoolFeeDecorator(ek EVMKeeper) EthMempoolFeeDecorator { return EthMempoolFeeDecorator{ - feemarketKeeper: fmk, - evmKeeper: ek, + evmKeeper: ek, } } // AnteHandle ensures that the provided fees meet a minimum threshold for the validator, // if this is a CheckTx. This is only for local mempool purposes, and thus // is only ran on check tx. +// It only do the check if london hardfork not enabled or feemarket not enabled, because in that case feemarket will take over the task. func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { if ctx.IsCheckTx() && !simulate { - if len(tx.GetMsgs()) != 1 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx") - } - msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) - } - - var feeAmt *big.Int - params := mfd.evmKeeper.GetParams(ctx) - chainID := mfd.evmKeeper.ChainID() - ethCfg := params.ChainConfig.EthereumConfig(chainID) - evmDenom := params.EvmDenom + ethCfg := params.ChainConfig.EthereumConfig(mfd.evmKeeper.ChainID()) baseFee := mfd.evmKeeper.BaseFee(ctx, ethCfg) - if baseFee != nil { - feeAmt = msg.GetEffectiveFee(baseFee) - } else { - feeAmt = msg.GetFee() - } - - glDec := sdk.NewDec(int64(msg.GetGas())) - requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec) - if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee) + if baseFee == nil { + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } + + evmDenom := params.EvmDenom + feeAmt := ethMsg.GetFee() + glDec := sdk.NewDec(int64(ethMsg.GetGas())) + requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec) + if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee) + } + } } } diff --git a/app/ante/eth_test.go b/app/ante/eth_test.go index f8f75fbc4b..80f5455baa 100644 --- a/app/ante/eth_test.go +++ b/app/ante/eth_test.go @@ -32,7 +32,7 @@ func (suite AnteTestSuite) TestEthSigVerificationDecorator() { reCheckTx bool expPass bool }{ - {"ReCheckTx", nil, true, false}, + {"ReCheckTx", &invalidTx{}, true, false}, {"invalid transaction type", &invalidTx{}, false, false}, { "invalid sender", @@ -145,7 +145,8 @@ func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() { } func (suite AnteTestSuite) TestEthNonceVerificationDecorator() { - dec := ante.NewEthNonceVerificationDecorator(suite.app.AccountKeeper) + suite.SetupTest() + dec := ante.NewEthIncrementSenderSequenceDecorator(suite.app.AccountKeeper) addr := tests.GenerateAddress() @@ -159,7 +160,7 @@ func (suite AnteTestSuite) TestEthNonceVerificationDecorator() { reCheckTx bool expPass bool }{ - {"ReCheckTx", nil, func() {}, true, true}, + {"ReCheckTx", &invalidTx{}, func() {}, true, false}, {"invalid transaction type", &invalidTx{}, func() {}, false, false}, {"sender account not found", tx, func() {}, false, false}, { @@ -292,7 +293,7 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() { } func (suite AnteTestSuite) TestCanTransferDecorator() { - dec := ante.NewCanTransferDecorator(suite.app.EvmKeeper, suite.app.FeeMarketKeeper) + dec := ante.NewCanTransferDecorator(suite.app.EvmKeeper) addr, privKey := tests.NewAddrKey() @@ -406,13 +407,13 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() { "invalid transaction type", &invalidTx{}, func() {}, - false, true, + 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, + false, false, }, { "account not set to store", @@ -467,7 +468,7 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() { } func (suite AnteTestSuite) TestEthSetupContextDecorator() { - dec := ante.NewEthSetUpContextDecorator() + dec := ante.NewEthSetUpContextDecorator(suite.app.EvmKeeper) tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) testCases := []struct { diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index 83d35a10d9..1defef7bc5 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -48,14 +48,13 @@ func (options HandlerOptions) Validate() error { func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler { return sdk.ChainAnteDecorators( - NewEthSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first - NewEthMempoolFeeDecorator(options.EvmKeeper, options.FeeMarketKeeper), // Check eth effective gas price against minimal-gas-prices + NewEthSetUpContextDecorator(options.EvmKeeper), // outermost AnteDecorator. SetUpContext must be called first + NewEthMempoolFeeDecorator(options.EvmKeeper), // Check eth effective gas price against minimal-gas-prices NewEthValidateBasicDecorator(options.EvmKeeper), NewEthSigVerificationDecorator(options.EvmKeeper), NewEthAccountVerificationDecorator(options.AccountKeeper, options.BankKeeper, options.EvmKeeper), - NewEthNonceVerificationDecorator(options.AccountKeeper), NewEthGasConsumeDecorator(options.EvmKeeper), - NewCanTransferDecorator(options.EvmKeeper, options.FeeMarketKeeper), + NewCanTransferDecorator(options.EvmKeeper), NewEthIncrementSenderSequenceDecorator(options.AccountKeeper), // innermost AnteDecorator. ) } diff --git a/app/ante/interfaces.go b/app/ante/interfaces.go index 520965439c..cfe435ef8a 100644 --- a/app/ante/interfaces.go +++ b/app/ante/interfaces.go @@ -25,6 +25,7 @@ type EVMKeeper interface { ) (sdk.Coins, error) BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int GetBalance(ctx sdk.Context, addr common.Address) *big.Int + ResetTransientGasUsed(ctx sdk.Context) } type protoTxProvider interface { diff --git a/client/testnet.go b/client/testnet.go index fb4e5e106b..4bee072e9f 100644 --- a/client/testnet.go +++ b/client/testnet.go @@ -25,6 +25,7 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdkserver "github.com/cosmos/cosmos-sdk/server" srvconfig "github.com/cosmos/cosmos-sdk/server/config" + "github.com/cosmos/cosmos-sdk/testutil" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -272,8 +273,7 @@ func initTestnetFiles( return err } - // TODO: remove when using Cosmos SDK v0.45 - addr, secret, err := GenerateSaveCoinKey(kb, nodeDirName, true, algo) + addr, secret, err := testutil.GenerateSaveCoinKey(kb, nodeDirName, "", true, algo) if err != nil { _ = os.RemoveAll(args.outputDir) return err @@ -564,37 +564,3 @@ func startTestnet(cmd *cobra.Command, args startArgs) error { return nil } - -// TODO: remove - -// GenerateSaveCoinKey returns the address of a public key, along with the secret -// phrase to recover the private key. -func GenerateSaveCoinKey(keybase keyring.Keyring, keyName string, overwrite bool, algo keyring.SignatureAlgo) (sdk.AccAddress, string, error) { - exists := false - _, err := keybase.Key(keyName) - if err == nil { - exists = true - } - - // ensure no overwrite - if !overwrite && exists { - return sdk.AccAddress([]byte{}), "", fmt.Errorf( - "key already exists, overwrite is disabled") - } - - // generate a private key, with recovery phrase - if exists { - err = keybase.Delete(keyName) - if err != nil { - return sdk.AccAddress([]byte{}), "", fmt.Errorf( - "failed to overwrite key") - } - } - - info, secret, err := keybase.NewMnemonic(keyName, keyring.English, sdk.GetConfig().GetFullBIP44Path(), keyring.DefaultBIP39Passphrase, algo) - if err != nil { - return sdk.AccAddress([]byte{}), "", err - } - - return sdk.AccAddress(info.GetPubKey().Address()), secret, nil -} diff --git a/go.mod b/go.mod index 6b542d6760..1359d7f775 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/btcsuite/btcd v0.22.0-beta github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce - github.com/cosmos/cosmos-sdk v0.44.5 + github.com/cosmos/cosmos-sdk v0.45.0 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/ibc-go/v3 v3.0.0-alpha2 github.com/davecgh/go-spew v1.1.1 @@ -152,8 +152,6 @@ require ( replace ( github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 - // TODO: remove once v0.45 is released - github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.44.6-0.20220104141845-0be9863cfed1 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 google.golang.org/grpc => google.golang.org/grpc v1.33.2 ) diff --git a/go.sum b/go.sum index 75f5c4bc19..ae0b3bc1e0 100644 --- a/go.sum +++ b/go.sum @@ -226,8 +226,8 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= -github.com/cosmos/cosmos-sdk v0.44.6-0.20220104141845-0be9863cfed1 h1:nN+l89gOFpd6ipMR6N/3qu+p/T4me2xMU1P6jc0zbQ8= -github.com/cosmos/cosmos-sdk v0.44.6-0.20220104141845-0be9863cfed1/go.mod h1:XXS/asyCqWNWkx2rW6pSuen+EVcpAFxq6khrhnZgHaQ= +github.com/cosmos/cosmos-sdk v0.45.0 h1:DHD+CIRZ+cYgiLXuTEUL/aprnfPsWSwaww/fIZEsZlk= +github.com/cosmos/cosmos-sdk v0.45.0/go.mod h1:XXS/asyCqWNWkx2rW6pSuen+EVcpAFxq6khrhnZgHaQ= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= diff --git a/init.sh b/init.sh index 5b80f348f0..de11d28139 100755 --- a/init.sh +++ b/init.sh @@ -87,4 +87,4 @@ if [[ $1 == "pending" ]]; then fi # Start the node (remove the --pruning=nothing flag if historical queries are not needed) -ethermintd start --pruning=nothing --evm.tracer=json $TRACE --log_level $LOGLEVEL --minimum-gas-prices=0.0001aphoton --json-rpc.api eth,txpool,personal,net,debug,web3,miner +ethermintd start --pruning=nothing --evm.tracer=json $TRACE --log_level $LOGLEVEL --minimum-gas-prices=0.0001aphoton --json-rpc.api eth,txpool,personal,net,debug,web3,miner --api.enable diff --git a/rpc/ethereum/backend/backend.go b/rpc/ethereum/backend/backend.go index 16063c770a..850737dcb5 100644 --- a/rpc/ethereum/backend/backend.go +++ b/rpc/ethereum/backend/backend.go @@ -10,10 +10,8 @@ import ( "time" "github.com/cosmos/cosmos-sdk/client/flags" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/server" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -66,7 +64,6 @@ type Backend interface { HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) PendingTransactions() ([]*sdk.Tx, error) - GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) GetCoinbase() (sdk.AccAddress, error) @@ -536,18 +533,6 @@ func (e *EVMBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, erro return ethHeader, nil } -// GetTransactionLogs returns the logs given a transaction hash. -// It returns an error if there's an encoding error. -// If no logs are found for the tx hash, the error is nil. -func (e *EVMBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) { - tx, err := e.GetTxByEthHash(txHash) - if err != nil { - return nil, err - } - - return TxLogsFromEvents(tx.TxResult.Events) -} - // PendingTransactions returns the transactions that are in the transaction pool // and have a from address that is one of the accounts this node manages. func (e *EVMBackend) PendingTransactions() ([]*sdk.Tx, error) { @@ -578,12 +563,12 @@ func (e *EVMBackend) GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error) { blockLogs := [][]*ethtypes.Log{} for _, txResult := range blockRes.TxsResults { - logs, err := TxLogsFromEvents(txResult.Events) + logs, err := AllTxLogsFromEvents(txResult.Events) if err != nil { return nil, err } - blockLogs = append(blockLogs, logs) + blockLogs = append(blockLogs, logs...) } return blockLogs, nil @@ -667,7 +652,7 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac } for _, tx := range txs { - msg, err := evmtypes.UnwrapEthereumMsg(tx) + msg, err := evmtypes.UnwrapEthereumMsg(tx, txHash) if err != nil { // not ethereum tx continue @@ -696,16 +681,18 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac return nil, errors.New("invalid ethereum tx") } + msgIndex, attrs := types.FindTxAttributes(res.TxResult.Events, hexTx) + if msgIndex < 0 { + return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx) + } + tx, err := e.clientCtx.TxConfig.TxDecoder()(res.Tx) if err != nil { return nil, err } - if len(tx.GetMsgs()) != 1 { - return nil, errors.New("invalid ethereum tx") - } - - msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + // the `msgIndex` is inferred from tx events, should be within the bound. + msg, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx) if !ok { return nil, errors.New("invalid ethereum tx") } @@ -718,7 +705,7 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac // Try to find txIndex from events found := false - txIndex, err := types.TxIndexFromEvents(res.TxResult.Events) + txIndex, err := types.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex) if err == nil { found = true } else { @@ -813,23 +800,6 @@ func (e *EVMBackend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash return common.Hash{}, err } - // Assemble transaction from fields - builder, ok := e.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) - if !ok { - e.logger.Error("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error()) - } - - option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) - if err != nil { - e.logger.Error("codectypes.NewAnyWithValue failed to pack an obvious value", "error", err.Error()) - return common.Hash{}, err - } - - builder.SetExtensionOptions(option) - if err = builder.SetMsgs(msg); err != nil { - e.logger.Error("builder.SetMsgs failed", "error", err.Error()) - } - // Query params to use the EVM denomination res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{}) if err != nil { @@ -837,19 +807,16 @@ func (e *EVMBackend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash return common.Hash{}, err } - txData, err := evmtypes.UnpackTxData(msg.Data) + // Assemble transaction from fields + tx, err := msg.BuildTx(e.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom) if err != nil { - e.logger.Error("failed to unpack tx data", "error", err.Error()) + e.logger.Error("build cosmos tx failed", "error", err.Error()) return common.Hash{}, err } - fees := sdk.Coins{sdk.NewCoin(res.Params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee()))} - builder.SetFeeAmount(fees) - builder.SetGasLimit(msg.GetGas()) - // Encode transaction by default Tx encoder txEncoder := e.clientCtx.TxConfig.TxEncoder() - txBytes, err := txEncoder(builder.GetTx()) + txBytes, err := txEncoder(tx) if err != nil { e.logger.Error("failed to encode eth tx using default encoder", "error", err.Error()) return common.Hash{}, err @@ -987,9 +954,9 @@ func (e *EVMBackend) ChainConfig() *params.ChainConfig { } // SuggestGasTipCap returns the suggested tip cap +// always return zero since we don't support tx prioritization yet. func (e *EVMBackend) SuggestGasTipCap() (*big.Int, error) { - out := new(big.Int).SetInt64(e.RPCMinGasPrice()) - return out, nil + return big.NewInt(0), nil } // BaseFee returns the base fee tracked by the Fee Market module. If the base fee is not enabled, @@ -1033,7 +1000,6 @@ func (e *EVMBackend) BaseFee(height int64) (*big.Int, error) { // GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a Tendermint block. // It also ensures consistency over the correct txs indexes across RPC endpoints func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx { - // nolint: prealloc var result []*evmtypes.MsgEthereumTx txResults := blockRes.TxsResults @@ -1050,16 +1016,15 @@ func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.Result e.logger.Debug("failed to decode transaction in block", "height", block.Block.Height, "error", err.Error()) continue } - if len(tx.GetMsgs()) != 1 { - continue - } - ethMsg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - if !ok { - continue - } + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + continue + } - result = append(result, ethMsg) + result = append(result, ethMsg) + } } return result diff --git a/rpc/ethereum/backend/utils.go b/rpc/ethereum/backend/utils.go index c572c63ae1..dc8884eb19 100644 --- a/rpc/ethereum/backend/utils.go +++ b/rpc/ethereum/backend/utils.go @@ -182,44 +182,76 @@ func (e *EVMBackend) getAccountNonce(accAddr common.Address, pending bool, heigh // add the uncommitted txs to the nonce counter // only supports `MsgEthereumTx` style tx for _, tx := range pendingTxs { - msg, err := evmtypes.UnwrapEthereumMsg(tx) - if err != nil { - // not ethereum tx - continue + for _, msg := range (*tx).GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + // not ethereum tx + break + } + + sender, err := ethMsg.GetSender(e.chainID) + if err != nil { + continue + } + if sender == accAddr { + nonce++ + } } + } - sender, err := msg.GetSender(e.chainID) - if err != nil { + return nonce, nil +} + +// AllTxLogsFromEvents parses all ethereum logs from cosmos events +func AllTxLogsFromEvents(events []abci.Event) ([][]*ethtypes.Log, error) { + allLogs := make([][]*ethtypes.Log, 0, 4) + for _, event := range events { + if event.Type != evmtypes.EventTypeTxLog { continue } - if sender == accAddr { - nonce++ + + logs, err := ParseTxLogsFromEvent(event) + if err != nil { + return nil, err } - } - return nonce, nil + allLogs = append(allLogs, logs) + } + return allLogs, nil } -// TxLogsFromEvents parses ethereum logs from cosmos events -func TxLogsFromEvents(events []abci.Event) ([]*ethtypes.Log, error) { - logs := make([]*evmtypes.Log, 0) +// TxLogsFromEvents parses ethereum logs from cosmos events for specific msg index +func TxLogsFromEvents(events []abci.Event, msgIndex int) ([]*ethtypes.Log, error) { for _, event := range events { if event.Type != evmtypes.EventTypeTxLog { continue } - for _, attr := range event.Attributes { - if !bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxLog)) { - continue - } + if msgIndex > 0 { + // not the eth tx we want + msgIndex-- + continue + } - var log evmtypes.Log - if err := json.Unmarshal(attr.Value, &log); err != nil { - return nil, err - } + return ParseTxLogsFromEvent(event) + } + return nil, fmt.Errorf("eth tx logs not found for message index %d", msgIndex) +} - logs = append(logs, &log) +// ParseTxLogsFromEvent parse tx logs from one event +func ParseTxLogsFromEvent(event abci.Event) ([]*ethtypes.Log, error) { + logs := make([]*evmtypes.Log, 0, len(event.Attributes)) + for _, attr := range event.Attributes { + if !bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxLog)) { + continue } + + var log evmtypes.Log + if err := json.Unmarshal(attr.Value, &log); err != nil { + return nil, err + } + + logs = append(logs, &log) } return evmtypes.LogsToEthereum(logs), nil } diff --git a/rpc/ethereum/namespaces/debug/api.go b/rpc/ethereum/namespaces/debug/api.go index ad516afefc..5dacaeb2aa 100644 --- a/rpc/ethereum/namespaces/debug/api.go +++ b/rpc/ethereum/namespaces/debug/api.go @@ -89,13 +89,17 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) ( return nil, err } + msgIndex, _ := rpctypes.FindTxAttributes(transaction.TxResult.Events, hash.Hex()) + if msgIndex < 0 { + return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hash.Hex()) + } + // check tx index is not out of bound if uint32(len(blk.Block.Txs)) < transaction.Index { a.logger.Debug("tx index out of bounds", "index", transaction.Index, "hash", hash.String(), "height", blk.Block.Height) return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height) } - // nolint: prealloc var predecessors []*evmtypes.MsgEthereumTx for _, txBz := range blk.Block.Txs[:transaction.Index] { tx, err := a.clientCtx.TxConfig.TxDecoder()(txBz) @@ -103,13 +107,14 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) ( a.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error()) continue } - msg := tx.GetMsgs()[0] - ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - continue - } + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + continue + } - predecessors = append(predecessors, ethMsg) + predecessors = append(predecessors, ethMsg) + } } tx, err := a.clientCtx.TxConfig.TxDecoder()(transaction.Tx) @@ -118,7 +123,16 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) ( return nil, err } - ethMessage, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + // add predecessor messages in current cosmos tx + for i := 0; i < msgIndex; i++ { + ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx) + if !ok { + continue + } + predecessors = append(predecessors, ethMsg) + } + + ethMessage, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx) if !ok { a.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx)) return nil, fmt.Errorf("invalid transaction type %T", tx) @@ -209,7 +223,6 @@ func (a *API) traceBlock(height rpctypes.BlockNumber, config *evmtypes.TraceConf txDecoder := a.clientCtx.TxConfig.TxDecoder() - // nolint: prealloc var txsMessages []*evmtypes.MsgEthereumTx for i, tx := range txs { decodedTx, err := txDecoder(tx) @@ -218,16 +231,14 @@ func (a *API) traceBlock(height rpctypes.BlockNumber, config *evmtypes.TraceConf continue } - messages := decodedTx.GetMsgs() - if len(messages) == 0 { - continue - } - ethMessage, ok := messages[0].(*evmtypes.MsgEthereumTx) - if !ok { - // Just considers Ethereum transactions - continue + for _, msg := range decodedTx.GetMsgs() { + ethMessage, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + // Just considers Ethereum transactions + continue + } + txsMessages = append(txsMessages, ethMessage) } - txsMessages = append(txsMessages, ethMessage) } // minus one to get the context at the beginning of the block diff --git a/rpc/ethereum/namespaces/eth/api.go b/rpc/ethereum/namespaces/eth/api.go index 6608883b91..a9f70e8690 100644 --- a/rpc/ethereum/namespaces/eth/api.go +++ b/rpc/ethereum/namespaces/eth/api.go @@ -6,7 +6,6 @@ import ( "fmt" "math" "math/big" - "strings" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -194,16 +193,22 @@ func (e *PublicAPI) Hashrate() hexutil.Uint64 { // GasPrice returns the current gas price based on Ethermint's gas price oracle. func (e *PublicAPI) GasPrice() (*hexutil.Big, error) { e.logger.Debug("eth_gasPrice") - tipcap, err := e.backend.SuggestGasTipCap() - if err != nil { - return nil, err - } - + var ( + result *big.Int + err error + ) if head := e.backend.CurrentHeader(); head.BaseFee != nil { - tipcap.Add(tipcap, head.BaseFee) + result, err = e.backend.SuggestGasTipCap() + if err != nil { + return nil, err + } + + result = result.Add(result, head.BaseFee) + } else { + result = big.NewInt(e.backend.RPCMinGasPrice()) } - return (*hexutil.Big)(tipcap), nil + return (*hexutil.Big)(result), nil } // MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic fee transactions. @@ -389,7 +394,20 @@ func (e *PublicAPI) GetCode(address common.Address, blockNrOrHash rpctypes.Block // GetTransactionLogs returns the logs given a transaction hash. func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) { e.logger.Debug("eth_getTransactionLogs", "hash", txHash) - return e.backend.GetTransactionLogs(txHash) + + hexTx := txHash.Hex() + res, err := e.backend.GetTxByEthHash(txHash) + if err != nil { + e.logger.Debug("tx not found", "hash", hexTx, "error", err.Error()) + return nil, nil + } + + msgIndex, _ := rpctypes.FindTxAttributes(res.TxResult.Events, hexTx) + if msgIndex < 0 { + return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx) + } + // parse tx logs from events + return backend.TxLogsFromEvents(res.TxResult.Events, msgIndex) } // Sign signs the provided data using the private key of address via Geth's signature standard. @@ -556,7 +574,8 @@ func (e *PublicAPI) Resend(ctx context.Context, args evmtypes.TransactionArgs, g } for _, tx := range pending { - p, err := evmtypes.UnwrapEthereumMsg(tx) + // FIXME does Resend api possible at all? https://github.com/tharsis/ethermint/issues/905 + p, err := evmtypes.UnwrapEthereumMsg(tx, common.Hash{}) if err != nil { // not valid ethereum tx continue @@ -686,12 +705,15 @@ func (e *PublicAPI) getTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) return nil, nil } - if len(tx.GetMsgs()) != 1 { + // find msg index in events + msgIndex := rpctypes.FindTxAttributesByIndex(res.TxResult.Events, uint64(idx)) + if msgIndex < 0 { e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) return nil, nil } var ok bool - msg, ok = tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + // msgIndex is inferred from tx events, should be within bound. + msg, ok = tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx) if !ok { e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) return nil, nil @@ -768,6 +790,11 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac return nil, nil } + msgIndex, attrs := rpctypes.FindTxAttributes(res.TxResult.Events, hexTx) + if msgIndex < 0 { + return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx) + } + resBlock, err := e.clientCtx.Client.Block(e.ctx, &res.Height) if err != nil { e.logger.Debug("block not found", "height", res.Height, "error", err.Error()) @@ -780,13 +807,15 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac return nil, fmt.Errorf("failed to decode tx: %w", err) } - msg, err := evmtypes.UnwrapEthereumMsg(&tx) - if err != nil { - e.logger.Debug("invalid tx", "error", err.Error()) - return nil, err + // the `msgIndex` is inferred from tx events, should be within the bound. + msg := tx.GetMsgs()[msgIndex] + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + e.logger.Debug(fmt.Sprintf("invalid tx type: %T", msg)) + return nil, fmt.Errorf("invalid tx type: %T", msg) } - txData, err := evmtypes.UnpackTxData(msg.Data) + txData, err := evmtypes.UnpackTxData(ethMsg.Data) if err != nil { e.logger.Error("failed to unpack tx data", "error", err.Error()) return nil, err @@ -799,37 +828,61 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac return nil, nil } - for i := 0; i <= int(res.Index) && i < len(blockRes.TxsResults); i++ { + for i := 0; i < int(res.Index) && i < len(blockRes.TxsResults); i++ { cumulativeGasUsed += uint64(blockRes.TxsResults[i].GasUsed) } + cumulativeGasUsed += rpctypes.AccumulativeGasUsedOfMsg(res.TxResult.Events, msgIndex) + + var gasUsed uint64 + if len(tx.GetMsgs()) == 1 { + // backward compatibility + gasUsed = uint64(res.TxResult.GasUsed) + } else { + gasUsed, err = rpctypes.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxGasUsed) + if err != nil { + return nil, err + } + } // Get the transaction result from the log + _, found := attrs[evmtypes.AttributeKeyEthereumTxFailed] var status hexutil.Uint - if strings.Contains(res.TxResult.GetLog(), evmtypes.AttributeKeyEthereumTxFailed) { + if found { status = hexutil.Uint(ethtypes.ReceiptStatusFailed) } else { status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful) } - from, err := msg.GetSender(e.chainIDEpoch) + from, err := ethMsg.GetSender(e.chainIDEpoch) if err != nil { return nil, err } - logs, err := e.backend.GetTransactionLogs(hash) + // parse tx logs from events + logs, err := backend.TxLogsFromEvents(res.TxResult.Events, msgIndex) if err != nil { e.logger.Debug("logs not found", "hash", hexTx, "error", err.Error()) } - // get eth index based on block's txs - var txIndex uint64 - msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes) - for i := range msgs { - if msgs[i].Hash == hexTx { - txIndex = uint64(i) - break + // Try to find txIndex from events + found = false + txIndex, err := rpctypes.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex) + if err == nil { + found = true + } else { + // Fallback to find tx index by iterating all valid eth transactions + msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes) + for i := range msgs { + if msgs[i].Hash == hexTx { + txIndex = uint64(i) + found = true + break + } } } + if !found { + return nil, errors.New("can't find index of ethereum tx") + } receipt := map[string]interface{}{ // Consensus fields: These fields are defined by the Yellow Paper @@ -842,7 +895,7 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac // They are stored in the chain database. "transactionHash": hash, "contractAddress": nil, - "gasUsed": hexutil.Uint64(res.TxResult.GasUsed), + "gasUsed": hexutil.Uint64(gasUsed), "type": hexutil.Uint(txData.TxType()), // Inclusion information: These fields provide information about the inclusion of the @@ -888,24 +941,26 @@ func (e *PublicAPI) GetPendingTransactions() ([]*rpctypes.RPCTransaction, error) result := make([]*rpctypes.RPCTransaction, 0, len(txs)) for _, tx := range txs { - msg, err := evmtypes.UnwrapEthereumMsg(tx) - if err != nil { - // not valid ethereum tx - continue - } + for _, msg := range (*tx).GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + // not valid ethereum tx + break + } - rpctx, err := rpctypes.NewTransactionFromMsg( - msg, - common.Hash{}, - uint64(0), - uint64(0), - e.chainIDEpoch, - ) - if err != nil { - return nil, err - } + rpctx, err := rpctypes.NewTransactionFromMsg( + ethMsg, + common.Hash{}, + uint64(0), + uint64(0), + e.chainIDEpoch, + ) + if err != nil { + return nil, err + } - result = append(result, rpctx) + result = append(result, rpctx) + } } return result, nil diff --git a/rpc/ethereum/namespaces/eth/filters/api.go b/rpc/ethereum/namespaces/eth/filters/api.go index 6488b6a6dd..5d6a788a1f 100644 --- a/rpc/ethereum/namespaces/eth/filters/api.go +++ b/rpc/ethereum/namespaces/eth/filters/api.go @@ -31,7 +31,6 @@ type Backend interface { GetLogsByNumber(blockNum types.BlockNumber) ([][]*ethtypes.Log, error) BlockBloom(height *int64) (ethtypes.Bloom, error) - GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) BloomStatus() (uint64, uint64) RPCFilterCap() int32 diff --git a/rpc/ethereum/types/utils.go b/rpc/ethereum/types/utils.go index 290af5eba7..31abc28645 100644 --- a/rpc/ethereum/types/utils.go +++ b/rpc/ethereum/types/utils.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/hex" - "errors" "fmt" "math/big" "strconv" @@ -25,17 +24,21 @@ import ( ) // RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes. -func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) (*evmtypes.MsgEthereumTx, error) { +func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) { tx, err := clientCtx.TxConfig.TxDecoder()(txBz) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } - ethTx, ok := tx.(*evmtypes.MsgEthereumTx) - if !ok { - return nil, fmt.Errorf("invalid transaction type %T, expected %T", tx, evmtypes.MsgEthereumTx{}) + ethTxs := make([]*evmtypes.MsgEthereumTx, len(tx.GetMsgs())) + for i, msg := range tx.GetMsgs() { + ethTx, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return nil, fmt.Errorf("invalid message type %T, expected %T", msg, &evmtypes.MsgEthereumTx{}) + } + ethTxs[i] = ethTx } - return ethTx, nil + return ethTxs, nil } // EthHeaderFromTendermint is an util function that returns an Ethereum Header @@ -252,25 +255,105 @@ func BaseFeeFromEvents(events []abci.Event) *big.Int { return nil } -// TxIndexFromEvents parses the tx index from cosmos events -func TxIndexFromEvents(events []abci.Event) (uint64, error) { +// FindTxAttributes returns the msg index of the eth tx in cosmos tx, and the attributes, +// returns -1 and nil if not found. +func FindTxAttributes(events []abci.Event, txHash string) (int, map[string]string) { + msgIndex := -1 for _, event := range events { if event.Type != evmtypes.EventTypeEthereumTx { continue } + msgIndex++ + + value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyEthereumTxHash)) + if !bytes.Equal(value, []byte(txHash)) { + continue + } + + // found, convert attributes to map for later lookup + attrs := make(map[string]string, len(event.Attributes)) for _, attr := range event.Attributes { - if bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxIndex)) { - result, err := strconv.ParseInt(string(attr.Value), 10, 64) - if err != nil { - return 0, err - } - if result < 0 { - return 0, errors.New("negative tx index") - } - return uint64(result), nil - } + attrs[string(attr.Key)] = string(attr.Value) + } + return msgIndex, attrs + } + // not found + return -1, nil +} + +// FindTxAttributesByIndex search the msg in tx events by txIndex +// returns the msgIndex, returns -1 if not found. +func FindTxAttributesByIndex(events []abci.Event, txIndex uint64) int { + strIndex := []byte(strconv.FormatUint(txIndex, 10)) + txIndexKey := []byte(evmtypes.AttributeKeyTxIndex) + msgIndex := -1 + for _, event := range events { + if event.Type != evmtypes.EventTypeEthereumTx { + continue + } + + msgIndex++ + + value := FindAttribute(event.Attributes, txIndexKey) + if !bytes.Equal(value, strIndex) { + continue + } + + // found, convert attributes to map for later lookup + return msgIndex + } + // not found + return -1 +} + +// FindAttribute find event attribute with specified key, if not found returns nil. +func FindAttribute(attrs []abci.EventAttribute, key []byte) []byte { + for _, attr := range attrs { + if !bytes.Equal(attr.Key, key) { + continue + } + return attr.Value + } + return nil +} + +// GetUint64Attribute parses the uint64 value from event attributes +func GetUint64Attribute(attrs map[string]string, key string) (uint64, error) { + value, found := attrs[key] + if !found { + return 0, fmt.Errorf("tx index attribute not found: %s", key) + } + var result int64 + result, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return 0, err + } + if result < 0 { + return 0, fmt.Errorf("negative tx index: %d", result) + } + return uint64(result), nil +} + +// AccumulativeGasUsedOfMsg accumulate the gas used by msgs before `msgIndex`. +func AccumulativeGasUsedOfMsg(events []abci.Event, msgIndex int) (gasUsed uint64) { + for _, event := range events { + if event.Type != evmtypes.EventTypeEthereumTx { + continue + } + + if msgIndex < 0 { + break + } + msgIndex-- + + value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyTxGasUsed)) + var result int64 + result, err := strconv.ParseInt(string(value), 10, 64) + if err != nil { + continue } + gasUsed += uint64(result) } - return 0, errors.New("not found") + return } diff --git a/rpc/websockets.go b/rpc/websockets.go index 80679f8c81..86629e3488 100644 --- a/rpc/websockets.go +++ b/rpc/websockets.go @@ -11,6 +11,7 @@ import ( "net/http" "sync" + "github.com/cosmos/cosmos-sdk/client" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/pkg/errors" @@ -73,7 +74,7 @@ type websocketsServer struct { logger log.Logger } -func NewWebsocketsServer(logger log.Logger, tmWSClient *rpcclient.WSClient, cfg config.Config) WebsocketsServer { +func NewWebsocketsServer(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient, cfg config.Config) WebsocketsServer { logger = logger.With("api", "websocket-server") _, port, _ := net.SplitHostPort(cfg.JSONRPC.Address) @@ -82,7 +83,7 @@ func NewWebsocketsServer(logger log.Logger, tmWSClient *rpcclient.WSClient, cfg wsAddr: cfg.JSONRPC.WsAddress, certFile: cfg.TLS.CertificatePath, keyFile: cfg.TLS.KeyPath, - api: newPubSubAPI(logger, tmWSClient), + api: newPubSubAPI(clientCtx, logger, tmWSClient), logger: logger, } } @@ -293,16 +294,18 @@ type pubSubAPI struct { filtersMu *sync.RWMutex filters map[rpc.ID]*wsSubscription logger log.Logger + clientCtx client.Context } // newPubSubAPI creates an instance of the ethereum PubSub API. -func newPubSubAPI(logger log.Logger, tmWSClient *rpcclient.WSClient) *pubSubAPI { +func newPubSubAPI(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient) *pubSubAPI { logger = logger.With("module", "websocket-client") return &pubSubAPI{ events: rpcfilters.NewEventSystem(logger, tmWSClient), filtersMu: new(sync.RWMutex), filters: make(map[rpc.ID]*wsSubscription), logger: logger, + clientCtx: clientCtx, } } @@ -680,40 +683,46 @@ func (api *pubSubAPI) subscribePendingTransactions(wsConn *wsConn) (rpc.ID, erro select { case ev := <-txsCh: data, _ := ev.Data.(tmtypes.EventDataTx) - txHash := common.BytesToHash(tmtypes.Tx(data.Tx).Hash()) + ethTxs, err := types.RawTxToEthTx(api.clientCtx, data.Tx) + if err != nil { + // not ethereum tx + continue + } api.filtersMu.RLock() - for subID, wsSub := range api.filters { - subID := subID - wsSub := wsSub - if wsSub.query != query { - continue - } - // write to ws conn - res := &SubscriptionNotification{ - Jsonrpc: "2.0", - Method: "eth_subscription", - Params: &SubscriptionResult{ - Subscription: subID, - Result: txHash, - }, - } - - err = wsSub.wsConn.WriteJSON(res) - if err != nil { - api.logger.Debug("error writing header, will drop peer", "error", err.Error()) - - try(func() { - api.filtersMu.Lock() - defer api.filtersMu.Unlock() - - if err != websocket.ErrCloseSent { - _ = wsSub.wsConn.Close() - } - - delete(api.filters, subID) - close(wsSub.unsubscribed) - }, api.logger, "closing websocket peer sub") + for _, ethTx := range ethTxs { + for subID, wsSub := range api.filters { + subID := subID + wsSub := wsSub + if wsSub.query != query { + continue + } + // write to ws conn + res := &SubscriptionNotification{ + Jsonrpc: "2.0", + Method: "eth_subscription", + Params: &SubscriptionResult{ + Subscription: subID, + Result: ethTx.Hash, + }, + } + + err = wsSub.wsConn.WriteJSON(res) + if err != nil { + api.logger.Debug("error writing header, will drop peer", "error", err.Error()) + + try(func() { + api.filtersMu.Lock() + defer api.filtersMu.Unlock() + + if err != websocket.ErrCloseSent { + _ = wsSub.wsConn.Close() + } + + delete(api.filters, subID) + close(wsSub.unsubscribed) + }, api.logger, "closing websocket peer sub") + } } } api.filtersMu.RUnlock() diff --git a/server/flags/flags.go b/server/flags/flags.go index 9d797854ef..e213f5bb17 100644 --- a/server/flags/flags.go +++ b/server/flags/flags.go @@ -24,6 +24,11 @@ const ( GRPCWebAddress = "grpc-web.address" ) +// RPCEnable Defines if Cosmos-sdk REST server should be enabled +const ( + RPCEnable = "api.enable" +) + // JSON-RPC flags const ( JSONRPCEnable = "json-rpc.enable" @@ -34,7 +39,6 @@ const ( JSONRPCEVMTimeout = "json-rpc.evm-timeout" JSONRPCTxFeeCap = "json-rpc.txfee-cap" JSONRPCFilterCap = "json-rpc.filter-cap" - JSONRPFeeHistoryCap = "json-rpc.feehistory-cap" JSONRPCLogsCap = "json-rpc.logs-cap" JSONRPCBlockRangeCap = "json-rpc.block-range-cap" ) diff --git a/server/json_rpc.go b/server/json_rpc.go index 204427fce7..315cc850dc 100644 --- a/server/json_rpc.go +++ b/server/json_rpc.go @@ -75,7 +75,7 @@ func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEn // allocate separate WS connection to Tendermint tmWsClient = ConnectTmWS(tmRPCAddr, tmEndpoint, ctx.Logger) - wsSrv := rpc.NewWebsocketsServer(ctx.Logger, tmWsClient, config) + wsSrv := rpc.NewWebsocketsServer(clientCtx, ctx.Logger, tmWsClient, config) wsSrv.Start() return httpSrv, httpSrvDone, nil } diff --git a/server/start.go b/server/start.go index 4ccd936ee1..97f1d33aae 100644 --- a/server/start.go +++ b/server/start.go @@ -149,6 +149,8 @@ which accepts a path for the resulting pprof file. cmd.Flags().Bool(srvflags.GRPCWebEnable, true, "Define if the gRPC-Web server should be enabled. (Note: gRPC must also be enabled.)") cmd.Flags().String(srvflags.GRPCWebAddress, serverconfig.DefaultGRPCWebAddress, "The gRPC-Web server address to listen on") + cmd.Flags().Bool(srvflags.RPCEnable, false, "Defines if Cosmos-sdk REST server should be enabled") + cmd.Flags().Bool(srvflags.JSONRPCEnable, true, "Define if the gRPC server should be enabled") cmd.Flags().StringSlice(srvflags.JSONRPCAPI, config.GetDefaultAPINamespaces(), "Defines a list of JSON-RPC namespaces that should be enabled") cmd.Flags().String(srvflags.JSONRPCAddress, config.DefaultJSONRPCAddress, "the JSON-RPC server address to listen on") diff --git a/tests/e2e/integration_test.go b/tests/e2e/integration_test.go index 4ca6e4b3a6..773f95acc0 100644 --- a/tests/e2e/integration_test.go +++ b/tests/e2e/integration_test.go @@ -1,9 +1,11 @@ package e2e_test import ( + "bytes" "context" "fmt" - "github.com/ethereum/go-ethereum/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" + evmtypes "github.com/tharsis/ethermint/x/evm/types" "math/big" "testing" @@ -42,6 +44,7 @@ type IntegrationTestSuite struct { network *network.Network gethClient *gethclient.Client + ethSigner ethtypes.Signer } func (s *IntegrationTestSuite) SetupSuite() { @@ -72,6 +75,9 @@ func (s *IntegrationTestSuite) SetupSuite() { s.Require().NoError(err) s.gethClient = gethclient.New(rpcClient) s.Require().NotNil(s.gethClient) + chainId, err := ethermint.ParseChainID(s.cfg.ChainID) + s.Require().NoError(err) + s.ethSigner = ethtypes.LatestSignerForChainID(chainId) } func (s *IntegrationTestSuite) TestChainID() { @@ -139,6 +145,20 @@ func (s *IntegrationTestSuite) TestBlock() { // TODO: parse Tm block to Ethereum and compare } +func (s *IntegrationTestSuite) TestBlockBloom() { + transactionHash, _ := s.deployTestContract() + receipt, err := s.network.Validators[0].JSONRPCClient.TransactionReceipt(s.ctx, transactionHash) + s.Require().NoError(err) + + number := receipt.BlockNumber + block, err := s.network.Validators[0].JSONRPCClient.BlockByNumber(s.ctx, number) + s.Require().NoError(err) + + lb := block.Bloom().Big() + s.Require().NotEqual(big.NewInt(0), lb) + s.Require().Equal(transactionHash.String(), block.Transactions()[0].Hash().String()) +} + func (s *IntegrationTestSuite) TestHeader() { blockNum, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx) s.Require().NoError(err) @@ -263,14 +283,403 @@ func (s *IntegrationTestSuite) TestSendTransactionContractDeploymentNoGas() { var data hexutil.Bytes err := data.UnmarshalText([]byte(bytecode)) - var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - tx := ethtypes.NewContractCreation(0, nil, 0x5208, nil, data) - tx, _ = ethtypes.SignTx(tx, ethtypes.HomesteadSigner{}, testKey) + chainID, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) - err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, tx) + owner := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(owner) + contractDeployTx := evmtypes.NewTxContract( + chainID, + nonce, + nil, // amount + 0x5208, // gasLimit + nil, // gasPrice + nil, nil, + data, // input + nil, // accesses + ) + contractDeployTx.From = owner.Hex() + err = contractDeployTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, contractDeployTx.AsTransaction()) s.Require().Error(err) } +func (s *IntegrationTestSuite) TestBlockTransactionCount() { + // start with clean block + err := s.network.WaitForNextBlock() + s.Require().NoError(err) + + signedTx := s.signValidTx(common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(10)) + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, signedTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + receipt := s.expectSuccessReceipt(signedTx.AsTransaction().Hash()) + // TransactionCount endpoint represents eth_getTransactionCountByHash + count, err := s.network.Validators[0].JSONRPCClient.TransactionCount(s.ctx, receipt.BlockHash) + s.Require().NoError(err) + s.Require().Equal(uint(1), count) + + // expect 0 response with random block hash + anyBlockHash := common.HexToHash("0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35") + count, err = s.network.Validators[0].JSONRPCClient.TransactionCount(s.ctx, anyBlockHash) + s.Require().NoError(err) + s.Require().NotEqual(uint(0), 0) +} + +func (s *IntegrationTestSuite) TestGetTransactionByBlockHashAndIndex() { + signedTx := s.signValidTx(common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(10)) + err := s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, signedTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + receipt := s.expectSuccessReceipt(signedTx.AsTransaction().Hash()) + + // TransactionInBlock endpoint represents eth_getTransactionByBlockHashAndIndex + transaction, err := s.network.Validators[0].JSONRPCClient.TransactionInBlock(s.ctx, receipt.BlockHash, 0) + s.Require().NoError(err) + s.Require().NotNil(transaction) + s.Require().Equal(receipt.TxHash, transaction.Hash()) +} + +func (s *IntegrationTestSuite) TestGetBalance() { + blockNumber, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx) + s.Require().NoError(err) + + initialBalance, err := s.network.Validators[0].JSONRPCClient.BalanceAt(s.ctx, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ed"), big.NewInt(int64(blockNumber))) + s.Require().NoError(err) + + amountToTransfer := big.NewInt(10) + signedTx := s.signValidTx(common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ed"), amountToTransfer) + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, signedTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + receipt := s.expectSuccessReceipt(signedTx.AsTransaction().Hash()) + finalBalance, err := s.network.Validators[0].JSONRPCClient.BalanceAt(s.ctx, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ed"), receipt.BlockNumber) + s.Require().NoError(err) + + var result big.Int + s.Require().Equal(result.Add(initialBalance, amountToTransfer), finalBalance) + + // test old balance is still the same + prevBalance, err := s.network.Validators[0].JSONRPCClient.BalanceAt(s.ctx, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ed"), big.NewInt(int64(blockNumber))) + s.Require().NoError(err) + s.Require().Equal(initialBalance, prevBalance) +} + +func (s *IntegrationTestSuite) TestGetLogs() { + //TODO create tests to cover different filterQuery params + _, contractAddr := s.deployERC20Contract() + + blockNum, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx) + s.Require().NoError(err) + + s.transferERC20Transaction(contractAddr, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(10)) + filterQuery := ethereum.FilterQuery{ + FromBlock: big.NewInt(int64(blockNum)), + } + + logs, err := s.network.Validators[0].JSONRPCClient.FilterLogs(s.ctx, filterQuery) + s.Require().NoError(err) + s.Require().NotNil(logs) + s.Require().Equal(1, len(logs)) + + expectedTopics := []common.Hash{ + common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), + common.HexToHash("0x000000000000000000000000" + fmt.Sprintf("%x", common.BytesToAddress(s.network.Validators[0].Address))), + common.HexToHash("0x000000000000000000000000378c50d9264c63f3f92b806d4ee56e9d86ffb3ec"), + } + s.Require().Equal(expectedTopics, logs[0].Topics) +} + +func (s *IntegrationTestSuite) TestTransactionReceiptERC20Transfer() { + //start with clean block + err := s.network.WaitForNextBlock() + s.Require().NoError(err) + // deploy erc20 contract + _, contractAddr := s.deployERC20Contract() + + amount := big.NewInt(10) + hash := s.transferERC20Transaction(contractAddr, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), amount) + transferReceipt := s.expectSuccessReceipt(hash) + logs := transferReceipt.Logs + s.Require().Equal(1, len(logs)) + s.Require().Equal(contractAddr, logs[0].Address) + + s.Require().Equal(amount, big.NewInt(0).SetBytes(logs[0].Data)) + + s.Require().Equal(false, logs[0].Removed) + s.Require().Equal(uint(0x0), logs[0].Index) + s.Require().Equal(uint(0x0), logs[0].TxIndex) + + expectedTopics := []common.Hash{ + common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), + common.HexToHash("0x000000000000000000000000" + fmt.Sprintf("%x", common.BytesToAddress(s.network.Validators[0].Address))), + common.HexToHash("0x000000000000000000000000378c50d9264c63f3f92b806d4ee56e9d86ffb3ec"), + } + s.Require().Equal(expectedTopics, logs[0].Topics) +} + +func (s *IntegrationTestSuite) TestGetCode() { + expectedCode := "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80636d4ce63c1461003b578063d04ad49514610059575b600080fd5b610043610075565b6040516100509190610132565b60405180910390f35b610073600480360381019061006e91906100f6565b61009e565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000813590506100f081610172565b92915050565b60006020828403121561010c5761010b61016d565b5b600061011a848285016100e1565b91505092915050565b61012c8161014d565b82525050565b60006020820190506101476000830184610123565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600080fd5b61017b8161014d565b811461018657600080fd5b5056fea26469706673582212204c98c8f28598d29acc328cb34578de54cbed70b20bf9364897d48b2381f0c78b64736f6c63430008070033" + + _, addr := s.deploySimpleStorageContract() + block, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx) + s.Require().NoError(err) + code, err := s.network.Validators[0].JSONRPCClient.CodeAt(s.ctx, addr, big.NewInt(int64(block))) + s.Require().NoError(err) + s.Require().Equal(expectedCode, hexutil.Encode(code)) +} + +func (s *IntegrationTestSuite) TestGetStorageAt() { + expectedStore := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5} + _, addr := s.deploySimpleStorageContract() + + s.storeValueStorageContract(addr, big.NewInt(5)) + block, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx) + s.Require().NoError(err) + + storage, err := s.network.Validators[0].JSONRPCClient.StorageAt(s.ctx, addr, common.BigToHash(big.NewInt(0)), big.NewInt(int64(block))) + s.Require().NoError(err) + s.Require().NotNil(storage) + s.Require().True(bytes.Equal(expectedStore, storage)) +} + +func (s *IntegrationTestSuite) getGasPrice() *big.Int { + gasPrice, err := s.network.Validators[0].JSONRPCClient.SuggestGasPrice(s.ctx) + s.Require().NoError(err) + return gasPrice +} + +func (s *IntegrationTestSuite) getAccountNonce(addr common.Address) uint64 { + nonce, err := s.network.Validators[0].JSONRPCClient.NonceAt(s.ctx, addr, nil) + s.Require().NoError(err) + return nonce +} + +func (s *IntegrationTestSuite) signValidTx(to common.Address, amount *big.Int) *evmtypes.MsgEthereumTx { + chainId, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) + + gasPrice := s.getGasPrice() + from := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(from) + + msgTx := evmtypes.NewTx( + chainId, + nonce, + &to, + amount, + 100000, + gasPrice, + big.NewInt(200), + nil, + nil, + nil, + ) + msgTx.From = from.Hex() + err = msgTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + return msgTx +} + +func (s *IntegrationTestSuite) signValidContractDeploymentTx(input []byte) *evmtypes.MsgEthereumTx { + chainId, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) + + gasPrice := s.getGasPrice() + from := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(from) + + msgTx := evmtypes.NewTxContract( + chainId, + nonce, + big.NewInt(10), + 134216, + gasPrice, + big.NewInt(200), + nil, + input, + nil, + ) + msgTx.From = from.Hex() + err = msgTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + return msgTx +} + +func (s *IntegrationTestSuite) deployTestContract() (transaction common.Hash, contractAddr common.Address) { + bytecode := "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029" + + var data hexutil.Bytes + err := data.UnmarshalText([]byte(bytecode)) + s.Require().NoError(err) + + return s.deployContract(data) +} + +func (s *IntegrationTestSuite) deployContract(data []byte) (transaction common.Hash, contractAddr common.Address) { + chainID, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) + + owner := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(owner) + + gas, err := s.network.Validators[0].JSONRPCClient.EstimateGas(s.ctx, ethereum.CallMsg{ + From: owner, + Data: data, + }) + s.Require().NoError(err) + + gasPrice := s.getGasPrice() + + contractDeployTx := evmtypes.NewTxContract( + chainID, + nonce, + nil, // amount + gas, // gasLimit + gasPrice, // gasPrice + nil, nil, + data, // input + nil, // accesses + ) + + contractDeployTx.From = owner.Hex() + err = contractDeployTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, contractDeployTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + + receipt := s.expectSuccessReceipt(contractDeployTx.AsTransaction().Hash()) + s.Require().NotNil(receipt.ContractAddress) + return contractDeployTx.AsTransaction().Hash(), receipt.ContractAddress +} + +// Deploys erc20 contract, commits block and returns contract address +func (s *IntegrationTestSuite) deployERC20Contract() (transaction common.Hash, contractAddr common.Address) { + owner := common.BytesToAddress(s.network.Validators[0].Address) + supply := sdk.NewIntWithDecimal(1000, 18).BigInt() + + ctorArgs, err := evmtypes.ERC20Contract.ABI.Pack("", owner, supply) + s.Require().NoError(err) + + data := append(evmtypes.ERC20Contract.Bin, ctorArgs...) + return s.deployContract(data) +} + +// Deploys SimpleStorageContract and,commits block and returns contract address +func (s *IntegrationTestSuite) deploySimpleStorageContract() (transaction common.Hash, contractAddr common.Address) { + ctorArgs, err := evmtypes.SimpleStorageContract.ABI.Pack("") + s.Require().NoError(err) + + data := append(evmtypes.SimpleStorageContract.Bin, ctorArgs...) + return s.deployContract(data) +} + +func (s *IntegrationTestSuite) expectSuccessReceipt(hash common.Hash) *ethtypes.Receipt { + receipt, err := s.network.Validators[0].JSONRPCClient.TransactionReceipt(s.ctx, hash) + s.Require().NoError(err) + s.Require().NotNil(receipt) + s.Require().Equal(uint64(0x1), receipt.Status) + return receipt +} + +func (s *IntegrationTestSuite) transferERC20Transaction(contractAddr, to common.Address, amount *big.Int) common.Hash { + chainID, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) + + transferData, err := evmtypes.ERC20Contract.ABI.Pack("transfer", to, amount) + s.Require().NoError(err) + owner := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(owner) + + gas, err := s.network.Validators[0].JSONRPCClient.EstimateGas(s.ctx, ethereum.CallMsg{ + To: &contractAddr, + From: owner, + Data: transferData, + }) + s.Require().NoError(err) + + gasPrice := s.getGasPrice() + ercTransferTx := evmtypes.NewTx( + chainID, + nonce, + &contractAddr, + nil, + gas, + gasPrice, + nil, nil, + transferData, + nil, + ) + + ercTransferTx.From = owner.Hex() + err = ercTransferTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, ercTransferTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + + receipt := s.expectSuccessReceipt(ercTransferTx.AsTransaction().Hash()) + s.Require().NotEmpty(receipt.Logs) + return ercTransferTx.AsTransaction().Hash() + +} + +func (s *IntegrationTestSuite) storeValueStorageContract(contractAddr common.Address, amount *big.Int) common.Hash { + chainID, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) + + transferData, err := evmtypes.SimpleStorageContract.ABI.Pack("store", amount) + s.Require().NoError(err) + owner := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(owner) + + gas, err := s.network.Validators[0].JSONRPCClient.EstimateGas(s.ctx, ethereum.CallMsg{ + To: &contractAddr, + From: owner, + Data: transferData, + }) + s.Require().NoError(err) + + gasPrice := s.getGasPrice() + ercTransferTx := evmtypes.NewTx( + chainID, + nonce, + &contractAddr, + nil, + gas, + gasPrice, + nil, nil, + transferData, + nil, + ) + + ercTransferTx.From = owner.Hex() + err = ercTransferTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, ercTransferTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + + s.expectSuccessReceipt(ercTransferTx.AsTransaction().Hash()) + return ercTransferTx.AsTransaction().Hash() +} + +// waits 2 blocks time to keep tests stable +func (s *IntegrationTestSuite) waitForTransaction() { + err := s.network.WaitForNextBlock() + err = s.network.WaitForNextBlock() + s.Require().NoError(err) +} + func TestIntegrationTestSuite(t *testing.T) { suite.Run(t, new(IntegrationTestSuite)) } diff --git a/tests/rpc/rpc_test.go b/tests/rpc/rpc_test.go index 97889cccf2..8ff18b6a06 100644 --- a/tests/rpc/rpc_test.go +++ b/tests/rpc/rpc_test.go @@ -8,10 +8,8 @@ package rpc import ( "bytes" - "encoding/hex" "encoding/json" "fmt" - "math/big" "net/http" "os" "testing" @@ -26,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" ) const ( @@ -35,9 +32,8 @@ const ( ) var ( - MODE = os.Getenv("MODE") - zeroString = "0x0" - from = []byte{} + MODE = os.Getenv("MODE") + from = []byte{} ) func TestMain(m *testing.M) { @@ -139,155 +135,6 @@ func callWithError(method string, params interface{}) (*Response, error) { return rpcRes, nil } -// turns a 0x prefixed hex string to a big.Int -func hexToBigInt(t *testing.T, in string) *big.Int { - s := in[2:] - b, err := hex.DecodeString(s) - require.NoError(t, err) - return big.NewInt(0).SetBytes(b) -} - -func TestBlockBloom(t *testing.T) { - hash := deployTestContractWithFunction(t) - receipt := waitForReceipt(t, hash) - - number := receipt["blockNumber"].(string) - param := []interface{}{number, false} - rpcRes := call(t, "eth_getBlockByNumber", param) - - block := make(map[string]interface{}) - err := json.Unmarshal(rpcRes.Result, &block) - require.NoError(t, err) - - lb := hexToBigInt(t, block["logsBloom"].(string)) - require.NotEqual(t, big.NewInt(0), lb) - require.Equal(t, hash.String(), block["transactions"].([]interface{})[0]) -} - -func TestEth_GetLogs_NoLogs(t *testing.T) { - param := make([]map[string][]string, 1) - param[0] = make(map[string][]string) - param[0]["topics"] = []string{} - call(t, "eth_getLogs", param) -} - -func TestEth_GetLogs_Topics_AB(t *testing.T) { - // TODO: this test passes on when run on its own, but fails when run with the other tests - if testing.Short() { - t.Skip("skipping TestEth_GetLogs_Topics_AB") - } - - rpcRes := call(t, "eth_blockNumber", []string{}) - - var res hexutil.Uint64 - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - - param := make([]map[string]interface{}, 1) - param[0] = make(map[string]interface{}) - param[0]["topics"] = []string{helloTopic, worldTopic} - param[0]["fromBlock"] = res.String() - - hash := deployTestContractWithFunction(t) - waitForReceipt(t, hash) - - rpcRes = call(t, "eth_getLogs", param) - - var logs []*ethtypes.Log - err = json.Unmarshal(rpcRes.Result, &logs) - require.NoError(t, err) - - require.Equal(t, 1, len(logs)) -} - -func TestEth_GetTransactionCount(t *testing.T) { - // TODO: this test passes on when run on its own, but fails when run with the other tests - if testing.Short() { - t.Skip("skipping TestEth_GetTransactionCount") - } - - prev := getNonce(t) - sendTestTransaction(t) - post := getNonce(t) - require.Equal(t, prev, post-1) -} - -func TestETH_GetBlockTransactionCountByHash(t *testing.T) { - txHash := sendTestTransaction(t) - - receipt := waitForReceipt(t, txHash) - require.NotNil(t, receipt, "transaction failed") - require.Equal(t, "0x1", receipt["status"].(string)) - - blockHash := receipt["blockHash"].(string) - param := []string{blockHash} - rpcRes := call(t, "eth_getBlockTransactionCountByHash", param) - - var res hexutil.Uint - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - require.Equal(t, "0x1", res.String()) -} - -func TestETH_GetBlockTransactionCountByHash_BlockHashNotFound(t *testing.T) { - anyBlockHash := "0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35" - param := []string{anyBlockHash} - rpcRes := call(t, "eth_getBlockTransactionCountByHash", param) - - var result interface{} - err := json.Unmarshal(rpcRes.Result, &result) - require.NoError(t, err) - require.Nil(t, result) -} - -func TestETH_GetTransactionByBlockHashAndIndex(t *testing.T) { - txHash := sendTestTransaction(t) - - receipt := waitForReceipt(t, txHash) - require.NotNil(t, receipt, "transaction failed") - require.Equal(t, "0x1", receipt["status"].(string)) - blockHash := receipt["blockHash"].(string) - - param := []string{blockHash, "0x0"} - rpcRes := call(t, "eth_getTransactionByBlockHashAndIndex", param) - - tx := make(map[string]interface{}) - err := json.Unmarshal(rpcRes.Result, &tx) - require.NoError(t, err) - require.NotNil(t, tx) - require.Equal(t, blockHash, tx["blockHash"].(string)) - require.Equal(t, "0x0", tx["transactionIndex"].(string)) -} - -func TestETH_GetTransactionByBlockHashAndIndex_BlockHashNotFound(t *testing.T) { - anyBlockHash := "0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35" - - param := []string{anyBlockHash, "0x0"} - rpcRes := call(t, "eth_getTransactionByBlockHashAndIndex", param) - - var result interface{} - err := json.Unmarshal(rpcRes.Result, &result) - require.NoError(t, err) - require.Nil(t, result) -} - -func TestEth_GetTransactionLogs(t *testing.T) { - // TODO: this test passes on when run on its own, but fails when run with the other tests - if testing.Short() { - t.Skip("skipping TestEth_GetTransactionLogs") - } - - hash, _ := deployTestContract(t) - - param := []string{hash.String()} - rpcRes := call(t, "eth_getTransactionLogs", param) - - logs := new([]*ethtypes.Log) - err := json.Unmarshal(rpcRes.Result, logs) - require.NoError(t, err) - require.Equal(t, 1, len(*logs)) -} - func TestEth_protocolVersion(t *testing.T) { expectedRes := hexutil.Uint(ethermint.ProtocolVersion) @@ -301,25 +148,6 @@ func TestEth_protocolVersion(t *testing.T) { require.Equal(t, expectedRes, res, "expected: %s got: %s\n", expectedRes.String(), rpcRes.Result) } -func TestEth_chainId(t *testing.T) { - rpcRes := call(t, "eth_chainId", []string{}) - - var res hexutil.Uint - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - require.NotEqual(t, "0x0", res.String()) -} - -func TestEth_blockNumber(t *testing.T) { - rpcRes := call(t, "eth_blockNumber", []string{}) - - var res hexutil.Uint64 - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - - t.Logf("Got block number: %s\n", res.String()) -} - func TestEth_coinbase(t *testing.T) { zeroAddress := hexutil.Bytes(common.Address{}.Bytes()) rpcRes := call(t, "eth_coinbase", []string{}) @@ -332,34 +160,6 @@ func TestEth_coinbase(t *testing.T) { require.NotEqual(t, zeroAddress.String(), res.String(), "expected: not %s got: %s\n", zeroAddress.String(), res.String()) } -func TestEth_GetBalance(t *testing.T) { - rpcRes := call(t, "eth_getBalance", []string{addrA, zeroString}) - - var res hexutil.Big - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - - t.Logf("Got balance %s for %s\n", res.String(), addrA) - - // 0 if x == y; where x is res, y is 0 - if res.ToInt().Cmp(big.NewInt(0)) != 0 { - t.Errorf("expected balance: %d, got: %s", 0, res.String()) - } -} - -func TestEth_GetStorageAt(t *testing.T) { - expectedRes := hexutil.Bytes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - rpcRes := call(t, "eth_getStorageAt", []string{addrA, fmt.Sprint(addrAStoreKey), zeroString}) - - var storage hexutil.Bytes - err := storage.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - - t.Logf("Got value [%X] for %s with key %X\n", storage, addrA, addrAStoreKey) - - require.True(t, bytes.Equal(storage, expectedRes), "expected: %d (%d bytes) got: %d (%d bytes)", expectedRes, len(expectedRes), storage, len(storage)) -} - func TestEth_GetProof(t *testing.T) { rpcRes := call(t, "eth_sendTransaction", makeEthTxParam()) @@ -387,49 +187,6 @@ func TestEth_GetProof(t *testing.T) { t.Logf("Got AccountResult %s", rpcRes.Result) } -func TestEth_GetCode(t *testing.T) { - expectedRes := hexutil.Bytes{} - rpcRes := call(t, "eth_getCode", []string{addrA, zeroString}) - - var code hexutil.Bytes - err := code.UnmarshalJSON(rpcRes.Result) - - require.NoError(t, err) - - t.Logf("Got code [%X] for %s\n", code, addrA) - require.True(t, bytes.Equal(expectedRes, code), "expected: %X got: %X", expectedRes, code) -} - -func TestEth_SendTransaction_Transfer(t *testing.T) { - rpcRes := call(t, "eth_sendTransaction", makeEthTxParam()) - - var hash hexutil.Bytes - err := json.Unmarshal(rpcRes.Result, &hash) - require.NoError(t, err) - - receipt := waitForReceipt(t, hash) - require.NotNil(t, receipt) - require.Equal(t, "0x1", receipt["status"].(string)) -} - -func TestEth_SendTransaction_ContractDeploy(t *testing.T) { - param := makeTestContractDeployParam(t, true) - rpcRes, err := callWithError("eth_sendTransaction", param) - require.NoError(t, err) - - var hash hexutil.Bytes - err = json.Unmarshal(rpcRes.Result, &hash) - require.NoError(t, err) -} - -func TestEth_SendTransaction_ContractDeploy_no_gas_param(t *testing.T) { - t.Skip("Moved to tests/e2e/integration_test.go#TestSendTransactionContractDeploymentNoGas") - param := makeTestContractDeployParam(t, false) - _, err := callWithError("eth_sendTransaction", param) - // server returns internal error. - require.Error(t, err) -} - func TestEth_NewFilter(t *testing.T) { param := make([]map[string][]string, 1) param[0] = make(map[string][]string) @@ -521,97 +278,6 @@ func sendTestTransaction(t *testing.T) hexutil.Bytes { return hash } -func TestEth_GetTransactionReceipt(t *testing.T) { - hash := sendTestTransaction(t) - - receipt := waitForReceipt(t, hash) - - require.NotNil(t, receipt, "transaction failed") - require.Equal(t, "0x1", receipt["status"].(string)) - require.Equal(t, []interface{}{}, receipt["logs"].([]interface{})) -} - -// deployTestERC20Contract deploys a contract that emits an event in the constructor -func deployTestERC20Contract(t *testing.T) common.Address { - gasPrice := GetGasPrice(t) - - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - - ctorArgs, err := evmtypes.ERC20Contract.ABI.Pack("", common.BytesToAddress(from), big.NewInt(100000000)) - require.NoError(t, err) - data := append(evmtypes.ERC20Contract.Bin, ctorArgs...) - param[0]["data"] = hexutil.Encode(data) - - param[0]["gas"] = "0x200000" - param[0]["gasPrice"] = gasPrice - - rpcRes := call(t, "eth_sendTransaction", param) - - var hash hexutil.Bytes - err = json.Unmarshal(rpcRes.Result, &hash) - require.NoError(t, err) - - receipt := expectSuccessReceipt(t, hash) - contractAddress := common.HexToAddress(receipt["contractAddress"].(string)) - require.NotEqual(t, common.Address{}, contractAddress) - - require.NotNil(t, receipt["logs"]) - - return contractAddress -} - -// sendTestERC20Transaction sends a typical erc20 transfer transaction -func sendTestERC20Transaction(t *testing.T, contract common.Address, amount *big.Int) hexutil.Bytes { - // transfer - gasPrice := GetGasPrice(t) - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - param[0]["to"] = contract.Hex() - data, err := evmtypes.ERC20Contract.ABI.Pack("transfer", common.BigToAddress(big.NewInt(1)), amount) - require.NoError(t, err) - param[0]["data"] = hexutil.Encode(data) - param[0]["gas"] = "0x50000" - param[0]["gasPrice"] = gasPrice - - rpcRes := call(t, "eth_sendTransaction", param) - - var hash hexutil.Bytes - err = json.Unmarshal(rpcRes.Result, &hash) - require.NoError(t, err) - return hash -} - -func TestEth_GetTransactionReceipt_ERC20Transfer(t *testing.T) { - // deploy erc20 contract - contract := deployTestERC20Contract(t) - amount := big.NewInt(10) - hash := sendTestERC20Transaction(t, contract, amount) - receipt := expectSuccessReceipt(t, hash) - - require.Equal(t, 1, len(receipt["logs"].([]interface{}))) - log := receipt["logs"].([]interface{})[0].(map[string]interface{}) - - require.Equal(t, contract, common.HexToAddress(log["address"].(string))) - - valueBz, err := hexutil.Decode(log["data"].(string)) - require.NoError(t, err) - require.Equal(t, amount, big.NewInt(0).SetBytes(valueBz)) - - require.Equal(t, false, log["removed"].(bool)) - require.Equal(t, "0x0", log["logIndex"].(string)) - require.Equal(t, "0x0", log["transactionIndex"].(string)) - - expectedTopics := []interface{}{ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000" + fmt.Sprintf("%x", from), - "0x0000000000000000000000000000000000000000000000000000000000000001", - } - require.Equal(t, expectedTopics, log["topics"].([]interface{})) -} - // deployTestContract deploys a contract that emits an event in the constructor func deployTestContract(t *testing.T) (hexutil.Bytes, map[string]interface{}) { gasPrice := GetGasPrice(t) @@ -635,24 +301,6 @@ func deployTestContract(t *testing.T) (hexutil.Bytes, map[string]interface{}) { return hash, receipt } -func TestEth_GetTransactionReceipt_ContractDeployment(t *testing.T) { - nonce := getNonce(t) - _, receipt := deployTestContract(t) - - addrBz, err := hexutil.Decode(receipt["contractAddress"].(string)) - require.NoError(t, err) - - addr := common.BytesToAddress(addrBz) - require.Equal(t, crypto.CreateAddress(common.BytesToAddress(from), uint64(nonce)), addr) - require.Greater(t, len(receipt["logs"].([]interface{})), 0) - - rpcRes := call(t, "eth_getCode", []string{addr.Hex(), "latest"}) - var code hexutil.Bytes - err = code.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - require.NotEmpty(t, code) -} - func getTransactionReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { param := []string{hash.String()} rpcRes := call(t, "eth_getTransactionReceipt", param) @@ -681,13 +329,6 @@ func waitForReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { } } -func expectSuccessReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { - receipt := waitForReceipt(t, hash) - require.NotNil(t, receipt, "transaction failed") - require.Equal(t, "0x1", receipt["status"].(string)) - return receipt -} - func TestEth_GetFilterChanges_NoTopics(t *testing.T) { rpcRes := call(t, "eth_blockNumber", []string{}) @@ -719,16 +360,6 @@ func TestEth_GetFilterChanges_NoTopics(t *testing.T) { require.Equal(t, 1, len(logs)) } -func TestEth_GetFilterChanges_Addresses(t *testing.T) { - t.Skip() - // TODO: need transaction receipts to determine contract deployment address -} - -func TestEth_GetFilterChanges_BlockHash(t *testing.T) { - t.Skip() - // TODO: need transaction receipts to determine tx block -} - // hash of Hello event var helloTopic = "0x775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd738898" @@ -838,11 +469,6 @@ func TestEth_GetFilterChanges_Topics_XB(t *testing.T) { require.Equal(t, 1, len(logs)) } -func TestEth_GetFilterChanges_Topics_XXC(t *testing.T) { - t.Skip() - // TODO: call test function, need tx receipts to determine contract address -} - func TestEth_PendingTransactionFilter(t *testing.T) { rpcRes := call(t, "eth_newPendingTransactionFilter", []string{}) @@ -865,52 +491,6 @@ func TestEth_PendingTransactionFilter(t *testing.T) { require.True(t, len(txs) >= 2, "could not get any txs", "changesRes.Result", string(changesRes.Result)) } -func getNonce(t *testing.T) hexutil.Uint64 { - param := []interface{}{hexutil.Bytes(from), "latest"} - rpcRes := call(t, "eth_getTransactionCount", param) - - var nonce hexutil.Uint64 - err := json.Unmarshal(rpcRes.Result, &nonce) - require.NoError(t, err) - return nonce -} - -func TestEth_EstimateGas(t *testing.T) { - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - param[0]["to"] = "0x1122334455667788990011223344556677889900" - param[0]["value"] = "0x1" - param[0]["gas"] = "0x5209" - rpcRes := call(t, "eth_estimateGas", param) - require.NotNil(t, rpcRes) - - var gas string - err := json.Unmarshal(rpcRes.Result, &gas) - require.NoError(t, err, string(rpcRes.Result)) - require.Equal(t, "0x5208", gas) -} - -func TestEth_EstimateGas_ContractDeployment(t *testing.T) { - t.Skip("Moved to tests/e2e/integration_test.go#TestEstimateGasContractDeployment") - bytecode := "0x608060405234801561001057600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a260d08061004d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032" - - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - param[0]["data"] = bytecode - - rpcRes := call(t, "eth_estimateGas", param) - require.NotNil(t, rpcRes) - require.NotEmpty(t, rpcRes.Result) - - var gas hexutil.Uint64 - err := json.Unmarshal(rpcRes.Result, &gas) - require.NoError(t, err, string(rpcRes.Result)) - - require.Equal(t, "0x1879c", gas.String()) -} - func TestEth_ExportAccount_WithStorage(t *testing.T) { t.Skip("skipping TestEth_ExportAccount_WithStorage due to the server haven't implmented yet") @@ -957,78 +537,6 @@ func TestEth_ExportAccount_WithStorage(t *testing.T) { require.NotEqual(t, evmtypes.Storage(nil), account.Storage) } -func TestEth_GetBlockByHash(t *testing.T) { - param := []interface{}{"0x1", false} - rpcRes := call(t, "eth_getBlockByNumber", param) - - block := make(map[string]interface{}) - err := json.Unmarshal(rpcRes.Result, &block) - require.NoError(t, err) - blockHash := block["hash"].(string) - - param = []interface{}{blockHash, false} - rpcRes = call(t, "eth_getBlockByHash", param) - block = make(map[string]interface{}) - err = json.Unmarshal(rpcRes.Result, &block) - require.NoError(t, err) - require.Equal(t, "0x1", block["number"].(string)) -} - -func TestEth_GetBlockByHash_BlockHashNotFound(t *testing.T) { - anyBlockHash := "0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35" - param := []interface{}{anyBlockHash, false} - rpcRes := call(t, "eth_getBlockByHash", param) - - var result interface{} - err := json.Unmarshal(rpcRes.Result, &result) - require.NoError(t, err) - require.Nil(t, result) -} - -func TestEth_GetBlockByNumber(t *testing.T) { - param := []interface{}{"0x1", false} - rpcRes := call(t, "eth_getBlockByNumber", param) - - block := make(map[string]interface{}) - err := json.Unmarshal(rpcRes.Result, &block) - require.NoError(t, err) - require.Equal(t, "0x", block["extraData"].(string)) - require.Equal(t, []interface{}{}, block["uncles"].([]interface{})) -} - -func TestEth_GetLogs(t *testing.T) { - rpcRes := call(t, "eth_blockNumber", []string{}) - - var res hexutil.Uint64 - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - - param := make([]map[string]interface{}, 1) - param[0] = make(map[string]interface{}) - param[0]["topics"] = []string{helloTopic, worldTopic} - param[0]["fromBlock"] = res.String() - - deployTestContractWithFunction(t) - - // get filter changes - logRes := call(t, "eth_getLogs", param) - - var logs []*ethtypes.Log - err = json.Unmarshal(logRes.Result, &logs) - require.NoError(t, err) - - require.Equal(t, 1, len(logs)) - - // filter log with address - param[0] = make(map[string]interface{}) - param[0]["address"] = "0x" + fmt.Sprintf("%x", from) - param[0]["fromBlock"] = res.String() - err = json.Unmarshal(logRes.Result, &logs) - require.NoError(t, err) - - require.Equal(t, 1, len(logs)) -} - func makeEthTxParam() []map[string]string { param := make([]map[string]string, 1) param[0] = make(map[string]string) @@ -1041,20 +549,6 @@ func makeEthTxParam() []map[string]string { return param } -func makeTestContractDeployParam(t *testing.T, withGas bool) []map[string]string { - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - param[0]["data"] = "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029" - if withGas { - gasPrice := GetGasPrice(t) - param[0]["gas"] = "0x200000" - param[0]["gasPrice"] = gasPrice - } - - return param -} - func TestEth_EthResend(t *testing.T) { tx := make(map[string]string) tx["from"] = "0x" + fmt.Sprintf("%x", from) diff --git a/tests/solidity/init-test-node.sh b/tests/solidity/init-test-node.sh index 7e1f7981b7..6f0cf15c6e 100755 --- a/tests/solidity/init-test-node.sh +++ b/tests/solidity/init-test-node.sh @@ -58,4 +58,4 @@ ethermintd collect-gentxs ethermintd validate-genesis # Start the node (remove the --pruning=nothing flag if historical queries are not needed) -ethermintd start --pruning=nothing --rpc.unsafe --keyring-backend test --log_level info --json-rpc.api eth,txpool,personal,net,debug,web3 +ethermintd start --pruning=nothing --rpc.unsafe --keyring-backend test --log_level info --json-rpc.api eth,txpool,personal,net,debug,web3 --api.enable diff --git a/tests/solidity/yarn.lock b/tests/solidity/yarn.lock index 08e54e5ff9..52b52aac59 100644 --- a/tests/solidity/yarn.lock +++ b/tests/solidity/yarn.lock @@ -7636,9 +7636,9 @@ flow-stoplight@^1.0.0: integrity sha1-SiksW8/4s5+mzAyxqFPYbyfu/3s= follow-redirects@^1.10.0: - version "1.14.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" - integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== + version "1.14.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" + integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== for-each@^0.3.3, for-each@~0.3.3: version "0.3.3" diff --git a/testutil/network/network.go b/testutil/network/network.go index 8d7a9151ae..5d23a55e79 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -41,6 +41,7 @@ import ( "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/simapp/params" storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/testutil" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -380,7 +381,7 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { return nil, err } - addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, true, algo) + addr, secret, err := testutil.GenerateSaveCoinKey(kb, nodeDirName, "", true, algo) if err != nil { return nil, err } diff --git a/x/evm/client/rest/rest.go b/x/evm/client/rest/rest.go index 81368e29f9..73e410fb74 100644 --- a/x/evm/client/rest/rest.go +++ b/x/evm/client/rest/rest.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "encoding/json" + "errors" "math/big" "net/http" "strings" @@ -94,17 +95,22 @@ func getEthTransactionByHash(clientCtx client.Context, hashHex string) ([]byte, blockHash := common.BytesToHash(block.Block.Header.Hash()) - ethTx, err := rpctypes.RawTxToEthTx(clientCtx, tx.Tx) + ethTxs, err := rpctypes.RawTxToEthTx(clientCtx, tx.Tx) if err != nil { return nil, err } height := uint64(tx.Height) - rpcTx, err := rpctypes.NewRPCTransaction(ethTx.AsTransaction(), blockHash, height, uint64(tx.Index), baseFee) - if err != nil { - return nil, err + for _, ethTx := range ethTxs { + if common.HexToHash(ethTx.Hash) == common.BytesToHash(hash) { + rpcTx, err := rpctypes.NewRPCTransaction(ethTx.AsTransaction(), blockHash, height, uint64(tx.Index), baseFee) + if err != nil { + return nil, err + } + return json.Marshal(rpcTx) + } } - return json.Marshal(rpcTx) + return nil, errors.New("eth tx not found") } diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index c9ebd8abf9..c6903eec54 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -6,6 +6,7 @@ import ( "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" @@ -301,3 +302,36 @@ func (k Keeper) BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int { } return baseFee } + +// ResetTransientGasUsed reset gas used to prepare for execution of current cosmos tx, called in ante handler. +func (k Keeper) ResetTransientGasUsed(ctx sdk.Context) { + store := ctx.TransientStore(k.transientKey) + store.Delete(types.KeyPrefixTransientGasUsed) +} + +// GetTransientGasUsed returns the gas used by current cosmos tx. +func (k Keeper) GetTransientGasUsed(ctx sdk.Context) uint64 { + store := ctx.TransientStore(k.transientKey) + bz := store.Get(types.KeyPrefixTransientGasUsed) + if len(bz) == 0 { + return 0 + } + return sdk.BigEndianToUint64(bz) +} + +// SetTransientGasUsed sets the gas used by current cosmos tx. +func (k Keeper) SetTransientGasUsed(ctx sdk.Context, gasUsed uint64) { + store := ctx.TransientStore(k.transientKey) + bz := sdk.Uint64ToBigEndian(gasUsed) + store.Set(types.KeyPrefixTransientGasUsed, bz) +} + +// AddTransientGasUsed accumulate gas used by each eth msgs included in current cosmos tx. +func (k Keeper) AddTransientGasUsed(ctx sdk.Context, gasUsed uint64) (uint64, error) { + result := k.GetTransientGasUsed(ctx) + gasUsed + if result < gasUsed { + return 0, sdkerrors.Wrap(types.ErrGasOverflow, "transient gas used") + } + k.SetTransientGasUsed(ctx, result) + return result, nil +} diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 4973863a66..e4464d47af 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -87,13 +87,16 @@ func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) { suite.consAddress = sdk.ConsAddress(priv.PubKey().Address()) suite.app = app.Setup(checkTx, func(app *app.EthermintApp, genesis simapp.GenesisState) simapp.GenesisState { + feemarketGenesis := feemarkettypes.DefaultGenesisState() if suite.enableFeemarket { - feemarketGenesis := feemarkettypes.DefaultGenesisState() feemarketGenesis.Params.EnableHeight = 1 feemarketGenesis.Params.NoBaseFee = false feemarketGenesis.BaseFee = sdk.NewInt(feemarketGenesis.Params.InitialBaseFee) - genesis[feemarkettypes.ModuleName] = app.AppCodec().MustMarshalJSON(feemarketGenesis) + } else { + feemarketGenesis.Params.NoBaseFee = true + feemarketGenesis.BaseFee = sdk.NewInt(0) } + genesis[feemarkettypes.ModuleName] = app.AppCodec().MustMarshalJSON(feemarketGenesis) if !suite.enableLondonHF { evmGenesis := types.DefaultGenesisState() maxInt := sdk.NewInt(math.MaxInt64) diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index f3cfe68e5b..50c49afc2d 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -38,7 +38,9 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t // add event for ethereum transaction hash format sdk.NewAttribute(types.AttributeKeyEthereumTxHash, response.Hash), // add event for index of valid ethereum tx - sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatInt(int64(txIndex), 10)), + sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatUint(txIndex, 10)), + // add event for eth tx gas used, we can't get it from cosmos tx result when it contains multiple eth tx msgs. + sdk.NewAttribute(types.AttributeKeyTxGasUsed, strconv.FormatUint(response.GasUsed, 10)), } if len(ctx.TxBytes()) > 0 { diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 1560b6168b..10b66aacf7 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -287,8 +287,13 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*t k.SetTxIndexTransient(ctx, uint64(txConfig.TxIndex)+1) - // update the gas used after refund - k.ResetGasMeterAndConsumeGas(ctx, res.GasUsed) + totalGasUsed, err := k.AddTransientGasUsed(ctx, res.GasUsed) + if err != nil { + return nil, sdkerrors.Wrap(err, "failed to add transient gas used") + } + + // reset the gas meter for current cosmos transaction + k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed) return res, nil } diff --git a/x/evm/keeper/state_transition_test.go b/x/evm/keeper/state_transition_test.go index c2586ba494..37314ccbb5 100644 --- a/x/evm/keeper/state_transition_test.go +++ b/x/evm/keeper/state_transition_test.go @@ -501,7 +501,7 @@ func (suite *KeeperTestSuite) TestEVMConfig() { suite.Require().NoError(err) suite.Require().Equal(types.DefaultParams(), cfg.Params) // london hardfork is enabled by default - suite.Require().Equal(new(big.Int), cfg.BaseFee) + suite.Require().Equal(big.NewInt(0), cfg.BaseFee) suite.Require().Equal(suite.address, cfg.CoinBase) suite.Require().Equal(types.DefaultParams().ChainConfig.EthereumConfig(big.NewInt(9000)), cfg.ChainConfig) } diff --git a/x/evm/keeper/utils.go b/x/evm/keeper/utils.go index 3846e03afe..ed4a2e2f0d 100644 --- a/x/evm/keeper/utils.go +++ b/x/evm/keeper/utils.go @@ -66,6 +66,11 @@ func (k Keeper) DeductTxCostsFromUserBalance( feeAmt = txData.Fee() } + if feeAmt.Sign() == 0 { + // zero fee, no need to deduct + return sdk.NewCoins(), nil + } + fees := sdk.Coins{sdk.NewCoin(denom, sdk.NewIntFromBigInt(feeAmt))} // deduct the full gas cost from the user balance diff --git a/x/evm/spec/02_state.md b/x/evm/spec/02_state.md index 800917f724..7b6d7197e8 100644 --- a/x/evm/spec/02_state.md +++ b/x/evm/spec/02_state.md @@ -19,6 +19,7 @@ The `x/evm` module keeps the following objects in state: | Block Bloom | Block bloom filter, used to accumulate the bloom filter of current block, emitted to events at end blocker. | `[]byte{1} + []byte(tx.Hash)` | `protobuf([]Log)` | Transient | | Tx Index | Index of current transaction in current block. | `[]byte{2}` | `BigEndian(uint64)` | Transient | | Log Size | Number of the logs emitted so far in current block. Used to decide the log index of following logs. | `[]byte{3}` | `BigEndian(uint64)` | Transient | +| Gas Used | Amount of gas used by ethereum messages of current cosmos-sdk tx, it's necessary when cosmos-sdk tx contains multiple ethereum messages. | `[]byte{4}` | `BigEndian(uint64)` | Transient | ## StateDB @@ -161,7 +162,7 @@ With `AddLog()` you can append the given ethereum `Log` to the list of Logs asso ## Keeper -The EVM module `Keeper` grants access to the EVM module state and implements `statedb.Keeper` interface to support the `StateDB` implementation. The Keeper contains a store key that allows the DB to write to a concrete subtree of the multistore that is only accessible to the EVM module. Instead of using a trie and database for querying and persistence (the `StateDB` implementation on Ethermint), use the Cosmos `KVStore` (key-value store) and Cosmos SDK `Keeper` to facilitate state transitions. +The EVM module `Keeper` grants access to the EVM module state and implements `statedb.Keeper` interface to support the `StateDB` implementation. The Keeper contains a store key that allows the DB to write to a concrete subtree of the multistore that is only accessible to the EVM module. Instead of using a trie and database for querying and persistence (the `StateDB` implementation on Ethermint), use the Cosmos `KVStore` (key-value store) and Cosmos SDK `Keeper` to facilitate state transitions. To support the interface functionality, it imports 4 module Keepers: diff --git a/x/evm/spec/07_events.md b/x/evm/spec/07_events.md index dc3e6a0a5e..0a749899db 100644 --- a/x/evm/spec/07_events.md +++ b/x/evm/spec/07_events.md @@ -16,6 +16,7 @@ The `x/evm` module emits the Cosmos SDK events after a state execution. The EVM | ethereum_tx | `"txHash"` | `{tendermint_hex_hash}` | | ethereum_tx | `"ethereumTxHash"` | `{hex_hash}` | | ethereum_tx | `"txIndex"` | `{tx_index}` | +| ethereum_tx | `"txGasUsed"` | `{gas_used}` | | tx_log | `"txLog"` | `{tx_log}` | | message | `"sender"` | `{eth_address}` | | message | `"action"` | `"ethereum"` | diff --git a/x/evm/types/SimpleStorageContract.json b/x/evm/types/SimpleStorageContract.json new file mode 100644 index 0000000000..4d8bb0afe8 --- /dev/null +++ b/x/evm/types/SimpleStorageContract.json @@ -0,0 +1,4 @@ +{ + "abi": "[\n\t{\n\t\t\"inputs\": [],\n\t\t\"name\": \"get\",\n\t\t\"outputs\": [\n\t\t\t{\n\t\t\t\t\"internalType\": \"uint160\",\n\t\t\t\t\"name\": \"\",\n\t\t\t\t\"type\": \"uint160\"\n\t\t\t}\n\t\t],\n\t\t\"stateMutability\": \"view\",\n\t\t\"type\": \"function\"\n\t},\n\t{\n\t\t\"inputs\": [\n\t\t\t{\n\t\t\t\t\"internalType\": \"uint160\",\n\t\t\t\t\"name\": \"input\",\n\t\t\t\t\"type\": \"uint160\"\n\t\t\t}\n\t\t],\n\t\t\"name\": \"store\",\n\t\t\"outputs\": [],\n\t\t\"stateMutability\": \"nonpayable\",\n\t\t\"type\": \"function\"\n\t}\n]", + "bin": "608060405234801561001057600080fd5b506101bf806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80636d4ce63c1461003b578063d04ad49514610059575b600080fd5b610043610075565b6040516100509190610132565b60405180910390f35b610073600480360381019061006e91906100f6565b61009e565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000813590506100f081610172565b92915050565b60006020828403121561010c5761010b61016d565b5b600061011a848285016100e1565b91505092915050565b61012c8161014d565b82525050565b60006020820190506101476000830184610123565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600080fd5b61017b8161014d565b811461018657600080fd5b5056fea26469706673582212204c98c8f28598d29acc328cb34578de54cbed70b20bf9364897d48b2381f0c78b64736f6c63430008070033" +} diff --git a/x/evm/types/compiled_contract.go b/x/evm/types/compiled_contract.go index e36704fc42..081d815bed 100644 --- a/x/evm/types/compiled_contract.go +++ b/x/evm/types/compiled_contract.go @@ -74,6 +74,12 @@ var ( // ERC20Contract is the compiled test erc20 contract ERC20Contract CompiledContract + //go:embed SimpleStorageContract.json + simpleStorageJSON []byte + + // SimpleStorageContract is the compiled test simple storage contract + SimpleStorageContract CompiledContract + //go:embed TestMessageCall.json testMessageCallJSON []byte @@ -99,4 +105,13 @@ func init() { if len(TestMessageCall.Bin) == 0 { panic("load contract failed") } + + err = json.Unmarshal(simpleStorageJSON, &SimpleStorageContract) + if err != nil { + panic(err) + } + + if len(TestMessageCall.Bin) == 0 { + panic("load contract failed") + } } diff --git a/x/evm/types/events.go b/x/evm/types/events.go index 0b8ca69a94..d9e8592892 100644 --- a/x/evm/types/events.go +++ b/x/evm/types/events.go @@ -11,6 +11,7 @@ const ( AttributeKeyTxHash = "txHash" AttributeKeyEthereumTxHash = "ethereumTxHash" AttributeKeyTxIndex = "txIndex" + AttributeKeyTxGasUsed = "txGasUsed" AttributeKeyTxType = "txType" AttributeKeyTxLog = "txLog" // tx failed in eth vm execution diff --git a/x/evm/types/key.go b/x/evm/types/key.go index 36e5a8fcbb..ade14eb20a 100644 --- a/x/evm/types/key.go +++ b/x/evm/types/key.go @@ -32,6 +32,7 @@ const ( prefixTransientBloom = iota + 1 prefixTransientTxIndex prefixTransientLogSize + prefixTransientGasUsed ) // KVStore key prefixes @@ -45,6 +46,7 @@ var ( KeyPrefixTransientBloom = []byte{prefixTransientBloom} KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex} KeyPrefixTransientLogSize = []byte{prefixTransientLogSize} + KeyPrefixTransientGasUsed = []byte{prefixTransientGasUsed} ) // AddressStoragePrefix returns a prefix to iterate over a given account storage. diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index 469fcd8a9c..7cec50c944 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -330,11 +330,10 @@ func (msg *MsgEthereumTx) BuildTx(b client.TxBuilder, evmDenom string) (signing. if err != nil { return nil, err } - fees := sdk.Coins{ - { - Denom: evmDenom, - Amount: sdk.NewIntFromBigInt(txData.Fee()), - }, + fees := make(sdk.Coins, 0) + feeAmt := sdk.NewIntFromBigInt(txData.Fee()) + if feeAmt.Sign() > 0 { + fees = append(fees, sdk.NewCoin(evmDenom, feeAmt)) } builder.SetExtensionOptions(option) diff --git a/x/evm/types/utils.go b/x/evm/types/utils.go index 94b7192f4c..16b1c78068 100644 --- a/x/evm/types/utils.go +++ b/x/evm/types/utils.go @@ -9,6 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) @@ -54,20 +55,22 @@ func DecodeTransactionLogs(data []byte) (TransactionLogs, error) { } // UnwrapEthereumMsg extract MsgEthereumTx from wrapping sdk.Tx -func UnwrapEthereumMsg(tx *sdk.Tx) (*MsgEthereumTx, error) { +func UnwrapEthereumMsg(tx *sdk.Tx, ethHash common.Hash) (*MsgEthereumTx, error) { if tx == nil { return nil, fmt.Errorf("invalid tx: nil") } - if len((*tx).GetMsgs()) != 1 { - return nil, fmt.Errorf("invalid tx type: %T", tx) - } - msg, ok := (*tx).GetMsgs()[0].(*MsgEthereumTx) - if !ok { - return nil, fmt.Errorf("invalid tx type: %T", tx) + for _, msg := range (*tx).GetMsgs() { + ethMsg, ok := msg.(*MsgEthereumTx) + if !ok { + return nil, fmt.Errorf("invalid tx type: %T", tx) + } + if ethMsg.AsTransaction().Hash() == ethHash { + return ethMsg, nil + } } - return msg, nil + return nil, fmt.Errorf("eth tx not found: %s", ethHash) } // BinSearch execute the binary search and hone in on an executable gas limit diff --git a/x/evm/types/utils_test.go b/x/evm/types/utils_test.go index 21aac53209..d3ec00e565 100644 --- a/x/evm/types/utils_test.go +++ b/x/evm/types/utils_test.go @@ -48,7 +48,7 @@ func TestEvmDataEncoding(t *testing.T) { } func TestUnwrapEthererumMsg(t *testing.T) { - _, err := evmtypes.UnwrapEthereumMsg(nil) + _, err := evmtypes.UnwrapEthereumMsg(nil, common.Hash{}) require.NotNil(t, err) encodingConfig := encoding.MakeConfig(app.ModuleBasics) @@ -56,14 +56,14 @@ func TestUnwrapEthererumMsg(t *testing.T) { builder, _ := clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) tx := builder.GetTx().(sdk.Tx) - _, err = evmtypes.UnwrapEthereumMsg(&tx) + _, err = evmtypes.UnwrapEthereumMsg(&tx, common.Hash{}) require.NotNil(t, err) msg := evmtypes.NewTx(big.NewInt(1), 0, &common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil, nil, []byte{}, nil) err = builder.SetMsgs(msg) tx = builder.GetTx().(sdk.Tx) - msg_, err := evmtypes.UnwrapEthereumMsg(&tx) + msg_, err := evmtypes.UnwrapEthereumMsg(&tx, msg.AsTransaction().Hash()) require.Nil(t, err) require.Equal(t, msg_, msg) } diff --git a/x/feemarket/keeper/grpc_query_test.go b/x/feemarket/keeper/grpc_query_test.go index 8bda0f2442..b97f4d311a 100644 --- a/x/feemarket/keeper/grpc_query_test.go +++ b/x/feemarket/keeper/grpc_query_test.go @@ -2,6 +2,7 @@ package keeper_test import ( sdk "github.com/cosmos/cosmos-sdk/types" + ethparams "github.com/ethereum/go-ethereum/params" "github.com/tharsis/ethermint/x/feemarket/types" ) @@ -41,9 +42,10 @@ func (suite *KeeperTestSuite) TestQueryBaseFee() { expPass bool }{ { - "pass - nil Base Fee", + "pass - default Base Fee", func() { - expRes = &types.QueryBaseFeeResponse{} + initialBaseFee := sdk.NewInt(ethparams.InitialBaseFee) + expRes = &types.QueryBaseFeeResponse{BaseFee: &initialBaseFee} }, true, }, diff --git a/x/feemarket/types/genesis.go b/x/feemarket/types/genesis.go index a81829e98e..b92f5b92ad 100644 --- a/x/feemarket/types/genesis.go +++ b/x/feemarket/types/genesis.go @@ -4,13 +4,15 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/params" ) // DefaultGenesisState sets default fee market genesis state. func DefaultGenesisState() *GenesisState { return &GenesisState{ - Params: DefaultParams(), - BaseFee: sdk.ZeroInt(), + Params: DefaultParams(), + // the default base fee should be initialized because the default enable height is zero. + BaseFee: sdk.NewIntFromUint64(params.InitialBaseFee), BlockGas: 0, } }