From 083f63ea85d157259f8147ede266e085cfb62d65 Mon Sep 17 00:00:00 2001 From: yihuang Date: Mon, 25 Aug 2025 15:52:01 +0800 Subject: [PATCH] Problem: dynamic erc20 precompiles are heavy Closes: #505 re-design bank precompile to support solidity erc20 contract. solc optimize fix readonly comment solc version add to app basic mock test framework test transfer test precompile through erc20 contract more failure test cases cleanup fix go lint use decimals from Display denom add to AvailableStaticPrecompiles add boilerplates use new precompile base fix executor fix test fix lint gas cost changed after register new precompile use cosmos-sdk gas config --- evmd/app.go | 4 +- precompiles/bank2/ERC20.bin | 1 + precompiles/bank2/ERC20.sol | 196 +++++++++++ precompiles/bank2/bank.go | 295 ++++++++++++++++ precompiles/bank2/bank_test.go | 328 ++++++++++++++++++ precompiles/bank2/erc20abi.json | 224 ++++++++++++ precompiles/bank2/interfaces.go | 19 + precompiles/bank2/mock.go | 141 ++++++++ precompiles/types/defaults.go | 6 + .../precompiles/staking/test_staking.go | 4 +- x/vm/statedb/{mock_test.go => mockkeeper.go} | 15 +- x/vm/types/precompiles.go | 2 + 12 files changed, 1224 insertions(+), 11 deletions(-) create mode 100644 precompiles/bank2/ERC20.bin create mode 100644 precompiles/bank2/ERC20.sol create mode 100644 precompiles/bank2/bank.go create mode 100644 precompiles/bank2/bank_test.go create mode 100644 precompiles/bank2/erc20abi.json create mode 100644 precompiles/bank2/interfaces.go create mode 100644 precompiles/bank2/mock.go rename x/vm/statedb/{mock_test.go => mockkeeper.go} (90%) diff --git a/evmd/app.go b/evmd/app.go index e47152852..9b5c6d9f2 100644 --- a/evmd/app.go +++ b/evmd/app.go @@ -4,9 +4,10 @@ import ( "encoding/json" "errors" "fmt" - precompiletypes "github.com/cosmos/evm/precompiles/types" "io" + precompiletypes "github.com/cosmos/evm/precompiles/types" + "os" "github.com/spf13/cast" @@ -492,6 +493,7 @@ func NewExampleApp( *app.StakingKeeper, app.DistrKeeper, app.BankKeeper, + app.BankKeeper, &app.Erc20Keeper, &app.TransferKeeper, app.IBCKeeper.ChannelKeeper, diff --git a/precompiles/bank2/ERC20.bin b/precompiles/bank2/ERC20.bin new file mode 100644 index 000000000..cbd738d66 --- /dev/null +++ b/precompiles/bank2/ERC20.bin @@ -0,0 +1 @@ +60a06040523461022357610ee28038038061001981610227565b9283398101906040818303126102235780516001600160401b0381116102235781019082601f830112156102235781516001600160401b03811161020f5761006a601f8201601f1916602001610227565b9381855260208285010111610223576020815f92828096018388015e8501015201516001600160a01b03811681036102235781516001600160401b03811161020f575f54600181811c91168015610205575b60208210146101f157601f811161018f575b50602092601f821160011461013057928192935f92610125575b50508160011b915f199060031b1c1916175f555b608052604051610c95908161024d8239608051818181610198015281816106680152610a940152f35b015190505f806100e8565b601f198216935f8052805f20915f5b868110610177575083600195961061015f575b505050811b015f556100fc565b01515f1960f88460031b161c191690555f8080610152565b9192602060018192868501518155019401920161013f565b5f80527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563601f830160051c810191602084106101e7575b601f0160051c01905b8181106101dc57506100ce565b5f81556001016101cf565b90915081906101c6565b634e487b7160e01b5f52602260045260245ffd5b90607f16906100bc565b634e487b7160e01b5f52604160045260245ffd5b5f80fd5b6040519190601f01601f191682016001600160401b0381118382101761020f5760405256fe60806040526004361015610011575f80fd5b5f3560e01c806306fdde03146100c4578063095ea7b3146100bf57806318160ddd146100ba57806323b872dd146100b5578063313ce567146100b057806370a08231146100ab57806376cdb03b146100a657806395d89b41146100a1578063a9059cbb1461009c578063c370b042146100975763dd62ed3e14610092575f80fd5b61090a565b6108d1565b610704565b61068c565b61061e565b610560565b6104d9565b6103bd565b61033a565b610216565b610112565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080809581815201938051918291828752018686015e5f8582860101520116010190565b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc576101c86101bc61016a6101966101546107c6565b6040519283915f6020840152602183019061099b565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610780565b7f0000000000000000000000000000000000000000000000000000000000000000610b83565b604051918291826100c9565b0390f35b5f80fd5b6004359073ffffffffffffffffffffffffffffffffffffffff821682036101cc57565b6024359073ffffffffffffffffffffffffffffffffffffffff821682036101cc57565b346101cc5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc5761024d6101d0565b602435331561030e5773ffffffffffffffffffffffffffffffffffffffff82169182156102e2576102a88291335f52600160205260405f209073ffffffffffffffffffffffffffffffffffffffff165f5260205260405f2090565b5560405190815233907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92590602090a3602060405160018152f35b7f94280d62000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b7fe602df05000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc5760206103b061016a61019661037b6107c6565b6040519283917f030000000000000000000000000000000000000000000000000000000000000087840152602183019061099b565b0151604051908152602090f35b346101cc5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc576103f46101d0565b6103fc6101f3565b6044359073ffffffffffffffffffffffffffffffffffffffff83165f5260016020526104493360405f209073ffffffffffffffffffffffffffffffffffffffff165f5260205260405f2090565b54927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8410610489575b61047d93506109ad565b60405160018152602090f35b8284106104a5576104a08361047d95033383610bdb565b610473565b82847ffb8f41b2000000000000000000000000000000000000000000000000000000005f523360045260245260445260645ffd5b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc57602061054f61016a61019661051a6107c6565b6040519283917f020000000000000000000000000000000000000000000000000000000000000087840152602183019061099b565b01516040515f9190911a8152602090f35b346101cc5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc576101c8602061060c7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000006101966105c36101d0565b61016a6105ce6107c6565b6040519485937f04000000000000000000000000000000000000000000000000000000000000008986015260601b166021840152603583019061099b565b01516040519081529081906020820190565b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc576101c86101bc61016a6101966106ce6107c6565b6040519283917f01000000000000000000000000000000000000000000000000000000000000006020840152602183019061099b565b346101cc5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc5761074861073e6101d0565b60243590336109ad565b602060405160018152f35b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176107c157604052565b610753565b604051905f5f548060011c916001821680156108c7575b60208410811461089a57838652859260208401919081156108635750600114610810575b5061080e92500383610780565b565b5f80805291507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b84831061084c575061080e9350015f610801565b805482840152869350602090920191600101610838565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682525061080e93151560051b0190505f610801565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b92607f16926107dd565b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc576101c86101bc6107c6565b346101cc5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc5760206109926109466101d0565b73ffffffffffffffffffffffffffffffffffffffff6109636101f3565b91165f526001835260405f209073ffffffffffffffffffffffffffffffffffffffff165f5260205260405f2090565b54604051908152f35b805191908290602001825e015f815290565b9073ffffffffffffffffffffffffffffffffffffffff8216918215610afa5773ffffffffffffffffffffffffffffffffffffffff8216938415610ace57610ab87fffffffffffffffffffffffffffffffffffffffff000000000000000000000000610a92610ac99461016a7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9784610a436107c6565b916040519687957f0500000000000000000000000000000000000000000000000000000000000000602088015260601b16602186015260601b166035840152866049840152606983019061099b565b7f0000000000000000000000000000000000000000000000000000000000000000610c49565b506040519081529081906020820190565b0390a3565b7fec442f05000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b7f96c6fd1e000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b3d15610b7e573d9067ffffffffffffffff82116107c15760405191610b7360207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184610780565b82523d5f602084013e565b606090565b5f918291602082519201905afa610b98610b26565b9015610ba15790565b610bd7906040519182917f0bcb658c000000000000000000000000000000000000000000000000000000008352600483016100c9565b0390fd5b73ffffffffffffffffffffffffffffffffffffffff1690811561030e5773ffffffffffffffffffffffffffffffffffffffff8116156102e257610c46915f52600160205260405f209073ffffffffffffffffffffffffffffffffffffffff165f5260205260405f2090565b55565b5f91829182602083519301915af1610b98610b2656fea26469706673582212206c349a091a8d116c46a490fccb327923675dd9203b2a952fe99cdb4b058beab664736f6c634300081e0033 \ No newline at end of file diff --git a/precompiles/bank2/ERC20.sol b/precompiles/bank2/ERC20.sol new file mode 100644 index 000000000..f98f7187d --- /dev/null +++ b/precompiles/bank2/ERC20.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +library BankPrecompile { + error BankError(bytes); + + enum BankMethod { + NAME, + SYMBOL, + DECIMALS, + TOTAL_SUPPLY, + BALANCE_OF, + TRANSFER_FROM + } + + function name(address bank, string memory denom) internal view returns (string memory) { + bytes memory result = _staticcall_bank(bank, abi.encodePacked(uint8(BankMethod.NAME), denom)); + return string(result); + } + + function symbol(address bank, string memory denom) internal view returns (string memory) { + bytes memory result = _staticcall_bank(bank, abi.encodePacked(uint8(BankMethod.SYMBOL), denom)); + return string(result); + } + + function decimals(address bank, string memory denom) internal view returns (uint8) { + bytes memory data = _staticcall_bank(bank, abi.encodePacked(uint8(BankMethod.DECIMALS), denom)); + + uint8 result; + assembly { + result := byte(0, mload(add(data, 0x20))) + } + return result; + } + + function totalSupply(address bank, string memory denom) internal view returns (uint256) { + bytes memory data = _staticcall_bank(bank, abi.encodePacked(uint8(BankMethod.TOTAL_SUPPLY), denom)); + + uint256 result; + assembly { + result := mload(add(data, 0x20)) + } + return result; + } + + function balanceOf(address bank, address account, string memory denom) internal view returns (uint256) { + bytes memory data = _staticcall_bank(bank, abi.encodePacked(uint8(BankMethod.BALANCE_OF), account, denom)); + + uint256 result; + assembly { + result := mload(add(data, 0x20)) + } + return result; + } + + function transferFrom(address bank, address from, address to, uint256 amount, string memory denom) internal returns (bool) { + _call_bank(bank, abi.encodePacked(uint8(BankMethod.TRANSFER_FROM), from, to, amount, denom)); + return true; + } + + function _staticcall_bank(address bank, bytes memory _calldata) internal view returns (bytes memory) { + (bool success, bytes memory data) = bank.staticcall(_calldata); + if (!success) { + revert BankError(data); + } + + return data; + } + + function _call_bank(address bank, bytes memory _calldata) internal returns (bytes memory) { + (bool success, bytes memory data) = bank.call(_calldata); + if (!success) { + revert BankError(data); + } + + return data; + } +} + +interface IERC20 { + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 value) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); +} + +interface IERC20Metadata is IERC20 { + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); +} + +interface IERC20Errors { + error ERC20InvalidSender(address sender); + error ERC20InvalidReceiver(address receiver); + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + error ERC20InvalidApprover(address approver); + error ERC20InvalidSpender(address spender); +} + +contract ERC20 is IERC20, IERC20Metadata, IERC20Errors { + using BankPrecompile for address; + + string public denom; + mapping(address account => mapping(address spender => uint256)) public allowance; + + address public immutable bank; + + constructor(string memory denom_, address bank_) { + denom = denom_; + bank = bank_; + } + + function name() public view returns (string memory) { + return bank.name(denom); + } + + function symbol() public view returns (string memory) { + return bank.symbol(denom); + } + + function decimals() public view returns (uint8) { + return bank.decimals(denom); + } + + function totalSupply() public view returns (uint256) { + return bank.totalSupply(denom); + } + + function balanceOf(address account) public view returns (uint256) { + return bank.balanceOf(account, denom); + } + + function transfer(address to, uint256 value) public returns (bool) { + _transfer(msg.sender, to, value); + return true; + } + + function approve(address spender, uint256 value) public returns (bool) { + _approve(msg.sender, spender, value); + return true; + } + + function transferFrom(address from, address to, uint256 value) public returns (bool) { + address spender = msg.sender; + _spendAllowance(from, spender, value); + _transfer(from, to, value); + return true; + } + + function _transfer(address from, address to, uint256 value) internal { + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + + bank.transferFrom(from, to, value, denom); + emit Transfer(from, to, value); + } + + function _approve(address owner, address spender, uint256 value) internal { + _approve(owner, spender, value, true); + } + + function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { + if (owner == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } + allowance[owner][spender] = value; + if (emitEvent) { + emit Approval(owner, spender, value); + } + } + + function _spendAllowance(address owner, address spender, uint256 value) internal virtual { + uint256 currentAllowance = allowance[owner][spender]; + if (currentAllowance < type(uint256).max) { + if (currentAllowance < value) { + revert ERC20InsufficientAllowance(spender, currentAllowance, value); + } + unchecked { + _approve(owner, spender, currentAllowance - value, false); + } + } + } +} diff --git a/precompiles/bank2/bank.go b/precompiles/bank2/bank.go new file mode 100644 index 000000000..13a5dc95c --- /dev/null +++ b/precompiles/bank2/bank.go @@ -0,0 +1,295 @@ +package bank2 + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + + _ "embed" + + cmn "github.com/cosmos/evm/precompiles/common" + evmtypes "github.com/cosmos/evm/x/vm/types" + + sdkmath "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +var ( + // generated with solc 0.8.30+commit.73712a01: + // solc --overwrite --optimize --optimize-runs 100000 --via-ir --bin -o . ERC20.sol + // + //go:embed ERC20.bin + ERC20BinHex string + + ERC20Bin []byte + ERC20Salt = common.FromHex("636dd1d57837e7dce61901468217da9975548dcb3ecc24d84567feb93cd11e36") + Create2FactoryAddress = common.HexToAddress("0x4e59b44847b379578588920ca78fbf26c0b4956c") +) + +var ( + ErrInputTooShort = errors.New("input too short") + ErrDenomNotFound = errors.New("denom not found") + ErrUnauthorized = errors.New("unauthorized") + + ErrUnknownMethod = "unknown method: %d" +) + +func init() { + var err error + ERC20Bin, err = hex.DecodeString(ERC20BinHex) + if err != nil { + panic(err) + } +} + +type BankMethod uint8 + +const ( + MethodName BankMethod = iota + MethodSymbol + MethodDecimals + MethodTotalSupply + MethodBalanceOf + MethodTransferFrom +) + +var _ vm.PrecompiledContract = &Precompile{} + +type Precompile struct { + cmn.Precompile + + msgServer BankMsgServer + bankKeeper BankKeeper +} + +func NewPrecompile(msgServer BankMsgServer, bankKeeper BankKeeper) *Precompile { + return &Precompile{ + Precompile: cmn.Precompile{ + KvGasConfig: storetypes.KVGasConfig(), + TransientKVGasConfig: storetypes.TransientGasConfig(), + ContractAddress: common.HexToAddress(evmtypes.Bank2PrecompileAddress), + }, + msgServer: msgServer, + bankKeeper: bankKeeper, + } +} + +func (p Precompile) RequiredGas(input []byte) uint64 { + if len(input) < 1 { + return 0 + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(BankMethod(input[0]))) +} + +// 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(method BankMethod) bool { + return method == MethodTransferFrom +} + +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ([]byte, error) { + return p.RunNativeAction(evm, contract, func(ctx sdk.Context) ([]byte, error) { + return p.Execute(ctx, contract, readonly) + }) +} + +// Name +// input format: abi.encodePacked(string denom) +// output format: abi.encodePacked(string) +func (p Precompile) Name(ctx sdk.Context, input []byte) ([]byte, error) { + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, string(input)) + if !found { + return nil, ErrDenomNotFound + } + + return []byte(metadata.Name), nil +} + +// Symbol +// +// input format: abi.encodePacked(string denom) +// output format: abi.encodePacked(string) +func (p Precompile) Symbol(ctx sdk.Context, input []byte) ([]byte, error) { + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, string(input)) + if !found { + return nil, ErrDenomNotFound + } + + return []byte(metadata.Symbol), nil +} + +// Decimals returns the exponent of the display denom unit +// +// input format: abi.encodePacked(string denom) +// output format: abi.encodePacked(uint8) +func (p Precompile) Decimals(ctx sdk.Context, input []byte) ([]byte, error) { + m, found := p.bankKeeper.GetDenomMetaData(ctx, string(input)) + if !found { + return nil, ErrDenomNotFound + } + + if len(m.DenomUnits) == 0 { + return []byte{0}, nil + } + + // look up Display denom unit + index := -1 + for i, denomUnit := range m.DenomUnits { + if denomUnit.Denom == m.Display { + index = i + break + } + } + + var exponent uint32 + if index == -1 { + exponent = 0 + } else { + exponent = m.DenomUnits[index].Exponent + } + + if exponent > math.MaxUint8 { + return nil, errors.New("exponent too large") + } + + return []byte{uint8(exponent)}, nil +} + +// TotalSupply +// input format: abi.encodePacked(string denom) +// output format: abi.encodePacked(uint256) +func (p Precompile) TotalSupply(ctx sdk.Context, input []byte) ([]byte, error) { + supply := p.bankKeeper.GetSupply(ctx, string(input)).Amount + return common.LeftPadBytes(supply.BigInt().Bytes(), 32), nil +} + +// BalanceOf +// input format: abi.encodePacked(address account, string denom) +func (p Precompile) BalanceOf(ctx sdk.Context, input []byte) ([]byte, error) { + if len(input) < 20 { + return nil, ErrInputTooShort + } + account := common.BytesToAddress(input[:20]) + denom := string(input[20:]) + balance := p.bankKeeper.GetBalance(ctx, account.Bytes(), denom).Amount + return common.LeftPadBytes(balance.BigInt().Bytes(), 32), nil +} + +// TransferFrom +// input format: abi.encodePacked(address from, address to, uint256 amount, string denom) +func (p Precompile) TransferFrom(ctx sdk.Context, caller common.Address, input []byte) ([]byte, error) { + if len(input) < 20*2+32 { + return nil, ErrInputTooShort + } + + from := common.BytesToAddress(input[:20]) + to := common.BytesToAddress(input[20 : 20+20]) + amount := new(big.Int).SetBytes(input[40 : 40+32]) + denom := string(input[72:]) + + // don't handle gas token here + if denom == evmtypes.GetEVMCoinDenom() { + return nil, errors.New("cannot transfer gas token with bank precompile") + } + + // authorization: only from address or deterministic erc20 contract address can call this method + if caller != from && caller != ERC20ContractAddress(p.Address(), denom) { + return nil, ErrUnauthorized + } + + coins := sdk.Coins{{Denom: denom, Amount: sdkmath.NewIntFromBigInt(amount)}} + if err := coins.Validate(); err != nil { + return nil, fmt.Errorf("invalid coins: %w", err) + } + + // execute the transfer with bank keeper + msg := banktypes.NewMsgSend(from.Bytes(), to.Bytes(), coins) + if _, err := p.msgServer.Send(ctx, msg); err != nil { + return nil, fmt.Errorf("failed to send coins: %w", err) + } + + return []byte{1}, nil +} + +func (p Precompile) Execute(ctx sdk.Context, contract *vm.Contract, readonly bool) ([]byte, error) { + // 1 byte method selector + if len(contract.Input) == 0 { + return nil, ErrInputTooShort + } + + method := BankMethod(contract.Input[0]) + if readonly && p.IsTransaction(method) { + return nil, vm.ErrWriteProtection + } + + input := contract.Input[1:] + switch method { + case MethodName: + return p.Name(ctx, input) + case MethodSymbol: + return p.Symbol(ctx, input) + case MethodDecimals: + return p.Decimals(ctx, input) + case MethodTotalSupply: + return p.TotalSupply(ctx, input) + case MethodBalanceOf: + return p.BalanceOf(ctx, input) + case MethodTransferFrom: + return p.TransferFrom(ctx, contract.Caller(), input) + } + + return nil, fmt.Errorf(ErrUnknownMethod, method) +} + +// ERC20ContractAddress computes the contract address deployed with create2 factory contract. +// create2 factory: https://github.com/Arachnid/deterministic-deployment-proxy +// +// `keccak(0xff || factory || salt || keccak(bytecode || ctor))[12:]` +func ERC20ContractAddress(contract common.Address, denom string) common.Address { + bz := crypto.Keccak256( + []byte{0xff}, + Create2FactoryAddress.Bytes(), + ERC20Salt, + crypto.Keccak256( + ERC20Bin, + ERC20Constructor(denom, contract), + ), + )[12:] + return common.BytesToAddress(bz) +} + +// ERC20Constructor builds the constructor args for the ERC20 contract, +// equivalent to `abi.encode(string denom, address bank)` +func ERC20Constructor(denom string, bank common.Address) []byte { + paddedDenomLen := padTo32(len(denom)) + bufSize := 32*3 + paddedDenomLen // string offset + bank + string length + denom + + buf := make([]byte, bufSize) + buf[31] = 32 * 2 // string offset + copy(buf[32+12:], bank.Bytes()) // bank contract + binary.BigEndian.PutUint64( // string length + buf[32*2+24:], + uint64(len(denom)), + ) + copy(buf[32*3:], []byte(denom)) // string data + return buf +} + +func padTo32(size int) int { + remainder := size % 32 + if remainder == 0 { + return size + } + return size + 32 - remainder +} diff --git a/precompiles/bank2/bank_test.go b/precompiles/bank2/bank_test.go new file mode 100644 index 000000000..ec59a1d7c --- /dev/null +++ b/precompiles/bank2/bank_test.go @@ -0,0 +1,328 @@ +package bank2 + +import ( + "math/big" + "slices" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" + + _ "embed" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/evm/testutil/constants" + evmtypes "github.com/cosmos/evm/x/vm/types" + + "cosmossdk.io/log" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/store" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +var ( + BankPrecompile = common.HexToAddress(evmtypes.Bank2PrecompileAddress) + + //go:embed erc20abi.json + ERC20ABIStr string + ERC20ABI abi.ABI + + GasLimit = uint64(100000000) +) + +func init() { + var err error + ERC20ABI, err = abi.JSON(strings.NewReader(ERC20ABIStr)) + if err != nil { + panic(err) + } + + _ = evmtypes.NewEVMConfigurator(). + WithEVMCoinInfo(constants.ExampleChainCoinInfo[constants.ExampleChainID]). + Configure() +} + +type TokenInfo struct { + Denom string + DisplayDenom string + Name string + Symbol string + Decimals byte +} + +func Setup(t *testing.T, token TokenInfo, mintTo common.Address, mintAmount uint64) *vm.EVM { + t.Helper() + nativeDenom := evmtypes.GetEVMCoinDenom() + + rawdb := dbm.NewMemDB() + logger := log.NewNopLogger() + ms := store.NewCommitMultiStore(rawdb, logger, nil) + ctx := sdk.NewContext(ms, cmtproto.Header{}, false, logger) + evm := NewMockEVM(ctx) + + bankKeeper := NewMockBankKeeper() + msgServer := NewBankMsgServer(bankKeeper) + precompile := NewPrecompile(msgServer, bankKeeper) + evm.WithPrecompiles(map[common.Address]vm.PrecompiledContract{ + precompile.Address(): precompile, + }) + + // init token + bankKeeper.registerDenom(token.Denom, banktypes.Metadata{ + Symbol: token.Symbol, Name: token.Name, Display: token.DisplayDenom, DenomUnits: []*banktypes.DenomUnit{ + { + Denom: token.Denom, + Exponent: 0, + }, + { + Denom: token.DisplayDenom, + Exponent: uint32(token.Decimals), + }, + }, + }) + bankKeeper.registerDenom(nativeDenom, banktypes.Metadata{ + Symbol: "NATIVE", Name: "Native Token", Display: evmtypes.GetEVMCoinDisplayDenom(), DenomUnits: []*banktypes.DenomUnit{ + { + Denom: nativeDenom, + Exponent: 0, + }, + { + Denom: evmtypes.GetEVMCoinDisplayDenom(), + Exponent: 18, + }, + }, + }) + bankKeeper.mint(mintTo.Bytes(), sdk.NewCoins(sdk.NewCoin(token.Denom, sdkmath.NewIntFromUint64(mintAmount)))) + bankKeeper.mint(mintTo.Bytes(), sdk.NewCoins(sdk.NewCoin(nativeDenom, sdkmath.NewIntFromUint64(mintAmount)))) + + DeployCreate2(t, evm) + DeployERC20(t, evm, BankPrecompile, token.Denom) + + return evm +} + +func TestERC20ContractAddress(t *testing.T) { + denom := "uatom" + contract := common.HexToAddress(evmtypes.Bank2PrecompileAddress) + expected := common.HexToAddress("0xdDe94B5b492d597317FD86d2A5baad9966BE2e3e") + + result := ERC20ContractAddress(contract, denom) + require.Equal(t, expected, result) +} + +// TestBankPrecompile tests calling bank precompile directly +func TestBankPrecompile(t *testing.T) { + user1 := common.BigToAddress(big.NewInt(1)) + user2 := common.BigToAddress(big.NewInt(2)) + token := TokenInfo{ + Denom: "denom", + DisplayDenom: "display", + Symbol: "COIN", + Name: "Test Coin", + Decimals: byte(18), + } + amount := uint64(1000) + erc20 := ERC20ContractAddress(BankPrecompile, token.Denom) + + setup := func(t *testing.T) *vm.EVM { + t.Helper() + return Setup(t, token, user1, amount) + } + + testCases := []struct { + name string + method BankMethod + caller common.Address + input []byte + output []byte + expErr error + }{ + {"name", MethodName, user1, []byte(token.Denom), []byte(token.Name), nil}, + {"symbol", MethodSymbol, user1, []byte(token.Denom), []byte(token.Symbol), nil}, + {"decimals", MethodDecimals, user1, []byte(token.Denom), []byte{token.Decimals}, nil}, + { + "totalSupply", MethodTotalSupply, user1, + []byte(token.Denom), + common.LeftPadBytes(new(big.Int).SetUint64(amount).Bytes(), 32), + nil, + }, + { + "balanceOf", MethodBalanceOf, user1, + slices.Concat(user1.Bytes(), []byte(token.Denom)), + common.LeftPadBytes(new(big.Int).SetUint64(amount).Bytes(), 32), + nil, + }, + { + "balanceOf-empty", MethodBalanceOf, user2, + slices.Concat(user2.Bytes(), []byte(token.Denom)), + common.LeftPadBytes([]byte{}, 32), + nil, + }, + { + "transferFrom-owner", MethodTransferFrom, user1, + slices.Concat(user1.Bytes(), user2.Bytes(), common.LeftPadBytes(big.NewInt(100).Bytes(), 32), []byte(token.Denom)), + []byte{1}, + nil, + }, + { + "transferFrom-erc20", MethodTransferFrom, erc20, + slices.Concat(user1.Bytes(), user2.Bytes(), common.LeftPadBytes(big.NewInt(100).Bytes(), 32), []byte(token.Denom)), + []byte{1}, + nil, + }, + { + "transferFrom-unauthorized", MethodTransferFrom, user2, + slices.Concat(user1.Bytes(), user2.Bytes(), common.LeftPadBytes(big.NewInt(100).Bytes(), 32), []byte(token.Denom)), + nil, + vm.ErrExecutionReverted, + }, + { + "transferFrom-insufficient-balance", MethodTransferFrom, user2, + slices.Concat(user2.Bytes(), user1.Bytes(), common.LeftPadBytes(big.NewInt(100).Bytes(), 32), []byte(token.Denom)), + nil, + vm.ErrExecutionReverted, + }, + {"invalid-method", 6, user1, nil, nil, vm.ErrExecutionReverted}, + {"name-invalid-denom", MethodName, user1, []byte("non-exist"), nil, vm.ErrExecutionReverted}, + {"symbol-invalid-denom", MethodSymbol, user1, []byte("non-exist"), nil, vm.ErrExecutionReverted}, + {"decimals-invalid-denom", MethodDecimals, user1, []byte("non-exist"), nil, vm.ErrExecutionReverted}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + evm := setup(t) + input := slices.Concat([]byte{byte(tc.method)}, tc.input) + ret, _, err := evm.Call(tc.caller, BankPrecompile, input, GasLimit, uint256.NewInt(0)) + if tc.expErr != nil { + require.Equal(t, tc.expErr, err) + } else { + require.NoError(t, err) + require.Equal(t, tc.output, ret) + } + }) + } +} + +// TestBankERC20 tests bank precompile through the ERC20 interface +func TestBankERC20(t *testing.T) { + zero := common.BigToAddress(big.NewInt(0)) + user1 := common.BigToAddress(big.NewInt(1)) + user2 := common.BigToAddress(big.NewInt(2)) + token := TokenInfo{ + Denom: "denom", + DisplayDenom: "display", + Symbol: "COIN", + Name: "Test Coin", + Decimals: byte(18), + } + amount := uint64(1000) + bigAmount := new(big.Int).SetUint64(amount) + erc20 := ERC20ContractAddress(BankPrecompile, token.Denom) + nativeERC20 := ERC20ContractAddress(BankPrecompile, evmtypes.GetEVMCoinDenom()) + + setup := func(t *testing.T) *vm.EVM { + t.Helper() + evm := Setup(t, token, user1, amount) + DeployERC20(t, evm, BankPrecompile, evmtypes.GetEVMCoinDenom()) + return evm + } + + testCases := []struct { + name string + method string + caller common.Address + token common.Address + input []interface{} + output []interface{} + expErr error + }{ + {"name", "name", zero, erc20, nil, []interface{}{token.Name}, nil}, + {"symbol", "symbol", zero, erc20, nil, []interface{}{token.Symbol}, nil}, + {"decimals", "decimals", zero, erc20, nil, []interface{}{token.Decimals}, nil}, + {"totalSupply", "totalSupply", zero, erc20, nil, []interface{}{bigAmount}, nil}, + { + "balanceOf", "balanceOf", zero, erc20, + []interface{}{user1}, + []interface{}{bigAmount}, + nil, + }, + { + "balanceOf-empty", "balanceOf", zero, erc20, + []interface{}{user2}, + []interface{}{common.Big0}, + nil, + }, + { + "transfer", "transfer", user1, erc20, + []interface{}{user2, big.NewInt(100)}, + []interface{}{true}, + nil, + }, + { + "transfer-insufficient-balance", "transfer", user2, erc20, + []interface{}{user1, big.NewInt(100)}, + nil, + vm.ErrExecutionReverted, + }, + { + "native-fail", "transfer", user1, nativeERC20, + []interface{}{user2, big.NewInt(100)}, + nil, + vm.ErrExecutionReverted, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + evm := setup(t) + + method, ok := ERC20ABI.Methods[tc.method] + require.True(t, ok, "method not found: %s", tc.method) + + input, err := method.Inputs.Pack(tc.input...) + require.NoError(t, err) + + ret, _, err := evm.Call(tc.caller, tc.token, slices.Concat(method.ID, input), GasLimit, uint256.NewInt(0)) + if tc.expErr != nil { + require.Equal(t, tc.expErr, err) + return + } + + require.NoError(t, err) + expOutput, err := method.Outputs.Pack(tc.output...) + require.NoError(t, err) + require.Equal(t, expOutput, ret) + }) + } +} + +// DeployCreate2 deploys the deterministic contract factory +// https://github.com/Arachnid/deterministic-deployment-proxy +func DeployCreate2(t *testing.T, evm *vm.EVM) { + t.Helper() + caller := common.HexToAddress("0x3fAB184622Dc19b6109349B94811493BF2a45362") + code := common.FromHex("604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3") + _, address, _, err := evm.Create(caller, code, GasLimit, uint256.NewInt(0)) + require.NoError(t, err) + require.Equal(t, Create2FactoryAddress, address) +} + +func DeployERC20(t *testing.T, evm *vm.EVM, bank common.Address, denom string) { + t.Helper() + caller := common.BigToAddress(common.Big0) + + input := slices.Concat(ERC20Salt, ERC20Bin, ERC20Constructor(denom, bank)) + _, _, err := evm.Call(caller, Create2FactoryAddress, input, GasLimit, uint256.NewInt(0)) + require.NoError(t, err) + + expAddress := ERC20ContractAddress(bank, denom) + require.NotEmpty(t, evm.StateDB.GetCode(expAddress)) +} diff --git a/precompiles/bank2/erc20abi.json b/precompiles/bank2/erc20abi.json new file mode 100644 index 000000000..177ac839e --- /dev/null +++ b/precompiles/bank2/erc20abi.json @@ -0,0 +1,224 @@ +[ + { + "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": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "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": [], + "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" + } +] diff --git a/precompiles/bank2/interfaces.go b/precompiles/bank2/interfaces.go new file mode 100644 index 000000000..30a024fb0 --- /dev/null +++ b/precompiles/bank2/interfaces.go @@ -0,0 +1,19 @@ +package bank2 + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +type BankMsgServer interface { + // Send defines a method for sending coins from one account to another account. + Send(context.Context, *banktypes.MsgSend) (*banktypes.MsgSendResponse, error) +} + +type BankKeeper interface { + GetSupply(ctx context.Context, denom string) sdk.Coin + GetDenomMetaData(ctx context.Context, denom string) (banktypes.Metadata, bool) + GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin +} diff --git a/precompiles/bank2/mock.go b/precompiles/bank2/mock.go new file mode 100644 index 000000000..a029dafa4 --- /dev/null +++ b/precompiles/bank2/mock.go @@ -0,0 +1,141 @@ +package bank2 + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/vm" + + "github.com/cosmos/evm/x/vm/statedb" + evmtypes "github.com/cosmos/evm/x/vm/types" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +type MockBankKeeper struct { + // use int64 for simplicity + balances map[string]map[string]int64 + supplies map[string]int64 + metadatas map[string]banktypes.Metadata +} + +type MockBankMsgServer struct { + keeper MockBankKeeper +} + +var ( + _ BankKeeper = MockBankKeeper{} + _ BankMsgServer = MockBankMsgServer{} +) + +func NewMockBankKeeper() MockBankKeeper { + return MockBankKeeper{ + balances: make(map[string]map[string]int64), + supplies: make(map[string]int64), + metadatas: make(map[string]banktypes.Metadata), + } +} + +func NewBankMsgServer(keeper MockBankKeeper) MockBankMsgServer { + return MockBankMsgServer{keeper} +} + +func (k MockBankKeeper) registerDenom(denom string, metadata banktypes.Metadata) { + k.metadatas[denom] = metadata +} + +func (k MockBankKeeper) mint(to sdk.AccAddress, amt sdk.Coins) { + for _, coin := range amt { + m := k.balances[string(to)] + if m == nil { + m = make(map[string]int64) + k.balances[string(to)] = m + } + amount := coin.Amount.Int64() + m[coin.Denom] += amount + k.supplies[coin.Denom] += amount + } +} + +func (k MockBankKeeper) burn(from sdk.AccAddress, amt sdk.Coins) error { + for _, coin := range amt { + amount := coin.Amount.Int64() + m, ok := k.balances[string(from)] + if !ok { + return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, "address: 0x%x, denom: %s, expect: %d, got: %d", from.Bytes(), coin.Denom, amount, 0) + } + available := m[coin.Denom] + if available < amount { + return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, "address: 0x%x, denom: %s, expect: %d, got: %d", from.Bytes(), coin.Denom, amount, available) + } + m[coin.Denom] = available - amount + k.supplies[coin.Denom] -= amount + } + return nil +} + +func (k MockBankKeeper) send(from sdk.AccAddress, to sdk.AccAddress, amt sdk.Coins) error { + if err := k.burn(from, amt); err != nil { + return err + } + k.mint(to, amt) + return nil +} + +func (k MockBankKeeper) GetSupply(ctx context.Context, denom string) sdk.Coin { + return sdk.NewCoin(denom, sdkmath.NewInt(k.supplies[denom])) +} + +func (k MockBankKeeper) GetDenomMetaData(ctx context.Context, denom string) (banktypes.Metadata, bool) { + md, ok := k.metadatas[denom] + return md, ok +} + +func (k MockBankKeeper) GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin { + amount := int64(0) + if m, ok := k.balances[string(addr)]; ok { + amount = m[denom] + } + + return sdk.NewCoin(denom, sdkmath.NewInt(amount)) +} + +func (ms MockBankMsgServer) Send(goCtx context.Context, msg *banktypes.MsgSend) (*banktypes.MsgSendResponse, error) { + from, err := sdk.AccAddressFromBech32(msg.FromAddress) + if err != nil { + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid from address: %s", err) + } + to, err := sdk.AccAddressFromBech32(msg.ToAddress) + if err != nil { + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid to address: %s", err) + } + if err := ms.keeper.send(from, to, msg.Amount); err != nil { + return nil, err + } + return &banktypes.MsgSendResponse{}, nil +} + +func NewMockEVM(ctx sdk.Context) *vm.EVM { + evmKeeper := statedb.NewMockKeeper() + db := statedb.New(ctx, evmKeeper, statedb.NewEmptyTxConfig()) + blockCtx := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + GetHash: nil, + GasLimit: 10000000, + BlockNumber: big.NewInt(1), + Time: 1, + Difficulty: big.NewInt(0), // unused. Only required in PoW context + BaseFee: big.NewInt(1000), + Random: &common.MaxHash, // need to be different than nil to signal it is after the merge and pick up the right opcodes + } + vmConfig := vm.Config{} + return vm.NewEVM(blockCtx, db, evmtypes.GetEthChainConfig(), vmConfig) +} diff --git a/precompiles/types/defaults.go b/precompiles/types/defaults.go index 49cb44125..7d41b2520 100644 --- a/precompiles/types/defaults.go +++ b/precompiles/types/defaults.go @@ -7,7 +7,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" bankprecompile "github.com/cosmos/evm/precompiles/bank" + bank2precompile "github.com/cosmos/evm/precompiles/bank2" "github.com/cosmos/evm/precompiles/bech32" cmn "github.com/cosmos/evm/precompiles/common" distprecompile "github.com/cosmos/evm/precompiles/distribution" @@ -76,6 +78,7 @@ const bech32PrecompileBaseGas = 6_000 func DefaultStaticPrecompiles( stakingKeeper stakingkeeper.Keeper, distributionKeeper distributionkeeper.Keeper, + bankKeeper2 bankkeeper.Keeper, bankKeeper cmn.BankKeeper, erc20Keeper *erc20Keeper.Keeper, transferKeeper *transferkeeper.Keeper, @@ -142,6 +145,8 @@ func DefaultStaticPrecompiles( options.ConsensusAddrCodec, ) + bank2Precompile := bank2precompile.NewPrecompile(bankkeeper.NewMsgServerImpl(bankKeeper2), bankKeeper2) + // Stateless precompiles precompiles[bech32Precompile.Address()] = bech32Precompile precompiles[p256Precompile.Address()] = p256Precompile @@ -153,6 +158,7 @@ func DefaultStaticPrecompiles( precompiles[bankPrecompile.Address()] = bankPrecompile precompiles[govPrecompile.Address()] = govPrecompile precompiles[slashingPrecompile.Address()] = slashingPrecompile + precompiles[bank2Precompile.Address()] = bank2Precompile return precompiles } diff --git a/tests/integration/precompiles/staking/test_staking.go b/tests/integration/precompiles/staking/test_staking.go index f66a2ba1a..52511d3ec 100644 --- a/tests/integration/precompiles/staking/test_staking.go +++ b/tests/integration/precompiles/staking/test_staking.go @@ -381,7 +381,7 @@ func (s *PrecompileTestSuite) TestRun() { s.Require().NoError(err, "failed to pack input") return input }, - 19103, // use enough gas to avoid out of gas error + 19367, // use enough gas to avoid out of gas error true, false, "write protection", @@ -391,7 +391,7 @@ func (s *PrecompileTestSuite) TestRun() { func(_ keyring.Key) []byte { return []byte("invalid") }, - 19103, // use enough gas to avoid out of gas error + 19367, // use enough gas to avoid out of gas error false, false, "no method with id", diff --git a/x/vm/statedb/mock_test.go b/x/vm/statedb/mockkeeper.go similarity index 90% rename from x/vm/statedb/mock_test.go rename to x/vm/statedb/mockkeeper.go index 198ccfc98..80092fa4c 100644 --- a/x/vm/statedb/mock_test.go +++ b/x/vm/statedb/mockkeeper.go @@ -1,4 +1,4 @@ -package statedb_test +package statedb import ( "errors" @@ -7,7 +7,6 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/cosmos/evm/x/vm/statedb" "github.com/cosmos/evm/x/vm/types" storetypes "cosmossdk.io/store/types" @@ -16,13 +15,13 @@ import ( ) var ( - _ statedb.Keeper = &MockKeeper{} + _ Keeper = &MockKeeper{} errAddress common.Address = common.BigToAddress(big.NewInt(100)) ) type MockAcount struct { - account statedb.Account - states statedb.Storage + account Account + states Storage } type MockKeeper struct { @@ -41,7 +40,7 @@ func NewMockKeeper() *MockKeeper { } } -func (k MockKeeper) GetAccount(_ sdk.Context, addr common.Address) *statedb.Account { +func (k MockKeeper) GetAccount(_ sdk.Context, addr common.Address) *Account { acct, ok := k.accounts[addr] if !ok { return nil @@ -67,7 +66,7 @@ func (k MockKeeper) ForEachStorage(_ sdk.Context, addr common.Address, cb func(k } } -func (k MockKeeper) SetAccount(_ sdk.Context, addr common.Address, account statedb.Account) error { +func (k MockKeeper) SetAccount(_ sdk.Context, addr common.Address, account Account) error { if addr == errAddress { return errors.New("mock db error") } @@ -77,7 +76,7 @@ func (k MockKeeper) SetAccount(_ sdk.Context, addr common.Address, account state acct.account = account k.accounts[addr] = acct } else { - k.accounts[addr] = MockAcount{account: account, states: make(statedb.Storage)} + k.accounts[addr] = MockAcount{account: account, states: make(Storage)} } return nil } diff --git a/x/vm/types/precompiles.go b/x/vm/types/precompiles.go index aa0c484eb..98ee962e2 100644 --- a/x/vm/types/precompiles.go +++ b/x/vm/types/precompiles.go @@ -13,6 +13,7 @@ const ( BankPrecompileAddress = "0x0000000000000000000000000000000000000804" GovPrecompileAddress = "0x0000000000000000000000000000000000000805" SlashingPrecompileAddress = "0x0000000000000000000000000000000000000806" + Bank2PrecompileAddress = "0x0000000000000000000000000000000000000807" ) // AvailableStaticPrecompiles defines the full list of all available EVM extension addresses. @@ -29,4 +30,5 @@ var AvailableStaticPrecompiles = []string{ BankPrecompileAddress, GovPrecompileAddress, SlashingPrecompileAddress, + Bank2PrecompileAddress, }