diff --git a/CHANGELOG.md b/CHANGELOG.md index ca1037d84..f4e5fa640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ for (1) ERC20 transfers with tokens that return false success values instead of throwing an error and (2) ERC20 transfers with other operations that don't bring about the expected resulting balance for the transfer recipient. - [#2091](https://github.com/NibiruChain/nibiru/pull/2091) - feat(evm): add fun token creation fee validation +- [#2093](https://github.com/NibiruChain/nibiru/pull/2093) - feat(evm): gas usage in precompiles: limits, local gas meters - [#2092](https://github.com/NibiruChain/nibiru/pull/2092) - feat(evm): add validation for wasm multi message execution - [#2094](https://github.com/NibiruChain/nibiru/pull/2094) - fix(evm): Following from the changs in #2086, this pull request implements a new `JournalChange` @@ -92,7 +93,7 @@ The `NibiruBankKeeper` holds a reference to the current EVM `StateDB` and record balance changes in wei as journal changes automatically. This guarantees that commits and reversions of the `StateDB` do not misalign with the state of the Bank module. This code change uses the `NibiruBankKeeper` on all modules that -depend on x/bank, such as the EVM and Wasm modules. +depend on x/bank, such as the EVM and Wasm modules. - [#2097](https://github.com/NibiruChain/nibiru/pull/2097) - feat(evm): Add new query to get dated price from the oracle precompile - [#2098](https://github.com/NibiruChain/nibiru/pull/2098) - test(evm): statedb tests for race conditions within funtoken precompile @@ -100,7 +101,6 @@ tests for race conditions within funtoken precompile - [#2101](https://github.com/NibiruChain/nibiru/pull/2101) - fix(evm): tx receipt proper marshalling - [#2105](https://github.com/NibiruChain/nibiru/pull/2105) - test(evm): precompile call with revert - #### Nibiru EVM | Before Audit 1 - 2024-10-18 - [#1837](https://github.com/NibiruChain/nibiru/pull/1837) - feat(eth): protos, eth types, and evm module types diff --git a/eth/rpc/backend/call_tx.go b/eth/rpc/backend/call_tx.go index 3e4de8037..66deeb8fb 100644 --- a/eth/rpc/backend/call_tx.go +++ b/eth/rpc/backend/call_tx.go @@ -295,10 +295,10 @@ func (b *Backend) DoCall( } if res.Failed() { - if res.VmError != vm.ErrExecutionReverted.Error() { - return nil, status.Error(codes.Internal, res.VmError) + if res.VmError == vm.ErrExecutionReverted.Error() { + return nil, evm.NewRevertError(res.Ret) } - return nil, evm.NewExecErrorWithReason(res.Ret) + return nil, status.Error(codes.Internal, res.VmError) } return res, nil diff --git a/eth/rpc/rpcapi/eth_api_test.go b/eth/rpc/rpcapi/eth_api_test.go index e20513f5e..1a599d2f1 100644 --- a/eth/rpc/rpcapi/eth_api_test.go +++ b/eth/rpc/rpcapi/eth_api_test.go @@ -193,7 +193,7 @@ func (s *NodeSuite) Test_EstimateGas() { } { msg.Value = msgValue _, err = s.ethClient.EstimateGas(context.Background(), msg) - s.ErrorContains(err, "StateDB: wei amount is too small") + s.ErrorContains(err, "wei amount is too small") } } diff --git a/evm-e2e/.gitignore b/evm-e2e/.gitignore new file mode 100644 index 000000000..3419dc450 --- /dev/null +++ b/evm-e2e/.gitignore @@ -0,0 +1,4 @@ +types +artifacts +cache +.env diff --git a/x/evm/embeds/artifacts/contracts/TestERC20TransferThenPrecompileSend.sol/TestERC20TransferThenPrecompileSend.json b/x/evm/embeds/artifacts/contracts/TestERC20TransferThenPrecompileSend.sol/TestERC20TransferThenPrecompileSend.json index 2d58f750e..3a1411d37 100644 --- a/x/evm/embeds/artifacts/contracts/TestERC20TransferThenPrecompileSend.sol/TestERC20TransferThenPrecompileSend.json +++ b/x/evm/embeds/artifacts/contracts/TestERC20TransferThenPrecompileSend.sol/TestERC20TransferThenPrecompileSend.json @@ -43,8 +43,8 @@ "type": "function" } ], - "bytecode": "0x608060405234801561001057600080fd5b50604051610974380380610974833981810160405281019061003291906100db565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610108565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a88261007d565b9050919050565b6100b88161009d565b81146100c357600080fd5b50565b6000815190506100d5816100af565b92915050565b6000602082840312156100f1576100f0610078565b5b60006100ff848285016100c6565b91505092915050565b61085d806101176000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063264c325814610030575b600080fd5b61004a6004803603810190610045919061049f565b61004c565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb85856040518363ffffffff1660e01b81526004016100a7929190610590565b6020604051808303816000875af11580156100c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100ea91906105f1565b610129576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101209061067b565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1683856040516024016101799392919061072a565b6040516020818303038152906040527f03003bc5000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161020391906107af565b6000604051808303816000865af19150503d8060008114610240576040519150601f19603f3d011682016040523d82523d6000602084013e610245565b606091505b5050905080604051602001610259906107ec565b604051602081830303815290604052906102a9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102a09190610805565b60405180910390fd5b505050505050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102f0826102c5565b9050919050565b610300816102e5565b811461030b57600080fd5b50565b60008135905061031d816102f7565b92915050565b6000819050919050565b61033681610323565b811461034157600080fd5b50565b6000813590506103538161032d565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103ac82610363565b810181811067ffffffffffffffff821117156103cb576103ca610374565b5b80604052505050565b60006103de6102b1565b90506103ea82826103a3565b919050565b600067ffffffffffffffff82111561040a57610409610374565b5b61041382610363565b9050602081019050919050565b82818337600083830152505050565b600061044261043d846103ef565b6103d4565b90508281526020810184848401111561045e5761045d61035e565b5b610469848285610420565b509392505050565b600082601f83011261048657610485610359565b5b813561049684826020860161042f565b91505092915050565b600080600080608085870312156104b9576104b86102bb565b5b60006104c78782880161030e565b94505060206104d887828801610344565b935050604085013567ffffffffffffffff8111156104f9576104f86102c0565b5b61050587828801610471565b925050606061051687828801610344565b91505092959194509250565b6000819050919050565b600061054761054261053d846102c5565b610522565b6102c5565b9050919050565b60006105598261052c565b9050919050565b600061056b8261054e565b9050919050565b61057b81610560565b82525050565b61058a81610323565b82525050565b60006040820190506105a56000830185610572565b6105b26020830184610581565b9392505050565b60008115159050919050565b6105ce816105b9565b81146105d957600080fd5b50565b6000815190506105eb816105c5565b92915050565b600060208284031215610607576106066102bb565b5b6000610615848285016105dc565b91505092915050565b600082825260208201905092915050565b7f4552432d3230207472616e73666572206661696c656400000000000000000000600082015250565b600061066560168361061e565b91506106708261062f565b602082019050919050565b6000602082019050818103600083015261069481610658565b9050919050565b60006106a6826102c5565b9050919050565b6106b68161069b565b82525050565b600081519050919050565b60005b838110156106e55780820151818401526020810190506106ca565b60008484015250505050565b60006106fc826106bc565b610706818561061e565b93506107168185602086016106c7565b61071f81610363565b840191505092915050565b600060608201905061073f60008301866106ad565b61074c6020830185610581565b818103604083015261075e81846106f1565b9050949350505050565b600081519050919050565b600081905092915050565b600061078982610768565b6107938185610773565b93506107a38185602086016106c7565b80840191505092915050565b60006107bb828461077e565b915081905092915050565b7f4661696c656420746f2063616c6c2062616e6b53656e64000000000000000000815250565b60006107f7826107c6565b601782019150819050919050565b6000602082019050818103600083015261081f81846106f1565b90509291505056fea26469706673582212203da3b9141c515078ba917a5b792ee843396b82d45e2ef60e0d6307c236e8c30664736f6c63430008180033", - "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063264c325814610030575b600080fd5b61004a6004803603810190610045919061049f565b61004c565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb85856040518363ffffffff1660e01b81526004016100a7929190610590565b6020604051808303816000875af11580156100c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100ea91906105f1565b610129576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101209061067b565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1683856040516024016101799392919061072a565b6040516020818303038152906040527f03003bc5000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161020391906107af565b6000604051808303816000865af19150503d8060008114610240576040519150601f19603f3d011682016040523d82523d6000602084013e610245565b606091505b5050905080604051602001610259906107ec565b604051602081830303815290604052906102a9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102a09190610805565b60405180910390fd5b505050505050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102f0826102c5565b9050919050565b610300816102e5565b811461030b57600080fd5b50565b60008135905061031d816102f7565b92915050565b6000819050919050565b61033681610323565b811461034157600080fd5b50565b6000813590506103538161032d565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103ac82610363565b810181811067ffffffffffffffff821117156103cb576103ca610374565b5b80604052505050565b60006103de6102b1565b90506103ea82826103a3565b919050565b600067ffffffffffffffff82111561040a57610409610374565b5b61041382610363565b9050602081019050919050565b82818337600083830152505050565b600061044261043d846103ef565b6103d4565b90508281526020810184848401111561045e5761045d61035e565b5b610469848285610420565b509392505050565b600082601f83011261048657610485610359565b5b813561049684826020860161042f565b91505092915050565b600080600080608085870312156104b9576104b86102bb565b5b60006104c78782880161030e565b94505060206104d887828801610344565b935050604085013567ffffffffffffffff8111156104f9576104f86102c0565b5b61050587828801610471565b925050606061051687828801610344565b91505092959194509250565b6000819050919050565b600061054761054261053d846102c5565b610522565b6102c5565b9050919050565b60006105598261052c565b9050919050565b600061056b8261054e565b9050919050565b61057b81610560565b82525050565b61058a81610323565b82525050565b60006040820190506105a56000830185610572565b6105b26020830184610581565b9392505050565b60008115159050919050565b6105ce816105b9565b81146105d957600080fd5b50565b6000815190506105eb816105c5565b92915050565b600060208284031215610607576106066102bb565b5b6000610615848285016105dc565b91505092915050565b600082825260208201905092915050565b7f4552432d3230207472616e73666572206661696c656400000000000000000000600082015250565b600061066560168361061e565b91506106708261062f565b602082019050919050565b6000602082019050818103600083015261069481610658565b9050919050565b60006106a6826102c5565b9050919050565b6106b68161069b565b82525050565b600081519050919050565b60005b838110156106e55780820151818401526020810190506106ca565b60008484015250505050565b60006106fc826106bc565b610706818561061e565b93506107168185602086016106c7565b61071f81610363565b840191505092915050565b600060608201905061073f60008301866106ad565b61074c6020830185610581565b818103604083015261075e81846106f1565b9050949350505050565b600081519050919050565b600081905092915050565b600061078982610768565b6107938185610773565b93506107a38185602086016106c7565b80840191505092915050565b60006107bb828461077e565b915081905092915050565b7f4661696c656420746f2063616c6c2062616e6b53656e64000000000000000000815250565b60006107f7826107c6565b601782019150819050919050565b6000602082019050818103600083015261081f81846106f1565b90509291505056fea26469706673582212203da3b9141c515078ba917a5b792ee843396b82d45e2ef60e0d6307c236e8c30664736f6c63430008180033", + "bytecode": "0x608060405234801561001057600080fd5b50604051610c4c380380610c4c833981810160405281019061003291906100db565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610108565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a88261007d565b9050919050565b6100b88161009d565b81146100c357600080fd5b50565b6000815190506100d5816100af565b92915050565b6000602082840312156100f1576100f0610078565b5b60006100ff848285016100c6565b91505092915050565b610b35806101176000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063264c325814610030575b600080fd5b61004a6004803603810190610045919061065c565b61004c565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb85856040518363ffffffff1660e01b81526004016100a792919061074d565b6020604051808303816000875af11580156100c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100ea91906107ae565b610129576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161012090610838565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff166303003bc560008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1684866040518463ffffffff1660e01b815260040161018a939291906108e7565b6020604051808303816000875af11580156101a9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101cd919061093a565b90508181146101db8261024d565b6101e48461024d565b6040516020016101f5929190610a61565b60405160208183030381529060405290610245576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161023c9190610aae565b60405180910390fd5b505050505050565b60606000600161025c8461031b565b01905060008167ffffffffffffffff81111561027b5761027a610531565b5b6040519080825280601f01601f1916602001820160405280156102ad5781602001600182028036833780820191505090505b509050600082602001820190505b600115610310578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a858161030457610303610ad0565b5b049450600085036102bb575b819350505050919050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310610379577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161036f5761036e610ad0565b5b0492506040810190505b6d04ee2d6d415b85acef810000000083106103b6576d04ee2d6d415b85acef810000000083816103ac576103ab610ad0565b5b0492506020810190505b662386f26fc1000083106103e557662386f26fc1000083816103db576103da610ad0565b5b0492506010810190505b6305f5e100831061040e576305f5e100838161040457610403610ad0565b5b0492506008810190505b612710831061043357612710838161042957610428610ad0565b5b0492506004810190505b60648310610456576064838161044c5761044b610ad0565b5b0492506002810190505b600a8310610465576001810190505b80915050919050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104ad82610482565b9050919050565b6104bd816104a2565b81146104c857600080fd5b50565b6000813590506104da816104b4565b92915050565b6000819050919050565b6104f3816104e0565b81146104fe57600080fd5b50565b600081359050610510816104ea565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61056982610520565b810181811067ffffffffffffffff8211171561058857610587610531565b5b80604052505050565b600061059b61046e565b90506105a78282610560565b919050565b600067ffffffffffffffff8211156105c7576105c6610531565b5b6105d082610520565b9050602081019050919050565b82818337600083830152505050565b60006105ff6105fa846105ac565b610591565b90508281526020810184848401111561061b5761061a61051b565b5b6106268482856105dd565b509392505050565b600082601f83011261064357610642610516565b5b81356106538482602086016105ec565b91505092915050565b6000806000806080858703121561067657610675610478565b5b6000610684878288016104cb565b945050602061069587828801610501565b935050604085013567ffffffffffffffff8111156106b6576106b561047d565b5b6106c28782880161062e565b92505060606106d387828801610501565b91505092959194509250565b6000819050919050565b60006107046106ff6106fa84610482565b6106df565b610482565b9050919050565b6000610716826106e9565b9050919050565b60006107288261070b565b9050919050565b6107388161071d565b82525050565b610747816104e0565b82525050565b6000604082019050610762600083018561072f565b61076f602083018461073e565b9392505050565b60008115159050919050565b61078b81610776565b811461079657600080fd5b50565b6000815190506107a881610782565b92915050565b6000602082840312156107c4576107c3610478565b5b60006107d284828501610799565b91505092915050565b600082825260208201905092915050565b7f4552432d3230207472616e73666572206661696c656400000000000000000000600082015250565b60006108226016836107db565b915061082d826107ec565b602082019050919050565b6000602082019050818103600083015261085181610815565b9050919050565b600061086382610482565b9050919050565b61087381610858565b82525050565b600081519050919050565b60005b838110156108a2578082015181840152602081019050610887565b60008484015250505050565b60006108b982610879565b6108c381856107db565b93506108d3818560208601610884565b6108dc81610520565b840191505092915050565b60006060820190506108fc600083018661086a565b610909602083018561073e565b818103604083015261091b81846108ae565b9050949350505050565b600081519050610934816104ea565b92915050565b6000602082840312156109505761094f610478565b5b600061095e84828501610925565b91505092915050565b600081905092915050565b7f4946756e546f6b656e2e62616e6b53656e64207375636365656465642062757460008201527f207472616e73666572726564207468652077726f6e6720616d6f756e74000000602082015250565b60006109ce603d83610967565b91506109d982610972565b603d82019050919050565b7f73656e74416d6f756e7420000000000000000000000000000000000000000000815250565b6000610a1582610879565b610a1f8185610967565b9350610a2f818560208601610884565b80840191505092915050565b7f6578706563746564200000000000000000000000000000000000000000000000815250565b6000610a6c826109c1565b9150610a77826109e4565b600b82019150610a878285610a0a565b9150610a9282610a3b565b600982019150610aa28284610a0a565b91508190509392505050565b60006020820190508181036000830152610ac881846108ae565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fdfea2646970667358221220f0624f3122eb5227189289362e14705abd19c84ea5ee45bc30dbf8eedcb157b064736f6c63430008180033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063264c325814610030575b600080fd5b61004a6004803603810190610045919061065c565b61004c565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb85856040518363ffffffff1660e01b81526004016100a792919061074d565b6020604051808303816000875af11580156100c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100ea91906107ae565b610129576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161012090610838565b60405180910390fd5b600061080073ffffffffffffffffffffffffffffffffffffffff166303003bc560008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1684866040518463ffffffff1660e01b815260040161018a939291906108e7565b6020604051808303816000875af11580156101a9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101cd919061093a565b90508181146101db8261024d565b6101e48461024d565b6040516020016101f5929190610a61565b60405160208183030381529060405290610245576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161023c9190610aae565b60405180910390fd5b505050505050565b60606000600161025c8461031b565b01905060008167ffffffffffffffff81111561027b5761027a610531565b5b6040519080825280601f01601f1916602001820160405280156102ad5781602001600182028036833780820191505090505b509050600082602001820190505b600115610310578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a858161030457610303610ad0565b5b049450600085036102bb575b819350505050919050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310610379577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161036f5761036e610ad0565b5b0492506040810190505b6d04ee2d6d415b85acef810000000083106103b6576d04ee2d6d415b85acef810000000083816103ac576103ab610ad0565b5b0492506020810190505b662386f26fc1000083106103e557662386f26fc1000083816103db576103da610ad0565b5b0492506010810190505b6305f5e100831061040e576305f5e100838161040457610403610ad0565b5b0492506008810190505b612710831061043357612710838161042957610428610ad0565b5b0492506004810190505b60648310610456576064838161044c5761044b610ad0565b5b0492506002810190505b600a8310610465576001810190505b80915050919050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104ad82610482565b9050919050565b6104bd816104a2565b81146104c857600080fd5b50565b6000813590506104da816104b4565b92915050565b6000819050919050565b6104f3816104e0565b81146104fe57600080fd5b50565b600081359050610510816104ea565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61056982610520565b810181811067ffffffffffffffff8211171561058857610587610531565b5b80604052505050565b600061059b61046e565b90506105a78282610560565b919050565b600067ffffffffffffffff8211156105c7576105c6610531565b5b6105d082610520565b9050602081019050919050565b82818337600083830152505050565b60006105ff6105fa846105ac565b610591565b90508281526020810184848401111561061b5761061a61051b565b5b6106268482856105dd565b509392505050565b600082601f83011261064357610642610516565b5b81356106538482602086016105ec565b91505092915050565b6000806000806080858703121561067657610675610478565b5b6000610684878288016104cb565b945050602061069587828801610501565b935050604085013567ffffffffffffffff8111156106b6576106b561047d565b5b6106c28782880161062e565b92505060606106d387828801610501565b91505092959194509250565b6000819050919050565b60006107046106ff6106fa84610482565b6106df565b610482565b9050919050565b6000610716826106e9565b9050919050565b60006107288261070b565b9050919050565b6107388161071d565b82525050565b610747816104e0565b82525050565b6000604082019050610762600083018561072f565b61076f602083018461073e565b9392505050565b60008115159050919050565b61078b81610776565b811461079657600080fd5b50565b6000815190506107a881610782565b92915050565b6000602082840312156107c4576107c3610478565b5b60006107d284828501610799565b91505092915050565b600082825260208201905092915050565b7f4552432d3230207472616e73666572206661696c656400000000000000000000600082015250565b60006108226016836107db565b915061082d826107ec565b602082019050919050565b6000602082019050818103600083015261085181610815565b9050919050565b600061086382610482565b9050919050565b61087381610858565b82525050565b600081519050919050565b60005b838110156108a2578082015181840152602081019050610887565b60008484015250505050565b60006108b982610879565b6108c381856107db565b93506108d3818560208601610884565b6108dc81610520565b840191505092915050565b60006060820190506108fc600083018661086a565b610909602083018561073e565b818103604083015261091b81846108ae565b9050949350505050565b600081519050610934816104ea565b92915050565b6000602082840312156109505761094f610478565b5b600061095e84828501610925565b91505092915050565b600081905092915050565b7f4946756e546f6b656e2e62616e6b53656e64207375636365656465642062757460008201527f207472616e73666572726564207468652077726f6e6720616d6f756e74000000602082015250565b60006109ce603d83610967565b91506109d982610972565b603d82019050919050565b7f73656e74416d6f756e7420000000000000000000000000000000000000000000815250565b6000610a1582610879565b610a1f8185610967565b9350610a2f818560208601610884565b80840191505092915050565b7f6578706563746564200000000000000000000000000000000000000000000000815250565b6000610a6c826109c1565b9150610a77826109e4565b600b82019150610a878285610a0a565b9150610a9282610a3b565b600982019150610aa28284610a0a565b91508190509392505050565b60006020820190508181036000830152610ac881846108ae565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fdfea2646970667358221220f0624f3122eb5227189289362e14705abd19c84ea5ee45bc30dbf8eedcb157b064736f6c63430008180033", "linkReferences": {}, "deployedLinkReferences": {} } diff --git a/x/evm/embeds/artifacts/contracts/TestFunTokenPrecompileLocalGas.sol/TestFunTokenPrecompileLocalGas.json b/x/evm/embeds/artifacts/contracts/TestFunTokenPrecompileLocalGas.sol/TestFunTokenPrecompileLocalGas.json new file mode 100644 index 000000000..723b779b5 --- /dev/null +++ b/x/evm/embeds/artifacts/contracts/TestFunTokenPrecompileLocalGas.sol/TestFunTokenPrecompileLocalGas.json @@ -0,0 +1,63 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "TestFunTokenPrecompileLocalGas", + "sourceName": "contracts/TestFunTokenPrecompileLocalGas.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "erc20_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "string", + "name": "bech32Recipient", + "type": "string" + } + ], + "name": "callBankSend", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "string", + "name": "bech32Recipient", + "type": "string" + }, + { + "internalType": "uint256", + "name": "customGas", + "type": "uint256" + } + ], + "name": "callBankSendLocalGas", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50604051610b6a380380610b6a833981810160405281019061003291906100db565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610108565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a88261007d565b9050919050565b6100b88161009d565b81146100c357600080fd5b50565b6000815190506100d5816100af565b92915050565b6000602082840312156100f1576100f0610078565b5b60006100ff848285016100c6565b91505092915050565b610a53806101176000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806359b6ed891461003b57806390d2b5e714610057575b600080fd5b6100556004803603810190610050919061066b565b610073565b005b610071600480360381019061006c91906106da565b610198565b005b600061080073ffffffffffffffffffffffffffffffffffffffff166303003bc58360008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1687876040518563ffffffff1660e01b81526004016100d593929190610805565b60206040518083038160008887f11580156100f4573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906101199190610858565b9050838114610127826102ba565b610130866102ba565b60405160200161014192919061097f565b60405160208183030381529060405290610191576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161018891906109cc565b60405180910390fd5b5050505050565b600061080073ffffffffffffffffffffffffffffffffffffffff166303003bc560008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1685856040518463ffffffff1660e01b81526004016101f993929190610805565b6020604051808303816000875af1158015610218573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061023c9190610858565b905082811461024a826102ba565b610253856102ba565b60405160200161026492919061097f565b604051602081830303815290604052906102b4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102ab91906109cc565b60405180910390fd5b50505050565b6060600060016102c984610388565b01905060008167ffffffffffffffff8111156102e8576102e7610540565b5b6040519080825280601f01601f19166020018201604052801561031a5781602001600182028036833780820191505090505b509050600082602001820190505b60011561037d578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a8581610371576103706109ee565b5b04945060008503610328575b819350505050919050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000083106103e6577a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000083816103dc576103db6109ee565b5b0492506040810190505b6d04ee2d6d415b85acef81000000008310610423576d04ee2d6d415b85acef81000000008381610419576104186109ee565b5b0492506020810190505b662386f26fc10000831061045257662386f26fc100008381610448576104476109ee565b5b0492506010810190505b6305f5e100831061047b576305f5e1008381610471576104706109ee565b5b0492506008810190505b61271083106104a0576127108381610496576104956109ee565b5b0492506004810190505b606483106104c357606483816104b9576104b86109ee565b5b0492506002810190505b600a83106104d2576001810190505b80915050919050565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b610502816104ef565b811461050d57600080fd5b50565b60008135905061051f816104f9565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6105788261052f565b810181811067ffffffffffffffff8211171561059757610596610540565b5b80604052505050565b60006105aa6104db565b90506105b6828261056f565b919050565b600067ffffffffffffffff8211156105d6576105d5610540565b5b6105df8261052f565b9050602081019050919050565b82818337600083830152505050565b600061060e610609846105bb565b6105a0565b90508281526020810184848401111561062a5761062961052a565b5b6106358482856105ec565b509392505050565b600082601f83011261065257610651610525565b5b81356106628482602086016105fb565b91505092915050565b600080600060608486031215610684576106836104e5565b5b600061069286828701610510565b935050602084013567ffffffffffffffff8111156106b3576106b26104ea565b5b6106bf8682870161063d565b92505060406106d086828701610510565b9150509250925092565b600080604083850312156106f1576106f06104e5565b5b60006106ff85828601610510565b925050602083013567ffffffffffffffff8111156107205761071f6104ea565b5b61072c8582860161063d565b9150509250929050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061076182610736565b9050919050565b61077181610756565b82525050565b610780816104ef565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b838110156107c05780820151818401526020810190506107a5565b60008484015250505050565b60006107d782610786565b6107e18185610791565b93506107f18185602086016107a2565b6107fa8161052f565b840191505092915050565b600060608201905061081a6000830186610768565b6108276020830185610777565b818103604083015261083981846107cc565b9050949350505050565b600081519050610852816104f9565b92915050565b60006020828403121561086e5761086d6104e5565b5b600061087c84828501610843565b91505092915050565b600081905092915050565b7f4946756e546f6b656e2e62616e6b53656e64207375636365656465642062757460008201527f207472616e73666572726564207468652077726f6e6720616d6f756e74000000602082015250565b60006108ec603d83610885565b91506108f782610890565b603d82019050919050565b7f73656e74416d6f756e7420000000000000000000000000000000000000000000815250565b600061093382610786565b61093d8185610885565b935061094d8185602086016107a2565b80840191505092915050565b7f6578706563746564200000000000000000000000000000000000000000000000815250565b600061098a826108df565b915061099582610902565b600b820191506109a58285610928565b91506109b082610959565b6009820191506109c08284610928565b91508190509392505050565b600060208201905081810360008301526109e681846107cc565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fdfea26469706673582212203b80aa046928bf6b13cd29898611f9a4f048688dc21bfd44094fd59d8080e66064736f6c63430008180033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806359b6ed891461003b57806390d2b5e714610057575b600080fd5b6100556004803603810190610050919061066b565b610073565b005b610071600480360381019061006c91906106da565b610198565b005b600061080073ffffffffffffffffffffffffffffffffffffffff166303003bc58360008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1687876040518563ffffffff1660e01b81526004016100d593929190610805565b60206040518083038160008887f11580156100f4573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906101199190610858565b9050838114610127826102ba565b610130866102ba565b60405160200161014192919061097f565b60405160208183030381529060405290610191576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161018891906109cc565b60405180910390fd5b5050505050565b600061080073ffffffffffffffffffffffffffffffffffffffff166303003bc560008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1685856040518463ffffffff1660e01b81526004016101f993929190610805565b6020604051808303816000875af1158015610218573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061023c9190610858565b905082811461024a826102ba565b610253856102ba565b60405160200161026492919061097f565b604051602081830303815290604052906102b4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102ab91906109cc565b60405180910390fd5b50505050565b6060600060016102c984610388565b01905060008167ffffffffffffffff8111156102e8576102e7610540565b5b6040519080825280601f01601f19166020018201604052801561031a5781602001600182028036833780820191505090505b509050600082602001820190505b60011561037d578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a8581610371576103706109ee565b5b04945060008503610328575b819350505050919050565b600080600090507a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000083106103e6577a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000083816103dc576103db6109ee565b5b0492506040810190505b6d04ee2d6d415b85acef81000000008310610423576d04ee2d6d415b85acef81000000008381610419576104186109ee565b5b0492506020810190505b662386f26fc10000831061045257662386f26fc100008381610448576104476109ee565b5b0492506010810190505b6305f5e100831061047b576305f5e1008381610471576104706109ee565b5b0492506008810190505b61271083106104a0576127108381610496576104956109ee565b5b0492506004810190505b606483106104c357606483816104b9576104b86109ee565b5b0492506002810190505b600a83106104d2576001810190505b80915050919050565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b610502816104ef565b811461050d57600080fd5b50565b60008135905061051f816104f9565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6105788261052f565b810181811067ffffffffffffffff8211171561059757610596610540565b5b80604052505050565b60006105aa6104db565b90506105b6828261056f565b919050565b600067ffffffffffffffff8211156105d6576105d5610540565b5b6105df8261052f565b9050602081019050919050565b82818337600083830152505050565b600061060e610609846105bb565b6105a0565b90508281526020810184848401111561062a5761062961052a565b5b6106358482856105ec565b509392505050565b600082601f83011261065257610651610525565b5b81356106628482602086016105fb565b91505092915050565b600080600060608486031215610684576106836104e5565b5b600061069286828701610510565b935050602084013567ffffffffffffffff8111156106b3576106b26104ea565b5b6106bf8682870161063d565b92505060406106d086828701610510565b9150509250925092565b600080604083850312156106f1576106f06104e5565b5b60006106ff85828601610510565b925050602083013567ffffffffffffffff8111156107205761071f6104ea565b5b61072c8582860161063d565b9150509250929050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061076182610736565b9050919050565b61077181610756565b82525050565b610780816104ef565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b838110156107c05780820151818401526020810190506107a5565b60008484015250505050565b60006107d782610786565b6107e18185610791565b93506107f18185602086016107a2565b6107fa8161052f565b840191505092915050565b600060608201905061081a6000830186610768565b6108276020830185610777565b818103604083015261083981846107cc565b9050949350505050565b600081519050610852816104f9565b92915050565b60006020828403121561086e5761086d6104e5565b5b600061087c84828501610843565b91505092915050565b600081905092915050565b7f4946756e546f6b656e2e62616e6b53656e64207375636365656465642062757460008201527f207472616e73666572726564207468652077726f6e6720616d6f756e74000000602082015250565b60006108ec603d83610885565b91506108f782610890565b603d82019050919050565b7f73656e74416d6f756e7420000000000000000000000000000000000000000000815250565b600061093382610786565b61093d8185610885565b935061094d8185602086016107a2565b80840191505092915050565b7f6578706563746564200000000000000000000000000000000000000000000000815250565b600061098a826108df565b915061099582610902565b600b820191506109a58285610928565b91506109b082610959565b6009820191506109c08284610928565b91508190509392505050565b600060208201905081810360008301526109e681846107cc565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fdfea26469706673582212203b80aa046928bf6b13cd29898611f9a4f048688dc21bfd44094fd59d8080e66064736f6c63430008180033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/x/evm/embeds/contracts/TestERC20TransferThenPrecompileSend.sol b/x/evm/embeds/contracts/TestERC20TransferThenPrecompileSend.sol index b51d0367e..984c00698 100644 --- a/x/evm/embeds/contracts/TestERC20TransferThenPrecompileSend.sol +++ b/x/evm/embeds/contracts/TestERC20TransferThenPrecompileSend.sol @@ -25,15 +25,21 @@ contract TestERC20TransferThenPrecompileSend { "ERC-20 transfer failed" ); - (bool success, ) = FUNTOKEN_PRECOMPILE_ADDRESS.call( - abi.encodeWithSignature( - "bankSend(address,uint256,string)", - erc20, - uint256(precompileAmount), - precompileRecipient - ) + uint256 sentAmount = FUNTOKEN_PRECOMPILE.bankSend( + erc20, + precompileAmount, + precompileRecipient ); - require(success, string.concat("Failed to call bankSend")); + require( + sentAmount == precompileAmount, + string.concat( + "IFunToken.bankSend succeeded but transferred the wrong amount", + "sentAmount ", + Strings.toString(sentAmount), + "expected ", + Strings.toString(precompileAmount) + ) + ); } } diff --git a/x/evm/embeds/contracts/TestFunTokenPrecompileLocalGas.sol b/x/evm/embeds/contracts/TestFunTokenPrecompileLocalGas.sol new file mode 100644 index 000000000..abb239381 --- /dev/null +++ b/x/evm/embeds/contracts/TestFunTokenPrecompileLocalGas.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IFunToken.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + +contract TestFunTokenPrecompileLocalGas { + address erc20; + + constructor(address erc20_) { + erc20 = erc20_; + } + + // Calls bankSend of the FunToken Precompile with the default gas. + // Internal call could use all the gas for the parent call. + function callBankSend( + uint256 amount, + string memory bech32Recipient + ) public { + uint256 sentAmount = FUNTOKEN_PRECOMPILE.bankSend( + erc20, + amount, + bech32Recipient + ); + require( + sentAmount == amount, + string.concat( + "IFunToken.bankSend succeeded but transferred the wrong amount", + "sentAmount ", + Strings.toString(sentAmount), + "expected ", + Strings.toString(amount) + ) + ); + } + + // Calls bankSend of the FunToken Precompile with the gas amount set in parameter. + // Internal call should fail if the gas provided is insufficient. + function callBankSendLocalGas( + uint256 amount, + string memory bech32Recipient, + uint256 customGas + ) public { + uint256 sentAmount = FUNTOKEN_PRECOMPILE.bankSend{gas: customGas}( + erc20, + amount, + bech32Recipient + ); + require( + sentAmount == amount, + string.concat( + "IFunToken.bankSend succeeded but transferred the wrong amount", + "sentAmount ", + Strings.toString(sentAmount), + "expected ", + Strings.toString(amount) + ) + ); + } +} diff --git a/x/evm/embeds/embeds.go b/x/evm/embeds/embeds.go index 202b26525..b81a4b276 100644 --- a/x/evm/embeds/embeds.go +++ b/x/evm/embeds/embeds.go @@ -29,6 +29,8 @@ var ( testErc20MaliciousNameJson []byte //go:embed artifacts/contracts/TestERC20MaliciousTransfer.sol/TestERC20MaliciousTransfer.json testErc20MaliciousTransferJson []byte + //go:embed artifacts/contracts/TestFunTokenPrecompileLocalGas.sol/TestFunTokenPrecompileLocalGas.json + testFunTokenPrecompileLocalGasJson []byte //go:embed artifacts/contracts/TestERC20TransferThenPrecompileSend.sol/TestERC20TransferThenPrecompileSend.json testERC20TransferThenPrecompileSendJson []byte //go:embed artifacts/contracts/TestNativeSendThenPrecompileSend.sol/TestNativeSendThenPrecompileSend.json @@ -82,6 +84,12 @@ var ( Name: "TestERC20MaliciousTransfer.sol", EmbedJSON: testErc20MaliciousTransferJson, } + // SmartContract_TestFunTokenPrecompileLocalGas is a test contract + // which allows precompile execution with custom local gas set (calling precompile within contract) + SmartContract_TestFunTokenPrecompileLocalGas = CompiledEvmContract{ + Name: "TestFunTokenPrecompileLocalGas.sol", + EmbedJSON: testFunTokenPrecompileLocalGasJson, + } // SmartContract_TestNativeSendThenPrecompileSendJson is a test contract // that performs two sends in a single call: a native nibi send and a precompile bankSend. // It tests a race condition where the state DB commit @@ -118,6 +126,7 @@ func init() { SmartContract_TestERC20.MustLoad() SmartContract_TestERC20MaliciousName.MustLoad() SmartContract_TestERC20MaliciousTransfer.MustLoad() + SmartContract_TestFunTokenPrecompileLocalGas.MustLoad() SmartContract_TestNativeSendThenPrecompileSendJson.MustLoad() SmartContract_TestERC20TransferThenPrecompileSend.MustLoad() SmartContract_TestPrecompileSelfCallRevert.MustLoad() diff --git a/x/evm/embeds/embeds_test.go b/x/evm/embeds/embeds_test.go index 94f208dbb..91a5bf830 100644 --- a/x/evm/embeds/embeds_test.go +++ b/x/evm/embeds/embeds_test.go @@ -16,6 +16,7 @@ func TestLoadContracts(t *testing.T) { embeds.SmartContract_TestERC20.MustLoad() embeds.SmartContract_TestERC20MaliciousName.MustLoad() embeds.SmartContract_TestERC20MaliciousTransfer.MustLoad() + embeds.SmartContract_TestFunTokenPrecompileLocalGas.MustLoad() embeds.SmartContract_TestNativeSendThenPrecompileSendJson.MustLoad() embeds.SmartContract_TestERC20TransferThenPrecompileSend.MustLoad() }) diff --git a/x/evm/errors.go b/x/evm/errors.go index 87d5c747b..94ce6d3d8 100644 --- a/x/evm/errors.go +++ b/x/evm/errors.go @@ -2,19 +2,11 @@ package evm import ( - "errors" "fmt" errorsmod "cosmossdk.io/errors" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -// Errors related to ERC20 calls and FunToken mappings. -const ( - ErrOwnable = "Ownable: caller is not the owner" ) const ( @@ -26,14 +18,10 @@ const ( codeErrInvalidRefund codeErrInvalidGasCap codeErrInvalidBaseFee - codeErrGasOverflow codeErrInvalidAccount - codeErrInvalidGasLimit codeErrInactivePrecompile ) -var ErrPostTxProcessing = errors.New("failed to execute post processing") - var ( // ErrInvalidState returns an error resulting from an invalid Storage State. ErrInvalidState = errorsmod.Register(ModuleName, codeErrInvalidState, "invalid storage state") @@ -59,53 +47,24 @@ var ( // ErrInvalidBaseFee returns an error if the base fee cap value is invalid ErrInvalidBaseFee = errorsmod.Register(ModuleName, codeErrInvalidBaseFee, "invalid base fee") - // ErrGasOverflow returns an error if gas computation overlow/underflow - ErrGasOverflow = errorsmod.Register(ModuleName, codeErrGasOverflow, "gas computation overflow/underflow") - // ErrInvalidAccount returns an error if the account is not an EVM compatible account ErrInvalidAccount = errorsmod.Register(ModuleName, codeErrInvalidAccount, "account type is not a valid ethereum account") - - // ErrInvalidGasLimit returns an error if gas limit value is invalid - ErrInvalidGasLimit = errorsmod.Register(ModuleName, codeErrInvalidGasLimit, "invalid gas limit") ) -// NewExecErrorWithReason unpacks the revert return bytes and returns a wrapped error +// NewRevertError unpacks the revert return bytes and returns a wrapped error // with the return reason. -func NewExecErrorWithReason(revertReason []byte) *RevertError { - result := common.CopyBytes(revertReason) - reason, errUnpack := abi.UnpackRevert(result) - - var err error - errPrefix := "execution reverted" - if errUnpack == nil { - reasonStr := reason - err = fmt.Errorf("%s with reason \"%v\"", errPrefix, reasonStr) - } else if string(result) != "" { - reasonStr := string(result) - err = fmt.Errorf("%s with reason \"%v\"", errPrefix, reasonStr) - } else { - err = errors.New(errPrefix) - } - return &RevertError{ - error: err, - reason: hexutil.Encode(result), +func NewRevertError(revertReason []byte) error { + reason, unpackingError := abi.UnpackRevert(revertReason) + + if unpackingError != nil { + return fmt.Errorf("execution reverted, but unable to parse reason \"%v\"", string(revertReason)) } + + return fmt.Errorf("execution reverted with reason \"%v\"", reason) } // RevertError is an API error that encompass an EVM revert with JSON error // code and a binary data blob. type RevertError struct { error - reason string // revert reason hex encoded -} - -// ErrorCode returns the JSON error code for a revert. -// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal -func (e *RevertError) ErrorCode() int { - return 3 -} - -// ErrorData returns the hex encoded revert reason. -func (e *RevertError) ErrorData() any { - return e.reason } diff --git a/x/evm/evmmodule/genesis_test.go b/x/evm/evmmodule/genesis_test.go index 72b884082..cc9d8c247 100644 --- a/x/evm/evmmodule/genesis_test.go +++ b/x/evm/evmmodule/genesis_test.go @@ -54,11 +54,11 @@ func (s *Suite) TestExportInitGenesis() { s.Require().NoError(err) // Transfer ERC-20 tokens to user A - _, err = deps.EvmKeeper.ERC20().Transfer(erc20Addr, fromUser, toUserA, amountToSendA, deps.Ctx) + _, _, err = deps.EvmKeeper.ERC20().Transfer(erc20Addr, fromUser, toUserA, amountToSendA, deps.Ctx) s.Require().NoError(err) // Transfer ERC-20 tokens to user B - _, err = deps.EvmKeeper.ERC20().Transfer(erc20Addr, fromUser, toUserB, amountToSendB, deps.Ctx) + _, _, err = deps.EvmKeeper.ERC20().Transfer(erc20Addr, fromUser, toUserB, amountToSendB, deps.Ctx) s.Require().NoError(err) // Create fungible token from bank coin diff --git a/x/evm/evmtest/test_deps.go b/x/evm/evmtest/test_deps.go index 6d2e830af..2d1948972 100644 --- a/x/evm/evmtest/test_deps.go +++ b/x/evm/evmtest/test_deps.go @@ -61,3 +61,8 @@ func (deps *TestDeps) GethSigner() gethcore.Signer { func (deps TestDeps) GoCtx() context.Context { return sdk.WrapSDKContext(deps.Ctx) } + +func (deps TestDeps) ResetGasMeter() { + deps.EvmKeeper.ResetTransientGasUsed(deps.Ctx) + deps.EvmKeeper.ResetGasMeterAndConsumeGas(deps.Ctx, 0) +} diff --git a/x/evm/keeper/call_contract.go b/x/evm/keeper/call_contract.go new file mode 100644 index 000000000..ad4ac90c7 --- /dev/null +++ b/x/evm/keeper/call_contract.go @@ -0,0 +1,150 @@ +package keeper + +import ( + "fmt" + "math/big" + "strings" + + "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + gethabi "github.com/ethereum/go-ethereum/accounts/abi" + gethcommon "github.com/ethereum/go-ethereum/common" + gethcore "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + + "github.com/NibiruChain/nibiru/v2/x/evm" +) + +// CallContract invokes a smart contract on the method specified by [methodName] +// using the given [args]. +// +// Parameters: +// - ctx: The SDK context for the transaction. +// - abi: The ABI (Application Binary Interface) of the smart contract. +// - fromAcc: The Ethereum address of the account initiating the contract call. +// - contract: Pointer to the Ethereum address of the contract to be called. +// - commit: Boolean flag indicating whether to commit the transaction (true) or simulate it (false). +// - methodName: The name of the contract method to be called. +// - args: Variadic parameter for the arguments to be passed to the contract method. +// +// Note: This function handles both contract method calls and simulations, +// depending on the 'commit' parameter. +func (k Keeper) CallContract( + ctx sdk.Context, + abi *gethabi.ABI, + fromAcc gethcommon.Address, + contract *gethcommon.Address, + commit bool, + gasLimit uint64, + methodName string, + args ...any, +) (evmResp *evm.MsgEthereumTxResponse, err error) { + contractInput, err := abi.Pack(methodName, args...) + if err != nil { + return nil, fmt.Errorf("failed to pack ABI args: %w", err) + } + evmResp, _, err = k.CallContractWithInput(ctx, fromAcc, contract, commit, contractInput, gasLimit) + return evmResp, err +} + +// CallContractWithInput invokes a smart contract with the given [contractInput] +// or deploys a new contract. +// +// Parameters: +// - ctx: The SDK context for the transaction. +// - fromAcc: The Ethereum address of the account initiating the contract call. +// - contract: Pointer to the Ethereum address of the contract. Nil if new +// contract is deployed. +// - commit: Boolean flag indicating whether to commit the transaction (true) +// or simulate it (false). +// - contractInput: Hexadecimal-encoded bytes use as input data to the call. +// +// Note: This function handles both contract method calls and simulations, +// depending on the 'commit' parameter. It uses a default gas limit. +func (k Keeper) CallContractWithInput( + ctx sdk.Context, + fromAcc gethcommon.Address, + contract *gethcommon.Address, + commit bool, + contractInput []byte, + gasLimit uint64, +) (evmResp *evm.MsgEthereumTxResponse, evmObj *vm.EVM, err error) { + // This is a `defer` pattern to add behavior that runs in the case that the + // error is non-nil, creating a concise way to add extra information. + defer HandleOutOfGasPanic(&err, "CallContractError") + nonce := k.GetAccNonce(ctx, fromAcc) + + unusedBigInt := big.NewInt(0) + evmMsg := gethcore.NewMessage( + fromAcc, + contract, + nonce, + unusedBigInt, // amount + gasLimit, + unusedBigInt, // gasFeeCap + unusedBigInt, // gasTipCap + unusedBigInt, // gasPrice + contractInput, + gethcore.AccessList{}, + !commit, // isFake + ) + + // Apply EVM message + evmCfg, err := k.GetEVMConfig( + ctx, + sdk.ConsAddress(ctx.BlockHeader().ProposerAddress), + k.EthChainID(ctx), + ) + if err != nil { + err = errors.Wrapf(err, "failed to load EVM config") + return + } + + // Generating TxConfig with an empty tx hash as there is no actual eth tx + // sent by a user + txConfig := k.TxConfig(ctx, gethcommon.BigToHash(big.NewInt(0))) + + evmResp, evmObj, err = k.ApplyEvmMsg( + ctx, evmMsg, evm.NewNoOpTracer(), commit, evmCfg, txConfig, true, + ) + if err != nil { + // We don't know the actual gas used, so consuming the gas limit + k.ResetGasMeterAndConsumeGas(ctx, gasLimit) + err = errors.Wrap(err, "failed to apply ethereum core message") + return + } + + if evmResp.Failed() { + k.ResetGasMeterAndConsumeGas(ctx, evmResp.GasUsed) + if strings.Contains(evmResp.VmError, vm.ErrOutOfGas.Error()) { + err = fmt.Errorf("gas required exceeds allowance (%d)", gasLimit) + return + } + if evmResp.VmError == vm.ErrExecutionReverted.Error() { + err = fmt.Errorf("VMError: %w", evm.NewRevertError(evmResp.Ret)) + return + } + err = fmt.Errorf("VMError: %s", evmResp.VmError) + return + } + + // Success, update block gas used and bloom filter + if commit { + blockGasUsed, err := k.AddToBlockGasUsed(ctx, evmResp.GasUsed) + if err != nil { + k.ResetGasMeterAndConsumeGas(ctx, ctx.GasMeter().Limit()) + return nil, nil, errors.Wrap(err, "error adding transient gas used to block") + } + k.ResetGasMeterAndConsumeGas(ctx, blockGasUsed) + k.updateBlockBloom(ctx, evmResp, uint64(txConfig.LogIndex)) + // TODO: remove after migrating logs + //err = k.EmitLogEvents(ctx, evmResp) + //if err != nil { + // return nil, nil, errors.Wrap(err, "error emitting tx logs") + //} + + // blockTxIdx := uint64(txConfig.TxIndex) + 1 + // k.EvmState.BlockTxIndex.Set(ctx, blockTxIdx) + } + return evmResp, evmObj, nil +} diff --git a/x/evm/keeper/erc20.go b/x/evm/keeper/erc20.go index 79c189f1e..ed32a5788 100644 --- a/x/evm/keeper/erc20.go +++ b/x/evm/keeper/erc20.go @@ -9,15 +9,23 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" gethabi "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" - gethcore "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - - serverconfig "github.com/NibiruChain/nibiru/v2/app/server/config" "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/embeds" ) +const ( + // Erc20GasLimitDeploy only used internally when deploying ERC20Minter. + // Deployment requires ~1_600_000 gas + Erc20GasLimitDeploy uint64 = 2_000_000 + // Erc20GasLimitQuery used only for querying name, symbol and decimals + // Cannot be heavy. Only if the contract is malicious. + Erc20GasLimitQuery uint64 = 100_000 + // Erc20GasLimitExecute used for transfer, mint and burn. + // All must not exceed 200_000 + Erc20GasLimitExecute uint64 = 200_000 +) + // ERC20 returns a mutable reference to the keeper with an ERC20 contract ABI and // Go functions corresponding to contract calls in the ERC20 standard like "mint" // and "transfer" in the ERC20 standard. @@ -52,12 +60,7 @@ func (e erc20Calls) Mint( contract, from, to gethcommon.Address, amount *big.Int, ctx sdk.Context, ) (evmResp *evm.MsgEthereumTxResponse, err error) { - input, err := e.ABI.Pack("mint", to, amount) - if err != nil { - return nil, fmt.Errorf("failed to pack ABI args: %w", err) - } - evmResp, _, err = e.CallContractWithInput(ctx, from, &contract, true, input) - return evmResp, err + return e.CallContract(ctx, e.ABI, from, &contract, true, Erc20GasLimitExecute, "mint", to, amount) } /* @@ -73,37 +76,32 @@ Transfer implements "ERC20.transfer" func (e erc20Calls) Transfer( contract, from, to gethcommon.Address, amount *big.Int, ctx sdk.Context, -) (balanceIncrease *big.Int, err error) { +) (balanceIncrease *big.Int, resp *evm.MsgEthereumTxResponse, err error) { recipientBalanceBefore, err := e.BalanceOf(contract, to, ctx) if err != nil { - return balanceIncrease, errors.Wrap(err, "failed to retrieve recipient balance") + return balanceIncrease, nil, errors.Wrap(err, "failed to retrieve recipient balance") } - input, err := e.ABI.Pack("transfer", to, amount) + resp, err = e.CallContract(ctx, e.ABI, from, &contract, true, Erc20GasLimitExecute, "transfer", to, amount) if err != nil { - return balanceIncrease, fmt.Errorf("failed to pack ABI args: %w", err) - } - - resp, _, err := e.CallContractWithInput(ctx, from, &contract, true, input) - if err != nil { - return balanceIncrease, err + return balanceIncrease, nil, err } var erc20Bool ERC20Bool err = e.ABI.UnpackIntoInterface(&erc20Bool, "transfer", resp.Ret) if err != nil { - return balanceIncrease, err + return balanceIncrease, nil, err } // Handle the case of success=false: https://github.com/NibiruChain/nibiru/issues/2080 success := erc20Bool.Value if !success { - return balanceIncrease, fmt.Errorf("transfer executed but returned success=false") + return balanceIncrease, nil, fmt.Errorf("transfer executed but returned success=false") } recipientBalanceAfter, err := e.BalanceOf(contract, to, ctx) if err != nil { - return balanceIncrease, errors.Wrap(err, "failed to retrieve recipient balance") + return balanceIncrease, nil, errors.Wrap(err, "failed to retrieve recipient balance") } balanceIncrease = new(big.Int).Sub(recipientBalanceAfter, recipientBalanceBefore) @@ -113,13 +111,13 @@ func (e erc20Calls) Transfer( // the call "amount". Instead, verify that the recipient got tokens and // return the amount. if balanceIncrease.Sign() <= 0 { - return balanceIncrease, fmt.Errorf( + return balanceIncrease, nil, fmt.Errorf( "amount of ERC20 tokens received MUST be positive: the balance of recipient %s would've changed by %v for token %s", to.Hex(), balanceIncrease.String(), contract.Hex(), ) } - return balanceIncrease, err + return balanceIncrease, resp, err } // BalanceOf retrieves the balance of an ERC20 token for a specific account. @@ -143,153 +141,7 @@ func (e erc20Calls) Burn( contract, from gethcommon.Address, amount *big.Int, ctx sdk.Context, ) (evmResp *evm.MsgEthereumTxResponse, err error) { - input, err := e.ABI.Pack("burn", amount) - if err != nil { - return - } - commit := true - evmResp, _, err = e.CallContractWithInput(ctx, from, &contract, commit, input) - return -} - -// CallContract invokes a smart contract on the method specified by [methodName] -// using the given [args]. -// -// Parameters: -// - ctx: The SDK context for the transaction. -// - abi: The ABI (Application Binary Interface) of the smart contract. -// - fromAcc: The Ethereum address of the account initiating the contract call. -// - contract: Pointer to the Ethereum address of the contract to be called. -// - commit: Boolean flag indicating whether to commit the transaction (true) or simulate it (false). -// - methodName: The name of the contract method to be called. -// - args: Variadic parameter for the arguments to be passed to the contract method. -// -// Note: This function handles both contract method calls and simulations, -// depending on the 'commit' parameter. It uses a default gas limit for -// simulations and estimates gas for actual transactions. -func (k Keeper) CallContract( - ctx sdk.Context, - abi *gethabi.ABI, - fromAcc gethcommon.Address, - contract *gethcommon.Address, - commit bool, - methodName string, - args ...any, -) (evmResp *evm.MsgEthereumTxResponse, err error) { - contractInput, err := abi.Pack(methodName, args...) - if err != nil { - return nil, fmt.Errorf("failed to pack ABI args: %w", err) - } - evmResp, _, err = k.CallContractWithInput( - ctx, fromAcc, contract, commit, contractInput, - ) - return evmResp, err -} - -// CallContractWithInput invokes a smart contract with the given [contractInput] -// or deploys a new contract. -// -// Parameters: -// - ctx: The SDK context for the transaction. -// - fromAcc: The Ethereum address of the account initiating the contract call. -// - contract: Pointer to the Ethereum address of the contract. Nil if new -// contract is deployed. -// - commit: Boolean flag indicating whether to commit the transaction (true) -// or simulate it (false). -// - contractInput: Hexadecimal-encoded bytes use as input data to the call. -// -// Note: This function handles both contract method calls and simulations, -// depending on the 'commit' parameter. It uses a default gas limit. -func (k Keeper) CallContractWithInput( - ctx sdk.Context, - fromAcc gethcommon.Address, - contract *gethcommon.Address, - commit bool, - contractInput []byte, -) (evmResp *evm.MsgEthereumTxResponse, evmObj *vm.EVM, err error) { - // This is a `defer` pattern to add behavior that runs in the case that the - // error is non-nil, creating a concise way to add extra information. - defer func() { - if err != nil { - err = fmt.Errorf("CallContractError: %w", err) - } - }() - nonce := k.GetAccNonce(ctx, fromAcc) - - // Gas cap sufficient for all "honest" ERC20 calls without malicious (gas - // intensive) code in contracts - gasLimit := serverconfig.DefaultEthCallGasLimit - - unusedBigInt := big.NewInt(0) - evmMsg := gethcore.NewMessage( - fromAcc, - contract, - nonce, - unusedBigInt, // amount - gasLimit, - unusedBigInt, // gasFeeCap - unusedBigInt, // gasTipCap - unusedBigInt, // gasPrice - contractInput, - gethcore.AccessList{}, - !commit, // isFake - ) - - // Apply EVM message - evmCfg, err := k.GetEVMConfig( - ctx, - sdk.ConsAddress(ctx.BlockHeader().ProposerAddress), - k.EthChainID(ctx), - ) - if err != nil { - err = errors.Wrapf(err, "failed to load EVM config") - return - } - - // Generating TxConfig with an empty tx hash as there is no actual eth tx - // sent by a user - txConfig := k.TxConfig(ctx, gethcommon.BigToHash(big.NewInt(0))) - - evmResp, evmObj, err = k.ApplyEvmMsg( - ctx, evmMsg, evm.NewNoOpTracer(), commit, evmCfg, txConfig, - ) - if err != nil { - // We don't know the actual gas used, so consuming the gas limit - k.ResetGasMeterAndConsumeGas(ctx, gasLimit) - err = errors.Wrap(err, "failed to apply ethereum core message") - return - } - if evmResp.Failed() { - k.ResetGasMeterAndConsumeGas(ctx, evmResp.GasUsed) - if evmResp.VmError != vm.ErrOutOfGas.Error() { - if evmResp.VmError == vm.ErrExecutionReverted.Error() { - err = fmt.Errorf("VMError: %w", evm.NewExecErrorWithReason(evmResp.Ret)) - return - } - err = fmt.Errorf("VMError: %s", evmResp.VmError) - return - } - err = fmt.Errorf("gas required exceeds allowance (%d)", gasLimit) - return - } else { - // Success, committing the state to ctx - if commit { - totalGasUsed, err := k.AddToBlockGasUsed(ctx, evmResp.GasUsed) - if err != nil { - k.ResetGasMeterAndConsumeGas(ctx, ctx.GasMeter().Limit()) - return nil, nil, errors.Wrap(err, "error adding transient gas used to block") - } - k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed) - k.updateBlockBloom(ctx, evmResp, uint64(txConfig.LogIndex)) - err = k.EmitEthereumTxEvents(ctx, contract, gethcore.LegacyTxType, evmMsg, evmResp) - if err != nil { - return nil, nil, errors.Wrap(err, "error emitting ethereum tx events") - } - blockTxIdx := uint64(txConfig.TxIndex) + 1 - k.EvmState.BlockTxIndex.Set(ctx, blockTxIdx) - } - return evmResp, evmObj, nil - } + return e.CallContract(ctx, e.ABI, from, &contract, true, Erc20GasLimitExecute, "burn", amount) } func (k Keeper) LoadERC20Name( @@ -317,10 +169,13 @@ func (k Keeper) LoadERC20String( methodName string, ) (out string, err error) { res, err := k.CallContract( - ctx, erc20Abi, + ctx, + erc20Abi, evm.EVM_MODULE_ADDRESS, &erc20Contract, - false, methodName, + false, + Erc20GasLimitQuery, + methodName, ) if err != nil { return out, err @@ -346,7 +201,9 @@ func (k Keeper) loadERC20Uint8( ctx, erc20Abi, evm.EVM_MODULE_ADDRESS, &erc20Contract, - false, methodName, + false, + Erc20GasLimitQuery, + methodName, ) if err != nil { return out, err @@ -375,6 +232,7 @@ func (k Keeper) LoadERC20BigInt( evm.EVM_MODULE_ADDRESS, &contract, false, + Erc20GasLimitQuery, methodName, args..., ) diff --git a/x/evm/keeper/erc20_test.go b/x/evm/keeper/erc20_test.go index c45ed10a1..9b9ef9715 100644 --- a/x/evm/keeper/erc20_test.go +++ b/x/evm/keeper/erc20_test.go @@ -20,7 +20,7 @@ func (s *Suite) TestERC20Calls() { contract, deps.Sender.EthAddr, evm.EVM_MODULE_ADDRESS, big.NewInt(69_420), deps.Ctx, ) - s.ErrorContains(err, evm.ErrOwnable) + s.ErrorContains(err, "Ownable: caller is not the owner") } s.T().Log("Mint tokens - Success") @@ -35,7 +35,7 @@ func (s *Suite) TestERC20Calls() { s.T().Log("Transfer - Not enough funds") { amt := big.NewInt(9_420) - _, err := deps.EvmKeeper.ERC20().Transfer(contract, deps.Sender.EthAddr, evm.EVM_MODULE_ADDRESS, amt, deps.Ctx) + _, _, err := deps.EvmKeeper.ERC20().Transfer(contract, deps.Sender.EthAddr, evm.EVM_MODULE_ADDRESS, amt, deps.Ctx) s.ErrorContains(err, "ERC20: transfer amount exceeds balance") // balances unchanged evmtest.AssertERC20BalanceEqual(s.T(), deps, contract, deps.Sender.EthAddr, big.NewInt(0)) @@ -45,7 +45,7 @@ func (s *Suite) TestERC20Calls() { s.T().Log("Transfer - Success (sanity check)") { amt := big.NewInt(9_420) - sentAmt, err := deps.EvmKeeper.ERC20().Transfer( + sentAmt, _, err := deps.EvmKeeper.ERC20().Transfer( contract, evm.EVM_MODULE_ADDRESS, deps.Sender.EthAddr, amt, deps.Ctx, ) s.Require().NoError(err) diff --git a/x/evm/keeper/funtoken_from_coin.go b/x/evm/keeper/funtoken_from_coin.go index 8075107c6..4dfaa5c92 100644 --- a/x/evm/keeper/funtoken_from_coin.go +++ b/x/evm/keeper/funtoken_from_coin.go @@ -79,7 +79,7 @@ func (k *Keeper) deployERC20ForBankCoin( // nil address for contract creation _, _, err = k.CallContractWithInput( - ctx, evm.EVM_MODULE_ADDRESS, nil, true, bytecodeForCall, + ctx, evm.EVM_MODULE_ADDRESS, nil, true, bytecodeForCall, Erc20GasLimitDeploy, ) if err != nil { return gethcommon.Address{}, errors.Wrap(err, "failed to deploy ERC20 contract") diff --git a/x/evm/keeper/funtoken_from_coin_test.go b/x/evm/keeper/funtoken_from_coin_test.go index 646391eea..29c3f2daa 100644 --- a/x/evm/keeper/funtoken_from_coin_test.go +++ b/x/evm/keeper/funtoken_from_coin_test.go @@ -225,6 +225,8 @@ func (s *FunTokenFromCoinSuite) TestConvertCoinToEvmAndBack() { ) s.Require().ErrorContains(err, "insufficient funds") + deps.ResetGasMeter() + s.T().Log("Convert erc-20 to back to bank coin") _, err = deps.EvmKeeper.CallContract( deps.Ctx, @@ -232,6 +234,7 @@ func (s *FunTokenFromCoinSuite) TestConvertCoinToEvmAndBack() { alice.EthAddr, &precompile.PrecompileAddr_FunToken, true, + precompile.FunTokenGasLimitBankSend, "bankSend", funToken.Erc20Addr.Address, big.NewInt(10), @@ -259,6 +262,7 @@ func (s *FunTokenFromCoinSuite) TestConvertCoinToEvmAndBack() { alice.EthAddr, &precompile.PrecompileAddr_FunToken, true, + precompile.FunTokenGasLimitBankSend, "bankSend", funToken.Erc20Addr.Address, big.NewInt(10), @@ -354,6 +358,7 @@ func (s *FunTokenFromCoinSuite) TestNativeSendThenPrecompileSend() { deps.Sender.EthAddr, &testContractAddr, true, + 10_000_000, // 100% sufficient gas "nativeSendThenPrecompileSend", []any{ alice.EthAddr, @@ -439,6 +444,7 @@ func (s *FunTokenFromCoinSuite) TestERC20TransferThenPrecompileSend() { deps.Sender.EthAddr, &testContractAddr, true, + 10_000_000, // 100% sufficient gas "erc20TransferThenPrecompileSend", alice.EthAddr, big.NewInt(1e6), // erc20 created with 6 decimals @@ -544,6 +550,7 @@ func (s *FunTokenFromCoinSuite) TestPrecompileSelfCallRevert() { deps.Sender.EthAddr, &testContractAddr, true, + precompile.FunTokenGasLimitBankSend, "selfCallTransferFunds", alice.EthAddr, evm.NativeToWei(big.NewInt(1e6)), // native send uses wei units, diff --git a/x/evm/keeper/funtoken_from_erc20_test.go b/x/evm/keeper/funtoken_from_erc20_test.go index 53db98b31..538d041d1 100644 --- a/x/evm/keeper/funtoken_from_erc20_test.go +++ b/x/evm/keeper/funtoken_from_erc20_test.go @@ -202,6 +202,7 @@ func (s *FunTokenFromErc20Suite) TestSendFromEvmToBank() { deps.Sender.EthAddr, &deployResp.ContractAddr, true, + keeper.Erc20GasLimitExecute, "mint", deps.Sender.EthAddr, big.NewInt(69_420), @@ -210,6 +211,8 @@ func (s *FunTokenFromErc20Suite) TestSendFromEvmToBank() { randomAcc := testutil.AccAddress() + deps.ResetGasMeter() + s.T().Log("send erc20 tokens to Bank") _, err = deps.EvmKeeper.CallContract( deps.Ctx, @@ -217,6 +220,7 @@ func (s *FunTokenFromErc20Suite) TestSendFromEvmToBank() { deps.Sender.EthAddr, &precompile.PrecompileAddr_FunToken, true, + precompile.FunTokenGasLimitBankSend, "bankSend", deployResp.ContractAddr, big.NewInt(1), @@ -231,6 +235,8 @@ func (s *FunTokenFromErc20Suite) TestSendFromEvmToBank() { deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, bankDemon).Amount, ) + deps.ResetGasMeter() + s.T().Log("sad: send too many erc20 tokens to Bank") evmResp, err := deps.EvmKeeper.CallContract( deps.Ctx, @@ -238,6 +244,7 @@ func (s *FunTokenFromErc20Suite) TestSendFromEvmToBank() { deps.Sender.EthAddr, &precompile.PrecompileAddr_FunToken, true, + precompile.FunTokenGasLimitBankSend, "bankSend", deployResp.ContractAddr, big.NewInt(70_000), @@ -246,6 +253,8 @@ func (s *FunTokenFromErc20Suite) TestSendFromEvmToBank() { s.T().Log("check balances") s.Require().Error(err, evmResp.String()) + deps.ResetGasMeter() + s.T().Log("send Bank tokens back to erc20") _, err = deps.EvmKeeper.ConvertCoinToEvm(sdk.WrapSDKContext(deps.Ctx), &evm.MsgConvertCoinToEvm{ @@ -358,6 +367,8 @@ func (s *FunTokenFromErc20Suite) TestFunTokenFromERC20MaliciousTransfer() { s.Require().NoError(err) randomAcc := testutil.AccAddress() + deps.ResetGasMeter() + s.T().Log("send erc20 tokens to cosmos") _, err = deps.EvmKeeper.CallContract( deps.Ctx, @@ -365,6 +376,7 @@ func (s *FunTokenFromErc20Suite) TestFunTokenFromERC20MaliciousTransfer() { deps.Sender.EthAddr, &precompile.PrecompileAddr_FunToken, true, + precompile.FunTokenGasLimitBankSend, "bankSend", deployResp.ContractAddr, big.NewInt(1), diff --git a/x/evm/keeper/gas_fees.go b/x/evm/keeper/gas_fees.go index c373b06ac..4d17de0bd 100644 --- a/x/evm/keeper/gas_fees.go +++ b/x/evm/keeper/gas_fees.go @@ -4,21 +4,18 @@ package keeper import ( "math/big" - gethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - gethcore "github.com/ethereum/go-ethereum/core/types" - "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" authante "github.com/cosmos/cosmos-sdk/x/auth/ante" - + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + gethcore "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/NibiruChain/nibiru/v2/x/evm" - - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) // GetEthIntrinsicGas returns the intrinsic gas cost for the transaction diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index 4e13254a9..4e423e881 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -284,7 +284,7 @@ func (k *Keeper) EthCall( txConfig := statedb.NewEmptyTxConfig(gethcommon.BytesToHash(ctx.HeaderHash())) // pass false to not commit StateDB - res, _, err := k.ApplyEvmMsg(ctx, msg, nil, false, cfg, txConfig) + res, _, err := k.ApplyEvmMsg(ctx, msg, nil, false, cfg, txConfig, false) if err != nil { return nil, grpcstatus.Error(grpccodes.Internal, err.Error()) } @@ -323,17 +323,25 @@ func (k Keeper) EstimateGasForEvmCallType( ctx := sdk.UnwrapSDKContext(goCtx) chainID := k.EthChainID(ctx) + cfg, err := k.GetEVMConfig(ctx, ParseProposerAddr(ctx, req.ProposerAddress), chainID) + if err != nil { + return nil, grpcstatus.Error(grpccodes.Internal, "failed to load evm config") + } if req.GasCap < gethparams.TxGas { return nil, grpcstatus.Errorf(grpccodes.InvalidArgument, "gas cap cannot be lower than %d", gethparams.TxGas) } var args evm.JsonTxArgs - err := json.Unmarshal(req.Args, &args) + err = json.Unmarshal(req.Args, &args) if err != nil { return nil, grpcstatus.Error(grpccodes.InvalidArgument, err.Error()) } + // ApplyMessageWithConfig expect correct nonce set in msg + nonce := k.GetAccNonce(ctx, args.GetFrom()) + args.Nonce = (*hexutil.Uint64)(&nonce) + // Binary search the gas requirement, as it may be higher than the amount used var ( lo = gethparams.TxGas - 1 @@ -361,16 +369,6 @@ func (k Keeper) EstimateGasForEvmCallType( } gasCap = hi - cfg, err := k.GetEVMConfig(ctx, ParseProposerAddr(ctx, req.ProposerAddress), chainID) - if err != nil { - return nil, grpcstatus.Error(grpccodes.Internal, "failed to load evm config") - } - - // ApplyMessageWithConfig expect correct nonce set in msg - nonce := k.GetAccNonce(ctx, args.GetFrom()) - args.Nonce = (*hexutil.Uint64)(&nonce) - - txConfig := statedb.NewEmptyTxConfig(gethcommon.BytesToHash(ctx.HeaderHash().Bytes())) // convert the tx args to an ethereum message msg, err := args.ToMessage(req.GasCap, cfg.BaseFeeWei) @@ -422,7 +420,8 @@ func (k Keeper) EstimateGasForEvmCallType( WithTransientKVGasConfig(storetypes.GasConfig{}) } // pass false to not commit StateDB - rsp, _, err = k.ApplyEvmMsg(tmpCtx, msg, nil, false, cfg, txConfig) + txConfig := statedb.NewEmptyTxConfig(gethcommon.BytesToHash(ctx.HeaderHash().Bytes())) + rsp, _, err = k.ApplyEvmMsg(tmpCtx, msg, nil, false, cfg, txConfig, false) if err != nil { if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit @@ -438,6 +437,7 @@ func (k Keeper) EstimateGasForEvmCallType( return nil, err } + // The gas limit is now the highest gas limit that results in an executable transaction // Reject the transaction as invalid if it still fails at the highest allowance if hi == gasCap { failed, result, err := executable(hi) @@ -445,17 +445,19 @@ func (k Keeper) EstimateGasForEvmCallType( return nil, fmt.Errorf("eth call exec error: %w", err) } - if failed { - if result != nil && result.VmError != vm.ErrOutOfGas.Error() { - if result.VmError == vm.ErrExecutionReverted.Error() { - return nil, fmt.Errorf("VMError: %w", evm.NewExecErrorWithReason(result.Ret)) - } - return nil, fmt.Errorf("VMError: %s", result.VmError) + if failed && result != nil { + if result.VmError == vm.ErrExecutionReverted.Error() { + return nil, fmt.Errorf("Estimate gas VMError: %w", evm.NewRevertError(result.Ret)) + } + + if result.VmError == vm.ErrOutOfGas.Error() { + return nil, fmt.Errorf("gas required exceeds allowance (%d)", gasCap) } - // Otherwise, the specified gas cap is too low - return nil, fmt.Errorf("gas required exceeds allowance (%d)", gasCap) + + return nil, fmt.Errorf("Estimgate gas VMError: %s", result.VmError) } } + return &evm.EstimateGasResponse{Gas: hi}, nil } @@ -518,7 +520,7 @@ func (k Keeper) TraceTx( ctx = ctx.WithGasMeter(eth.NewInfiniteGasMeterWithLimit(msg.Gas())). WithKVGasConfig(storetypes.GasConfig{}). WithTransientKVGasConfig(storetypes.GasConfig{}) - rsp, _, err := k.ApplyEvmMsg(ctx, msg, evm.NewNoOpTracer(), true, cfg, txConfig) + rsp, _, err := k.ApplyEvmMsg(ctx, msg, evm.NewNoOpTracer(), true, cfg, txConfig, false) if err != nil { continue } @@ -800,7 +802,7 @@ func (k *Keeper) TraceEthTxMsg( ctx = ctx.WithGasMeter(eth.NewInfiniteGasMeterWithLimit(msg.Gas())). WithKVGasConfig(storetypes.GasConfig{}). WithTransientKVGasConfig(storetypes.GasConfig{}) - res, _, err := k.ApplyEvmMsg(ctx, msg, tracer, commitMessage, cfg, txConfig) + res, _, err := k.ApplyEvmMsg(ctx, msg, tracer, commitMessage, cfg, txConfig, false) if err != nil { return nil, 0, grpcstatus.Error(grpccodes.Internal, err.Error()) } diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index dd757ced9..6d413e687 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -2,14 +2,15 @@ package keeper import ( + "fmt" "math/big" + "cosmossdk.io/math" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" gethparams "github.com/ethereum/go-ethereum/params" - sdkerrors "cosmossdk.io/errors" - "cosmossdk.io/math" + "cosmossdk.io/errors" "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/codec" @@ -101,17 +102,25 @@ func (k Keeper) EthChainID(ctx sdk.Context) *big.Int { // block tx. func (k *Keeper) AddToBlockGasUsed( ctx sdk.Context, gasUsed uint64, -) (uint64, error) { - result := k.EvmState.BlockGasUsed.GetOr(ctx, 0) + gasUsed - if result < gasUsed { - return 0, sdkerrors.Wrap(evm.ErrGasOverflow, "transient gas used") +) (blockGasUsed uint64, err error) { + // Either k.EvmState.BlockGasUsed.GetOr() or k.EvmState.BlockGasUsed.Set() + // also consume gas and could panic. + defer HandleOutOfGasPanic(&err, "") + + blockGasUsed = k.EvmState.BlockGasUsed.GetOr(ctx, 0) + gasUsed + if blockGasUsed < gasUsed { + return 0, errors.Wrap(core.ErrGasUintOverflow, "transient gas used") } - k.EvmState.BlockGasUsed.Set(ctx, result) - return result, nil + k.EvmState.BlockGasUsed.Set(ctx, blockGasUsed) + + return blockGasUsed, nil } -// GetMinGasMultiplier returns minimum gas multiplier. -func (k Keeper) GetMinGasMultiplier(ctx sdk.Context) math.LegacyDec { +// GetMinGasUsedMultiplier - value from 0 to 1 +// When executing evm msg, user specifies gasLimit. +// If the gasLimit is X times higher than the actual gasUsed then +// we update gasUsed = max(gasUsed, gasLimit * minGasUsedPct) +func (k Keeper) GetMinGasUsedMultiplier(ctx sdk.Context) math.LegacyDec { return math.LegacyNewDecWithPrec(50, 2) // 50% } @@ -140,3 +149,20 @@ func (k Keeper) Tracer( ) vm.EVMLogger { return evm.NewTracer(k.tracer, msg, ethCfg, ctx.BlockHeight()) } + +// HandleOutOfGasPanic gracefully captures "out of gas" panic and just sets the value to err +func HandleOutOfGasPanic(err *error, format string) func() { + return func() { + if r := recover(); r != nil { + switch r.(type) { + case sdk.ErrorOutOfGas: + *err = vm.ErrOutOfGas + default: + panic(r) + } + } + if err != nil && format != "" { + *err = fmt.Errorf("%s: %w", format, *err) + } + } +} diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index dbc1e8f74..e7002d528 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -62,42 +62,45 @@ func (k *Keeper) EthereumTx( tmpCtx, commitCtx := ctx.CacheContext() // pass true to commit the StateDB - evmResp, _, err = k.ApplyEvmMsg(tmpCtx, evmMsg, nil, true, evmConfig, txConfig) + evmResp, _, err = k.ApplyEvmMsg(tmpCtx, evmMsg, nil, true, evmConfig, txConfig, false) if err != nil { // when a transaction contains multiple msg, as long as one of the msg fails // all gas will be deducted. so is not msg.Gas() k.ResetGasMeterAndConsumeGas(ctx, ctx.GasMeter().Limit()) - return nil, errors.Wrap(err, "failed to apply ethereum core message") + return nil, errors.Wrap(err, "EthereumTx: failed to apply ethereum core message") } if !evmResp.Failed() { commitCtx() } + k.updateBlockBloom(ctx, evmResp, uint64(txConfig.LogIndex)) + + blockGasUsed, err := k.AddToBlockGasUsed(ctx, evmResp.GasUsed) + if err != nil { + return nil, errors.Wrap(err, "EthereumTx: error adding transient gas used to block") + } // refund gas in order to match the Ethereum gas consumption instead of the // default SDK one. refundGas := uint64(0) - if evmMsg.Gas() > evmResp.GasUsed { - refundGas = evmMsg.Gas() - evmResp.GasUsed + if evmMsg.Gas() > blockGasUsed { + refundGas = evmMsg.Gas() - blockGasUsed } weiPerGas := txMsg.EffectiveGasPriceWeiPerGas(evmConfig.BaseFeeWei) if err = k.RefundGas(ctx, evmMsg.From(), refundGas, weiPerGas); err != nil { - return nil, errors.Wrapf(err, "error refunding leftover gas to sender %s", evmMsg.From()) - } - - k.updateBlockBloom(ctx, evmResp, uint64(txConfig.LogIndex)) - - totalGasUsed, err := k.AddToBlockGasUsed(ctx, evmResp.GasUsed) - if err != nil { - return nil, errors.Wrap(err, "error adding transient gas used to block") + return nil, errors.Wrapf(err, "EthereumTx: error refunding leftover gas to sender %s", evmMsg.From()) } // reset the gas meter for current TxMsg (EthereumTx) - k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed) + k.ResetGasMeterAndConsumeGas(ctx, blockGasUsed) err = k.EmitEthereumTxEvents(ctx, tx.To(), tx.Type(), evmMsg, evmResp) if err != nil { - return nil, errors.Wrap(err, "error emitting ethereum tx events") + return nil, errors.Wrap(err, "EthereumTx: error emitting ethereum tx events") + } + err = k.EmitLogEvents(ctx, evmResp) + if err != nil { + return nil, errors.Wrap(err, "EthereumTx: error emitting tx logs") } blockTxIdx := uint64(txConfig.TxIndex) + 1 @@ -235,18 +238,21 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc { // 1. set up the initial access list // // # Tracer parameter -// // It should be a `vm.Tracer` object or nil, if pass `nil`, it'll create a default one based on keeper options. // // # Commit parameter -// // If commit is true, the `StateDB` will be committed, otherwise discarded. +// +// # fullRefundLeftoverGas parameter +// For internal calls like funtokens, user does not specify gas limit explicitly. +// In this case we don't apply any caps for refund and refund 100% func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool, evmConfig *statedb.EVMConfig, txConfig statedb.TxConfig, + fullRefundLeftoverGas bool, ) (resp *evm.MsgEthereumTxResponse, evmObj *vm.EVM, err error) { var ( ret []byte // return bytes from evm execution @@ -270,10 +276,13 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, sender := vm.AccountRef(msg.From()) contractCreation := msg.To() == nil - intrinsicGas, err := k.GetEthIntrinsicGas(ctx, msg, evmConfig.ChainConfig, contractCreation) + intrinsicGas, err := core.IntrinsicGas( + msg.Data(), msg.AccessList(), + contractCreation, true, true, + ) if err != nil { // should have already been checked on Ante Handler - return nil, evmObj, errors.Wrap(err, "intrinsic gas failed") + return nil, evmObj, errors.Wrap(err, "ApplyEvmMsg: intrinsic gas overflowed") } // Check if the provided gas in the message is enough to cover the intrinsic @@ -286,7 +295,7 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, // eth_estimateGas will check for this exact error return nil, evmObj, errors.Wrapf( core.ErrIntrinsicGas, - "apply message msg.Gas = %d, intrinsic gas = %d.", + "ApplyEvmMsg: provided msg.Gas (%d) is less than intrinsic gas cost (%d)", leftoverGas, intrinsicGas, ) } @@ -304,7 +313,7 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, msgWei, err := ParseWeiAsMultipleOfMicronibi(msg.Value()) if err != nil { - return nil, evmObj, err + return nil, evmObj, errors.Wrapf(err, "ApplyEvmMsg: invalid wei amount %s", msg.Value()) } if contractCreation { @@ -329,21 +338,6 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, ) } - // After EIP-3529: refunds are capped to gasUsed / 5 - refundQuotient := params.RefundQuotientEIP3529 - - // calculate gas refund - if msg.Gas() < leftoverGas { - return nil, evmObj, errors.Wrap(evm.ErrGasOverflow, "apply message") - } - // refund gas - temporaryGasUsed := msg.Gas() - leftoverGas - refund := GasToRefund(stateDB.GetRefund(), temporaryGasUsed, refundQuotient) - - // update leftoverGas and temporaryGasUsed with refund amount - leftoverGas += refund - temporaryGasUsed -= refund - // EVM execution error needs to be available for the JSON-RPC client var vmError string if vmErr != nil { @@ -353,22 +347,38 @@ func (k *Keeper) ApplyEvmMsg(ctx sdk.Context, // The dirty states in `StateDB` is either committed or discarded after return if commit { if err := stateDB.Commit(); err != nil { - return nil, evmObj, fmt.Errorf("failed to commit stateDB: %w", err) + return nil, evmObj, errors.Wrap(err, "ApplyEvmMsg: failed to commit stateDB") } } + // Rare case of uint64 gas overflow + if msg.Gas() < leftoverGas { + return nil, evmObj, errors.Wrapf(core.ErrGasUintOverflow, "ApplyEvmMsg: message gas limit (%d) < leftover gas (%d)", msg.Gas(), leftoverGas) + } - gasLimit := math.LegacyNewDec(int64(msg.Gas())) - minGasMultiplier := k.GetMinGasMultiplier(ctx) - minimumGasUsed := gasLimit.Mul(minGasMultiplier) + // GAS REFUND + // If msg.Gas() > gasUsed, we need to refund extra gas. + // leftoverGas = amount of extra (not used) gas. + // If the msg comes from user, we apply refundQuotient capping the refund to 20% of used gas + // If msg is internal (funtoken), we refund 100% - if !minimumGasUsed.TruncateInt().IsUint64() { - return nil, evmObj, errors.Wrapf(evm.ErrGasOverflow, "minimumGasUsed(%s) is not a uint64", minimumGasUsed.TruncateInt().String()) + refundQuotient := params.RefundQuotientEIP3529 // EIP-3529: refunds are capped to gasUsed / 5 + minGasUsedPct := k.GetMinGasUsedMultiplier(ctx) // Evmos invention: https://github.com/evmos/ethermint/issues/1085 + if fullRefundLeftoverGas { + refundQuotient = 1 // 100% refund + minGasUsedPct = math.LegacyZeroDec() // no minimum, get the actual gasUsed value } + temporaryGasUsed := msg.Gas() - leftoverGas + refund := GasToRefund(stateDB.GetRefund(), temporaryGasUsed, refundQuotient) + // update leftoverGas and temporaryGasUsed with refund amount + leftoverGas += refund + temporaryGasUsed -= refund if msg.Gas() < leftoverGas { - return nil, evmObj, errors.Wrapf(evm.ErrGasOverflow, "message gas limit < leftover gas (%d < %d)", msg.Gas(), leftoverGas) + return nil, evmObj, errors.Wrapf(core.ErrGasUintOverflow, "ApplyEvmMsg: message gas limit (%d) < leftover gas (%d)", msg.Gas(), leftoverGas) } + // Min gas used is a % of gasLimit + minimumGasUsed := math.LegacyNewDec(int64(msg.Gas())).Mul(minGasUsedPct) gasUsed := math.LegacyMaxDec(minimumGasUsed, math.LegacyNewDec(int64(temporaryGasUsed))).TruncateInt().Uint64() // This resulting "leftoverGas" is used by the tracer. This happens as a @@ -395,7 +405,7 @@ func ParseWeiAsMultipleOfMicronibi(weiInt *big.Int) (newWeiInt *big.Int, err err tenPow12 := new(big.Int).Exp(big.NewInt(10), big.NewInt(12), nil) if weiInt.Cmp(tenPow12) < 0 { return weiInt, fmt.Errorf( - "wei amount is too small (%s), cannot transfer less than 1 micronibi. 10^18 wei == 1 NIBI == 10^6 micronibi", weiInt) + "wei amount is too small (%s), cannot transfer less than 1 micronibi. 1 NIBI == 10^6 micronibi == 10^18 wei", weiInt) } // truncate to highest micronibi amount @@ -526,6 +536,7 @@ func (k Keeper) convertCoinToEvmBornCoin( evm.EVM_MODULE_ADDRESS, &erc20Addr, true, + Erc20GasLimitExecute, "mint", recipient, coin.Amount.BigInt(), @@ -578,7 +589,7 @@ func (k Keeper) convertCoinToEvmBornERC20( // converted to its Bank Coin representation, a balance of the ERC20 is left // inside the EVM module account in order to convert the coins back to // ERC20s. - actualSentAmount, err := k.ERC20().Transfer( + actualSentAmount, _, err := k.ERC20().Transfer( erc20Addr, evm.EVM_MODULE_ADDRESS, recipient, @@ -635,20 +646,9 @@ func (k *Keeper) EmitEthereumTxEvents( } err := ctx.EventManager().EmitTypedEvent(eventEthereumTx) if err != nil { - return errors.Wrap(err, "failed to emit event ethereum tx") + return errors.Wrap(err, "EmitEthereumTxEvents: failed to emit event ethereum tx") } - // Typed event: eth.evm.v1.EventTxLog - txLogs := make([]string, len(evmResp.Logs)) - for i, log := range evmResp.Logs { - value, err := json.Marshal(log) - if err != nil { - return errors.Wrap(err, "failed to encode log") - } - txLogs[i] = string(value) - } - _ = ctx.EventManager().EmitTypedEvent(&evm.EventTxLog{TxLogs: txLogs}) - // Untyped event: "message", used for tendermint subscription ctx.EventManager().EmitEvent( sdk.NewEvent( @@ -684,6 +684,25 @@ func (k *Keeper) EmitEthereumTxEvents( return nil } +// EmitLogEvents emits all types of EVM events applicable to a particular execution case +func (k *Keeper) EmitLogEvents( + ctx sdk.Context, + evmResp *evm.MsgEthereumTxResponse, +) error { + // Typed event: eth.evm.v1.EventTxLog + txLogs := make([]string, len(evmResp.Logs)) + for i, log := range evmResp.Logs { + value, err := json.Marshal(log) + if err != nil { + return errors.Wrap(err, "failed to encode log") + } + txLogs[i] = string(value) + } + _ = ctx.EventManager().EmitTypedEvent(&evm.EventTxLog{TxLogs: txLogs}) + + return nil +} + // updateBlockBloom updates transient block bloom filter func (k *Keeper) updateBlockBloom( ctx sdk.Context, diff --git a/x/evm/msg.go b/x/evm/msg.go index e1e4edc6e..53b607106 100644 --- a/x/evm/msg.go +++ b/x/evm/msg.go @@ -15,7 +15,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/ante" "github.com/cosmos/cosmos-sdk/x/auth/signing" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" @@ -154,7 +154,7 @@ func (msg MsgEthereumTx) ValidateBasic() error { // Validate Size_ field, should be kept empty if msg.Size_ != 0 { - return errorsmod.Wrapf(errortypes.ErrInvalidRequest, "tx size is deprecated") + return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "tx size is deprecated") } txData, err := UnpackTxData(msg.Data) @@ -166,12 +166,12 @@ func (msg MsgEthereumTx) ValidateBasic() error { // prevent txs with 0 gas to fill up the mempool if gas == 0 { - return errorsmod.Wrap(ErrInvalidGasLimit, "gas limit must not be zero") + return errorsmod.Wrap(sdkerrors.ErrInvalidGasLimit, "gas limit must not be zero") } // prevent gas limit from overflow if g := new(big.Int).SetUint64(gas); !g.IsInt64() { - return errorsmod.Wrap(ErrGasOverflow, "gas limit must be less than math.MaxInt64") + return errorsmod.Wrap(core.ErrGasUintOverflow, "gas limit must be less than math.MaxInt64") } if err := txData.Validate(); err != nil { @@ -181,7 +181,7 @@ func (msg MsgEthereumTx) ValidateBasic() error { // Validate EthHash field after validated txData to avoid panic txHash := msg.AsTransaction().Hash().Hex() if msg.Hash != txHash { - return errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid tx hash %s, expected: %s", msg.Hash, txHash) + return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "invalid tx hash %s, expected: %s", msg.Hash, txHash) } return nil diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index b1fc0239d..167c815f5 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -23,6 +23,18 @@ var _ vm.PrecompiledContract = (*precompileFunToken)(nil) // using the ERC20's `FunToken` mapping. var PrecompileAddr_FunToken = gethcommon.HexToAddress("0x0000000000000000000000000000000000000800") +const ( + // FunTokenGasLimitBankSend consists of gas for 3 calls: + // 1. transfer erc20 from sender to module + // ~60_000 gas for regular erc20 transfer (our own ERC20Minter contract) + // could be higher for user created contracts, let's cap with 200_000 + // 2. mint native coin (made from erc20) or burn erc20 token (made from coin) + // ~60_000 gas for either mint or burn + // 3. send from module to account: + // ~65_000 gas (bank send) + FunTokenGasLimitBankSend uint64 = 400_000 +) + func (p precompileFunToken) Address() gethcommon.Address { return PrecompileAddr_FunToken } @@ -47,16 +59,19 @@ func (p precompileFunToken) Run( defer func() { err = ErrPrecompileRun(err, p) }() - start, err := OnRunStart(evm, contract.Input, p.ABI()) + startResult, err := OnRunStart(evm, contract.Input, p.ABI(), contract.Gas) if err != nil { return nil, err } - p.evmKeeper.Bank.StateDB = start.StateDB + p.evmKeeper.Bank.StateDB = startResult.StateDB - method := start.Method + // Gracefully handles "out of gas" + defer HandleOutOfGasPanic(&err)() + + method := startResult.Method switch PrecompileMethod(method.Name) { case FunTokenMethod_BankSend: - bz, err = p.bankSend(start, contract.CallerAddress, readonly) + bz, err = p.bankSend(startResult, contract.CallerAddress, readonly) default: // Note that this code path should be impossible to reach since // "DecomposeInput" parses methods directly from the ABI. @@ -66,6 +81,9 @@ func (p precompileFunToken) Run( if err != nil { return nil, err } + + // Gas consumed by a local gas meter + contract.UseGas(startResult.CacheCtx.GasMeter().GasConsumed()) return bz, err } @@ -91,11 +109,11 @@ type precompileFunToken struct { // function bankSend(address erc20, uint256 amount, string memory to) external; // ``` func (p precompileFunToken) bankSend( - start OnRunStartResult, + startResult OnRunStartResult, caller gethcommon.Address, readOnly bool, ) (bz []byte, err error) { - ctx, method, args := start.CacheCtx, start.Method, start.Args + ctx, method, args := startResult.CacheCtx, startResult.Method, startResult.Args if err := assertNotReadonlyTx(readOnly, method); err != nil { return nil, err } @@ -105,6 +123,8 @@ func (p precompileFunToken) bankSend( return } + var evmResponses []*evm.MsgEthereumTxResponse + // ERC20 must have FunToken mapping funtokens := p.evmKeeper.FunTokens.Collect( ctx, p.evmKeeper.FunTokens.Indexes.ERC20Addr.ExactMatch(ctx, erc20), @@ -128,10 +148,11 @@ func (p precompileFunToken) bankSend( // Caller transfers ERC20 to the EVM account transferTo := evm.EVM_MODULE_ADDRESS - gotAmount, err := p.evmKeeper.ERC20().Transfer(erc20, caller, transferTo, amount, ctx) + gotAmount, transferResp, err := p.evmKeeper.ERC20().Transfer(erc20, caller, transferTo, amount, ctx) if err != nil { return nil, fmt.Errorf("error in ERC20.transfer from caller to EVM account: %w", err) } + evmResponses = append(evmResponses, transferResp) // EVM account mints FunToken.BankDenom to module account coinToSend := sdk.NewCoin(funtoken.BankDenom, math.NewIntFromBigInt(gotAmount)) @@ -140,17 +161,18 @@ func (p precompileFunToken) bankSend( // owns the ERC20 contract and was the original minter of the ERC20 tokens. // Since we're sending them away and want accurate total supply tracking, the // tokens need to be burned. - _, err = p.evmKeeper.ERC20().Burn(erc20, evm.EVM_MODULE_ADDRESS, gotAmount, ctx) - if err != nil { - err = fmt.Errorf("ERC20.Burn: %w", err) + burnResp, e := p.evmKeeper.ERC20().Burn(erc20, evm.EVM_MODULE_ADDRESS, gotAmount, ctx) + if e != nil { + err = fmt.Errorf("ERC20.Burn: %w", e) return } + evmResponses = append(evmResponses, burnResp) } else { // NOTE: The NibiruBankKeeper needs to reference the current [vm.StateDB] before // any operation that has the potential to use Bank send methods. This will // guarantee that [evmkeeper.Keeper.SetAccBalance] journal changes are // recorded if wei (NIBI) is transferred. - p.evmKeeper.Bank.StateDB = start.StateDB + p.evmKeeper.Bank.StateDB = startResult.StateDB err = p.evmKeeper.Bank.MintCoins(ctx, evm.ModuleName, sdk.NewCoins(coinToSend)) if err != nil { return nil, fmt.Errorf("mint failed for module \"%s\" (%s): contract caller %s: %w", @@ -165,7 +187,7 @@ func (p precompileFunToken) bankSend( // any operation that has the potential to use Bank send methods. This will // guarantee that [evmkeeper.Keeper.SetAccBalance] journal changes are // recorded if wei (NIBI) is transferred. - p.evmKeeper.Bank.StateDB = start.StateDB + p.evmKeeper.Bank.StateDB = startResult.StateDB err = p.evmKeeper.Bank.SendCoinsFromModuleToAccount( ctx, evm.ModuleName, @@ -177,6 +199,11 @@ func (p precompileFunToken) bankSend( evm.ModuleName, evm.EVM_MODULE_ADDRESS.Hex(), caller.Hex(), err, ) } + for _, resp := range evmResponses { + for _, log := range resp.Logs { + startResult.StateDB.AddLog(log.ToEthereum()) + } + } // TODO: UD-DEBUG: feat: Emit EVM events diff --git a/x/evm/precompile/funtoken_test.go b/x/evm/precompile/funtoken_test.go index a17836582..6a6fe29d7 100644 --- a/x/evm/precompile/funtoken_test.go +++ b/x/evm/precompile/funtoken_test.go @@ -14,6 +14,7 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm" "github.com/NibiruChain/nibiru/v2/x/evm/embeds" "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" + "github.com/NibiruChain/nibiru/v2/x/evm/keeper" "github.com/NibiruChain/nibiru/v2/x/evm/precompile" ) @@ -25,6 +26,16 @@ func TestSuite(t *testing.T) { type FuntokenSuite struct { suite.Suite + deps evmtest.TestDeps + funtoken evm.FunToken +} + +func (s *FuntokenSuite) SetupSuite() { + s.deps = evmtest.NewTestDeps() + + s.T().Log("Create FunToken from coin") + bankDenom := "unibi" + s.funtoken = evmtest.CreateFunTokenForBankCoin(&s.deps, bankDenom, &s.Suite) } func (s *FuntokenSuite) TestFailToPackABI() { @@ -83,6 +94,7 @@ func (s *FuntokenSuite) TestHappyPath() { s.T().Log("Create FunToken mapping and ERC20") bankDenom := "unibi" funtoken := evmtest.CreateFunTokenForBankCoin(&deps, bankDenom, &s.Suite) + erc20 := funtoken.Erc20Addr.Address s.T().Log("Balances of the ERC20 should start empty") @@ -93,14 +105,14 @@ func (s *FuntokenSuite) TestHappyPath() { deps.App.BankKeeper, deps.Ctx, deps.Sender.NibiruAddr, - sdk.NewCoins(sdk.NewCoin(bankDenom, sdk.NewInt(69_420))), + sdk.NewCoins(sdk.NewCoin(s.funtoken.BankDenom, sdk.NewInt(69_420))), )) _, err := deps.EvmKeeper.ConvertCoinToEvm( sdk.WrapSDKContext(deps.Ctx), &evm.MsgConvertCoinToEvm{ Sender: deps.Sender.NibiruAddr.String(), - BankCoin: sdk.NewCoin(bankDenom, sdk.NewInt(69_420)), + BankCoin: sdk.NewCoin(s.funtoken.BankDenom, sdk.NewInt(69_420)), ToEthAddr: eth.EIP55Addr{ Address: deps.Sender.EthAddr, }, @@ -110,11 +122,8 @@ func (s *FuntokenSuite) TestHappyPath() { s.T().Log("Mint tokens - Fail from non-owner") { - input, err := embeds.SmartContract_ERC20Minter.ABI.Pack("mint", deps.Sender.EthAddr, big.NewInt(69_420)) - s.NoError(err) - _, _, err = deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &erc20, true, input, - ) + s.deps.ResetGasMeter() + _, err = deps.EvmKeeper.CallContract(deps.Ctx, embeds.SmartContract_ERC20Minter.ABI, deps.Sender.EthAddr, &erc20, true, keeper.Erc20GasLimitExecute, "mint", deps.Sender.EthAddr, big.NewInt(69_420)) s.ErrorContains(err, "Ownable: caller is not the owner") } @@ -126,6 +135,7 @@ func (s *FuntokenSuite) TestHappyPath() { input, err := embeds.SmartContract_FunToken.ABI.Pack(string(precompile.FunTokenMethod_BankSend), callArgs...) s.NoError(err) + deps.ResetGasMeter() _, ethTxResp, err := evmtest.CallContractTx( &deps, precompile.PrecompileAddr_FunToken, @@ -145,6 +155,7 @@ func (s *FuntokenSuite) TestHappyPath() { s.Equal(sdk.NewInt(420).String(), deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, funtoken.BankDenom).Amount.String(), ) + s.deps.ResetGasMeter() s.Require().NotNil(deps.EvmKeeper.Bank.StateDB) s.T().Log("Parse the response contract addr and response bytes") @@ -157,3 +168,86 @@ func (s *FuntokenSuite) TestHappyPath() { s.NoError(err) s.Require().Equal("420", sentAmt.String()) } + +func (s *FuntokenSuite) TestPrecompileLocalGas() { + deps := s.deps + randomAcc := testutil.AccAddress() + + deployResp, err := evmtest.DeployContract( + &deps, embeds.SmartContract_TestFunTokenPrecompileLocalGas, + s.funtoken.Erc20Addr.Address, + ) + s.Require().NoError(err) + contractAddr := deployResp.ContractAddr + + s.T().Log("Fund sender's wallet") + s.Require().NoError(testapp.FundAccount( + deps.App.BankKeeper, + deps.Ctx, + deps.Sender.NibiruAddr, + sdk.NewCoins(sdk.NewCoin(s.funtoken.BankDenom, sdk.NewInt(1000))), + )) + + s.T().Log("Fund contract with erc20 coins") + _, err = deps.EvmKeeper.ConvertCoinToEvm( + sdk.WrapSDKContext(deps.Ctx), + &evm.MsgConvertCoinToEvm{ + Sender: deps.Sender.NibiruAddr.String(), + BankCoin: sdk.NewCoin(s.funtoken.BankDenom, sdk.NewInt(1000)), + ToEthAddr: eth.EIP55Addr{ + Address: contractAddr, + }, + }, + ) + s.Require().NoError(err) + + s.deps.ResetGasMeter() + + s.T().Log("Happy: callBankSend with default gas") + _, err = deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_TestFunTokenPrecompileLocalGas.ABI, + deps.Sender.EthAddr, + &contractAddr, + true, + precompile.FunTokenGasLimitBankSend, + "callBankSend", + big.NewInt(1), + randomAcc.String(), + ) + s.Require().NoError(err) + + s.deps.ResetGasMeter() + + s.T().Log("Happy: callBankSend with local gas - sufficient gas amount") + _, err = deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_TestFunTokenPrecompileLocalGas.ABI, + deps.Sender.EthAddr, + &contractAddr, + true, + precompile.FunTokenGasLimitBankSend, // gasLimit for the entire call + "callBankSendLocalGas", + big.NewInt(1), // erc20 amount + randomAcc.String(), // to + big.NewInt(int64(precompile.FunTokenGasLimitBankSend)), // customGas + ) + s.Require().NoError(err) + + s.deps.ResetGasMeter() + + s.T().Log("Sad: callBankSend with local gas - insufficient gas amount") + _, err = deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_TestFunTokenPrecompileLocalGas.ABI, + deps.Sender.EthAddr, + &contractAddr, + true, + precompile.FunTokenGasLimitBankSend, // gasLimit for the entire call + "callBankSendLocalGas", + big.NewInt(1), // erc20 amount + randomAcc.String(), // to + big.NewInt(50_000), // customGas - too small + ) + s.Require().ErrorContains(err, "execution reverted") +} diff --git a/x/evm/precompile/oracle.go b/x/evm/precompile/oracle.go index cf461a3c4..6aae97bcc 100644 --- a/x/evm/precompile/oracle.go +++ b/x/evm/precompile/oracle.go @@ -42,7 +42,7 @@ func (p precompileOracle) Run( defer func() { err = ErrPrecompileRun(err, p) }() - startResult, err := OnRunStart(evm, contract.Input, p.ABI()) + startResult, err := OnRunStart(evm, contract.Input, p.ABI(), contract.Gas) if err != nil { return nil, err } diff --git a/x/evm/precompile/oracle_test.go b/x/evm/precompile/oracle_test.go index fa07d72d6..1051e12aa 100644 --- a/x/evm/precompile/oracle_test.go +++ b/x/evm/precompile/oracle_test.go @@ -14,6 +14,8 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm/precompile" ) +const OracleGasLimitQuery = 100_000 + func (s *OracleSuite) TestOracle_FailToPackABI() { testcases := []struct { name string @@ -60,10 +62,15 @@ func (s *OracleSuite) TestOracle_HappyPath() { deps.Ctx = deps.Ctx.WithBlockTime(time.Unix(69, 420)).WithBlockHeight(69) deps.App.OracleKeeper.SetPrice(deps.Ctx, "unibi:uusd", sdk.MustNewDecFromStr("0.067")) - input, err := embeds.SmartContract_Oracle.ABI.Pack("queryExchangeRate", "unibi:uusd") - s.NoError(err) - resp, _, err := deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Oracle, true, input, + resp, err := deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_Oracle.ABI, + deps.Sender.EthAddr, + &precompile.PrecompileAddr_Oracle, + false, + OracleGasLimitQuery, + "queryExchangeRate", + "unibi:uusd", ) s.NoError(err) diff --git a/x/evm/precompile/precompile.go b/x/evm/precompile/precompile.go index 234a27f4b..cf3056a5c 100644 --- a/x/evm/precompile/precompile.go +++ b/x/evm/precompile/precompile.go @@ -19,13 +19,12 @@ import ( "github.com/NibiruChain/collections" store "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" gethabi "github.com/ethereum/go-ethereum/accounts/abi" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" gethparams "github.com/ethereum/go-ethereum/params" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/NibiruChain/nibiru/v2/app/keepers" "github.com/NibiruChain/nibiru/v2/x/evm/statedb" ) @@ -179,7 +178,7 @@ type OnRunStartResult struct { // } // ``` func OnRunStart( - evm *vm.EVM, contractInput []byte, abi *gethabi.ABI, + evm *vm.EVM, contractInput []byte, abi *gethabi.ABI, gasLimit uint64, ) (res OnRunStartResult, err error) { method, args, err := decomposeInput(abi, contractInput) if err != nil { @@ -203,6 +202,11 @@ func OnRunStart( return res, fmt.Errorf("error committing dirty journal entries: %w", err) } + // Switching to a local gas meter to enforce gas limit check for a precompile + cacheCtx = cacheCtx.WithGasMeter(sdk.NewGasMeter(gasLimit)). + WithKVGasConfig(store.KVGasConfig()). + WithTransientKVGasConfig(store.TransientGasConfig()) + return OnRunStartResult{ Args: args, CacheCtx: cacheCtx, @@ -222,3 +226,16 @@ var isMutation map[PrecompileMethod]bool = map[PrecompileMethod]bool{ OracleMethod_queryExchangeRate: false, } + +func HandleOutOfGasPanic(err *error) func() { + return func() { + if r := recover(); r != nil { + switch r.(type) { + case sdk.ErrorOutOfGas: + *err = vm.ErrOutOfGas + default: + panic(r) + } + } + } +} diff --git a/x/evm/precompile/test/export.go b/x/evm/precompile/test/export.go index 24700ce85..ef1b91737 100644 --- a/x/evm/precompile/test/export.go +++ b/x/evm/precompile/test/export.go @@ -21,6 +21,13 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm/precompile" ) +// rough gas limits for wasm execution - used in tests only +const ( + WasmGasLimitInstantiate uint64 = 1_000_000 + WasmGasLimitExecute uint64 = 10_000_000 + WasmGasLimitQuery uint64 = 200_000 +) + // SetupWasmContracts stores all Wasm bytecode and has the "deps.Sender" // instantiate each Wasm contract using the precompile. func SetupWasmContracts(deps *evmtest.TestDeps, s *suite.Suite) ( @@ -56,16 +63,17 @@ func SetupWasmContracts(deps *evmtest.TestDeps, s *suite.Suite) ( msgArgsBz, err := json.Marshal(m.Msg) s.NoError(err) - callArgs := []any{m.Admin, m.CodeID, msgArgsBz, m.Label, []precompile.WasmBankCoin{}} - input, err := embeds.SmartContract_Wasm.ABI.Pack( + ethTxResp, err := deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_Wasm.ABI, + deps.Sender.EthAddr, + &precompile.PrecompileAddr_Wasm, + true, + WasmGasLimitInstantiate, string(precompile.WasmMethod_instantiate), - callArgs..., + []any{m.Admin, m.CodeID, msgArgsBz, m.Label, []precompile.WasmBankCoin{}}..., ) - s.Require().NoError(err) - ethTxResp, _, err := deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, - ) s.Require().NoError(err) s.Require().NotEmpty(ethTxResp.Ret) @@ -150,27 +158,27 @@ func AssertWasmCounterState( deps evmtest.TestDeps, wasmContract sdk.AccAddress, wantCount int64, -) (evmObj *vm.EVM) { +) { msgArgsBz := []byte(` { "count": {} } - `) - - callArgs := []any{ - // string memory contractAddr - wasmContract.String(), - // bytes memory req - msgArgsBz, - } - input, err := embeds.SmartContract_Wasm.ABI.Pack( +`) + + ethTxResp, err := deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_Wasm.ABI, + deps.Sender.EthAddr, + &precompile.PrecompileAddr_Wasm, + true, + WasmGasLimitQuery, string(precompile.WasmMethod_query), - callArgs..., - ) - s.Require().NoError(err) - - ethTxResp, evmObj, err := deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + []any{ + // string memory contractAddr + wasmContract.String(), + // bytes memory req + msgArgsBz, + }..., ) s.Require().NoError(err) s.Require().NotEmpty(ethTxResp.Ret) @@ -191,16 +199,14 @@ func AssertWasmCounterState( s.T().Log("Response is a JSON-encoded struct from the Wasm contract") var wasmMsg wasm.RawContractMessage - err = json.Unmarshal(queryResp, &wasmMsg) - s.NoError(err) + s.NoError(json.Unmarshal(queryResp, &wasmMsg)) s.NoError(wasmMsg.ValidateBasic()) + var typedResp QueryMsgCountResp - err = json.Unmarshal(wasmMsg, &typedResp) - s.NoError(err) + s.NoError(json.Unmarshal(queryResp, &typedResp)) s.EqualValues(wantCount, typedResp.Count) s.EqualValues(deps.Sender.NibiruAddr.String(), typedResp.Owner) - return evmObj } // Result of QueryMsg::Count from the [hello_world_counter] Wasm contract: @@ -292,8 +298,15 @@ func IncrementWasmCounterWithExecuteMulti( ) s.Require().NoError(err) + deps.ResetGasMeter() + ethTxResp, evmObj, err := deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, finalizeTx, input, + deps.Ctx, + deps.Sender.EthAddr, + &precompile.PrecompileAddr_Wasm, + finalizeTx, + input, + WasmGasLimitExecute, ) s.Require().NoError(err) s.Require().NotEmpty(ethTxResp.Ret) diff --git a/x/evm/precompile/wasm.go b/x/evm/precompile/wasm.go index 0c7753564..c42df76eb 100644 --- a/x/evm/precompile/wasm.go +++ b/x/evm/precompile/wasm.go @@ -38,11 +38,14 @@ func (p precompileWasm) Run( defer func() { err = ErrPrecompileRun(err, p) }() - startResult, err := OnRunStart(evm, contract.Input, p.ABI()) + startResult, err := OnRunStart(evm, contract.Input, p.ABI(), contract.Gas) if err != nil { return nil, err } + // Gracefully handles "out of gas" + defer HandleOutOfGasPanic(&err)() + // NOTE: The NibiruBankKeeper needs to reference the current [vm.StateDB] before // any operation that has the potential to use Bank send methods. This will // guarantee that [evmkeeper.Keeper.SetAccBalance] journal changes are @@ -68,6 +71,10 @@ func (p precompileWasm) Run( if err != nil { return nil, err } + + // Gas consumed by a local gas meter + contract.UseGas(startResult.CacheCtx.GasMeter().GasConsumed()) + return bz, err } diff --git a/x/evm/precompile/wasm_test.go b/x/evm/precompile/wasm_test.go index 72c45046c..b0f40dd79 100644 --- a/x/evm/precompile/wasm_test.go +++ b/x/evm/precompile/wasm_test.go @@ -19,6 +19,12 @@ import ( "github.com/stretchr/testify/suite" ) +// rough gas limits for wasm execution - used in tests only +const ( + WasmGasLimitQuery uint64 = 200_000 + WasmGasLimitExecute uint64 = 1_000_000 +) + type WasmSuite struct { suite.Suite } @@ -42,19 +48,19 @@ func (s *WasmSuite) TestExecuteHappy() { err = json.Unmarshal(fundsJson, &funds) s.Require().NoError(err, "fundsJson %s, funds %s", fundsJson, funds) - callArgs := []any{ - wasmContract.String(), - msgArgsBz, - funds, - } - input, err := embeds.SmartContract_Wasm.ABI.Pack( + ethTxResp, err := deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_Wasm.ABI, + deps.Sender.EthAddr, + &precompile.PrecompileAddr_Wasm, + true, + WasmGasLimitExecute, string(precompile.WasmMethod_execute), - callArgs..., - ) - s.Require().NoError(err) - - ethTxResp, _, err := deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + []any{ + wasmContract.String(), + msgArgsBz, + funds, + }..., ) s.Require().NoError(err) s.Require().NotEmpty(ethTxResp.Ret) @@ -72,23 +78,26 @@ func (s *WasmSuite) TestExecuteHappy() { } } `, coinDenom, deps.Sender.NibiruAddr)) - callArgs = []any{ - wasmContract.String(), - msgArgsBz, - funds, - } - input, err = embeds.SmartContract_Wasm.ABI.Pack( + + ethTxResp, err = deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_Wasm.ABI, + deps.Sender.EthAddr, + &precompile.PrecompileAddr_Wasm, + true, + WasmGasLimitExecute, string(precompile.WasmMethod_execute), - callArgs..., - ) - s.Require().NoError(err) - ethTxResp, _, err = deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + []any{ + wasmContract.String(), + msgArgsBz, + funds, + }..., ) + s.Require().NoError(err) s.Require().NotEmpty(ethTxResp.Ret) evmtest.AssertBankBalanceEqual( - s.T(), deps, coinDenom, deps.Sender.EthAddr, big.NewInt(69420), + s.T(), deps, coinDenom, deps.Sender.EthAddr, big.NewInt(69_420), ) } @@ -126,19 +135,20 @@ func (s *WasmSuite) assertWasmCounterStateRaw( wasmContract sdk.AccAddress, wantCount int64, ) { - keyBz := []byte(`state`) - callArgs := []any{ - wasmContract.String(), - keyBz, - } - input, err := embeds.SmartContract_Wasm.ABI.Pack( - string(precompile.WasmMethod_queryRaw), - callArgs..., - ) - s.Require().NoError(err) + deps.ResetGasMeter() - ethTxResp, _, err := deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + ethTxResp, err := deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_Wasm.ABI, + deps.Sender.EthAddr, + &precompile.PrecompileAddr_Wasm, + true, + WasmGasLimitQuery, + string(precompile.WasmMethod_queryRaw), + []any{ + wasmContract.String(), + []byte(`state`), + }..., ) s.Require().NoError(err) s.Require().NotEmpty(ethTxResp.Ret) @@ -300,16 +310,17 @@ func (s *WasmSuite) TestSadArgsExecute() { s.Run(tc.name, func() { deps := evmtest.NewTestDeps() - callArgs := tc.callArgs - input, err := abi.Pack( + ethTxResp, err := deps.EvmKeeper.CallContract( + deps.Ctx, + abi, + deps.Sender.EthAddr, + &precompile.PrecompileAddr_Wasm, + true, + WasmGasLimitExecute, string(tc.methodName), - callArgs..., + tc.callArgs..., ) - s.Require().NoError(err) - ethTxResp, _, err := deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, - ) s.Require().ErrorContains(err, tc.wantError, "ethTxResp %v", ethTxResp) }) } @@ -432,15 +443,15 @@ func (s *WasmSuite) TestExecuteMultiValidation() { for _, tc := range testCases { s.Run(tc.name, func() { - callArgs := []any{tc.executeMsgs} - input, err := embeds.SmartContract_Wasm.ABI.Pack( + ethTxResp, err := deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_Wasm.ABI, + deps.Sender.EthAddr, + &precompile.PrecompileAddr_Wasm, + true, + WasmGasLimitExecute, string(precompile.WasmMethod_executeMulti), - callArgs..., - ) - s.Require().NoError(err) - - ethTxResp, _, err := deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + []any{tc.executeMsgs}..., ) if tc.wantError != "" { @@ -478,15 +489,15 @@ func (s *WasmSuite) TestExecuteMultiPartialExecution() { }, } - callArgs := []any{executeMsgs} - input, err := embeds.SmartContract_Wasm.ABI.Pack( + ethTxResp, err := deps.EvmKeeper.CallContract( + deps.Ctx, + embeds.SmartContract_Wasm.ABI, + deps.Sender.EthAddr, + &precompile.PrecompileAddr_Wasm, + true, + WasmGasLimitExecute, string(precompile.WasmMethod_executeMulti), - callArgs..., - ) - s.Require().NoError(err) - - ethTxResp, _, err := deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + []any{executeMsgs}..., ) // Verify that the call failed diff --git a/x/evm/statedb/journal_test.go b/x/evm/statedb/journal_test.go index c1c74110c..75fc9b6a0 100644 --- a/x/evm/statedb/journal_test.go +++ b/x/evm/statedb/journal_test.go @@ -10,6 +10,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/NibiruChain/nibiru/v2/x/evm/keeper" + serverconfig "github.com/NibiruChain/nibiru/v2/app/server/config" "github.com/NibiruChain/nibiru/v2/x/common" "github.com/NibiruChain/nibiru/v2/x/common/testutil/testapp" @@ -52,7 +54,12 @@ func (s *Suite) TestComplexJournalChanges() { input, err := deps.EvmKeeper.ERC20().ABI.Pack("mint", to, amount) s.Require().NoError(err) _, evmObj, err := deps.EvmKeeper.CallContractWithInput( - deps.Ctx, deps.Sender.EthAddr, &contract, true, input, + deps.Ctx, + deps.Sender.EthAddr, + &contract, + true, + input, + keeper.Erc20GasLimitExecute, ) s.Require().NoError(err)