diff --git a/app/app.go b/app/app.go index 7c4186af5c..cc271fc35f 100644 --- a/app/app.go +++ b/app/app.go @@ -570,6 +570,7 @@ func New( precompiles.StatefulContracts( &app.FungibleKeeper, app.StakingKeeper, + app.BankKeeper, appCodec, storetypes.TransientGasConfig(), ), diff --git a/precompiles/bank/IBank.abi b/precompiles/bank/IBank.abi new file mode 100644 index 0000000000..03eee6d210 --- /dev/null +++ b/precompiles/bank/IBank.abi @@ -0,0 +1,74 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/precompiles/bank/IBank.go b/precompiles/bank/IBank.go new file mode 100644 index 0000000000..8f95c51c08 --- /dev/null +++ b/precompiles/bank/IBank.go @@ -0,0 +1,254 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package bank + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// IBankMetaData contains all meta data concerning the IBank contract. +var IBankMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"deposit\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", +} + +// IBankABI is the input ABI used to generate the binding from. +// Deprecated: Use IBankMetaData.ABI instead. +var IBankABI = IBankMetaData.ABI + +// IBank is an auto generated Go binding around an Ethereum contract. +type IBank struct { + IBankCaller // Read-only binding to the contract + IBankTransactor // Write-only binding to the contract + IBankFilterer // Log filterer for contract events +} + +// IBankCaller is an auto generated read-only Go binding around an Ethereum contract. +type IBankCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// IBankTransactor is an auto generated write-only Go binding around an Ethereum contract. +type IBankTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// IBankFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type IBankFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// IBankSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type IBankSession struct { + Contract *IBank // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// IBankCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type IBankCallerSession struct { + Contract *IBankCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// IBankTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type IBankTransactorSession struct { + Contract *IBankTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// IBankRaw is an auto generated low-level Go binding around an Ethereum contract. +type IBankRaw struct { + Contract *IBank // Generic contract binding to access the raw methods on +} + +// IBankCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type IBankCallerRaw struct { + Contract *IBankCaller // Generic read-only contract binding to access the raw methods on +} + +// IBankTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type IBankTransactorRaw struct { + Contract *IBankTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewIBank creates a new instance of IBank, bound to a specific deployed contract. +func NewIBank(address common.Address, backend bind.ContractBackend) (*IBank, error) { + contract, err := bindIBank(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &IBank{IBankCaller: IBankCaller{contract: contract}, IBankTransactor: IBankTransactor{contract: contract}, IBankFilterer: IBankFilterer{contract: contract}}, nil +} + +// NewIBankCaller creates a new read-only instance of IBank, bound to a specific deployed contract. +func NewIBankCaller(address common.Address, caller bind.ContractCaller) (*IBankCaller, error) { + contract, err := bindIBank(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &IBankCaller{contract: contract}, nil +} + +// NewIBankTransactor creates a new write-only instance of IBank, bound to a specific deployed contract. +func NewIBankTransactor(address common.Address, transactor bind.ContractTransactor) (*IBankTransactor, error) { + contract, err := bindIBank(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &IBankTransactor{contract: contract}, nil +} + +// NewIBankFilterer creates a new log filterer instance of IBank, bound to a specific deployed contract. +func NewIBankFilterer(address common.Address, filterer bind.ContractFilterer) (*IBankFilterer, error) { + contract, err := bindIBank(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &IBankFilterer{contract: contract}, nil +} + +// bindIBank binds a generic wrapper to an already deployed contract. +func bindIBank(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := IBankMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_IBank *IBankRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _IBank.Contract.IBankCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_IBank *IBankRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _IBank.Contract.IBankTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_IBank *IBankRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _IBank.Contract.IBankTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_IBank *IBankCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _IBank.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_IBank *IBankTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _IBank.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_IBank *IBankTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _IBank.Contract.contract.Transact(opts, method, params...) +} + +// BalanceOf is a free data retrieval call binding the contract method 0xf7888aec. +// +// Solidity: function balanceOf(address zrc20, address user) view returns(uint256 balance) +func (_IBank *IBankCaller) BalanceOf(opts *bind.CallOpts, zrc20 common.Address, user common.Address) (*big.Int, error) { + var out []interface{} + err := _IBank.contract.Call(opts, &out, "balanceOf", zrc20, user) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// BalanceOf is a free data retrieval call binding the contract method 0xf7888aec. +// +// Solidity: function balanceOf(address zrc20, address user) view returns(uint256 balance) +func (_IBank *IBankSession) BalanceOf(zrc20 common.Address, user common.Address) (*big.Int, error) { + return _IBank.Contract.BalanceOf(&_IBank.CallOpts, zrc20, user) +} + +// BalanceOf is a free data retrieval call binding the contract method 0xf7888aec. +// +// Solidity: function balanceOf(address zrc20, address user) view returns(uint256 balance) +func (_IBank *IBankCallerSession) BalanceOf(zrc20 common.Address, user common.Address) (*big.Int, error) { + return _IBank.Contract.BalanceOf(&_IBank.CallOpts, zrc20, user) +} + +// Deposit is a paid mutator transaction binding the contract method 0x47e7ef24. +// +// Solidity: function deposit(address zrc20, uint256 amount) returns(bool success) +func (_IBank *IBankTransactor) Deposit(opts *bind.TransactOpts, zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IBank.contract.Transact(opts, "deposit", zrc20, amount) +} + +// Deposit is a paid mutator transaction binding the contract method 0x47e7ef24. +// +// Solidity: function deposit(address zrc20, uint256 amount) returns(bool success) +func (_IBank *IBankSession) Deposit(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IBank.Contract.Deposit(&_IBank.TransactOpts, zrc20, amount) +} + +// Deposit is a paid mutator transaction binding the contract method 0x47e7ef24. +// +// Solidity: function deposit(address zrc20, uint256 amount) returns(bool success) +func (_IBank *IBankTransactorSession) Deposit(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IBank.Contract.Deposit(&_IBank.TransactOpts, zrc20, amount) +} + +// Withdraw is a paid mutator transaction binding the contract method 0xf3fef3a3. +// +// Solidity: function withdraw(address zrc20, uint256 amount) returns(bool success) +func (_IBank *IBankTransactor) Withdraw(opts *bind.TransactOpts, zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IBank.contract.Transact(opts, "withdraw", zrc20, amount) +} + +// Withdraw is a paid mutator transaction binding the contract method 0xf3fef3a3. +// +// Solidity: function withdraw(address zrc20, uint256 amount) returns(bool success) +func (_IBank *IBankSession) Withdraw(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IBank.Contract.Withdraw(&_IBank.TransactOpts, zrc20, amount) +} + +// Withdraw is a paid mutator transaction binding the contract method 0xf3fef3a3. +// +// Solidity: function withdraw(address zrc20, uint256 amount) returns(bool success) +func (_IBank *IBankTransactorSession) Withdraw(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IBank.Contract.Withdraw(&_IBank.TransactOpts, zrc20, amount) +} diff --git a/precompiles/bank/IBank.json b/precompiles/bank/IBank.json new file mode 100644 index 0000000000..808e0039c0 --- /dev/null +++ b/precompiles/bank/IBank.json @@ -0,0 +1,76 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} diff --git a/precompiles/bank/IBank.sol b/precompiles/bank/IBank.sol new file mode 100644 index 0000000000..7926d6e695 --- /dev/null +++ b/precompiles/bank/IBank.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +/// @title IBank Interface for Cross-chain Token Deposits and Withdrawals +/// @notice This interface defines the functions for depositing ZRC20 tokens and withdrawing Cosmos tokens, +/// as well as querying the balance of Cosmos tokens corresponding to a given ZRC20 token. +/// @dev This contract interacts with a precompiled contract at a fixed address. + +/// @dev The IBank contract's precompiled address. +address constant IBANK_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000067; // Address 103 + +/// @dev The IBank contract instance using the precompiled address. +IBank constant IBANK_CONTRACT = IBank(IBANK_PRECOMPILE_ADDRESS); + +/// @dev Interface for the IBank contract. +interface IBank { + /// @notice Deposit a ZRC20 token and mint the corresponding Cosmos token to the user's account. + /// @param zrc20 The ZRC20 token address to be deposited. + /// @param amount The amount of ZRC20 tokens to deposit. + /// @return success Boolean indicating whether the deposit was successful. + function deposit( + address zrc20, + uint256 amount + ) external returns (bool success); + + /// @notice Withdraw Cosmos tokens and convert them back to the corresponding ZRC20 token for the user. + /// @param zrc20 The ZRC20 token address for the corresponding Cosmos token. + /// @param amount The amount of Cosmos tokens to withdraw. + /// @return success Boolean indicating whether the withdrawal was successful. + function withdraw( + address zrc20, + uint256 amount + ) external returns (bool success); + + /// @notice Retrieve the Cosmos token balance corresponding to a specific ZRC20 token for a given user. + /// @param zrc20 The ZRC20 cosmos token denomination to check the balance for. + /// @param user The address of the user to retrieve the balance for. + /// @return balance The balance of the Cosmos token for the specified ZRC20 token and user. + function balanceOf( + address zrc20, + address user + ) external view returns (uint256 balance); +} diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go new file mode 100644 index 0000000000..279dc81378 --- /dev/null +++ b/precompiles/bank/bank.go @@ -0,0 +1,193 @@ +package bank + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + bank "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + ptypes "github.com/zeta-chain/node/precompiles/types" +) + +const ( + // Write methods. + DepositMethodName = "deposit" + WithdrawMethodName = "withdraw" + + // Read methods. + BalanceOfMethodName = "balanceOf" +) + +var ( + ABI abi.ABI + ContractAddress = common.HexToAddress("0x0000000000000000000000000000000000000067") + GasRequiredByMethod = map[[4]byte]uint64{} + ViewMethod = map[[4]byte]bool{} +) + +func init() { + initABI() +} + +func initABI() { + if err := ABI.UnmarshalJSON([]byte(IBankMetaData.ABI)); err != nil { + panic(err) + } + + GasRequiredByMethod = map[[4]byte]uint64{} + for methodName := range ABI.Methods { + var methodID [4]byte + copy(methodID[:], ABI.Methods[methodName].ID[:4]) + switch methodName { + case DepositMethodName: + GasRequiredByMethod[methodID] = 200000 + case WithdrawMethodName: + GasRequiredByMethod[methodID] = 200000 + case BalanceOfMethodName: + GasRequiredByMethod[methodID] = 10000 + default: + GasRequiredByMethod[methodID] = 0 + } + } +} + +type Contract struct { + ptypes.BaseContract + + bankKeeper bank.Keeper + cdc codec.Codec + kvGasConfig storetypes.GasConfig +} + +func NewIBankContract( + bankKeeper bank.Keeper, + cdc codec.Codec, + kvGasConfig storetypes.GasConfig, +) *Contract { + return &Contract{ + BaseContract: ptypes.NewBaseContract(ContractAddress), + bankKeeper: bankKeeper, + cdc: cdc, + kvGasConfig: kvGasConfig, + } +} + +// Address() is required to implement the PrecompiledContract interface. +func (c *Contract) Address() common.Address { + return ContractAddress +} + +// Abi() is required to implement the PrecompiledContract interface. +func (c *Contract) Abi() abi.ABI { + return ABI +} + +// RequiredGas is required to implement the PrecompiledContract interface. +// The gas has to be calculated deterministically based on the input. +func (c *Contract) RequiredGas(input []byte) uint64 { + // get methodID (first 4 bytes) + var methodID [4]byte + copy(methodID[:], input[:4]) + // base cost to prevent large input size + baseCost := uint64(len(input)) * c.kvGasConfig.WriteCostPerByte + if ViewMethod[methodID] { + baseCost = uint64(len(input)) * c.kvGasConfig.ReadCostPerByte + } + + if requiredGas, ok := GasRequiredByMethod[methodID]; ok { + return requiredGas + baseCost + } + + // Can not happen, but return 0 if the method is not found. + return 0 +} + +// Run is the entrypoint of the precompiled contract, it switches over the input method, +// and execute them accordingly. +func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { + method, err := ABI.MethodById(contract.Input[:4]) + if err != nil { + return nil, err + } + + args, err := method.Inputs.Unpack(contract.Input[4:]) + if err != nil { + return nil, err + } + + stateDB := evm.StateDB.(ptypes.ExtStateDB) + + switch method.Name { + case DepositMethodName: + if readOnly { + return nil, nil + } + + return nil, nil + // TODO + + case WithdrawMethodName: + if readOnly { + return nil, nil + } + + return nil, nil + // TODO + + case BalanceOfMethodName: + var res []byte + execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { + res, err = c.balanceOf(ctx, method, args) + return err + }) + if execErr != nil { + return nil, err + } + return res, nil + + default: + return nil, ptypes.ErrInvalidMethod{ + Method: method.Name, + } + } +} + +func (c *Contract) balanceOf( + ctx sdk.Context, + method *abi.Method, + args []interface{}, +) (result []byte, err error) { + if len(args) != 2 { + return nil, &(ptypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 2, + }) + } + + tokenAddr, addr := args[0].(common.Address), args[1].(common.Address) + + accAddr, err := sdk.AccAddressFromHexUnsafe(addr.String()) + if err != nil { + return nil, err + } + + // TODO: Create a function to handle token denoms. + tokenDenom := fmt.Sprintf("evm/%s", tokenAddr.String()) + + coin := c.bankKeeper.GetBalance(ctx, accAddr, tokenDenom) + + if !coin.IsValid() { + return nil, &ptypes.ErrInvalidCoin{ + Got: coin.GetDenom(), + Negative: coin.IsNegative(), + Nil: coin.IsNil(), + } + } + + return method.Outputs.Pack(coin.Amount) +} diff --git a/precompiles/bank/bindings.go b/precompiles/bank/bindings.go new file mode 100644 index 0000000000..d127657566 --- /dev/null +++ b/precompiles/bank/bindings.go @@ -0,0 +1,7 @@ +//go:generate sh -c "solc IBank.sol --combined-json abi | jq '.contracts.\"IBank.sol:IBank\"' > IBank.json" +//go:generate sh -c "cat IBank.json | jq .abi > IBank.abi" +//go:generate sh -c "abigen --abi IBank.abi --pkg bank --type IBank --out IBank.go" + +package bank + +var _ Contract diff --git a/precompiles/precompiles.go b/precompiles/precompiles.go index 1adf65005f..ff4ca288ac 100644 --- a/precompiles/precompiles.go +++ b/precompiles/precompiles.go @@ -4,12 +4,14 @@ import ( "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdktypes "github.com/cosmos/cosmos-sdk/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" ethparams "github.com/ethereum/go-ethereum/params" evmkeeper "github.com/zeta-chain/ethermint/x/evm/keeper" + "github.com/zeta-chain/node/precompiles/bank" "github.com/zeta-chain/node/precompiles/prototype" "github.com/zeta-chain/node/precompiles/staking" fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" @@ -21,12 +23,14 @@ import ( var EnabledStatefulContracts = map[common.Address]bool{ prototype.ContractAddress: true, staking.ContractAddress: true, + bank.ContractAddress: true, } // StatefulContracts returns all the registered precompiled contracts. func StatefulContracts( fungibleKeeper *fungiblekeeper.Keeper, stakingKeeper *stakingkeeper.Keeper, + bankKeeper bankkeeper.Keeper, cdc codec.Codec, gasConfig storetypes.GasConfig, ) (precompiledContracts []evmkeeper.CustomContractFn) { @@ -53,5 +57,14 @@ func StatefulContracts( precompiledContracts = append(precompiledContracts, stakingContract) } + if EnabledStatefulContracts[bank.ContractAddress] { + bankContract := func(_ sdktypes.Context, _ ethparams.Rules) vm.PrecompiledContract { + return bank.NewIBankContract(bankKeeper, cdc, gasConfig) + } + + // Append the staking contract to the precompiledContracts slice. + precompiledContracts = append(precompiledContracts, bankContract) + } + return precompiledContracts } diff --git a/precompiles/precompiles_test.go b/precompiles/precompiles_test.go index 998240e644..a0b55572ea 100644 --- a/precompiles/precompiles_test.go +++ b/precompiles/precompiles_test.go @@ -25,7 +25,7 @@ func Test_StatefulContracts(t *testing.T) { } // StatefulContracts() should return all the enabled contracts. - contracts := StatefulContracts(k, &sdkk.StakingKeeper, appCodec, gasConfig) + contracts := StatefulContracts(k, &sdkk.StakingKeeper, sdkk.BankKeeper, appCodec, gasConfig) require.NotNil(t, contracts, "StatefulContracts() should not return a nil slice") require.Len(t, contracts, expectedContracts, "StatefulContracts() should return all the enabled contracts") diff --git a/precompiles/types/errors.go b/precompiles/types/errors.go index 0cc6928541..f6df4542ee 100644 --- a/precompiles/types/errors.go +++ b/precompiles/types/errors.go @@ -3,8 +3,9 @@ package types import "fmt" /* -Address related errors + Address related errors */ + type ErrInvalidAddr struct { Got string } @@ -14,8 +15,9 @@ func (e ErrInvalidAddr) Error() string { } /* -Argument related errors + Argument related errors */ + type ErrInvalidNumberOfArgs struct { Got, Expect int } @@ -33,8 +35,23 @@ func (e ErrInvalidArgument) Error() string { } /* -Method related errors + Coin related errors +*/ + +type ErrInvalidCoin struct { + Got string + Negative bool + Nil bool +} + +func (e ErrInvalidCoin) Error() string { + return fmt.Sprintf("invalid coin: denom: %s, is negative: %v, is nil: %v", e.Got, e.Negative, e.Nil) +} + +/* + Method related errors */ + type ErrInvalidMethod struct { Method string }