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, }