From 4099e865eb045e0be03ef69db9d48b591df86995 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Thu, 12 Sep 2024 19:24:31 +0200 Subject: [PATCH] Include CallContract interface --- cmd/zetae2e/config/localnet.yml | 4 + cmd/zetae2e/local/precompiles.go | 1 + .../localnet/orchestrator/start-zetae2e.sh | 5 +- contrib/localnet/scripts/start-zetacored.sh | 3 + e2e/e2etests/test_precompiles_bank.go | 93 ++++++++-------- precompiles/bank/bank.go | 16 ++- precompiles/bank/call_contract.go | 57 ---------- precompiles/bank/method_deposit.go | 100 +++++++++++++----- precompiles/types/types.go | 60 +++++++++++ x/fungible/keeper/evm.go | 24 +++++ 10 files changed, 221 insertions(+), 142 deletions(-) delete mode 100644 precompiles/bank/call_contract.go diff --git a/cmd/zetae2e/config/localnet.yml b/cmd/zetae2e/config/localnet.yml index 48791750e0..e3bfbd0446 100644 --- a/cmd/zetae2e/config/localnet.yml +++ b/cmd/zetae2e/config/localnet.yml @@ -61,6 +61,10 @@ additional_accounts: bech32_address: "zeta1nry9yeg6njhjrp2ctppa8558vqxal9fxk69zxg" evm_address: "0x98c852651A9CAF2185585843d3D287600Ddf9526" private_key: "bf9456c679bb5a952a9a137fcfc920e0413efdb97c36de1e57455763084230cb" + user_bank_precompile: + bech32_address: "zeta1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr84sgapz" + evm_address: "0x0000000000000000000000000000000000000067" + private_key: "" policy_accounts: emergency_policy_account: bech32_address: "zeta16m2cnrdwtgweq4njc6t470vl325gw4kp6s7tap" diff --git a/cmd/zetae2e/local/precompiles.go b/cmd/zetae2e/local/precompiles.go index 400db0d973..245f5f2d7c 100644 --- a/cmd/zetae2e/local/precompiles.go +++ b/cmd/zetae2e/local/precompiles.go @@ -27,6 +27,7 @@ func statefulPrecompilesTestRoutine( deployerRunner, account, runner.NewLogger(verbose, color.FgRed, "precompiles"), + runner.WithZetaTxServer(deployerRunner.ZetaTxServer), ) if err != nil { return err diff --git a/contrib/localnet/orchestrator/start-zetae2e.sh b/contrib/localnet/orchestrator/start-zetae2e.sh index a00929a0a3..22816d5ca0 100644 --- a/contrib/localnet/orchestrator/start-zetae2e.sh +++ b/contrib/localnet/orchestrator/start-zetae2e.sh @@ -117,7 +117,7 @@ fund_eth_from_config '.additional_accounts.user_admin.evm_address' 10000 "admin fund_eth_from_config '.additional_accounts.user_migration.evm_address' 10000 "migration tester" # unlock precompile tests accounts -fund_eth_from_config '.additional_accounts.user_precompile.evm_address' 10000 "precompile tester" +fund_eth_from_config '.additional_accounts.user_precompile.evm_address' 10000 "precompiles tester" # unlock v2 ethers tests accounts fund_eth_from_config '.additional_accounts.user_v2_ether.evm_address' 10000 "V2 ethers tester" @@ -131,9 +131,6 @@ fund_eth_from_config '.additional_accounts.user_v2_ether_revert.evm_address' 100 # unlock v2 erc20 revert tests accounts fund_eth_from_config '.additional_accounts.user_v2_erc20_revert.evm_address' 10000 "V2 ERC20 revert tester" -# unlock precompile tests accounts -fund_eth_from_config '.additional_accounts.user_precompile.evm_address' 10000 "precompiles tester" - # unlock local solana relayer accounts if host solana > /dev/null; then solana_url=$(config_str '.rpcs.solana') diff --git a/contrib/localnet/scripts/start-zetacored.sh b/contrib/localnet/scripts/start-zetacored.sh index 1f5e2fca12..fdad77e632 100755 --- a/contrib/localnet/scripts/start-zetacored.sh +++ b/contrib/localnet/scripts/start-zetacored.sh @@ -269,6 +269,9 @@ then # v2 erc20 revert tester address=$(yq -r '.additional_accounts.user_v2_erc20_revert.bech32_address' /root/config.yml) zetacored add-genesis-account "$address" 100000000000000000000000000azeta +# bank precompile user + address=$(yq -r '.additional_accounts.user_bank_precompile.bech32_address' /root/config.yml) + zetacored add-genesis-account "$address" 100000000000000000000000000azeta # 3. Copy the genesis.json to all the nodes .And use it to create a gentx for every node zetacored gentx operator 1000000000000000000000azeta --chain-id=$CHAINID --keyring-backend=$KEYRING --gas-prices 20000000000azeta diff --git a/e2e/e2etests/test_precompiles_bank.go b/e2e/e2etests/test_precompiles_bank.go index 823b1a5942..9fda3492ca 100644 --- a/e2e/e2etests/test_precompiles_bank.go +++ b/e2e/e2etests/test_precompiles_bank.go @@ -15,78 +15,71 @@ import ( func TestPrecompilesBank(r *runner.E2ERunner, args []string) { require.Len(r, args, 0, "No arguments expected") + // Increase the gasLimit. It's required because of the gas consumed by precompiled functions. previousGasLimit := r.ZEVMAuth.GasLimit r.ZEVMAuth.GasLimit = 10_000_000 defer func() { r.ZEVMAuth.GasLimit = previousGasLimit }() - // Set owner and spender for legibility. - owner, spender := r.EVMAddress(), bank.ContractAddress - fmt.Println("owner ", owner.String()) - fmt.Println("spender ", spender.String()) - fmt.Println("ERC20ZRC20 ", r.ERC20ZRC20Addr.String()) + spender, bankAddr := r.EVMAddress(), bank.ContractAddress - // Fund owner with 200 token. - tx, err := r.ERC20ZRC20.Transfer(r.ZEVMAuth, owner, big.NewInt(200)) - require.NoError(r, err, "Error funding owner with ERC20ZRC20") + // Deposit and approve 50 WZETA for the test. + approveAmount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(50)) + r.DepositAndApproveWZeta(approveAmount) + fmt.Printf("DEBUG: approveAmount %s\n", approveAmount.String()) - receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - fmt.Printf("funding owner tx receipt: %+v\n", receipt) - utils.RequireTxSuccessful(r, receipt, "funding owner tx") + initialBalance, err := r.WZeta.BalanceOf(&bind.CallOpts{Context: r.Ctx}, spender) + fmt.Printf("DEBUG: initialBalance %s\n", initialBalance.String()) + require.NoError(r, err, "Error approving allowance for bank contract") + require.EqualValues(r, approveAmount, initialBalance, "spender balance should be 50") // Create a bank contract caller. bankContract, err := bank.NewIBank(bank.ContractAddress, r.ZEVMClient) require.NoError(r, err, "Failed to create bank contract caller") - // Get the initial balance of the owner in ERC20ZRC20 tokens. Should be 200. - ownerERC20InitialBalance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{Context: r.Ctx}, owner) - require.NoError(r, err, "Error retrieving initial owner balance") - require.EqualValues(r, uint64(0), ownerERC20InitialBalance.Uint64(), "Initial ERC20ZRC20 has to be 200") - fmt.Println("owner balance ERC20: ", ownerERC20InitialBalance) - - // Get the balance of the owner in coins "zevm/0x12345". This calls bank.balanceOf(). + // Get the balance of the spender in coins "zevm/WZetaAddr". This calls bank.balanceOf(). // BalanceOf will convert the ZRC20 address to a Cosmos denom formatted as "zevm/0x12345". - retBalanceOf, err := bankContract.BalanceOf(&bind.CallOpts{Context: r.Ctx}, r.ERC20ZRC20Addr, owner) + retBalanceOf, err := bankContract.BalanceOf(&bind.CallOpts{Context: r.Ctx}, r.WZetaAddr, spender) + fmt.Printf("DEBUG: initial bank.balanceOf() %s\n", retBalanceOf.String()) require.NoError(r, err, "Error calling bank.balanceOf()") require.EqualValues(r, uint64(0), retBalanceOf.Uint64(), "Initial cosmos coins balance has to be 0") - fmt.Println("owner balance zevm/coin: ", retBalanceOf) - // Allow the bank contract to spend 100 ERC20ZRC20 tokens. - tx, err = r.ERC20ZRC20.Approve(r.ZEVMAuth, spender, big.NewInt(100)) + // Allow the bank contract to spend 25 WZeta tokens. + tx, err := r.WZeta.Approve(r.ZEVMAuth, bankAddr, big.NewInt(25)) require.NoError(r, err, "Error approving allowance for bank contract") - fmt.Printf("approve allowance tx: %+v\n", tx) - - receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - utils.RequireTxSuccessful(r, receipt, "approve allowance tx") - fmt.Printf("approve allowance tx receipt: %+v\n", receipt) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + require.EqualValues(r, uint64(1), receipt.Status, "approve allowance tx failed") - // Check the allowance of the bank in ERC20ZRC20 tokens. Should be 100. - balance, err := r.ERC20ZRC20.Allowance(&bind.CallOpts{Context: r.Ctx}, owner, spender) + // Check the allowance of the bank in WZeta tokens. Should be 25. + balance, err := r.WZeta.Allowance(&bind.CallOpts{Context: r.Ctx}, spender, bankAddr) + fmt.Printf("DEBUG: bank allowance %s\n", balance.String()) require.NoError(r, err, "Error retrieving bank allowance") - require.EqualValues(r, uint64(100), balance.Uint64(), "Error allowance for bank contract") - fmt.Printf("bank allowance: %v\n", balance) - - // Call deposit with 100 coins. - tx, err = bankContract.Deposit(r.ZEVMAuth, r.ERC20ZRC20Addr, big.NewInt(100)) - require.NoError(r, err, "Error calling bank.deposit") - utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - fmt.Printf("Deposit tx: %+v\n", tx) - - // Check the balance of the owner in coins "zevm/0x12345". - retBalanceOf, err = bankContract.BalanceOf(nil, r.ERC20ZRC20Addr, owner) - require.NoError(r, err, "Error calling balanceOf") - require.EqualValues(r, uint64(100), retBalanceOf.Uint64(), "balanceOf result has to be 100") - fmt.Printf("owner balance zevm/coin (should increase): %+v\n", retBalanceOf) - - // Check the balance of the owner in r.ERC20ZRC20Addr, should be 100 less. - ownerERC20FinalBalance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{Context: r.Ctx}, owner) + require.EqualValues(r, uint64(25), balance.Uint64(), "Error allowance for bank contract") + + // Call deposit with 25 coins. + tx, err = bankContract.Deposit(r.ZEVMAuth, r.WZetaAddr, big.NewInt(25)) + fmt.Printf("DEBUG: bank.deposit() tx hash %s\n", tx.Hash().String()) + require.NoError(r, err, "Error calling bank.deposit()") + + r.Logger.Info("Waiting for 5 blocks") + r.WaitForBlocks(5) + fmt.Printf("DEBUG: bank.deposit() tx %+v\n", tx) + + // Check the balance of the spender in coins "zevm/WZetaAddr". + retBalanceOf, err = bankContract.BalanceOf(&bind.CallOpts{Context: r.Ctx}, r.WZetaAddr, spender) + fmt.Printf("DEBUG: final bank.balanceOf() tx %s\n", retBalanceOf.String()) + require.NoError(r, err, "Error calling bank.balanceOf()") + require.EqualValues(r, uint64(25), retBalanceOf.Uint64(), "balanceOf result has to be 25") + + // Check the balance of the spender in r.WZeta, should be 100 less. + finalBalance, err := r.WZeta.BalanceOf(&bind.CallOpts{Context: r.Ctx}, spender) + fmt.Printf("DEBUG: final WZeta balance %s\n", finalBalance.String()) require.NoError(r, err, "Error retrieving final owner balance") - fmt.Printf("owner final ERC20 balance (should decrease): %+v\n", retBalanceOf) require.EqualValues( r, - ownerERC20InitialBalance.Uint64()-100, // expected - ownerERC20FinalBalance.Uint64(), // actual - "Final balance should be initial - 100", + initialBalance.Uint64()-25, // expected + finalBalance.Uint64(), // actual + "Final balance should be initial - 25", ) } diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index fae3d8fbec..7acebd1ab2 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -1,6 +1,8 @@ package bank import ( + "fmt" + "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -103,6 +105,7 @@ func (c *Contract) RequiredGas(input []byte) uint64 { // Run is the entrypoint of the precompiled contract, it switches over the input method, // and execute them accordingly. func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byte, error) { + fmt.Println("DEBUG: bank.Run()") method, err := ABI.MethodById(contract.Input[:4]) if err != nil { return nil, err @@ -117,31 +120,40 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt switch method.Name { case DepositMethodName: + fmt.Println("DEBUG: bank.Run(): DepositMethodName") if readOnly { - return nil, nil + return nil, ptypes.ErrUnexpected{ + Got: "method not allowed in read-only mode " + method.Name, + } } var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { + fmt.Println("DEBUG: bank.Run(): DepositMethodName: ExecuteNativeAction c.deposit()") res, err = c.deposit(ctx, method, contract.CallerAddress, args) return err }) if execErr != nil { + fmt.Printf("DEBUG: bank.Run(): execErr %s", execErr.Error()) return nil, err } return res, nil case WithdrawMethodName: if readOnly { - return nil, nil + return nil, ptypes.ErrUnexpected{ + Got: "method not allowed in read-only mode " + method.Name, + } } return nil, nil // TODO case BalanceOfMethodName: + fmt.Println("DEBUG: bank.Run(): BalanceOfMethodName") var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { + fmt.Println("DEBUG: bank.Run(): DepositMethodName: ExecuteNativeAction c.balanceOf()") res, err = c.balanceOf(ctx, method, args) return err }) diff --git a/precompiles/bank/call_contract.go b/precompiles/bank/call_contract.go deleted file mode 100644 index 8277f96b8a..0000000000 --- a/precompiles/bank/call_contract.go +++ /dev/null @@ -1,57 +0,0 @@ -package bank - -import ( - "math/big" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - - ptypes "github.com/zeta-chain/node/precompiles/types" -) - -// CallContract calls a given contract on behalf of the precompiled contract. -// Note that the precompile contract address is hardcoded. -func (c *Contract) CallContract( - ctx sdk.Context, - abi *abi.ABI, - dst common.Address, - method string, - args []interface{}, -) ([]interface{}, error) { - res, err := c.fungibleKeeper.CallEVM( - ctx, // ctx - *abi, // abi - ContractAddress, // from - dst, // to - big.NewInt(0), // value - nil, // gasLimit - true, // commit - false, // noEthereumTxEvent - method, // method - args..., // args - ) - if err != nil { - return nil, &ptypes.ErrUnexpected{ - When: "CallEVM " + method, - Got: err.Error(), - } - } - - if res.VmError != "" { - return nil, &ptypes.ErrUnexpected{ - When: "VmError " + method, - Got: res.VmError, - } - } - - ret, err := abi.Methods[method].Outputs.Unpack(res.Ret) - if err != nil { - return nil, &ptypes.ErrUnexpected{ - When: "Unpack " + method, - Got: err.Error(), - } - } - - return ret, nil -} diff --git a/precompiles/bank/method_deposit.go b/precompiles/bank/method_deposit.go index a1214d0a06..4a8cf39d9d 100644 --- a/precompiles/bank/method_deposit.go +++ b/precompiles/bank/method_deposit.go @@ -1,6 +1,7 @@ package bank import ( + "fmt" "math/big" "cosmossdk.io/math" @@ -19,8 +20,8 @@ func (c *Contract) deposit( caller common.Address, args []interface{}, ) (result []byte, err error) { - // This function is developed using the - // Check - Effects - Interactions pattern: + fmt.Printf("DEBUG: deposit()\n") + // This function is developed using the Check - Effects - Interactions pattern: // 1. Check everything is correct. if len(args) != 2 { return nil, &(ptypes.ErrInvalidNumberOfArgs{ @@ -29,48 +30,74 @@ func (c *Contract) deposit( }) } + // Unpack parameters for function deposit. // function deposit(address zrc20, uint256 amount) external returns (bool success); - zrc20Addr, amount := args[0].(common.Address), args[1].(*big.Int) - if amount.Sign() < 0 || amount == nil || amount == new(big.Int) { + zrc20Addr, ok := args[0].(common.Address) + if !ok { + return nil, &ptypes.ErrInvalidAddr{ + Got: zrc20Addr.String(), + } + } + + amount, ok := args[1].(*big.Int) + if !ok || amount.Sign() < 0 || amount == nil || amount == new(big.Int) { return nil, &ptypes.ErrInvalidAmount{ Got: amount.String(), } } + fmt.Printf("DEBUG: deposit(): zrc20Addr (ERC20ZRC20) %s\n", zrc20Addr.String()) + fmt.Printf("DEBUG: deposit(): caller %s\n", caller.String()) // Initialize the ZRC20 ABI, as we need to call the balanceOf and allowance methods. zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() if err != nil { + fmt.Printf("DEBUG: deposit(): zrc20.ZRC20MetaData.GetAbi() error %s\n", err.Error()) return nil, &ptypes.ErrUnexpected{ - When: "ZRC20MetaData.GetAbi", + When: "ZRC20MetaData.GetAbi()", Got: err.Error(), } } + fmt.Printf("DEBUG: deposit(): zrc20ABI %v\n", zrc20ABI) // Check for enough balance. // function balanceOf(address account) public view virtual override returns (uint256) - argsBalanceOf := []interface{}{caller} - - resBalanceOf, err := c.CallContract(ctx, zrc20ABI, zrc20Addr, "balanceOf", argsBalanceOf) + resBalanceOf, err := c.CallContract( + ctx, + &c.fungibleKeeper, + zrc20ABI, + ContractAddress, + zrc20Addr, + "balanceOf", + []interface{}{caller}, + ) if err != nil { + fmt.Printf("DEBUG: deposit(): balanceOf c.CallContract error %s\n", err.Error()) return nil, &ptypes.ErrUnexpected{ When: "balanceOf", Got: err.Error(), } } + fmt.Printf("DEBUG: deposit(): resBalanceOf %v\n", resBalanceOf) - balance := resBalanceOf[0].(*big.Int) - if balance.Cmp(amount) < 0 { - return nil, &ptypes.ErrUnexpected{ - When: "balance0f", - Got: "not enough balance", + balance, ok := resBalanceOf[0].(*big.Int) + if !ok || balance.Cmp(amount) < 0 { + return nil, &ptypes.ErrInvalidAmount{ + Got: "not enough balance", } } + fmt.Printf("DEBUG: deposit(): balanceOf caller %v\n", balance.Uint64()) // Check for enough allowance. // function allowance(address owner, address spender) public view virtual override returns (uint256) - argsAllowance := []interface{}{caller, ContractAddress} - - resAllowance, err := c.CallContract(ctx, zrc20ABI, zrc20Addr, "allowance", argsAllowance) + resAllowance, err := c.CallContract( + ctx, + &c.fungibleKeeper, + zrc20ABI, + ContractAddress, + zrc20Addr, + "allowance", + []interface{}{caller, ContractAddress}, + ) if err != nil { return nil, &ptypes.ErrUnexpected{ When: "allowance", @@ -78,13 +105,13 @@ func (c *Contract) deposit( } } - allowance := resAllowance[0].(*big.Int) - if allowance.Cmp(amount) < 0 { - return nil, &ptypes.ErrUnexpected{ - When: "allowance", - Got: "not enough allowance", + allowance, ok := resAllowance[0].(*big.Int) + if !ok || allowance.Cmp(amount) < 0 { + return nil, &ptypes.ErrInvalidAmount{ + Got: "not enough allowance", } } + fmt.Printf("DEBUG: deposit(): allowance caller %v\n", allowance.Uint64()) // Handle the toAddr: // check it's valid and not blocked. @@ -95,11 +122,12 @@ func (c *Contract) deposit( Reason: "empty address", } } + fmt.Printf("DEBUG: deposit(): caller toAddr %s\n", toAddr.String()) if c.bankKeeper.BlockedAddr(toAddr) { return nil, &ptypes.ErrInvalidAddr{ Got: toAddr.String(), - Reason: "blocked by bank keeper", + Reason: "destination address blocked by bank keeper", } } @@ -109,6 +137,7 @@ func (c *Contract) deposit( // - Mint coins. // - Send coins to the caller. tokenDenom := ZRC20ToCosmosDenom(zrc20Addr) + coin := sdk.NewCoin(tokenDenom, math.NewIntFromBigInt(amount)) if !coin.IsValid() { return nil, &ptypes.ErrInvalidCoin{ @@ -132,9 +161,15 @@ func (c *Contract) deposit( // 2. Effect: subtract balance. // function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) - argsTransferFrom := []interface{}{caller, ContractAddress, amount} - - resTransferFrom, err := c.CallContract(ctx, zrc20ABI, zrc20Addr, "transferFrom", argsTransferFrom) + resTransferFrom, err := c.CallContract( + ctx, + &c.fungibleKeeper, + zrc20ABI, + ContractAddress, + zrc20Addr, + "transferFrom", + []interface{}{caller, ContractAddress, amount}, + ) if err != nil { return nil, &ptypes.ErrUnexpected{ When: "transferFrom", @@ -142,28 +177,35 @@ func (c *Contract) deposit( } } - transferred := resTransferFrom[0].(bool) - if !transferred { + transferred, ok := resTransferFrom[0].(bool) + if !ok || !transferred { return nil, &ptypes.ErrUnexpected{ When: "transferFrom", Got: "transaction not successful", } } + fmt.Printf("DEBUG: deposit(): transferred %v\n", transferred) // 3. Interactions: create cosmos coin and send. - if err := c.bankKeeper.MintCoins(ctx, types.ModuleName, coinSet); err != nil { + err = c.bankKeeper.MintCoins(ctx, types.ModuleName, coinSet) + if err != nil { + fmt.Printf("DEBUG: deposit(): MintCoins error %s\n", err.Error()) return nil, &ptypes.ErrUnexpected{ When: "MintCoins", Got: err.Error(), } } + fmt.Printf("DEBUG: deposit(): MintCoins finished\n") - if err := c.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, toAddr, coinSet); err != nil { + err = c.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, toAddr, coinSet) + if err != nil { + fmt.Printf("DEBUG: deposit(): SendCoinsFromModuleToAccount error %s\n", err.Error()) return nil, &ptypes.ErrUnexpected{ When: "SendCoinsFromModuleToAccount", Got: err.Error(), } } + fmt.Printf("DEBUG: deposit(): SendCoinsFromModuleToAccount finished\n") return method.Outputs.Pack(true) } diff --git a/precompiles/types/types.go b/precompiles/types/types.go index 7bd4a57bfa..9d33b7f822 100644 --- a/precompiles/types/types.go +++ b/precompiles/types/types.go @@ -4,9 +4,12 @@ import ( "math/big" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" "github.com/zeta-chain/ethermint/x/evm/statedb" + + fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" ) // Interface compliance. @@ -30,8 +33,19 @@ type Registrable interface { RegistryKey() common.Address } +type ContractCaller interface { + CallContract(ctx sdk.Context, + fungibleKeeper *fungiblekeeper.Keeper, + abi *abi.ABI, + from common.Address, + dst common.Address, + method string, + args []interface{}) ([]interface{}, error) +} + type BaseContract interface { Registrable + ContractCaller } // A baseContract implements Registrable and BaseContract interfaces. @@ -52,3 +66,49 @@ func (c *baseContract) RegistryKey() common.Address { func BytesToBigInt(data []byte) *big.Int { return big.NewInt(0).SetBytes(data[:]) } + +func (c *baseContract) CallContract( + ctx sdk.Context, + fungibleKeeper *fungiblekeeper.Keeper, + abi *abi.ABI, + from common.Address, + dst common.Address, + method string, + args []interface{}, +) ([]interface{}, error) { + res, err := fungibleKeeper.CallEVM( + ctx, // ctx + *abi, // abi + from, // from + dst, // to + big.NewInt(0), // value + nil, // gasLimit + true, // commit + false, // noEthereumTxEvent + method, // method + args..., // args + ) + if err != nil { + return nil, &ErrUnexpected{ + When: "CallEVM " + method, + Got: err.Error(), + } + } + + if res.VmError != "" { + return nil, &ErrUnexpected{ + When: "VmError " + method, + Got: res.VmError, + } + } + + ret, err := abi.Methods[method].Outputs.Unpack(res.Ret) + if err != nil { + return nil, &ErrUnexpected{ + When: "Unpack " + method, + Got: err.Error(), + } + } + + return ret, nil +} diff --git a/x/fungible/keeper/evm.go b/x/fungible/keeper/evm.go index 3f975a23ea..89182ffc8d 100644 --- a/x/fungible/keeper/evm.go +++ b/x/fungible/keeper/evm.go @@ -670,6 +670,7 @@ func (k Keeper) CallEVM( } k.Logger(ctx).Debug("calling EVM", "from", from, "contract", contract, "value", value, "method", method) + fmt.Printf("DEBUG: CallEVM: calling EVM from %s contract %s value %s method %s\n", from, contract, value, method) resp, err := k.CallEVMWithData(ctx, from, &contract, data, commit, noEthereumTxEvent, value, gasLimit) if err != nil { errMes := fmt.Sprintf( @@ -709,18 +710,30 @@ func (k Keeper) CallEVMWithData( value *big.Int, gasLimit *big.Int, ) (*evmtypes.MsgEthereumTxResponse, error) { + fmt.Printf( + "DEBUG: CallEVMWithData with from %s, contract %s, commit %v, gasLimit %v, noEthereumTxEvent %v, value %v\n", + from, + contract, + commit, + gasLimit, + noEthereumTxEvent, + value, + ) nonce, err := k.authKeeper.GetSequence(ctx, from.Bytes()) if err != nil { + fmt.Printf("DEBUG: CallEVMWithData error GetSequence: %s\n", err.Error()) return nil, err } gasCap := config.DefaultGasCap if commit && gasLimit == nil { + fmt.Printf("DEBUG: CallEVMWithData entered if commit and gasLimit == nil\n") args, err := json.Marshal(evmtypes.TransactionArgs{ From: &from, To: contract, Data: (*hexutil.Bytes)(&data), }) if err != nil { + fmt.Printf("DEBUG: CallEVMWithData entered if commit and gasLimit == nil, ERROR: %s\n", err.Error()) return nil, cosmoserrors.Wrapf(sdkerrors.ErrJSONMarshal, "failed to marshal tx args: %s", err.Error()) } @@ -729,6 +742,7 @@ func (k Keeper) CallEVMWithData( GasCap: config.DefaultGasCap, }) if err != nil { + fmt.Printf("DEBUG: CallEVMWithData error EstimateGas: %s\n", err.Error()) return nil, err } gasCap = gasRes.Gas @@ -752,13 +766,21 @@ func (k Keeper) CallEVMWithData( ) k.evmKeeper.WithChainID(ctx) //FIXME: set chainID for signer; should not need to do this; but seems necessary. Why? k.Logger(ctx).Debug("call evm", "gasCap", gasCap, "chainid", k.evmKeeper.ChainID(), "ctx.chainid", ctx.ChainID()) + fmt.Printf( + "DEBUG: CallEVMWithData: call evm gasCap %d chainid %d ctx.chainid %d\n", + gasCap, + k.evmKeeper.ChainID(), + ctx.ChainID(), + ) res, err := k.evmKeeper.ApplyMessage(ctx, msg, evmtypes.NewNoOpTracer(), commit) if err != nil { + fmt.Printf("DEBUG: ApplyMessage error: %s\n", err.Error()) return nil, err } // Emit events and log for the transaction if it is committed if commit { + fmt.Printf("DEBUG: Enter commit\n") msgBytes, err := json.Marshal(msg) if err != nil { return nil, cosmoserrors.Wrap(err, "failed to encode msg") @@ -832,8 +854,10 @@ func (k Keeper) CallEVMWithData( k.evmKeeper.SetLogSizeTransient(ctx, (k.evmKeeper.GetLogSizeTransient(ctx))+uint64(len(logs))) } } + fmt.Printf("DEBUG: Finish commit\n") if res.Failed() { + fmt.Printf("DEBUG: Enter res.Failed()\n") return res, cosmoserrors.Wrapf(evmtypes.ErrVMExecution, "%s: ret 0x%x", res.VmError, res.Ret) }