diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index a7c235e97b..d4d149b8c0 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -30,6 +30,7 @@ jobs: PROTOBUF_CONFIG_FILE: .protolint.yml VALIDATE_NATURAL_LANGUAGE: false VALIDATE_OPENAPI: false + VALIDATE_JAVASCRIPT_STANDARD: false VALIDATE_JSCPD: false VALIDATE_GO: false VALIDATE_GO_MODULES: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ac4783487..939ad75957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,7 +64,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ - (evm) [#2936](https://github.com/evmos/evmos/pull/2936) Add query for EVM config. - (app) [#2937](https://github.com/evmos/evmos/pull/2937) Fix conversion on the `CheckTxFee` ante handler and allow zero coins refunds. - (deps) [#2967](https://github.com/evmos/evmos/pull/2967) Bump CometBFT to `v0.38.15`. -- (precompiles) [#2966](https://github.com/evmos/evmos/pull/2966) Add safety check that ERC20 precompiles cannot receive funds. +- (precompiles) [#2943](https://github.com/evmos/evmos/pull/2943) Add WERC-20 precompile. +- (precompiles) [#2966](https://github.com/evmos/evmos/pull/2966) Add safety check that ERC-20 precompiles cannot receive funds. ### Bug Fixes diff --git a/contracts/compiled_contracts/WEVMOS.json b/contracts/compiled_contracts/WEVMOS.json deleted file mode 100644 index 6dfa7bc3b2..0000000000 --- a/contracts/compiled_contracts/WEVMOS.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "abi": "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"guy\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"deposit\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - "bin": "60806040526040518060400160405280600d81526020017f577261707065642045766d6f73000000000000000000000000000000000000008152506000908051906020019061004f9291906100ca565b506040518060400160405280600681526020017f5745564d4f5300000000000000000000000000000000000000000000000000008152506001908051906020019061009b9291906100ca565b506012600260006101000a81548160ff021916908360ff1602179055503480156100c457600080fd5b5061016f565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061010b57805160ff1916838001178555610139565b82800160010185558215610139579182015b8281111561013857825182559160200191906001019061011d565b5b509050610146919061014a565b5090565b61016c91905b80821115610168576000816000905550600101610150565b5090565b90565b610cb18061017e6000396000f3fe60806040526004361061009c5760003560e01c8063313ce56711610064578063313ce567146102a257806370a08231146102d357806395d89b4114610338578063a9059cbb146103c8578063d0e30db01461043b578063dd62ed3e146104455761009c565b806306fdde03146100a6578063095ea7b31461013657806318160ddd146101a957806323b872dd146101d45780632e1a7d4d14610267575b6100a46104ca565b005b3480156100b257600080fd5b506100bb610567565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100fb5780820151818401526020810190506100e0565b50505050905090810190601f1680156101285780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561014257600080fd5b5061018f6004803603604081101561015957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610605565b604051808215151515815260200191505060405180910390f35b3480156101b557600080fd5b506101be6106f7565b6040518082815260200191505060405180910390f35b3480156101e057600080fd5b5061024d600480360360608110156101f757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506106ff565b604051808215151515815260200191505060405180910390f35b34801561027357600080fd5b506102a06004803603602081101561028a57600080fd5b8101908080359060200190929190505050610a48565b005b3480156102ae57600080fd5b506102b7610b79565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102df57600080fd5b50610322600480360360208110156102f657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610b8c565b6040518082815260200191505060405180910390f35b34801561034457600080fd5b5061034d610ba4565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561038d578082015181840152602081019050610372565b50505050905090810190601f1680156103ba5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156103d457600080fd5b50610421600480360360408110156103eb57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c42565b604051808215151515815260200191505060405180910390f35b6104436104ca565b005b34801561045157600080fd5b506104b46004803603604081101561046857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610c57565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105fd5780601f106105d2576101008083540402835291602001916105fd565b820191906000526020600020905b8154815290600101906020018083116105e057829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b600047905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561074d57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415801561082557507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b1561093e5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156108b357600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610a9457600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050158015610b27573d6000803e3d6000fd5b503373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c3a5780601f10610c0f57610100808354040283529160200191610c3a565b820191906000526020600020905b815481529060010190602001808311610c1d57829003601f168201915b505050505081565b6000610c4f3384846106ff565b905092915050565b600460205281600052604060002060205280600052604060002060009150915050548156fea265627a7a7231582097a4d4c7b547d39e9799336e80c1cddc2df92ceec381f9a5f1df8aed270234bd64736f6c63430005110032" - } \ No newline at end of file diff --git a/contracts/hardhat.config.js b/contracts/hardhat.config.js index 8dcd414c25..e170ff7477 100644 --- a/contracts/hardhat.config.js +++ b/contracts/hardhat.config.js @@ -1,7 +1,17 @@ /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { - solidity: '0.8.20', + solidity: { + compilers: [ + { + version: "0.8.20", + }, + // This version is required to compile the werc9 contract. + { + version: "0.4.22", + }, + ], + }, paths: { - sources: './solidity' - } -} + sources: "./solidity", + }, +}; diff --git a/contracts/wevmos.go b/contracts/wevmos.go deleted file mode 100644 index c9881fe43f..0000000000 --- a/contracts/wevmos.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright Tharsis Labs Ltd.(Evmos) -// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) - -package contracts - -import ( - _ "embed" // embed compiled smart contract - "encoding/json" - - evmtypes "github.com/evmos/evmos/v20/x/evm/types" -) - -var ( - //go:embed compiled_contracts/WEVMOS.json - WEVMOSJSON []byte - - // WEVMOSContract is the compiled contract of WEVMOS - WEVMOSContract evmtypes.CompiledContract -) - -func init() { - err := json.Unmarshal(WEVMOSJSON, &WEVMOSContract) - if err != nil { - panic(err) - } - - if len(WEVMOSContract.Bin) == 0 { - panic("failed to load WEVMOS smart contract") - } -} diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index e10caae441..a3f1a7896c 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -13,6 +13,7 @@ import ( storetypes "cosmossdk.io/store/types" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" cmn "github.com/evmos/evmos/v20/precompiles/common" erc20keeper "github.com/evmos/evmos/v20/x/erc20/keeper" @@ -143,6 +144,6 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ // IsTransaction checks if the given method name corresponds to a transaction or query. // It returns false since all bank methods are queries. -func (Precompile) IsTransaction(_ string) bool { +func (Precompile) IsTransaction(_ *abi.Method) bool { return false } diff --git a/precompiles/common/precompile.go b/precompiles/common/precompile.go index a9828a074b..3df2e8e317 100644 --- a/precompiles/common/precompile.go +++ b/precompiles/common/precompile.go @@ -74,7 +74,7 @@ func (p Precompile) RunSetup( evm *vm.EVM, contract *vm.Contract, readOnly bool, - isTransaction func(name string) bool, + isTransaction func(name *abi.Method) bool, ) (ctx sdk.Context, stateDB *statedb.StateDB, s snapshot, method *abi.Method, gasConfig storetypes.Gas, args []interface{}, err error) { //nolint:revive stateDB, ok := evm.StateDB.(*statedb.StateDB) if !ok { @@ -125,7 +125,7 @@ func (p Precompile) RunSetup( } // return error if trying to write to state during a read-only call - if readOnly && isTransaction(method.Name) { + if readOnly && isTransaction(method) { return sdk.Context{}, nil, s, nil, uint64(0), nil, vm.ErrWriteProtection } diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index bf6835f8e9..89a4bb267f 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -10,6 +10,7 @@ import ( storetypes "cosmossdk.io/store/types" authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" cmn "github.com/evmos/evmos/v20/precompiles/common" "github.com/evmos/evmos/v20/x/evm/core/vm" @@ -76,7 +77,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 { return 0 } - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) + return p.Precompile.RequiredGas(input, p.IsTransaction(method)) } // Run executes the precompiled contract distribution methods defined in the ABI. @@ -146,8 +147,8 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ // - SetWithdrawAddress // - WithdrawDelegatorRewards // - WithdrawValidatorCommission -func (Precompile) IsTransaction(methodName string) bool { - switch methodName { +func (Precompile) IsTransaction(method *abi.Method) bool { + switch method.Name { case ClaimRewardsMethod, SetWithdrawAddressMethod, WithdrawDelegatorRewardsMethod, diff --git a/precompiles/distribution/distribution_test.go b/precompiles/distribution/distribution_test.go index 505e91e960..105ef8e4af 100644 --- a/precompiles/distribution/distribution_test.go +++ b/precompiles/distribution/distribution_test.go @@ -7,6 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/evmos/evmos/v20/app" @@ -18,44 +19,44 @@ import ( func (s *PrecompileTestSuite) TestIsTransaction() { testCases := []struct { name string - method string + method abi.Method isTx bool }{ { distribution.SetWithdrawAddressMethod, - s.precompile.Methods[distribution.SetWithdrawAddressMethod].Name, + s.precompile.Methods[distribution.SetWithdrawAddressMethod], true, }, { distribution.WithdrawDelegatorRewardsMethod, - s.precompile.Methods[distribution.WithdrawDelegatorRewardsMethod].Name, + s.precompile.Methods[distribution.WithdrawDelegatorRewardsMethod], true, }, { distribution.WithdrawValidatorCommissionMethod, - s.precompile.Methods[distribution.WithdrawValidatorCommissionMethod].Name, + s.precompile.Methods[distribution.WithdrawValidatorCommissionMethod], true, }, { distribution.FundCommunityPoolMethod, - s.precompile.Methods[distribution.FundCommunityPoolMethod].Name, + s.precompile.Methods[distribution.FundCommunityPoolMethod], true, }, { distribution.ValidatorDistributionInfoMethod, - s.precompile.Methods[distribution.ValidatorDistributionInfoMethod].Name, + s.precompile.Methods[distribution.ValidatorDistributionInfoMethod], false, }, { "invalid", - "invalid", + abi.Method{}, false, }, } for _, tc := range testCases { s.Run(tc.name, func() { - s.Require().Equal(s.precompile.IsTransaction(tc.method), tc.isTx) + s.Require().Equal(s.precompile.IsTransaction(&tc.method), tc.isTx) }) } } diff --git a/precompiles/erc20/erc20.go b/precompiles/erc20/erc20.go index ea960bf4ad..21b2e13949 100644 --- a/precompiles/erc20/erc20.go +++ b/precompiles/erc20/erc20.go @@ -48,8 +48,9 @@ var _ vm.PrecompiledContract = &Precompile{} type Precompile struct { cmn.Precompile tokenPair erc20types.TokenPair - bankKeeper bankkeeper.Keeper transferKeeper transferkeeper.Keeper + // BankKeeper is a public field so that the werc20 precompile can use it. + BankKeeper bankkeeper.Keeper } // NewPrecompile creates a new ERC-20 Precompile instance as a @@ -74,7 +75,7 @@ func NewPrecompile( TransientKVGasConfig: storetypes.GasConfig{}, }, tokenPair: tokenPair, - bankKeeper: bankKeeper, + BankKeeper: bankKeeper, transferKeeper: transferKeeper, } // Address defines the address of the ERC-20 precompile contract. @@ -164,8 +165,8 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ } // IsTransaction checks if the given method name corresponds to a transaction or query. -func (Precompile) IsTransaction(methodName string) bool { - switch methodName { +func (Precompile) IsTransaction(method *abi.Method) bool { + switch method.Name { case TransferMethod, TransferFromMethod, auth.ApproveMethod, diff --git a/precompiles/erc20/erc20_test.go b/precompiles/erc20/erc20_test.go index 299b138e40..7c139c5ff2 100644 --- a/precompiles/erc20/erc20_test.go +++ b/precompiles/erc20/erc20_test.go @@ -14,18 +14,28 @@ func (s *PrecompileTestSuite) TestIsTransaction() { s.SetupTest() // Queries - s.Require().False(s.precompile.IsTransaction(erc20.BalanceOfMethod)) - s.Require().False(s.precompile.IsTransaction(erc20.DecimalsMethod)) - s.Require().False(s.precompile.IsTransaction(erc20.NameMethod)) - s.Require().False(s.precompile.IsTransaction(erc20.SymbolMethod)) - s.Require().False(s.precompile.IsTransaction(erc20.TotalSupplyMethod)) + method := s.precompile.Methods[erc20.BalanceOfMethod] + s.Require().False(s.precompile.IsTransaction(&method)) + method = s.precompile.Methods[erc20.DecimalsMethod] + s.Require().False(s.precompile.IsTransaction(&method)) + method = s.precompile.Methods[erc20.NameMethod] + s.Require().False(s.precompile.IsTransaction(&method)) + method = s.precompile.Methods[erc20.SymbolMethod] + s.Require().False(s.precompile.IsTransaction(&method)) + method = s.precompile.Methods[erc20.TotalSupplyMethod] + s.Require().False(s.precompile.IsTransaction(&method)) // Transactions - s.Require().True(s.precompile.IsTransaction(auth.ApproveMethod)) - s.Require().True(s.precompile.IsTransaction(auth.IncreaseAllowanceMethod)) - s.Require().True(s.precompile.IsTransaction(auth.DecreaseAllowanceMethod)) - s.Require().True(s.precompile.IsTransaction(erc20.TransferMethod)) - s.Require().True(s.precompile.IsTransaction(erc20.TransferFromMethod)) + method = s.precompile.Methods[auth.ApproveMethod] + s.Require().True(s.precompile.IsTransaction(&method)) + method = s.precompile.Methods[auth.IncreaseAllowanceMethod] + s.Require().True(s.precompile.IsTransaction(&method)) + method = s.precompile.Methods[auth.DecreaseAllowanceMethod] + s.Require().True(s.precompile.IsTransaction(&method)) + method = s.precompile.Methods[erc20.TransferMethod] + s.Require().True(s.precompile.IsTransaction(&method)) + method = s.precompile.Methods[erc20.TransferFromMethod] + s.Require().True(s.precompile.IsTransaction(&method)) } func (s *PrecompileTestSuite) TestRequiredGas() { diff --git a/precompiles/erc20/query.go b/precompiles/erc20/query.go index e381773e88..07c0ceef31 100644 --- a/precompiles/erc20/query.go +++ b/precompiles/erc20/query.go @@ -51,7 +51,7 @@ func (p Precompile) Name( method *abi.Method, _ []interface{}, ) ([]byte, error) { - metadata, found := p.bankKeeper.GetDenomMetaData(ctx, p.tokenPair.Denom) + metadata, found := p.BankKeeper.GetDenomMetaData(ctx, p.tokenPair.Denom) if found { return method.Outputs.Pack(metadata.Name) } @@ -75,7 +75,7 @@ func (p Precompile) Symbol( method *abi.Method, _ []interface{}, ) ([]byte, error) { - metadata, found := p.bankKeeper.GetDenomMetaData(ctx, p.tokenPair.Denom) + metadata, found := p.BankKeeper.GetDenomMetaData(ctx, p.tokenPair.Denom) if found { return method.Outputs.Pack(metadata.Symbol) } @@ -99,7 +99,7 @@ func (p Precompile) Decimals( method *abi.Method, _ []interface{}, ) ([]byte, error) { - metadata, found := p.bankKeeper.GetDenomMetaData(ctx, p.tokenPair.Denom) + metadata, found := p.BankKeeper.GetDenomMetaData(ctx, p.tokenPair.Denom) if !found { denomTrace, err := ibc.GetDenomTrace(p.transferKeeper, ctx, p.tokenPair.Denom) if err != nil { @@ -152,7 +152,7 @@ func (p Precompile) TotalSupply( method *abi.Method, _ []interface{}, ) ([]byte, error) { - supply := p.bankKeeper.GetSupply(ctx, p.tokenPair.Denom) + supply := p.BankKeeper.GetSupply(ctx, p.tokenPair.Denom) return method.Outputs.Pack(supply.Amount.BigInt()) } @@ -171,7 +171,7 @@ func (p Precompile) BalanceOf( return nil, err } - balance := p.bankKeeper.GetBalance(ctx, account.Bytes(), p.tokenPair.Denom) + balance := p.BankKeeper.GetBalance(ctx, account.Bytes(), p.tokenPair.Denom) return method.Outputs.Pack(balance.Amount.BigInt()) } diff --git a/precompiles/erc20/tx.go b/precompiles/erc20/tx.go index 665bfa6509..4d1f0c7f2e 100644 --- a/precompiles/erc20/tx.go +++ b/precompiles/erc20/tx.go @@ -92,7 +92,7 @@ func (p *Precompile) transfer( var prevAllowance *big.Int if ownerIsSpender { - msgSrv := bankkeeper.NewMsgServerImpl(p.bankKeeper) + msgSrv := bankkeeper.NewMsgServerImpl(p.BankKeeper) _, err = msgSrv.Send(ctx, msg) } else { _, _, prevAllowance, err = GetAuthzExpirationAndAllowance(p.AuthzKeeper, ctx, spenderAddr, from, p.tokenPair.Denom) diff --git a/precompiles/erc20/types.go b/precompiles/erc20/types.go index 3b52ad7560..399a7e31ff 100644 --- a/precompiles/erc20/types.go +++ b/precompiles/erc20/types.go @@ -12,13 +12,6 @@ import ( "github.com/ethereum/go-ethereum/common" ) -const ( - // WEVMOSContractMainnet is the WEVMOS contract address for mainnet - WEVMOSContractMainnet = "0xD4949664cD82660AaE99bEdc034a0deA8A0bd517" - // WEVMOSContractTestnet is the WEVMOS contract address for testnet - WEVMOSContractTestnet = "0xcc491f589b45d4a3c679016195b3fb87d7848210" -) - // EventTransfer defines the event data for the ERC20 Transfer events. type EventTransfer struct { From common.Address diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index 559b6c5507..dd6867b81b 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -82,7 +82,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 { return 0 } - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) + return p.Precompile.RequiredGas(input, p.IsTransaction(method)) } // Run executes the precompiled contract gov methods defined in the ABI. @@ -143,8 +143,8 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ // Available gov transactions are: // - Vote // - VoteWeighted -func (Precompile) IsTransaction(methodName string) bool { - switch methodName { +func (Precompile) IsTransaction(method *abi.Method) bool { + switch method.Name { case VoteMethod, VoteWeightedMethod: return true default: diff --git a/precompiles/gov/gov_test.go b/precompiles/gov/gov_test.go index 5b3408e871..cfe72d5052 100644 --- a/precompiles/gov/gov_test.go +++ b/precompiles/gov/gov_test.go @@ -3,6 +3,7 @@ package gov_test import ( "math/big" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -16,24 +17,24 @@ import ( func (s *PrecompileTestSuite) TestIsTransaction() { testCases := []struct { name string - method string + method abi.Method isTx bool }{ { gov.VoteMethod, - s.precompile.Methods[gov.VoteMethod].Name, + s.precompile.Methods[gov.VoteMethod], true, }, { "invalid", - "invalid", + abi.Method{}, false, }, } for _, tc := range testCases { s.Run(tc.name, func() { - s.Require().Equal(s.precompile.IsTransaction(tc.method), tc.isTx) + s.Require().Equal(s.precompile.IsTransaction(&tc.method), tc.isTx) }) } } diff --git a/precompiles/ics20/ics20.go b/precompiles/ics20/ics20.go index d3a20a0fdc..b9f9068dfe 100644 --- a/precompiles/ics20/ics20.go +++ b/precompiles/ics20/ics20.go @@ -10,6 +10,7 @@ import ( storetypes "cosmossdk.io/store/types" authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" channelkeeper "github.com/cosmos/ibc-go/v8/modules/core/04-channel/keeper" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/evmos/evmos/v20/precompiles/authorization" cmn "github.com/evmos/evmos/v20/precompiles/common" @@ -83,7 +84,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 { return 0 } - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) + return p.Precompile.RequiredGas(input, p.IsTransaction(method)) } // Run executes the precompiled contract IBC transfer methods defined in the ABI. @@ -151,8 +152,8 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ // - Revoke // - IncreaseAllowance // - DecreaseAllowance -func (Precompile) IsTransaction(method string) bool { - switch method { +func (Precompile) IsTransaction(method *abi.Method) bool { + switch method.Name { case TransferMethod, authorization.ApproveMethod, authorization.RevokeMethod, diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 8f339a6e61..de1b19426c 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -81,7 +81,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 { return 0 } - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) + return p.Precompile.RequiredGas(input, p.IsTransaction(method)) } // Run executes the precompiled contract staking methods defined in the ABI. @@ -168,8 +168,8 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ // - Revoke // - IncreaseAllowance // - DecreaseAllowance -func (Precompile) IsTransaction(method string) bool { - switch method { +func (Precompile) IsTransaction(method *abi.Method) bool { + switch method.Name { case CreateValidatorMethod, EditValidatorMethod, DelegateMethod, diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index a63c01abf6..07c2de0902 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -8,6 +8,7 @@ import ( "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/evmos/evmos/v20/x/evm/core/vm" @@ -24,64 +25,64 @@ import ( func (s *PrecompileTestSuite) TestIsTransaction() { testCases := []struct { name string - method string + method abi.Method isTx bool }{ { authorization.ApproveMethod, - s.precompile.Methods[authorization.ApproveMethod].Name, + s.precompile.Methods[authorization.ApproveMethod], true, }, { authorization.IncreaseAllowanceMethod, - s.precompile.Methods[authorization.IncreaseAllowanceMethod].Name, + s.precompile.Methods[authorization.IncreaseAllowanceMethod], true, }, { authorization.DecreaseAllowanceMethod, - s.precompile.Methods[authorization.DecreaseAllowanceMethod].Name, + s.precompile.Methods[authorization.DecreaseAllowanceMethod], true, }, { staking.CreateValidatorMethod, - s.precompile.Methods[staking.CreateValidatorMethod].Name, + s.precompile.Methods[staking.CreateValidatorMethod], true, }, { staking.DelegateMethod, - s.precompile.Methods[staking.DelegateMethod].Name, + s.precompile.Methods[staking.DelegateMethod], true, }, { staking.UndelegateMethod, - s.precompile.Methods[staking.UndelegateMethod].Name, + s.precompile.Methods[staking.UndelegateMethod], true, }, { staking.RedelegateMethod, - s.precompile.Methods[staking.RedelegateMethod].Name, + s.precompile.Methods[staking.RedelegateMethod], true, }, { staking.CancelUnbondingDelegationMethod, - s.precompile.Methods[staking.CancelUnbondingDelegationMethod].Name, + s.precompile.Methods[staking.CancelUnbondingDelegationMethod], true, }, { staking.DelegationMethod, - s.precompile.Methods[staking.DelegationMethod].Name, + s.precompile.Methods[staking.DelegationMethod], false, }, { "invalid", - "invalid", + abi.Method{}, false, }, } for _, tc := range testCases { s.Run(tc.name, func() { - s.Require().Equal(s.precompile.IsTransaction(tc.method), tc.isTx) + s.Require().Equal(s.precompile.IsTransaction(&tc.method), tc.isTx) }) } } diff --git a/precompiles/vesting/vesting.go b/precompiles/vesting/vesting.go index 6a4a1542b0..d2a00ea038 100644 --- a/precompiles/vesting/vesting.go +++ b/precompiles/vesting/vesting.go @@ -14,6 +14,7 @@ import ( "cosmossdk.io/log" storetypes "cosmossdk.io/store/types" authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" cmn "github.com/evmos/evmos/v20/precompiles/common" "github.com/evmos/evmos/v20/x/evm/core/vm" @@ -49,7 +50,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 { return 0 } - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) + return p.Precompile.RequiredGas(input, p.IsTransaction(method)) } // NewPrecompile creates a new vesting Precompile instance as a @@ -137,8 +138,8 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [ // - UpdateVestingFunder // - ConvertVestingAccount // - Approve -func (Precompile) IsTransaction(method string) bool { - switch method { +func (Precompile) IsTransaction(method *abi.Method) bool { + switch method.Name { case CreateClawbackVestingAccountMethod, FundVestingAccountMethod, ClawbackMethod, diff --git a/precompiles/werc20/IWERC20.sol b/precompiles/werc20/IWERC20.sol new file mode 100644 index 0000000000..6a2e019e55 --- /dev/null +++ b/precompiles/werc20/IWERC20.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.18; + +import "./../erc20/IERC20MetadataAllowance.sol"; + +/** + * @author Evmos Team + * @title Wrapped ERC20 Interface + * @dev Interface for representing the native EVM token as a wrapped ERC20 standard. + */ +interface IWERC20 is IERC20MetadataAllowance { + /// @dev Emitted when the native tokens are deposited in exchange for the wrapped ERC20. + /// @param dst The account for which the deposit is made. + /// @param wad The amount of native tokens deposited. + event Deposit(address indexed dst, uint256 wad); + + /// @dev Emitted when the native token is withdrawn. + /// @param src The account for which the withdrawal is made. + /// @param wad The amount of native tokens withdrawn. + event Withdrawal(address indexed src, uint256 wad); + + /// @dev Default fallback payable function. Must call the deposit method in implementing contracts. + fallback() external payable; + + /// @dev Default receive payable function. Must call the deposit method in implementing contracts. + receive() external payable; + + /// @dev Deposits native tokens in exchange for wrapped ERC20 token. + /// @dev Emits a Deposit Event. + function deposit() external payable; + + /// @dev Withdraws native tokens from wrapped ERC20 token. + /// @dev Emits a Withdrawal Event. + /// @param wad The amount of native tokens to be withdrawn. + function withdraw(uint256 wad) external; +} diff --git a/precompiles/werc20/abi.json b/precompiles/werc20/abi.json new file mode 100644 index 0000000000..eac5c1280f --- /dev/null +++ b/precompiles/werc20/abi.json @@ -0,0 +1,347 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "IWERC20", + "sourceName": "solidity/precompiles/werc20/IWERC20.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "src", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "wad", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "bytecode": "0x", + "deployedBytecode": "0x", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/precompiles/werc20/events.go b/precompiles/werc20/events.go new file mode 100644 index 0000000000..6f1a677ffa --- /dev/null +++ b/precompiles/werc20/events.go @@ -0,0 +1,81 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package werc20 + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + cmn "github.com/evmos/evmos/v20/precompiles/common" +) + +const ( + // EventTypeDeposit is the key of the event type for the Deposit transaction. + EventTypeDeposit = "Deposit" + // EventTypeWithdrawal is the key of the event type for the Withdraw transaction. + EventTypeWithdrawal = "Withdrawal" +) + +// EmitDepositEvent creates a new Deposit event emitted after a Deposit transaction. +func (p Precompile) EmitDepositEvent( + ctx sdk.Context, + stateDB vm.StateDB, + caller common.Address, + amount *big.Int, +) error { + event := p.ABI.Events[EventTypeDeposit] + return p.createWERC20Event(ctx, stateDB, event, caller, amount) +} + +// EmitWithdrawalEvent creates a new Withdrawal event emitted after a Withdraw transaction. +func (p Precompile) EmitWithdrawalEvent( + ctx sdk.Context, + stateDB vm.StateDB, + src common.Address, + amount *big.Int, +) error { + event := p.ABI.Events[EventTypeWithdrawal] + return p.createWERC20Event(ctx, stateDB, event, src, amount) +} + +// createWERC20Event adds to the StateDB a log representing an event for the +// WERC20 precompile. +func (p Precompile) createWERC20Event( + ctx sdk.Context, + stateDB vm.StateDB, + event abi.Event, + address common.Address, + amount *big.Int, +) error { + // Prepare the event topics + topics := make([]common.Hash, 2) + + topics[0] = event.ID + + var err error + topics[1], err = cmn.MakeTopic(address) + if err != nil { + return err + } + + arguments := abi.Arguments{event.Inputs[1]} + packed, err := arguments.Pack(amount) + if err != nil { + return err + } + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: packed, + BlockNumber: uint64(ctx.BlockHeight()), //nolint:gosec // G115 + }) + + return nil +} diff --git a/precompiles/werc20/events_test.go b/precompiles/werc20/events_test.go new file mode 100644 index 0000000000..81b6fc041a --- /dev/null +++ b/precompiles/werc20/events_test.go @@ -0,0 +1,207 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package werc20_test + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + cmn "github.com/evmos/evmos/v20/precompiles/common" + "github.com/evmos/evmos/v20/precompiles/werc20" + "github.com/evmos/evmos/v20/testutil/integration/evmos/factory" + "github.com/evmos/evmos/v20/testutil/integration/evmos/grpc" + "github.com/evmos/evmos/v20/testutil/integration/evmos/keyring" + "github.com/evmos/evmos/v20/testutil/integration/evmos/network" + "github.com/evmos/evmos/v20/utils" + erc20types "github.com/evmos/evmos/v20/x/erc20/types" + evmtypes "github.com/evmos/evmos/v20/x/evm/types" +) + +type PrecompileUnitTestSuite struct { + suite.Suite + + network *network.UnitTestNetwork + factory factory.TxFactory + grpcHandler grpc.Handler + keyring keyring.Keyring + + // WEVMOS related fields + precompile *werc20.Precompile + precompileAddrHex string +} + +func TestPrecompileUnitTestSuite(t *testing.T) { + suite.Run(t, new(PrecompileUnitTestSuite)) +} + +// SetupTest allows to configure the testing suite embedding a network with a +// custom chainID. This is important to check that the correct address is used +// for the precompile. +func (s *PrecompileUnitTestSuite) SetupTest(chainID string) { + keyring := keyring.New(2) + + integrationNetwork := network.NewUnitTestNetwork( + network.WithChainID(chainID), + network.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), + ) + grpcHandler := grpc.NewIntegrationHandler(integrationNetwork) + txFactory := factory.New(integrationNetwork, grpcHandler) + + s.network = integrationNetwork + s.factory = txFactory + s.grpcHandler = grpcHandler + s.keyring = keyring + + s.precompileAddrHex = erc20types.GetWEVMOSContractHex(chainID) + + ctx := integrationNetwork.GetContext() + + tokenPairID := s.network.App.Erc20Keeper.GetTokenPairID(ctx, evmtypes.GetEVMCoinDenom()) + tokenPair, found := s.network.App.Erc20Keeper.GetTokenPair(ctx, tokenPairID) + s.Require().True(found, "expected wevmos precompile to be registered in the tokens map") + s.Require().Equal(s.precompileAddrHex, tokenPair.Erc20Address, "expected a different address of the contract") + + precompile, err := werc20.NewPrecompile( + tokenPair, + s.network.App.BankKeeper, + s.network.App.AuthzKeeper, + s.network.App.TransferKeeper, + ) + s.Require().NoError(err, "failed to instantiate the werc20 precompile") + s.Require().NotNil(precompile) + s.precompile = precompile +} + +type DepositEvent struct { + Dst common.Address + Wad *big.Int +} + +type WithdrawalEvent struct { + Src common.Address + Wad *big.Int +} + +//nolint:dupl +func (s *PrecompileUnitTestSuite) TestEmitDepositEvent() { + testCases := []struct { + name string + chainID string + }{ + { + name: "mainnet", + chainID: utils.MainnetChainID + "-1", + }, { + name: "testnet", + chainID: utils.TestnetChainID + "-1", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest(tc.chainID) + caller := s.keyring.GetAddr(0) + amount := new(big.Int).SetInt64(1_000) + + stateDB := s.network.GetStateDB() + + err := s.precompile.EmitDepositEvent( + s.network.GetContext(), + stateDB, + caller, + amount, + ) + s.Require().NoError(err, "expected deposit event to be emitted successfully") + + log := stateDB.Logs()[0] + + // Check on the address + s.Require().Equal(log.Address, s.precompile.Address()) + + // Check on the topics + event := s.precompile.ABI.Events[werc20.EventTypeDeposit] + s.Require().Equal( + crypto.Keccak256Hash([]byte(event.Sig)), + common.HexToHash(log.Topics[0].Hex()), + ) + var adddressTopic common.Hash + copy(adddressTopic[common.HashLength-common.AddressLength:], caller[:]) + s.Require().Equal(adddressTopic, log.Topics[1]) + + s.Require().EqualValues(log.BlockNumber, s.network.GetContext().BlockHeight()) + + // Verify data + var depositEvent DepositEvent + err = cmn.UnpackLog(s.precompile.ABI, &depositEvent, werc20.EventTypeDeposit, *log) + s.Require().NoError(err, "unable to unpack log into deposit event") + + s.Require().Equal(caller, depositEvent.Dst, "expected different destination address") + s.Require().Equal(amount, depositEvent.Wad, "expected different amount") + }) + } +} + +//nolint:dupl +func (s *PrecompileUnitTestSuite) TestEmitWithdrawalEvent() { + testCases := []struct { + name string + chainID string + }{ + { + name: "mainnet", + chainID: utils.MainnetChainID + "-1", + }, { + name: "testnet", + chainID: utils.TestnetChainID + "-1", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest(tc.chainID) + caller := s.keyring.GetAddr(0) + amount := new(big.Int).SetInt64(1_000) + + stateDB := s.network.GetStateDB() + + err := s.precompile.EmitWithdrawalEvent( + s.network.GetContext(), + stateDB, + caller, + amount, + ) + s.Require().NoError(err, "expected withdrawal event to be emitted successfully") + + log := stateDB.Logs()[0] + + // Check on the address + s.Require().Equal(log.Address, s.precompile.Address()) + + // Check on the topics + event := s.precompile.ABI.Events[werc20.EventTypeWithdrawal] + s.Require().Equal( + crypto.Keccak256Hash([]byte(event.Sig)), + common.HexToHash(log.Topics[0].Hex()), + ) + var adddressTopic common.Hash + copy(adddressTopic[common.HashLength-common.AddressLength:], caller[:]) + s.Require().Equal(adddressTopic, log.Topics[1]) + + s.Require().EqualValues(log.BlockNumber, s.network.GetContext().BlockHeight()) + + // Verify data + var withdrawalEvent WithdrawalEvent + err = cmn.UnpackLog(s.precompile.ABI, &withdrawalEvent, werc20.EventTypeWithdrawal, *log) + s.Require().NoError(err, "unable to unpack log into withdrawal event") + + s.Require().Equal(caller, withdrawalEvent.Src, "expected different source address") + s.Require().Equal(amount, withdrawalEvent.Wad, "expected different amount") + }) + } +} diff --git a/precompiles/werc20/integration_test.go b/precompiles/werc20/integration_test.go new file mode 100644 index 0000000000..401100cf31 --- /dev/null +++ b/precompiles/werc20/integration_test.go @@ -0,0 +1,597 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package werc20_test + +import ( + "math/big" + "strings" + "testing" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + auth "github.com/evmos/evmos/v20/precompiles/authorization" + "github.com/evmos/evmos/v20/precompiles/erc20" + "github.com/evmos/evmos/v20/precompiles/testutil" + "github.com/evmos/evmos/v20/precompiles/werc20" + "github.com/evmos/evmos/v20/precompiles/werc20/testdata" + "github.com/evmos/evmos/v20/testutil/integration/evmos/factory" + "github.com/evmos/evmos/v20/testutil/integration/evmos/grpc" + "github.com/evmos/evmos/v20/testutil/integration/evmos/keyring" + "github.com/evmos/evmos/v20/testutil/integration/evmos/network" + utiltx "github.com/evmos/evmos/v20/testutil/tx" + erc20types "github.com/evmos/evmos/v20/x/erc20/types" + evmtypes "github.com/evmos/evmos/v20/x/evm/types" + feemarkettypes "github.com/evmos/evmos/v20/x/feemarket/types" + + "github.com/ethereum/go-ethereum/common" + + //nolint:revive // dot imports are fine for Ginkgo + . "github.com/onsi/ginkgo/v2" + //nolint:revive // dot imports are fine for Ginkgo + . "github.com/onsi/gomega" +) + +// ------------------------------------------------------------------------------------------------- +// Integration test suite +// ------------------------------------------------------------------------------------------------- + +type PrecompileIntegrationTestSuite struct { + network *network.UnitTestNetwork + factory factory.TxFactory + grpcHandler grpc.Handler + keyring keyring.Keyring + + wrappedCoinDenom string + + // WEVMOS related fields + precompile *werc20.Precompile + precompileAddrHex string +} + +func TestPrecompileIntegrationTestSuite(t *testing.T) { + // Run Ginkgo integration tests + RegisterFailHandler(Fail) + RunSpecs(t, "WEVMOS precompile test suite") +} + +// checkAndReturnBalance check that the balance of the address is the same in +// the smart contract and in the balance and returns the amount. +func (is *PrecompileIntegrationTestSuite) checkAndReturnBalance( + balanceCheck testutil.LogCheckArgs, + callsData CallsData, + address common.Address, +) *big.Int { + txArgs, balancesArgs := callsData.getTxAndCallArgs(directCall, erc20.BalanceOfMethod, address) + txArgs.GasLimit = 1_000_000_000_000 + + _, ethRes, err := is.factory.CallContractAndCheckLogs(callsData.sender.Priv, txArgs, balancesArgs, balanceCheck) + Expect(err).ToNot(HaveOccurred(), "failed to execute balanceOf") + var erc20Balance *big.Int + err = is.precompile.UnpackIntoInterface(&erc20Balance, erc20.BalanceOfMethod, ethRes.Ret) + Expect(err).ToNot(HaveOccurred(), "failed to unpack result") + + addressAcc := sdk.AccAddress(address.Bytes()) + balanceAfter, err := is.grpcHandler.GetBalance(addressAcc, is.wrappedCoinDenom) + Expect(err).ToNot(HaveOccurred(), "expected no error getting balance") + + Expect(erc20Balance.String()).To(Equal(balanceAfter.Balance.Amount.BigInt().String()), "expected return balance from contract equal to bank") + return erc20Balance +} + +// ------------------------------------------------------------------------------------------------- +// Integration tests +// ------------------------------------------------------------------------------------------------- + +var _ = When("a user interact with the WEVMOS precompiled contract", func() { + var ( + is *PrecompileIntegrationTestSuite + passCheck, failCheck testutil.LogCheckArgs + transferCheck, depositCheck, withdrawCheck testutil.LogCheckArgs + + callsData CallsData + + txSender, user keyring.Key + + revertContractAddr common.Address + ) + + depositAmount := big.NewInt(1e18) + withdrawAmount := depositAmount + transferAmount := depositAmount + + BeforeEach(func() { + is = new(PrecompileIntegrationTestSuite) + keyring := keyring.New(2) + + txSender = keyring.GetKey(0) + user = keyring.GetKey(1) + + // Set the base fee to zero to allow for zero cost tx. The final gas cost is + // not part of the logic tested here so this makes testing more easy. + customGenesis := network.CustomGenesisState{} + feemarketGenesis := feemarkettypes.DefaultGenesisState() + feemarketGenesis.Params.NoBaseFee = true + customGenesis[feemarkettypes.ModuleName] = feemarketGenesis + + integrationNetwork := network.NewUnitTestNetwork( + network.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), + network.WithCustomGenesis(customGenesis), + ) + grpcHandler := grpc.NewIntegrationHandler(integrationNetwork) + txFactory := factory.New(integrationNetwork, grpcHandler) + + is.network = integrationNetwork + is.factory = txFactory + is.grpcHandler = grpcHandler + is.keyring = keyring + + is.wrappedCoinDenom = evmtypes.GetEVMCoinDenom() + is.precompileAddrHex = erc20types.GetWEVMOSContractHex(is.network.GetChainID()) + + ctx := integrationNetwork.GetContext() + + // Perform some check before adding the precompile to the suite. + + // Check that WEVMOS is part of the native precompiles. + erc20Params := is.network.App.Erc20Keeper.GetParams(ctx) + Expect(erc20Params.NativePrecompiles).To( + ContainElement(is.precompileAddrHex), + "expected wevmos to be in the native precompiles", + ) + _, found := is.network.App.BankKeeper.GetDenomMetaData(ctx, evmtypes.GetEVMCoinDenom()) + Expect(found).To(BeTrue(), "expected native token metadata to be registered") + + // Check that WEVMOS is registered in the token pairs map. + tokenPairID := is.network.App.Erc20Keeper.GetTokenPairID(ctx, is.wrappedCoinDenom) + tokenPair, found := is.network.App.Erc20Keeper.GetTokenPair(ctx, tokenPairID) + Expect(found).To(BeTrue(), "expected wevmos precompile to be registered in the tokens map") + Expect(tokenPair.Erc20Address).To(Equal(is.precompileAddrHex)) + + precompileAddr := common.HexToAddress(is.precompileAddrHex) + tokenPair = erc20types.NewTokenPair( + precompileAddr, + evmtypes.GetEVMCoinDenom(), + erc20types.OWNER_MODULE, + ) + precompile, err := werc20.NewPrecompile( + tokenPair, + is.network.App.BankKeeper, + is.network.App.AuthzKeeper, + is.network.App.TransferKeeper, + ) + Expect(err).ToNot(HaveOccurred(), "failed to instantiate the werc20 precompile") + is.precompile = precompile + + // Setup of the contract calling into the precompile to tests revert + // edge cases and proper handling of snapshots. + revertCallerContract, err := testdata.LoadWEVMOS9TestCaller() + Expect(err).ToNot(HaveOccurred(), "failed to load werc20 reverter caller contract") + + txArgs := evmtypes.EvmTxArgs{} + txArgs.GasTipCap = new(big.Int).SetInt64(0) + txArgs.GasLimit = 1_000_000_000_000 + revertContractAddr, err = is.factory.DeployContract( + txSender.Priv, + txArgs, + factory.ContractDeploymentData{ + Contract: revertCallerContract, + ConstructorArgs: []interface{}{ + common.HexToAddress(is.precompileAddrHex), + }, + }, + ) + Expect(err).ToNot(HaveOccurred(), "failed to deploy werc20 reverter contract") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + // Support struct used to simplify transactions creation. + callsData = CallsData{ + sender: txSender, + + precompileAddr: precompileAddr, + precompileABI: precompile.ABI, + + precompileReverterAddr: revertContractAddr, + precompileReverterABI: revertCallerContract.ABI, + } + + // Utility types used to check the different events emitted. + failCheck = testutil.LogCheckArgs{ABIEvents: is.precompile.Events} + passCheck = failCheck.WithExpPass(true) + withdrawCheck = passCheck.WithExpEvents(werc20.EventTypeWithdrawal) + depositCheck = passCheck.WithExpEvents(werc20.EventTypeDeposit) + transferCheck = passCheck.WithExpEvents(erc20.EventTypeTransfer) + }) + Context("calling a specific wrapped coin method", func() { + Context("and funds are part of the transaction", func() { + When("the method is deposit", func() { + It("it should return funds to sender and emit the event", func() { + // Store initial balance to verify that sender + // balance remains the same after the contract call. + initBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, werc20.DepositMethod) + txArgs.Amount = depositAmount + + _, _, err := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected error calling the precompile") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + finalBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(finalBalance.String()).To(Equal(initBalance.String())) + }) + It("it should consume at least the deposit requested gas", func() { + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, werc20.DepositMethod) + txArgs.Amount = depositAmount + + _, ethRes, _ := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + Expect(ethRes.GasUsed).To(BeNumerically(">=", werc20.DepositRequiredGas), "expected different gas used for deposit") + }) + }) + //nolint:dupl + When("no calldata is provided", func() { + It("it should call the receive which behave like deposit", func() { + initBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, "") + txArgs.Amount = depositAmount + + _, _, err := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected error calling the precompile") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + finalBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(finalBalance).To(Equal(initBalance)) + }) + It("it should consume at least the deposit requested gas", func() { + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, werc20.DepositMethod) + txArgs.Amount = depositAmount + + _, ethRes, _ := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + Expect(ethRes.GasUsed).To(BeNumerically(">=", werc20.DepositRequiredGas), "expected different gas used for receive") + }) + }) + When("the specified method is too short", func() { + It("it should call the fallback which behave like deposit", func() { + initBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, "") + txArgs.Amount = depositAmount + // Short method is directly set in the input to skip ABI validation + txArgs.Input = []byte{1, 2, 3} + + _, _, err := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected error calling the precompile") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + finalBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(finalBalance).To(Equal(initBalance)) + }) + It("it should consume at least the deposit requested gas", func() { + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, "") + txArgs.Amount = depositAmount + // Short method is directly set in the input to skip ABI validation + txArgs.Input = []byte{1, 2, 3} + + _, ethRes, _ := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + Expect(ethRes.GasUsed).To(BeNumerically(">=", werc20.DepositRequiredGas), "expected different gas used for fallback") + }) + }) + When("the specified method does not exist", func() { + It("it should call the fallback which behave like deposit", func() { + initBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, "") + txArgs.Amount = depositAmount + // Wrong method is directly set in the input to skip ABI validation + txArgs.Input = []byte("nonExistingMethod") + + _, _, err := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected error calling the precompile") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + finalBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(finalBalance).To(Equal(initBalance)) + }) + It("it should consume at least the deposit requested gas", func() { + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, "") + txArgs.Amount = depositAmount + // Wrong method is directly set in the input to skip ABI validation + txArgs.Input = []byte("nonExistingMethod") + + _, ethRes, _ := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + Expect(ethRes.GasUsed).To(BeNumerically(">=", werc20.DepositRequiredGas), "expected different gas used for fallback") + }) + }) + }) + Context("and funds are NOT part of the transaction", func() { + When("the method is withdraw", func() { + It("it should fail if user doesn't have enough funds", func() { + // Store initial balance to verify withdraw is a no-op and sender + // balance remains the same after the contract call. + initBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + newUserAcc, newUserPriv := utiltx.NewAccAddressAndKey() + newUserBalance := sdk.Coins{sdk.Coin{ + Denom: evmtypes.GetEVMCoinDenom(), + Amount: math.NewIntFromBigInt(withdrawAmount).SubRaw(1), + }} + err := is.network.App.BankKeeper.SendCoins(is.network.GetContext(), user.AccAddr, newUserAcc, newUserBalance) + Expect(err).ToNot(HaveOccurred(), "expected no error sending tokens") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, werc20.WithdrawMethod, withdrawAmount) + + _, _, err = is.factory.CallContractAndCheckLogs(newUserPriv, txArgs, callArgs, withdrawCheck) + Expect(err).To(HaveOccurred(), "expected an error because not enough funds") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + finalBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(finalBalance).To(Equal(initBalance)) + }) + It("it should be a no-op and emit the event", func() { + initBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, werc20.WithdrawMethod, withdrawAmount) + + _, _, err := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, withdrawCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected error calling the precompile") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + finalBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(finalBalance).To(Equal(initBalance)) + }) + It("it should consume at least the withdraw requested gas", func() { + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, werc20.WithdrawMethod, withdrawAmount) + + _, ethRes, _ := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, withdrawCheck) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + Expect(ethRes.GasUsed).To(BeNumerically(">=", werc20.WithdrawRequiredGas), "expected different gas used for withdraw") + }) + }) + //nolint:dupl + When("no calldata is provided", func() { + It("it should call the fallback which behave like deposit", func() { + initBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, "") + txArgs.Amount = depositAmount + + _, _, err := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected error calling the precompile") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + finalBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(finalBalance).To(Equal(initBalance)) + }) + It("it should consume at least the deposit requested gas", func() { + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, werc20.DepositMethod) + txArgs.Amount = depositAmount + + _, ethRes, _ := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + Expect(ethRes.GasUsed).To(BeNumerically(">=", werc20.DepositRequiredGas), "expected different gas used for receive") + }) + }) + When("the specified method is too short", func() { + It("it should call the fallback which behave like deposit", func() { + initBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, "") + txArgs.Amount = depositAmount + // Short method is directly set in the input to skip ABI validation + txArgs.Input = []byte{1, 2, 3} + + _, _, err := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected error calling the precompile") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + finalBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(finalBalance).To(Equal(initBalance)) + }) + It("it should consume at least the deposit requested gas", func() { + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, "") + txArgs.Amount = depositAmount + // Short method is directly set in the input to skip ABI validation + txArgs.Input = []byte{1, 2, 3} + + _, ethRes, _ := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + Expect(ethRes.GasUsed).To(BeNumerically(">=", werc20.DepositRequiredGas), "expected different gas used for fallback") + }) + }) + When("the specified method does not exist", func() { + It("it should call the fallback which behave like deposit", func() { + initBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, "") + txArgs.Amount = depositAmount + // Wrong method is directly set in the input to skip ABI validation + txArgs.Input = []byte("nonExistingMethod") + + _, _, err := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected error calling the precompile") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + finalBalance := is.checkAndReturnBalance(passCheck, callsData, user.Addr) + Expect(finalBalance).To(Equal(initBalance)) + }) + It("it should consume at least the deposit requested gas", func() { + txArgs, callArgs := callsData.getTxAndCallArgs(directCall, "") + txArgs.Amount = depositAmount + // Wrong method is directly set in the input to skip ABI validation + txArgs.Input = []byte("nonExistingMethod") + + _, ethRes, _ := is.factory.CallContractAndCheckLogs(user.Priv, txArgs, callArgs, depositCheck) + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + Expect(ethRes.GasUsed).To(BeNumerically(">=", werc20.DepositRequiredGas), "expected different gas used for fallback") + }) + }) + }) + }) + Context("calling a reverter contract", func() { + When("to call the deposit", func() { + It("it should return funds to the last sender and emit the event", func() { + ctx := is.network.GetContext() + + txArgs, callArgs := callsData.getTxAndCallArgs(contractCall, "depositWithRevert", false, false) + txArgs.Amount = depositAmount + + _, _, err := is.factory.CallContractAndCheckLogs(txSender.Priv, txArgs, callArgs, depositCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected error calling the precompile") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + finalBalance := is.network.App.BankKeeper.GetAllBalances(ctx, revertContractAddr.Bytes()) + Expect(finalBalance.AmountOf(evmtypes.GetEVMCoinDenom()).String()).To(Equal(depositAmount.String()), "expected final balance equal to deposit") + }) + }) + DescribeTable("to call the deposit", func(before, after bool) { + ctx := is.network.GetContext() + + initBalance := is.network.App.BankKeeper.GetAllBalances(ctx, txSender.AccAddr) + + txArgs, callArgs := callsData.getTxAndCallArgs(contractCall, "depositWithRevert", before, after) + txArgs.Amount = depositAmount + + _, _, err := is.factory.CallContractAndCheckLogs(txSender.Priv, txArgs, callArgs, depositCheck) + Expect(err).To(HaveOccurred(), "execution should have reverted") + Expect(is.network.NextBlock()).ToNot(HaveOccurred(), "error on NextBlock") + + finalBalance := is.network.App.BankKeeper.GetAllBalances(ctx, txSender.AccAddr) + Expect(finalBalance.String()).To(Equal(initBalance.String()), "expected final balance equal to initial") + }, + Entry("it should not move funds and dont emit the event reverting before changing state", true, false), + Entry("it should not move funds and dont emit the event reverting after changing state", false, true), + ) + }) + Context("calling an erc20 method", func() { + When("transferring tokens", func() { + It("it should transfer tokens to a receiver using `transfer`", func() { + ctx := is.network.GetContext() + + senderBalance := is.network.App.BankKeeper.GetAllBalances(ctx, txSender.AccAddr) + receiverBalance := is.network.App.BankKeeper.GetAllBalances(ctx, user.AccAddr) + + txArgs, transferArgs := callsData.getTxAndCallArgs(directCall, erc20.TransferMethod, user.Addr, transferAmount) + transferCoins := sdk.Coins{sdk.NewInt64Coin(is.wrappedCoinDenom, transferAmount.Int64())} + + _, _, err := is.factory.CallContractAndCheckLogs(txSender.Priv, txArgs, transferArgs, transferCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected result calling contract") + + senderBalanceAfter := is.network.App.BankKeeper.GetAllBalances(ctx, txSender.AccAddr) + receiverBalanceAfter := is.network.App.BankKeeper.GetAllBalances(ctx, user.AccAddr) + Expect(senderBalanceAfter).To(Equal(senderBalance.Sub(transferCoins...))) + Expect(receiverBalanceAfter).To(Equal(receiverBalance.Add(transferCoins...))) + }) + It("it should transfer tokens to a receiver using `transferFrom`", func() { + ctx := is.network.GetContext() + + senderBalance := is.network.App.BankKeeper.GetAllBalances(ctx, txSender.AccAddr) + receiverBalance := is.network.App.BankKeeper.GetAllBalances(ctx, user.AccAddr) + + txArgs, transferArgs := callsData.getTxAndCallArgs(directCall, erc20.TransferFromMethod, txSender.Addr, user.Addr, transferAmount) + transferCoins := sdk.Coins{sdk.NewInt64Coin(is.wrappedCoinDenom, transferAmount.Int64())} + + transferCheck := passCheck.WithExpEvents(erc20.EventTypeTransfer, auth.EventTypeApproval) + _, _, err := is.factory.CallContractAndCheckLogs(txSender.Priv, txArgs, transferArgs, transferCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected result calling contract") + + senderBalanceAfter := is.network.App.BankKeeper.GetAllBalances(ctx, txSender.AccAddr) + receiverBalanceAfter := is.network.App.BankKeeper.GetAllBalances(ctx, user.AccAddr) + Expect(senderBalanceAfter).To(Equal(senderBalance.Sub(transferCoins...))) + Expect(receiverBalanceAfter).To(Equal(receiverBalance.Add(transferCoins...))) + }) + }) + When("querying information", func() { + Context("to retrieve a balance", func() { + It("should return the correct balance for an existing account", func() { + // Query the balance + txArgs, balancesArgs := callsData.getTxAndCallArgs(directCall, erc20.BalanceOfMethod, txSender.Addr) + + _, ethRes, err := is.factory.CallContractAndCheckLogs(txSender.Priv, txArgs, balancesArgs, passCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected result calling contract") + + expBalance := is.network.App.BankKeeper.GetBalance(is.network.GetContext(), txSender.AccAddr, is.wrappedCoinDenom) + + var balance *big.Int + err = is.precompile.UnpackIntoInterface(&balance, erc20.BalanceOfMethod, ethRes.Ret) + Expect(err).ToNot(HaveOccurred(), "failed to unpack result") + Expect(balance).To(Equal(expBalance.Amount.BigInt()), "expected different balance") + }) + It("should return 0 for a new account", func() { + // Query the balance + txArgs, balancesArgs := callsData.getTxAndCallArgs(directCall, erc20.BalanceOfMethod, utiltx.GenerateAddress()) + + _, ethRes, err := is.factory.CallContractAndCheckLogs(txSender.Priv, txArgs, balancesArgs, passCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected result calling contract") + + var balance *big.Int + err = is.precompile.UnpackIntoInterface(&balance, erc20.BalanceOfMethod, ethRes.Ret) + Expect(err).ToNot(HaveOccurred(), "failed to unpack result") + Expect(balance.Int64()).To(Equal(int64(0)), "expected different balance") + }) + }) + It("should return the correct name", func() { + txArgs, nameArgs := callsData.getTxAndCallArgs(directCall, erc20.NameMethod) + + _, ethRes, err := is.factory.CallContractAndCheckLogs(txSender.Priv, txArgs, nameArgs, passCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected result calling contract") + + var name string + err = is.precompile.UnpackIntoInterface(&name, erc20.NameMethod, ethRes.Ret) + Expect(err).ToNot(HaveOccurred(), "failed to unpack result") + Expect(name).To(ContainSubstring("Evmos"), "expected different name") + }) + + It("should return the correct symbol", func() { + txArgs, symbolArgs := callsData.getTxAndCallArgs(directCall, erc20.SymbolMethod) + + _, ethRes, err := is.factory.CallContractAndCheckLogs(txSender.Priv, txArgs, symbolArgs, passCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected result calling contract") + + var symbol string + err = is.precompile.UnpackIntoInterface(&symbol, erc20.SymbolMethod, ethRes.Ret) + Expect(err).ToNot(HaveOccurred(), "failed to unpack result") + Expect(symbol).To(ContainSubstring("EVMOS"), "expected different symbol") + }) + + It("should return the decimals", func() { + txArgs, decimalsArgs := callsData.getTxAndCallArgs(directCall, erc20.DecimalsMethod) + + _, ethRes, err := is.factory.CallContractAndCheckLogs(txSender.Priv, txArgs, decimalsArgs, passCheck) + Expect(err).ToNot(HaveOccurred(), "unexpected result calling contract") + + var decimals uint8 + err = is.precompile.UnpackIntoInterface(&decimals, erc20.DecimalsMethod, ethRes.Ret) + Expect(err).ToNot(HaveOccurred(), "failed to unpack result") + + chainID := strings.Split(is.network.GetChainID(), "-")[0] + coinInfo := evmtypes.ChainsCoinInfo[chainID] + Expect(decimals).To(Equal(uint8(coinInfo.Decimals)), "expected different decimals") + }, + ) + }) + }) +}) diff --git a/precompiles/werc20/testdata/WEVMOS9.json b/precompiles/werc20/testdata/WEVMOS9.json new file mode 100644 index 0000000000..484cf6b5b9 --- /dev/null +++ b/precompiles/werc20/testdata/WEVMOS9.json @@ -0,0 +1,288 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "WEVMOS9", + "sourceName": "solidity/precompiles/werc20/testdata/WEVMOS9.sol", + "abi": [ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "guy", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "src", + "type": "address" + }, + { + "name": "dst", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "wad", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "dst", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "deposit", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": true, + "name": "guy", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": true, + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + } + ], + "bytecode": "0x60806040526040805190810160405280600d81526020017f577261707065642045766d6f73000000000000000000000000000000000000008152506000908051906020019061004f9291906100ca565b506040805190810160405280600681526020017f5745564d4f5300000000000000000000000000000000000000000000000000008152506001908051906020019061009b9291906100ca565b506012600260006101000a81548160ff021916908360ff1602179055503480156100c457600080fd5b5061016f565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061010b57805160ff1916838001178555610139565b82800160010185558215610139579182015b8281111561013857825182559160200191906001019061011d565b5b509050610146919061014a565b5090565b61016c91905b80821115610168576000816000905550600101610150565b5090565b90565b610c848061017e6000396000f3006080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014957806318160ddd146101ae57806323b872dd146101d95780632e1a7d4d1461025e578063313ce5671461028b57806370a08231146102bc57806395d89b4114610313578063a9059cbb146103a3578063d0e30db014610408578063dd62ed3e14610412575b6100b7610489565b005b3480156100c557600080fd5b506100ce610526565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010e5780820151818401526020810190506100f3565b50505050905090810190601f16801561013b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015557600080fd5b50610194600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506105c4565b604051808215151515815260200191505060405180910390f35b3480156101ba57600080fd5b506101c36106b6565b6040518082815260200191505060405180910390f35b3480156101e557600080fd5b50610244600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506106d5565b604051808215151515815260200191505060405180910390f35b34801561026a57600080fd5b5061028960048036038101908080359060200190929190505050610a22565b005b34801561029757600080fd5b506102a0610b55565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102c857600080fd5b506102fd600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610b68565b6040518082815260200191505060405180910390f35b34801561031f57600080fd5b50610328610b80565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561036857808201518184015260208101905061034d565b50505050905090810190601f1680156103955780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156103af57600080fd5b506103ee600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c1e565b604051808215151515815260200191505060405180910390f35b610410610489565b005b34801561041e57600080fd5b50610473600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610c33565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105bc5780601f10610591576101008083540402835291602001916105bc565b820191906000526020600020905b81548152906001019060200180831161059f57829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561072557600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107fd57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156109185781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561088d57600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a7057600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050158015610b03573d6000803e3d6000fd5b503373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c165780601f10610beb57610100808354040283529160200191610c16565b820191906000526020600020905b815481529060010190602001808311610bf957829003601f168201915b505050505081565b6000610c2b3384846106d5565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820db857be5acdb9fff88465fff82532b1f9016393994729caa282163d5e3bd9da10029", + "deployedBytecode": "0x6080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014957806318160ddd146101ae57806323b872dd146101d95780632e1a7d4d1461025e578063313ce5671461028b57806370a08231146102bc57806395d89b4114610313578063a9059cbb146103a3578063d0e30db014610408578063dd62ed3e14610412575b6100b7610489565b005b3480156100c557600080fd5b506100ce610526565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010e5780820151818401526020810190506100f3565b50505050905090810190601f16801561013b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015557600080fd5b50610194600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506105c4565b604051808215151515815260200191505060405180910390f35b3480156101ba57600080fd5b506101c36106b6565b6040518082815260200191505060405180910390f35b3480156101e557600080fd5b50610244600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506106d5565b604051808215151515815260200191505060405180910390f35b34801561026a57600080fd5b5061028960048036038101908080359060200190929190505050610a22565b005b34801561029757600080fd5b506102a0610b55565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102c857600080fd5b506102fd600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610b68565b6040518082815260200191505060405180910390f35b34801561031f57600080fd5b50610328610b80565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561036857808201518184015260208101905061034d565b50505050905090810190601f1680156103955780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156103af57600080fd5b506103ee600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c1e565b604051808215151515815260200191505060405180910390f35b610410610489565b005b34801561041e57600080fd5b50610473600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610c33565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105bc5780601f10610591576101008083540402835291602001916105bc565b820191906000526020600020905b81548152906001019060200180831161059f57829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561072557600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107fd57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156109185781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561088d57600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a7057600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050158015610b03573d6000803e3d6000fd5b503373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c165780601f10610beb57610100808354040283529160200191610c16565b820191906000526020600020905b815481529060010190602001808311610bf957829003601f168201915b505050505081565b6000610c2b3384846106d5565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820db857be5acdb9fff88465fff82532b1f9016393994729caa282163d5e3bd9da10029", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/contracts/WEVMOS.sol b/precompiles/werc20/testdata/WEVMOS9.sol similarity index 98% rename from contracts/WEVMOS.sol rename to precompiles/werc20/testdata/WEVMOS9.sol index abcf4dae4d..813c364dc5 100644 --- a/contracts/WEVMOS.sol +++ b/precompiles/werc20/testdata/WEVMOS9.sol @@ -20,17 +20,17 @@ pragma solidity >=0.4.22 <0.6; contract WEVMOS9 { - string public name = "Wrapped Evmos"; - string public symbol = "WEVMOS"; - uint8 public decimals = 18; + string public name = "Wrapped Evmos"; + string public symbol = "WEVMOS"; + uint8 public decimals = 18; - event Approval(address indexed src, address indexed guy, uint wad); - event Transfer(address indexed src, address indexed dst, uint wad); - event Deposit(address indexed dst, uint wad); - event Withdrawal(address indexed src, uint wad); + event Approval(address indexed src, address indexed guy, uint wad); + event Transfer(address indexed src, address indexed dst, uint wad); + event Deposit(address indexed dst, uint wad); + event Withdrawal(address indexed src, uint wad); - mapping (address => uint) public balanceOf; - mapping (address => mapping (address => uint)) public allowance; + mapping(address => uint) public balanceOf; + mapping(address => mapping(address => uint)) public allowance; function() external payable { deposit(); @@ -62,10 +62,11 @@ contract WEVMOS9 { return transferFrom(msg.sender, dst, wad); } - function transferFrom(address src, address dst, uint wad) - public - returns (bool) - { + function transferFrom( + address src, + address dst, + uint wad + ) public returns (bool) { require(balanceOf[src] >= wad); if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) { @@ -82,7 +83,6 @@ contract WEVMOS9 { } } - /* GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 @@ -759,4 +759,4 @@ the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . -*/ \ No newline at end of file +*/ diff --git a/precompiles/werc20/testdata/WEVMOS9TestCaller.json b/precompiles/werc20/testdata/WEVMOS9TestCaller.json new file mode 100644 index 0000000000..874d620de9 --- /dev/null +++ b/precompiles/werc20/testdata/WEVMOS9TestCaller.json @@ -0,0 +1,79 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "WEVMOS9TestCaller", + "sourceName": "solidity/precompiles/werc20/testdata/WEVMOS9TestCaller.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address payable", + "name": "_wrappedTokenAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "Log", + "type": "event" + }, + { + "inputs": [], + "name": "WEVMOS", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "counter", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "before", + "type": "bool" + }, + { + "internalType": "bool", + "name": "aft", + "type": "bool" + } + ], + "name": "depositWithRevert", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } + ], + "bytecode": "0x60a060405234801561001057600080fd5b506040516105a23803806105a2833981810160405281019061003291906100d6565b8073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff16815250506000808190555050610103565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a382610078565b9050919050565b6100b381610098565b81146100be57600080fd5b50565b6000815190506100d0816100aa565b92915050565b6000602082840312156100ec576100eb610073565b5b60006100fa848285016100c1565b91505092915050565b60805161047e6101246000396000818160c901526101fc015261047e6000f3fe6080604052600436106100345760003560e01c80635dab6f8c1461003957806361bc221a146100555780637cf5b4fc14610080575b600080fd5b610053600480360381019061004e919061025b565b6100ab565b005b34801561006157600080fd5b5061006a6101f4565b60405161007791906102b4565b60405180910390f35b34801561008c57600080fd5b506100956101fa565b6040516100a29190610310565b60405180910390f35b6000808154809291906100bd9061035a565b919050555060003490507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561012f57600080fd5b505af1158015610143573d6000803e3d6000fd5b5050505050821561019057600061018f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610186906103ff565b60405180910390fd5b5b6000808154809291906101a29061041f565b919050555081156101ef5760006101ee576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101e5906103ff565b60405180910390fd5b5b505050565b60005481565b7f000000000000000000000000000000000000000000000000000000000000000081565b600080fd5b60008115159050919050565b61023881610223565b811461024357600080fd5b50565b6000813590506102558161022f565b92915050565b600080604083850312156102725761027161021e565b5b600061028085828601610246565b925050602061029185828601610246565b9150509250929050565b6000819050919050565b6102ae8161029b565b82525050565b60006020820190506102c960008301846102a5565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102fa826102cf565b9050919050565b61030a816102ef565b82525050565b60006020820190506103256000830184610301565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006103658261029b565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036103975761039661032b565b5b600182019050919050565b600082825260208201905092915050565b7f7265766572742068657265000000000000000000000000000000000000000000600082015250565b60006103e9600b836103a2565b91506103f4826103b3565b602082019050919050565b60006020820190508181036000830152610418816103dc565b9050919050565b600061042a8261029b565b91506000820361043d5761043c61032b565b5b60018203905091905056fea26469706673582212203d3e97b0a77f9600830da2e2cbaeb60b338f3a8a14102bdb21638d4010f269fd64736f6c63430008140033", + "deployedBytecode": "0x6080604052600436106100345760003560e01c80635dab6f8c1461003957806361bc221a146100555780637cf5b4fc14610080575b600080fd5b610053600480360381019061004e919061025b565b6100ab565b005b34801561006157600080fd5b5061006a6101f4565b60405161007791906102b4565b60405180910390f35b34801561008c57600080fd5b506100956101fa565b6040516100a29190610310565b60405180910390f35b6000808154809291906100bd9061035a565b919050555060003490507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561012f57600080fd5b505af1158015610143573d6000803e3d6000fd5b5050505050821561019057600061018f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610186906103ff565b60405180910390fd5b5b6000808154809291906101a29061041f565b919050555081156101ef5760006101ee576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101e5906103ff565b60405180910390fd5b5b505050565b60005481565b7f000000000000000000000000000000000000000000000000000000000000000081565b600080fd5b60008115159050919050565b61023881610223565b811461024357600080fd5b50565b6000813590506102558161022f565b92915050565b600080604083850312156102725761027161021e565b5b600061028085828601610246565b925050602061029185828601610246565b9150509250929050565b6000819050919050565b6102ae8161029b565b82525050565b60006020820190506102c960008301846102a5565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102fa826102cf565b9050919050565b61030a816102ef565b82525050565b60006020820190506103256000830184610301565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006103658261029b565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036103975761039661032b565b5b600182019050919050565b600082825260208201905092915050565b7f7265766572742068657265000000000000000000000000000000000000000000600082015250565b60006103e9600b836103a2565b91506103f4826103b3565b602082019050919050565b60006020820190508181036000830152610418816103dc565b9050919050565b600061042a8261029b565b91506000820361043d5761043c61032b565b5b60018203905091905056fea26469706673582212203d3e97b0a77f9600830da2e2cbaeb60b338f3a8a14102bdb21638d4010f269fd64736f6c63430008140033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/precompiles/werc20/testdata/WEVMOS9TestCaller.sol b/precompiles/werc20/testdata/WEVMOS9TestCaller.sol new file mode 100644 index 0000000000..327297c28d --- /dev/null +++ b/precompiles/werc20/testdata/WEVMOS9TestCaller.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.17; + +import "../IWERC20.sol"; + +contract WEVMOS9TestCaller { + address payable public immutable WEVMOS; + uint256 public counter; + + constructor(address payable _wrappedTokenAddress) { + WEVMOS = _wrappedTokenAddress; + counter = 0; + } + + event Log(string message); + + function depositWithRevert(bool before, bool aft) public payable { + counter++; + + uint amountIn = msg.value; + IWERC20(WEVMOS).deposit{value: amountIn}(); + + if (before) { + require(false, "revert here"); + } + + counter--; + + if (aft) { + require(false, "revert here"); + } + return; + } +} diff --git a/precompiles/werc20/testdata/wevmos9.go b/precompiles/werc20/testdata/wevmos9.go new file mode 100644 index 0000000000..c48a23c0a6 --- /dev/null +++ b/precompiles/werc20/testdata/wevmos9.go @@ -0,0 +1,15 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package testdata + +import ( + contractutils "github.com/evmos/evmos/v20/contracts/utils" + evmtypes "github.com/evmos/evmos/v20/x/evm/types" +) + +// LoadWEVMOS9Contract load the WEVMOS9 contract from the json representation of +// the Solidity contract. +func LoadWEVMOS9Contract() (evmtypes.CompiledContract, error) { + return contractutils.LoadContractFromJSONFile("WEVMOS9.json") +} diff --git a/precompiles/werc20/testdata/wevmos9_test_caller.go b/precompiles/werc20/testdata/wevmos9_test_caller.go new file mode 100644 index 0000000000..295becc016 --- /dev/null +++ b/precompiles/werc20/testdata/wevmos9_test_caller.go @@ -0,0 +1,13 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package testdata + +import ( + contractutils "github.com/evmos/evmos/v20/contracts/utils" + evmtypes "github.com/evmos/evmos/v20/x/evm/types" +) + +func LoadWEVMOS9TestCaller() (evmtypes.CompiledContract, error) { + return contractutils.LoadContractFromJSONFile("WEVMOS9TestCaller.json") +} diff --git a/precompiles/werc20/tx.go b/precompiles/werc20/tx.go new file mode 100644 index 0000000000..4c8cb11699 --- /dev/null +++ b/precompiles/werc20/tx.go @@ -0,0 +1,88 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package werc20 + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + cmn "github.com/evmos/evmos/v20/precompiles/common" + "github.com/evmos/evmos/v20/x/evm/core/vm" + evmtypes "github.com/evmos/evmos/v20/x/evm/types" +) + +const ( + // DepositMethod defines the ABI method name for the IWERC20 deposit + // transaction. + DepositMethod = "deposit" + // WithdrawMethod defines the ABI method name for the IWERC20 withdraw + // transaction. + WithdrawMethod = "withdraw" +) + +// Deposit handles the payable deposit function. It retrieves the deposited amount +// and sends it back to the sender using the bank keeper. +func (p Precompile) Deposit( + ctx sdk.Context, + contract *vm.Contract, + stateDB vm.StateDB, +) ([]byte, error) { + caller := contract.Caller() + depositedAmount := contract.Value() + + callerAccAddress := sdk.AccAddress(caller.Bytes()) + precompileAccAddr := sdk.AccAddress(p.Address().Bytes()) + + // Send the coins back to the sender + if err := p.BankKeeper.SendCoins( + ctx, + precompileAccAddr, + callerAccAddress, + sdk.NewCoins(sdk.Coin{ + Denom: evmtypes.GetEVMCoinDenom(), + Amount: math.NewIntFromBigInt(depositedAmount), + }), + ); err != nil { + return nil, err + } + + // Add the entries to the statedb journal since the function signature of + // the associated Solidity interface payable. + p.SetBalanceChangeEntries( + cmn.NewBalanceChangeEntry(caller, depositedAmount, cmn.Add), + cmn.NewBalanceChangeEntry(p.Address(), depositedAmount, cmn.Sub), + ) + + if err := p.EmitDepositEvent(ctx, stateDB, caller, depositedAmount); err != nil { + return nil, err + } + + return nil, nil +} + +// Withdraw is a no-op and mock function that provides the same interface as the +// WETH contract to support equality between the native coin and its wrapped +// ERC-20 (e.g. EVMOS and WEVMOS). +func (p Precompile) Withdraw(ctx sdk.Context, contract *vm.Contract, stateDB vm.StateDB, args []interface{}) ([]byte, error) { + amount, ok := args[0].(*big.Int) + if !ok { + return nil, fmt.Errorf("invalid argument type: %T", args[0]) + } + amountInt := math.NewIntFromBigInt(amount) + + caller := contract.Caller() + callerAccAddress := sdk.AccAddress(caller.Bytes()) + nativeBalance := p.BankKeeper.GetBalance(ctx, callerAccAddress, evmtypes.GetEVMCoinDenom()) + if nativeBalance.Amount.LT(amountInt) { + return nil, fmt.Errorf("account balance %v is lower than withdraw balance %v", nativeBalance.Amount, amountInt) + } + + if err := p.EmitWithdrawalEvent(ctx, stateDB, caller, amount); err != nil { + return nil, err + } + return nil, nil +} diff --git a/precompiles/werc20/utils_test.go b/precompiles/werc20/utils_test.go new file mode 100644 index 0000000000..fb433d273b --- /dev/null +++ b/precompiles/werc20/utils_test.go @@ -0,0 +1,70 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package werc20_test + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "github.com/evmos/evmos/v20/testutil/integration/evmos/factory" + "github.com/evmos/evmos/v20/testutil/integration/evmos/keyring" + evmtypes "github.com/evmos/evmos/v20/x/evm/types" +) + +// callType constants to differentiate between +// the different types of call to the precompile. +type callType int + +const ( + directCall callType = iota + contractCall +) + +// CallsData is a helper struct to hold the addresses and ABIs for the +// different contract instances that are subject to testing here. +type CallsData struct { + // This field is used to perform transactions that are not relevant for + // testing purposes like query to the contract. + sender keyring.Key + + // precompileReverter is used to call into the werc20 interface and + precompileReverterAddr common.Address + precompileReverterABI abi.ABI + + precompileAddr common.Address + precompileABI abi.ABI +} + +// getTxCallArgs is a helper function to return the correct call arguments and +// transaction data for a given call type. +func (cd CallsData) getTxAndCallArgs( + callType callType, + methodName string, + args ...interface{}, +) (evmtypes.EvmTxArgs, factory.CallArgs) { + txArgs := evmtypes.EvmTxArgs{} + callArgs := factory.CallArgs{} + + switch callType { + case directCall: + txArgs.To = &cd.precompileAddr + callArgs.ContractABI = cd.precompileABI + case contractCall: + txArgs.To = &cd.precompileReverterAddr + callArgs.ContractABI = cd.precompileReverterABI + } + + callArgs.MethodName = methodName + callArgs.Args = args + + // Setting gas tip cap to zero to have zero gas price. + txArgs.GasTipCap = new(big.Int).SetInt64(0) + // Gas limit is added only to skip the estimate gas call + // that makes debugging more complex. + txArgs.GasLimit = 1_000_000_000_000 + + return txArgs, callArgs +} diff --git a/precompiles/werc20/werc20.go b/precompiles/werc20/werc20.go new file mode 100644 index 0000000000..fde416da34 --- /dev/null +++ b/precompiles/werc20/werc20.go @@ -0,0 +1,162 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package werc20 + +import ( + "embed" + "fmt" + "slices" + + "github.com/evmos/evmos/v20/x/evm/core/vm" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + cmn "github.com/evmos/evmos/v20/precompiles/common" + erc20 "github.com/evmos/evmos/v20/precompiles/erc20" + erc20types "github.com/evmos/evmos/v20/x/erc20/types" + transferkeeper "github.com/evmos/evmos/v20/x/ibc/transfer/keeper" +) + +// abiPath defines the path to the WERC-20 precompile ABI JSON file. +const abiPath = "abi.json" + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +var _ vm.PrecompiledContract = &Precompile{} + +// Precompile defines the precompiled contract for WERC20. +type Precompile struct { + *erc20.Precompile +} + +const ( + // DepositRequiredGas defines the gas required for the Deposit transaction. + DepositRequiredGas uint64 = 23_878 + // WithdrawRequiredGas defines the gas required for the Withdraw transaction. + WithdrawRequiredGas uint64 = 9207 +) + +// LoadABI loads the IWERC20 ABI from the embedded abi.json file +// for the werc20 precompile. +func LoadABI() (abi.ABI, error) { + return cmn.LoadABI(f, abiPath) +} + +// NewPrecompile creates a new WERC20 Precompile instance implementing the +// PrecompiledContract interface. This type wraps around the ERC20 Precompile +// instance to provide additional methods. +func NewPrecompile( + tokenPair erc20types.TokenPair, + bankKeeper bankkeeper.Keeper, + authzKeeper authzkeeper.Keeper, + transferKeeper transferkeeper.Keeper, +) (*Precompile, error) { + newABI, err := LoadABI() + if err != nil { + return nil, fmt.Errorf("error loading the ABI: %w", err) + } + + erc20Precompile, err := erc20.NewPrecompile(tokenPair, bankKeeper, authzKeeper, transferKeeper) + if err != nil { + return nil, fmt.Errorf("error instantiating the ERC20 precompile: %w", err) + } + + // use the IWERC20 ABI + erc20Precompile.Precompile.ABI = newABI + + return &Precompile{ + Precompile: erc20Precompile, + }, nil +} + +// Address returns the address of the WERC20 precompiled contract. +func (p Precompile) Address() common.Address { + return p.Precompile.Address() +} + +// RequiredGas calculates the contract gas use. +func (p Precompile) RequiredGas(input []byte) uint64 { + // TODO: these values were obtained from Remix using the WEVMOS9.sol. + // We should execute the transactions from Evmos testnet + // to ensure parity in the values. + + // If there is no method ID, then it's the fallback or receive case + if len(input) < 4 { + return DepositRequiredGas + } + + methodID := input[:4] + method, err := p.MethodById(methodID) + if err != nil { + return 0 + } + + switch method.Name { + case DepositMethod: + return DepositRequiredGas + case WithdrawMethod: + return WithdrawRequiredGas + default: + return p.Precompile.RequiredGas(input) + } +} + +// Run executes the precompiled contract WERC20 methods defined in the ABI. +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { + ctx, stateDB, snapshot, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + + // This handles any out of gas errors that may occur during the execution of + // a precompile tx or query. It avoids panics and returns the out of gas error so + // the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + switch { + case method.Type == abi.Fallback, + method.Type == abi.Receive, + method.Name == DepositMethod: + bz, err = p.Deposit(ctx, contract, stateDB) + case method.Name == WithdrawMethod: + bz, err = p.Withdraw(ctx, contract, stateDB, args) + default: + // ERC20 transactions and queries + bz, err = p.Precompile.HandleMethod(ctx, contract, stateDB, method, args) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost) { + return nil, vm.ErrOutOfGas + } + + if err := p.AddJournalEntries(stateDB, snapshot); err != nil { + return nil, err + } + return bz, nil +} + +// IsTransaction returns true if the given method name correspond to a +// transaction. Returns false otherwise. +func (p Precompile) IsTransaction(method *abi.Method) bool { + txMethodName := []string{DepositMethod, WithdrawMethod} + txMethodType := []abi.FunctionType{abi.Fallback, abi.Receive} + + if slices.Contains(txMethodName, method.Name) || slices.Contains(txMethodType, method.Type) { + return true + } + + return p.Precompile.IsTransaction(method) +} diff --git a/scripts/compile_smart_contracts/compile_smart_contracts.py b/scripts/compile_smart_contracts/compile_smart_contracts.py index 3e26ead0f2..7a1339d626 100644 --- a/scripts/compile_smart_contracts/compile_smart_contracts.py +++ b/scripts/compile_smart_contracts/compile_smart_contracts.py @@ -279,6 +279,8 @@ def is_relative_target(path: Path) -> bool: return path == RELATIVE_TARGET +# TODO: in the abi, the sourceName field refere to the path of the temporary +# directory for the compilation. It should be updated. def compile_files(repo_path: Path, added_contract: Union[str, None] = None): """ This function compiles the Solidity contracts in the repository diff --git a/tests/nix_tests/hardhat/hardhat.config.js b/tests/nix_tests/hardhat/hardhat.config.js index 8d44d481e2..fa8fe794f9 100644 --- a/tests/nix_tests/hardhat/hardhat.config.js +++ b/tests/nix_tests/hardhat/hardhat.config.js @@ -1,4 +1,4 @@ -require('@nomicfoundation/hardhat-toolbox') +require("@nomicfoundation/hardhat-toolbox"); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { @@ -7,12 +7,16 @@ module.exports = { { // NOTE: changing compiler version may break tests, // as the expected gas and bytecodes may be different - version: '0.8.18' - } - ] + version: "0.8.18", + }, + // This version is required to compile the werc9 contract. + { + version: "0.4.22", + }, + ], }, typechain: { - outDir: 'typechain', - target: 'ethers-v6' - } -} + outDir: "typechain", + target: "ethers-v6", + }, +}; diff --git a/tests/solidity/suites/precompiles/hardhat.config.js b/tests/solidity/suites/precompiles/hardhat.config.js index 3dcc74c13e..cd19d9ed1b 100644 --- a/tests/solidity/suites/precompiles/hardhat.config.js +++ b/tests/solidity/suites/precompiles/hardhat.config.js @@ -1,18 +1,26 @@ -require('@nomicfoundation/hardhat-toolbox') +require("@nomicfoundation/hardhat-toolbox"); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: { - compilers: [{ version: '0.8.18' }] + compilers: [ + { + version: "0.8.18", + }, + // This version is required to compile the werc9 contract. + { + version: "0.4.22", + }, + ], }, networks: { evmos: { - url: 'http://127.0.0.1:8545', + url: "http://127.0.0.1:8545", chainId: 9002, accounts: [ - '0x88CBEAD91AEE890D27BF06E003ADE3D4E952427E88F88D31D61D3EF5E5D54305', - '0x3B7955D25189C99A7468192FCBC6429205C158834053EBE3F78F4512AB432DB9' - ] - } - } -} + "0x88CBEAD91AEE890D27BF06E003ADE3D4E952427E88F88D31D61D3EF5E5D54305", + "0x3B7955D25189C99A7468192FCBC6429205C158834053EBE3F78F4512AB432DB9", + ], + }, + }, +}; diff --git a/testutil/integration/evmos/network/chain_id_modifiers.go b/testutil/integration/evmos/network/chain_id_modifiers.go new file mode 100644 index 0000000000..5912f315eb --- /dev/null +++ b/testutil/integration/evmos/network/chain_id_modifiers.go @@ -0,0 +1,120 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +// +// This files contains handler for the testing suite that has to be run to +// modify the chain configuration depending on the chainID + +package network + +import ( + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/evmos/evmos/v20/utils" + erc20types "github.com/evmos/evmos/v20/x/erc20/types" + evmtypes "github.com/evmos/evmos/v20/x/evm/types" +) + +// updateErc20GenesisStateForChainID modify the default genesis state for the +// bank module of the testing suite depending on the chainID. +func updateBankGenesisStateForChainID(chainID string, bankGenesisState banktypes.GenesisState) banktypes.GenesisState { + metadata := generateBankGenesisMetadata(chainID) + bankGenesisState.DenomMetadata = []banktypes.Metadata{metadata} + + return bankGenesisState +} + +// generateBankGenesisMetadata generates the metadata +// for the Evm coin depending on the chainID. +func generateBankGenesisMetadata(chainID string) banktypes.Metadata { + if utils.IsTestnet(chainID) { + return banktypes.Metadata{ + Description: "The native EVM, governance and staking token of the Evmos testnet", + Base: "atevmos", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "atevmos", + Exponent: 0, + }, + { + Denom: "tevmos", + Exponent: 18, + }, + }, + Name: "tEvmos", + Symbol: "tEVMOS", + Display: "tevmos", + } + } + + return banktypes.Metadata{ + Description: "The native EVM, governance and staking token of the Evmos mainnet", + Base: "aevmos", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "aevmos", + Exponent: 0, + }, + { + Denom: "evmos", + Exponent: 18, + }, + }, + Name: "Evmos", + Symbol: "EVMOS", + Display: "evmos", + } +} + +// updateErc20GenesisStateForChainID modify the default genesis state for the +// erc20 module on the testing suite depending on the chainID. +func updateErc20GenesisStateForChainID(chainID string, erc20GenesisState erc20types.GenesisState) erc20types.GenesisState { + if !utils.IsTestnet(chainID) { + return erc20GenesisState + } + + erc20GenesisState.Params = updateErc20Params(chainID, erc20GenesisState.Params) + erc20GenesisState.TokenPairs = updateErc20TokenPairs(chainID, erc20GenesisState.TokenPairs) + + return erc20GenesisState +} + +// updateErc20Params modifies the erc20 module params to use the correct +// WEVMOS contract depending on ChainID +func updateErc20Params(chainID string, params erc20types.Params) erc20types.Params { + mainnetAddress := erc20types.GetWEVMOSContractHex(utils.MainnetChainID) + testnetAddress := erc20types.GetWEVMOSContractHex(chainID) + + nativePrecompiles := make([]string, len(params.NativePrecompiles)) + for i, nativePrecompile := range params.NativePrecompiles { + if nativePrecompile == mainnetAddress { + nativePrecompiles[i] = testnetAddress + } else { + nativePrecompiles[i] = nativePrecompile + } + } + params.NativePrecompiles = nativePrecompiles + return params +} + +// updateErc20TokenPairs modifies the erc20 token pairs to use the correct +// WEVMOS depending on ChainID +func updateErc20TokenPairs(chainID string, tokenPairs []erc20types.TokenPair) []erc20types.TokenPair { + testnetAddress := erc20types.GetWEVMOSContractHex(chainID) + coinInfo := evmtypes.ChainsCoinInfo[utils.MainnetChainID] + + mainnetAddress := erc20types.GetWEVMOSContractHex(utils.MainnetChainID) + + updatedTokenPairs := make([]erc20types.TokenPair, len(tokenPairs)) + for i, tokenPair := range tokenPairs { + if tokenPair.Erc20Address == mainnetAddress { + updatedTokenPairs[i] = erc20types.TokenPair{ + Erc20Address: testnetAddress, + Denom: coinInfo.Denom, + Enabled: tokenPair.Enabled, + ContractOwner: tokenPair.ContractOwner, + } + } else { + updatedTokenPairs[i] = tokenPair + } + } + return updatedTokenPairs +} diff --git a/testutil/integration/evmos/network/setup.go b/testutil/integration/evmos/network/setup.go index 28a3d25a42..7128ccbdec 100644 --- a/testutil/integration/evmos/network/setup.go +++ b/testutil/integration/evmos/network/setup.go @@ -332,7 +332,8 @@ func setDefaultBankGenesisState(evmosApp *app.Evmos, genesisState evmostypes.Gen []banktypes.Metadata{}, []banktypes.SendEnabled{}, ) - genesisState[banktypes.ModuleName] = evmosApp.AppCodec().MustMarshalJSON(bankGenesis) + updatedBankGen := updateBankGenesisStateForChainID(evmosApp.ChainID(), *bankGenesis) + genesisState[banktypes.ModuleName] = evmosApp.AppCodec().MustMarshalJSON(&updatedBankGen) return genesisState } @@ -451,7 +452,8 @@ func setDefaultGovGenesisState(evmosApp *app.Evmos, genesisState evmostypes.Gene func setDefaultErc20GenesisState(evmosApp *app.Evmos, genesisState evmostypes.GenesisState) evmostypes.GenesisState { erc20Gen := erc20types.DefaultGenesisState() - genesisState[erc20types.ModuleName] = evmosApp.AppCodec().MustMarshalJSON(erc20Gen) + updatedErc20Gen := updateErc20GenesisStateForChainID(evmosApp.ChainID(), *erc20Gen) + genesisState[erc20types.ModuleName] = evmosApp.AppCodec().MustMarshalJSON(&updatedErc20Gen) return genesisState } diff --git a/x/erc20/keeper/precompiles.go b/x/erc20/keeper/precompiles.go index e5b19678b3..33dd0508f3 100644 --- a/x/erc20/keeper/precompiles.go +++ b/x/erc20/keeper/precompiles.go @@ -10,6 +10,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/evmos/evmos/v20/precompiles/erc20" + "github.com/evmos/evmos/v20/precompiles/werc20" "github.com/evmos/evmos/v20/x/erc20/types" "github.com/evmos/evmos/v20/x/evm/core/vm" ) @@ -20,18 +21,25 @@ func (k Keeper) GetERC20PrecompileInstance( address common.Address, ) (contract vm.PrecompiledContract, found bool, err error) { params := k.GetParams(ctx) - if k.IsAvailableERC20Precompile(¶ms, address) { - precompile, err := k.InstantiateERC20Precompile(ctx, address) - if err != nil { - return nil, false, errorsmod.Wrapf(err, "precompiled contract not initialized: %s", address.String()) - } - return precompile, true, nil + if !k.IsAvailableERC20Precompile(¶ms, address) { + return nil, false, nil } - return nil, false, nil + + isNative := params.IsNativePrecompile(address) + + precompile, err := k.InstantiateERC20Precompile(ctx, address, isNative) + if err != nil { + return nil, false, errorsmod.Wrapf(err, "precompiled contract not initialized: %s", address.String()) + } + + return precompile, true, nil } -// InstantiateERC20Precompile returns an ERC20 precompile instance for the given contract address -func (k Keeper) InstantiateERC20Precompile(ctx sdk.Context, contractAddr common.Address) (vm.PrecompiledContract, error) { +// InstantiateERC20Precompile returns an ERC20 precompile instance for the given +// contract address. +// If the `hasWrappedMethods` boolean is true, the ERC20 instance returned +// exposes methods for `withdraw` and `deposit` as it is common for wrapped tokens. +func (k Keeper) InstantiateERC20Precompile(ctx sdk.Context, contractAddr common.Address, hasWrappedMethods bool) (vm.PrecompiledContract, error) { address := contractAddr.String() // check if the precompile is an ERC20 contract id := k.GetTokenPairID(ctx, address) @@ -42,11 +50,18 @@ func (k Keeper) InstantiateERC20Precompile(ctx sdk.Context, contractAddr common. if !ok { return nil, fmt.Errorf("token pair not found: %s", address) } + + if hasWrappedMethods { + return werc20.NewPrecompile(pair, k.bankKeeper, k.authzKeeper, *k.transferKeeper) + } + return erc20.NewPrecompile(pair, k.bankKeeper, k.authzKeeper, *k.transferKeeper) } -// IsAvailableDynamicPrecompile returns true if the given precompile address is contained in the -// EVM keeper's available dynamic precompiles precompiles params. +// IsAvailableERC20Precompile returns true if the given precompile address +// is contained in the params of the erc20 module. +// The available ERC-20 precompiles consist of the dynamic precompiles and the native +// ones. func (k Keeper) IsAvailableERC20Precompile(params *types.Params, address common.Address) bool { return params.IsNativePrecompile(address) || params.IsDynamicPrecompile(address) diff --git a/x/erc20/types/params.go b/x/erc20/types/params.go index 8ab297b3e1..ed6d2a2c67 100644 --- a/x/erc20/types/params.go +++ b/x/erc20/types/params.go @@ -7,9 +7,11 @@ import ( "bytes" "fmt" "slices" + "strings" "github.com/ethereum/go-ethereum/common" "github.com/evmos/evmos/v20/types" + "github.com/evmos/evmos/v20/utils" ) const ( @@ -19,6 +21,25 @@ const ( WEVMOSContractTestnet = "0xcc491f589b45d4a3c679016195b3fb87d7848210" ) +// chainsWEVMOSHex is an utility map used to retrieve the WEVMOS contract +// address in hex format from the chain ID. +var chainsWEVMOSHex = map[string]string{ + utils.MainnetChainID: WEVMOSContractMainnet, + utils.TestnetChainID: WEVMOSContractTestnet, +} + +// GetWEVMOSContractHex returns the hex format of address for the WEVMOS contract given the +// chainID. If the chainID is not found, it defaults to the mainnet address. +func GetWEVMOSContractHex(chainID string) string { + id := strings.Split(chainID, "-")[0] + address, found := chainsWEVMOSHex[id] + // default to mainnet address + if !found { + address = chainsWEVMOSHex[utils.MainnetChainID] + } + return address +} + // Parameter store key var ( ParamStoreKeyEnableErc20 = []byte("EnableErc20") diff --git a/x/evm/genesis_test.go b/x/evm/genesis_test.go index aff6e7e40f..e4ac1815c6 100644 --- a/x/evm/genesis_test.go +++ b/x/evm/genesis_test.go @@ -9,11 +9,11 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/evmos/evmos/v20/contracts" "github.com/evmos/evmos/v20/crypto/ethsecp256k1" - "github.com/evmos/evmos/v20/precompiles/erc20" testfactory "github.com/evmos/evmos/v20/testutil/integration/evmos/factory" testhandler "github.com/evmos/evmos/v20/testutil/integration/evmos/grpc" testkeyring "github.com/evmos/evmos/v20/testutil/integration/evmos/keyring" testnetwork "github.com/evmos/evmos/v20/testutil/integration/evmos/network" + erc20 "github.com/evmos/evmos/v20/x/erc20/types" "github.com/evmos/evmos/v20/x/evm" "github.com/evmos/evmos/v20/x/evm/statedb" "github.com/evmos/evmos/v20/x/evm/types" diff --git a/x/evm/keeper/call_evm_test.go b/x/evm/keeper/call_evm_test.go index 372e5c23fc..7f8df2f4ed 100644 --- a/x/evm/keeper/call_evm_test.go +++ b/x/evm/keeper/call_evm_test.go @@ -54,7 +54,7 @@ func (suite *KeeperTestSuite) TestCallEVMWithData() { expPass bool }{ { - "unknown method", + "pass with unknown method", types.ModuleAddress, func() []byte { account := utiltx.GenerateAddress() @@ -62,7 +62,7 @@ func (suite *KeeperTestSuite) TestCallEVMWithData() { return data }, false, - false, + true, }, { "pass", @@ -76,13 +76,13 @@ func (suite *KeeperTestSuite) TestCallEVMWithData() { true, }, { - "fail empty data", + "pass with empty data", types.ModuleAddress, func() []byte { return []byte{} }, false, - false, + true, }, { diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 3ee90a2bb9..2ff79cadce 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -62,7 +62,7 @@ type Keeper struct { ss paramstypes.Subspace // precompiles defines the map of all available precompiled smart contracts. - // Some these precompiled contracts might not be active depending on the EVM + // Some of these precompiled contracts might not be active depending on the EVM // parameters. precompiles map[common.Address]vm.PrecompiledContract } diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index 255e0f663b..770dadb9ea 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -19,12 +19,12 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/evmos/evmos/v20/contracts" - "github.com/evmos/evmos/v20/precompiles/erc20" testfactory "github.com/evmos/evmos/v20/testutil/integration/evmos/factory" testhandler "github.com/evmos/evmos/v20/testutil/integration/evmos/grpc" testkeyring "github.com/evmos/evmos/v20/testutil/integration/evmos/keyring" "github.com/evmos/evmos/v20/testutil/integration/evmos/network" utiltx "github.com/evmos/evmos/v20/testutil/tx" + erc20 "github.com/evmos/evmos/v20/x/erc20/types" "github.com/evmos/evmos/v20/x/evm/core/vm" "github.com/evmos/evmos/v20/x/evm/statedb" "github.com/evmos/evmos/v20/x/evm/types"