From 091e4f6b398c75da9368072f1af3b07c8ff59e7b Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Mon, 6 Jan 2025 09:26:54 +0100 Subject: [PATCH] fix: make crosschain-call with invalid withdraw revert (#3321) * withdrawer contract * deposit and withdraw dust test * add changelog * add test * don't commit state and revert when process withdraw fails * fix tests * add more assertions * update contract * update contract 2 * add back bitcoin tests * Update e2e/e2etests/test_bitcoin_deposit_and_withdraw_with_dust.go Co-authored-by: skosito * Update x/crosschain/keeper/evm_deposit.go Co-authored-by: skosito * add comment --------- Co-authored-by: skosito --- changelog.md | 1 + cmd/zetae2e/local/local.go | 1 + e2e/contracts/withdrawer/Withdrawer.abi | 116 +++++++ e2e/contracts/withdrawer/Withdrawer.bin | 1 + e2e/contracts/withdrawer/Withdrawer.go | 283 ++++++++++++++++++ e2e/contracts/withdrawer/Withdrawer.json | 119 ++++++++ e2e/contracts/withdrawer/Withdrawer.sol | 37 +++ e2e/contracts/withdrawer/bindings.go | 6 + e2e/e2etests/e2etests.go | 13 +- ..._bitcoin_deposit_and_withdraw_with_dust.go | 58 ++++ x/crosschain/keeper/evm_deposit.go | 27 +- x/crosschain/keeper/evm_deposit_test.go | 22 +- x/crosschain/keeper/evm_hooks.go | 6 +- x/crosschain/keeper/evm_hooks_test.go | 16 +- 14 files changed, 676 insertions(+), 30 deletions(-) create mode 100644 e2e/contracts/withdrawer/Withdrawer.abi create mode 100644 e2e/contracts/withdrawer/Withdrawer.bin create mode 100644 e2e/contracts/withdrawer/Withdrawer.go create mode 100644 e2e/contracts/withdrawer/Withdrawer.json create mode 100644 e2e/contracts/withdrawer/Withdrawer.sol create mode 100644 e2e/contracts/withdrawer/bindings.go create mode 100644 e2e/e2etests/test_bitcoin_deposit_and_withdraw_with_dust.go diff --git a/changelog.md b/changelog.md index 72d1ea2c59..5bb11d40cf 100644 --- a/changelog.md +++ b/changelog.md @@ -32,6 +32,7 @@ * [3278](https://github.com/zeta-chain/node/pull/3278) - enforce checksum format for asset address in ZRC20 * [3289](https://github.com/zeta-chain/node/pull/3289) - remove all dynamic peer discovery (zetaclient) * [3314](https://github.com/zeta-chain/node/pull/3314) - update `last_scanned_block_number` metrics more frequently for Solana chain +* [3321](https://github.com/zeta-chain/node/pull/3321) - make crosschain-call with invalid withdraw revert ## v24.0.0 diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 65b5fdcf44..8368e0d0bc 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -303,6 +303,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { bitcoinDepositTestsAdvanced := []string{ e2etests.TestBitcoinDepositAndCallRevertWithDustName, e2etests.TestBitcoinStdMemoDepositAndCallRevertOtherAddressName, + e2etests.TestBitcoinDepositAndWithdrawWithDustName, } bitcoinWithdrawTests := []string{ e2etests.TestBitcoinWithdrawSegWitName, diff --git a/e2e/contracts/withdrawer/Withdrawer.abi b/e2e/contracts/withdrawer/Withdrawer.abi new file mode 100644 index 0000000000..9d462137f0 --- /dev/null +++ b/e2e/contracts/withdrawer/Withdrawer.abi @@ -0,0 +1,116 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_withdrawAmount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "origin", + "type": "bytes" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "chainID", + "type": "uint256" + } + ], + "internalType": "struct Context", + "name": "context", + "type": "tuple" + }, + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "origin", + "type": "bytes" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "chainID", + "type": "uint256" + } + ], + "internalType": "struct Context", + "name": "context", + "type": "tuple" + }, + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onCrossChainCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/e2e/contracts/withdrawer/Withdrawer.bin b/e2e/contracts/withdrawer/Withdrawer.bin new file mode 100644 index 0000000000..7f0200971d --- /dev/null +++ b/e2e/contracts/withdrawer/Withdrawer.bin @@ -0,0 +1 @@ +60a0604052348015600f57600080fd5b506040516107f63803806107f68339818101604052810190602f91906072565b806080818152505050609a565b600080fd5b6000819050919050565b6052816041565b8114605c57600080fd5b50565b600081519050606c81604b565b92915050565b6000602082840312156085576084603c565b5b6000609184828501605f565b91505092915050565b6080516107346100c260003960008181609e0152818161018d01526102e201526107346000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063534844a2146100465780635bcfd61614610064578063de43156e14610080575b600080fd5b61004e61009c565b60405161005b9190610383565b60405180910390f35b61007e600480360381019061007991906104bb565b6100c0565b005b61009a600480360381019061009591906104bb565b610215565b005b7f000000000000000000000000000000000000000000000000000000000000000081565b8373ffffffffffffffffffffffffffffffffffffffff1663095ea7b3857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6040518363ffffffff1660e01b815260040161011b92919061056e565b6020604051808303816000875af115801561013a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061015e91906105cf565b508373ffffffffffffffffffffffffffffffffffffffff1663c701262686806000019061018b919061060b565b7f00000000000000000000000000000000000000000000000000000000000000006040518463ffffffff1660e01b81526004016101ca939291906106cc565b6020604051808303816000875af11580156101e9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061020d91906105cf565b505050505050565b8373ffffffffffffffffffffffffffffffffffffffff1663095ea7b3857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6040518363ffffffff1660e01b815260040161027092919061056e565b6020604051808303816000875af115801561028f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102b391906105cf565b508373ffffffffffffffffffffffffffffffffffffffff1663c70126268680600001906102e0919061060b565b7f00000000000000000000000000000000000000000000000000000000000000006040518463ffffffff1660e01b815260040161031f939291906106cc565b6020604051808303816000875af115801561033e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061036291906105cf565b505050505050565b6000819050919050565b61037d8161036a565b82525050565b60006020820190506103986000830184610374565b92915050565b600080fd5b600080fd5b600080fd5b6000606082840312156103c3576103c26103a8565b5b81905092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006103f7826103cc565b9050919050565b610407816103ec565b811461041257600080fd5b50565b600081359050610424816103fe565b92915050565b6104338161036a565b811461043e57600080fd5b50565b6000813590506104508161042a565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f84011261047b5761047a610456565b5b8235905067ffffffffffffffff8111156104985761049761045b565b5b6020830191508360018202830111156104b4576104b3610460565b5b9250929050565b6000806000806000608086880312156104d7576104d661039e565b5b600086013567ffffffffffffffff8111156104f5576104f46103a3565b5b610501888289016103ad565b955050602061051288828901610415565b945050604061052388828901610441565b935050606086013567ffffffffffffffff811115610544576105436103a3565b5b61055088828901610465565b92509250509295509295909350565b610568816103ec565b82525050565b6000604082019050610583600083018561055f565b6105906020830184610374565b9392505050565b60008115159050919050565b6105ac81610597565b81146105b757600080fd5b50565b6000815190506105c9816105a3565b92915050565b6000602082840312156105e5576105e461039e565b5b60006105f3848285016105ba565b91505092915050565b600080fd5b600080fd5b600080fd5b60008083356001602003843603038112610628576106276105fc565b5b80840192508235915067ffffffffffffffff82111561064a57610649610601565b5b60208301925060018202360383131561066657610665610606565b5b509250929050565b600082825260208201905092915050565b82818337600083830152505050565b6000601f19601f8301169050919050565b60006106ab838561066e565b93506106b883858461067f565b6106c18361068e565b840190509392505050565b600060408201905081810360008301526106e781858761069f565b90506106f66020830184610374565b94935050505056fea2646970667358221220eb0d0178243bc765ecffd41945dfc69d032eaf9e1347d0b6ee2ec8408676acd564736f6c634300081a0033 diff --git a/e2e/contracts/withdrawer/Withdrawer.go b/e2e/contracts/withdrawer/Withdrawer.go new file mode 100644 index 0000000000..99bd5fdfe3 --- /dev/null +++ b/e2e/contracts/withdrawer/Withdrawer.go @@ -0,0 +1,283 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package withdrawer + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// Context is an auto generated low-level Go binding around an user-defined struct. +type Context struct { + Origin []byte + Sender common.Address + ChainID *big.Int +} + +// WithdrawerMetaData contains all meta data concerning the Withdrawer contract. +var WithdrawerMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_withdrawAmount\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes\",\"name\":\"origin\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainID\",\"type\":\"uint256\"}],\"internalType\":\"structContext\",\"name\":\"context\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"onCall\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes\",\"name\":\"origin\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainID\",\"type\":\"uint256\"}],\"internalType\":\"structContext\",\"name\":\"context\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"onCrossChainCall\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawAmount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x60a0604052348015600f57600080fd5b506040516107f63803806107f68339818101604052810190602f91906072565b806080818152505050609a565b600080fd5b6000819050919050565b6052816041565b8114605c57600080fd5b50565b600081519050606c81604b565b92915050565b6000602082840312156085576084603c565b5b6000609184828501605f565b91505092915050565b6080516107346100c260003960008181609e0152818161018d01526102e201526107346000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063534844a2146100465780635bcfd61614610064578063de43156e14610080575b600080fd5b61004e61009c565b60405161005b9190610383565b60405180910390f35b61007e600480360381019061007991906104bb565b6100c0565b005b61009a600480360381019061009591906104bb565b610215565b005b7f000000000000000000000000000000000000000000000000000000000000000081565b8373ffffffffffffffffffffffffffffffffffffffff1663095ea7b3857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6040518363ffffffff1660e01b815260040161011b92919061056e565b6020604051808303816000875af115801561013a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061015e91906105cf565b508373ffffffffffffffffffffffffffffffffffffffff1663c701262686806000019061018b919061060b565b7f00000000000000000000000000000000000000000000000000000000000000006040518463ffffffff1660e01b81526004016101ca939291906106cc565b6020604051808303816000875af11580156101e9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061020d91906105cf565b505050505050565b8373ffffffffffffffffffffffffffffffffffffffff1663095ea7b3857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6040518363ffffffff1660e01b815260040161027092919061056e565b6020604051808303816000875af115801561028f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102b391906105cf565b508373ffffffffffffffffffffffffffffffffffffffff1663c70126268680600001906102e0919061060b565b7f00000000000000000000000000000000000000000000000000000000000000006040518463ffffffff1660e01b815260040161031f939291906106cc565b6020604051808303816000875af115801561033e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061036291906105cf565b505050505050565b6000819050919050565b61037d8161036a565b82525050565b60006020820190506103986000830184610374565b92915050565b600080fd5b600080fd5b600080fd5b6000606082840312156103c3576103c26103a8565b5b81905092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006103f7826103cc565b9050919050565b610407816103ec565b811461041257600080fd5b50565b600081359050610424816103fe565b92915050565b6104338161036a565b811461043e57600080fd5b50565b6000813590506104508161042a565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f84011261047b5761047a610456565b5b8235905067ffffffffffffffff8111156104985761049761045b565b5b6020830191508360018202830111156104b4576104b3610460565b5b9250929050565b6000806000806000608086880312156104d7576104d661039e565b5b600086013567ffffffffffffffff8111156104f5576104f46103a3565b5b610501888289016103ad565b955050602061051288828901610415565b945050604061052388828901610441565b935050606086013567ffffffffffffffff811115610544576105436103a3565b5b61055088828901610465565b92509250509295509295909350565b610568816103ec565b82525050565b6000604082019050610583600083018561055f565b6105906020830184610374565b9392505050565b60008115159050919050565b6105ac81610597565b81146105b757600080fd5b50565b6000815190506105c9816105a3565b92915050565b6000602082840312156105e5576105e461039e565b5b60006105f3848285016105ba565b91505092915050565b600080fd5b600080fd5b600080fd5b60008083356001602003843603038112610628576106276105fc565b5b80840192508235915067ffffffffffffffff82111561064a57610649610601565b5b60208301925060018202360383131561066657610665610606565b5b509250929050565b600082825260208201905092915050565b82818337600083830152505050565b6000601f19601f8301169050919050565b60006106ab838561066e565b93506106b883858461067f565b6106c18361068e565b840190509392505050565b600060408201905081810360008301526106e781858761069f565b90506106f66020830184610374565b94935050505056fea2646970667358221220eb0d0178243bc765ecffd41945dfc69d032eaf9e1347d0b6ee2ec8408676acd564736f6c634300081a0033", +} + +// WithdrawerABI is the input ABI used to generate the binding from. +// Deprecated: Use WithdrawerMetaData.ABI instead. +var WithdrawerABI = WithdrawerMetaData.ABI + +// WithdrawerBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use WithdrawerMetaData.Bin instead. +var WithdrawerBin = WithdrawerMetaData.Bin + +// DeployWithdrawer deploys a new Ethereum contract, binding an instance of Withdrawer to it. +func DeployWithdrawer(auth *bind.TransactOpts, backend bind.ContractBackend, _withdrawAmount *big.Int) (common.Address, *types.Transaction, *Withdrawer, error) { + parsed, err := WithdrawerMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(WithdrawerBin), backend, _withdrawAmount) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Withdrawer{WithdrawerCaller: WithdrawerCaller{contract: contract}, WithdrawerTransactor: WithdrawerTransactor{contract: contract}, WithdrawerFilterer: WithdrawerFilterer{contract: contract}}, nil +} + +// Withdrawer is an auto generated Go binding around an Ethereum contract. +type Withdrawer struct { + WithdrawerCaller // Read-only binding to the contract + WithdrawerTransactor // Write-only binding to the contract + WithdrawerFilterer // Log filterer for contract events +} + +// WithdrawerCaller is an auto generated read-only Go binding around an Ethereum contract. +type WithdrawerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// WithdrawerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type WithdrawerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// WithdrawerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type WithdrawerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// WithdrawerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type WithdrawerSession struct { + Contract *Withdrawer // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// WithdrawerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type WithdrawerCallerSession struct { + Contract *WithdrawerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// WithdrawerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type WithdrawerTransactorSession struct { + Contract *WithdrawerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// WithdrawerRaw is an auto generated low-level Go binding around an Ethereum contract. +type WithdrawerRaw struct { + Contract *Withdrawer // Generic contract binding to access the raw methods on +} + +// WithdrawerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type WithdrawerCallerRaw struct { + Contract *WithdrawerCaller // Generic read-only contract binding to access the raw methods on +} + +// WithdrawerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type WithdrawerTransactorRaw struct { + Contract *WithdrawerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewWithdrawer creates a new instance of Withdrawer, bound to a specific deployed contract. +func NewWithdrawer(address common.Address, backend bind.ContractBackend) (*Withdrawer, error) { + contract, err := bindWithdrawer(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Withdrawer{WithdrawerCaller: WithdrawerCaller{contract: contract}, WithdrawerTransactor: WithdrawerTransactor{contract: contract}, WithdrawerFilterer: WithdrawerFilterer{contract: contract}}, nil +} + +// NewWithdrawerCaller creates a new read-only instance of Withdrawer, bound to a specific deployed contract. +func NewWithdrawerCaller(address common.Address, caller bind.ContractCaller) (*WithdrawerCaller, error) { + contract, err := bindWithdrawer(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &WithdrawerCaller{contract: contract}, nil +} + +// NewWithdrawerTransactor creates a new write-only instance of Withdrawer, bound to a specific deployed contract. +func NewWithdrawerTransactor(address common.Address, transactor bind.ContractTransactor) (*WithdrawerTransactor, error) { + contract, err := bindWithdrawer(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &WithdrawerTransactor{contract: contract}, nil +} + +// NewWithdrawerFilterer creates a new log filterer instance of Withdrawer, bound to a specific deployed contract. +func NewWithdrawerFilterer(address common.Address, filterer bind.ContractFilterer) (*WithdrawerFilterer, error) { + contract, err := bindWithdrawer(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &WithdrawerFilterer{contract: contract}, nil +} + +// bindWithdrawer binds a generic wrapper to an already deployed contract. +func bindWithdrawer(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := WithdrawerMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Withdrawer *WithdrawerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Withdrawer.Contract.WithdrawerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Withdrawer *WithdrawerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Withdrawer.Contract.WithdrawerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Withdrawer *WithdrawerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Withdrawer.Contract.WithdrawerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Withdrawer *WithdrawerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Withdrawer.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Withdrawer *WithdrawerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Withdrawer.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Withdrawer *WithdrawerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Withdrawer.Contract.contract.Transact(opts, method, params...) +} + +// WithdrawAmount is a free data retrieval call binding the contract method 0x534844a2. +// +// Solidity: function withdrawAmount() view returns(uint256) +func (_Withdrawer *WithdrawerCaller) WithdrawAmount(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _Withdrawer.contract.Call(opts, &out, "withdrawAmount") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// WithdrawAmount is a free data retrieval call binding the contract method 0x534844a2. +// +// Solidity: function withdrawAmount() view returns(uint256) +func (_Withdrawer *WithdrawerSession) WithdrawAmount() (*big.Int, error) { + return _Withdrawer.Contract.WithdrawAmount(&_Withdrawer.CallOpts) +} + +// WithdrawAmount is a free data retrieval call binding the contract method 0x534844a2. +// +// Solidity: function withdrawAmount() view returns(uint256) +func (_Withdrawer *WithdrawerCallerSession) WithdrawAmount() (*big.Int, error) { + return _Withdrawer.Contract.WithdrawAmount(&_Withdrawer.CallOpts) +} + +// OnCall is a paid mutator transaction binding the contract method 0x5bcfd616. +// +// Solidity: function onCall((bytes,address,uint256) context, address zrc20, uint256 , bytes ) returns() +func (_Withdrawer *WithdrawerTransactor) OnCall(opts *bind.TransactOpts, context Context, zrc20 common.Address, arg2 *big.Int, arg3 []byte) (*types.Transaction, error) { + return _Withdrawer.contract.Transact(opts, "onCall", context, zrc20, arg2, arg3) +} + +// OnCall is a paid mutator transaction binding the contract method 0x5bcfd616. +// +// Solidity: function onCall((bytes,address,uint256) context, address zrc20, uint256 , bytes ) returns() +func (_Withdrawer *WithdrawerSession) OnCall(context Context, zrc20 common.Address, arg2 *big.Int, arg3 []byte) (*types.Transaction, error) { + return _Withdrawer.Contract.OnCall(&_Withdrawer.TransactOpts, context, zrc20, arg2, arg3) +} + +// OnCall is a paid mutator transaction binding the contract method 0x5bcfd616. +// +// Solidity: function onCall((bytes,address,uint256) context, address zrc20, uint256 , bytes ) returns() +func (_Withdrawer *WithdrawerTransactorSession) OnCall(context Context, zrc20 common.Address, arg2 *big.Int, arg3 []byte) (*types.Transaction, error) { + return _Withdrawer.Contract.OnCall(&_Withdrawer.TransactOpts, context, zrc20, arg2, arg3) +} + +// OnCrossChainCall is a paid mutator transaction binding the contract method 0xde43156e. +// +// Solidity: function onCrossChainCall((bytes,address,uint256) context, address zrc20, uint256 , bytes ) returns() +func (_Withdrawer *WithdrawerTransactor) OnCrossChainCall(opts *bind.TransactOpts, context Context, zrc20 common.Address, arg2 *big.Int, arg3 []byte) (*types.Transaction, error) { + return _Withdrawer.contract.Transact(opts, "onCrossChainCall", context, zrc20, arg2, arg3) +} + +// OnCrossChainCall is a paid mutator transaction binding the contract method 0xde43156e. +// +// Solidity: function onCrossChainCall((bytes,address,uint256) context, address zrc20, uint256 , bytes ) returns() +func (_Withdrawer *WithdrawerSession) OnCrossChainCall(context Context, zrc20 common.Address, arg2 *big.Int, arg3 []byte) (*types.Transaction, error) { + return _Withdrawer.Contract.OnCrossChainCall(&_Withdrawer.TransactOpts, context, zrc20, arg2, arg3) +} + +// OnCrossChainCall is a paid mutator transaction binding the contract method 0xde43156e. +// +// Solidity: function onCrossChainCall((bytes,address,uint256) context, address zrc20, uint256 , bytes ) returns() +func (_Withdrawer *WithdrawerTransactorSession) OnCrossChainCall(context Context, zrc20 common.Address, arg2 *big.Int, arg3 []byte) (*types.Transaction, error) { + return _Withdrawer.Contract.OnCrossChainCall(&_Withdrawer.TransactOpts, context, zrc20, arg2, arg3) +} diff --git a/e2e/contracts/withdrawer/Withdrawer.json b/e2e/contracts/withdrawer/Withdrawer.json new file mode 100644 index 0000000000..26bfe6df7b --- /dev/null +++ b/e2e/contracts/withdrawer/Withdrawer.json @@ -0,0 +1,119 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_withdrawAmount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "origin", + "type": "bytes" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "chainID", + "type": "uint256" + } + ], + "internalType": "struct Context", + "name": "context", + "type": "tuple" + }, + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "origin", + "type": "bytes" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "chainID", + "type": "uint256" + } + ], + "internalType": "struct Context", + "name": "context", + "type": "tuple" + }, + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onCrossChainCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bin": "60a0604052348015600f57600080fd5b506040516107f63803806107f68339818101604052810190602f91906072565b806080818152505050609a565b600080fd5b6000819050919050565b6052816041565b8114605c57600080fd5b50565b600081519050606c81604b565b92915050565b6000602082840312156085576084603c565b5b6000609184828501605f565b91505092915050565b6080516107346100c260003960008181609e0152818161018d01526102e201526107346000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063534844a2146100465780635bcfd61614610064578063de43156e14610080575b600080fd5b61004e61009c565b60405161005b9190610383565b60405180910390f35b61007e600480360381019061007991906104bb565b6100c0565b005b61009a600480360381019061009591906104bb565b610215565b005b7f000000000000000000000000000000000000000000000000000000000000000081565b8373ffffffffffffffffffffffffffffffffffffffff1663095ea7b3857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6040518363ffffffff1660e01b815260040161011b92919061056e565b6020604051808303816000875af115801561013a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061015e91906105cf565b508373ffffffffffffffffffffffffffffffffffffffff1663c701262686806000019061018b919061060b565b7f00000000000000000000000000000000000000000000000000000000000000006040518463ffffffff1660e01b81526004016101ca939291906106cc565b6020604051808303816000875af11580156101e9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061020d91906105cf565b505050505050565b8373ffffffffffffffffffffffffffffffffffffffff1663095ea7b3857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6040518363ffffffff1660e01b815260040161027092919061056e565b6020604051808303816000875af115801561028f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102b391906105cf565b508373ffffffffffffffffffffffffffffffffffffffff1663c70126268680600001906102e0919061060b565b7f00000000000000000000000000000000000000000000000000000000000000006040518463ffffffff1660e01b815260040161031f939291906106cc565b6020604051808303816000875af115801561033e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061036291906105cf565b505050505050565b6000819050919050565b61037d8161036a565b82525050565b60006020820190506103986000830184610374565b92915050565b600080fd5b600080fd5b600080fd5b6000606082840312156103c3576103c26103a8565b5b81905092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006103f7826103cc565b9050919050565b610407816103ec565b811461041257600080fd5b50565b600081359050610424816103fe565b92915050565b6104338161036a565b811461043e57600080fd5b50565b6000813590506104508161042a565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f84011261047b5761047a610456565b5b8235905067ffffffffffffffff8111156104985761049761045b565b5b6020830191508360018202830111156104b4576104b3610460565b5b9250929050565b6000806000806000608086880312156104d7576104d661039e565b5b600086013567ffffffffffffffff8111156104f5576104f46103a3565b5b610501888289016103ad565b955050602061051288828901610415565b945050604061052388828901610441565b935050606086013567ffffffffffffffff811115610544576105436103a3565b5b61055088828901610465565b92509250509295509295909350565b610568816103ec565b82525050565b6000604082019050610583600083018561055f565b6105906020830184610374565b9392505050565b60008115159050919050565b6105ac81610597565b81146105b757600080fd5b50565b6000815190506105c9816105a3565b92915050565b6000602082840312156105e5576105e461039e565b5b60006105f3848285016105ba565b91505092915050565b600080fd5b600080fd5b600080fd5b60008083356001602003843603038112610628576106276105fc565b5b80840192508235915067ffffffffffffffff82111561064a57610649610601565b5b60208301925060018202360383131561066657610665610606565b5b509250929050565b600082825260208201905092915050565b82818337600083830152505050565b6000601f19601f8301169050919050565b60006106ab838561066e565b93506106b883858461067f565b6106c18361068e565b840190509392505050565b600060408201905081810360008301526106e781858761069f565b90506106f66020830184610374565b94935050505056fea2646970667358221220eb0d0178243bc765ecffd41945dfc69d032eaf9e1347d0b6ee2ec8408676acd564736f6c634300081a0033" +} diff --git a/e2e/contracts/withdrawer/Withdrawer.sol b/e2e/contracts/withdrawer/Withdrawer.sol new file mode 100644 index 0000000000..13ac900ace --- /dev/null +++ b/e2e/contracts/withdrawer/Withdrawer.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +struct Context { + bytes origin; + address sender; + uint256 chainID; +} + +interface IZRC20 { + function approve(address spender, uint256 amount) external returns (bool); + function withdraw(bytes memory to, uint256 amount) external returns (bool); +} + +// Withdrawer is a simple contract performing a withdraw of deposited ZRC20 +// The amount to withdraw can be set during the contract deployment, it also to tests some edge cases like withdrawing BTC dust amount +contract Withdrawer { + uint256 immutable public withdrawAmount; + + constructor(uint256 _withdrawAmount) { + withdrawAmount = _withdrawAmount; + } + + // perform a withdraw on cross chain call + function onCrossChainCall(Context calldata context, address zrc20, uint256, bytes calldata) external { + // perform withdrawal with the target token + IZRC20(zrc20).approve(address(zrc20), type(uint256).max); + IZRC20(zrc20).withdraw(context.origin, withdrawAmount); + } + + // perform a withdraw on cross chain call, v2 + function onCall(Context calldata context, address zrc20, uint256, bytes calldata) external { + // perform withdrawal with the target token + IZRC20(zrc20).approve(address(zrc20), type(uint256).max); + IZRC20(zrc20).withdraw(context.origin, withdrawAmount); + } +} \ No newline at end of file diff --git a/e2e/contracts/withdrawer/bindings.go b/e2e/contracts/withdrawer/bindings.go new file mode 100644 index 0000000000..0ec483e439 --- /dev/null +++ b/e2e/contracts/withdrawer/bindings.go @@ -0,0 +1,6 @@ +//go:generate sh -c "solc --evm-version paris Withdrawer.sol --combined-json abi,bin --allow-paths .. | jq '.contracts.\"Withdrawer.sol:Withdrawer\"' > Withdrawer.json" +//go:generate sh -c "cat Withdrawer.json | jq .abi > Withdrawer.abi" +//go:generate sh -c "cat Withdrawer.json | jq .bin | tr -d '\"' > Withdrawer.bin" +//go:generate sh -c "abigen --abi Withdrawer.abi --bin Withdrawer.bin --pkg withdrawer --type Withdrawer --out Withdrawer.go" + +package withdrawer diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 1b9f3ef419..884573d2fa 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -75,6 +75,7 @@ const ( TestBitcoinDepositAndCallName = "bitcoin_deposit_and_call" TestBitcoinDepositAndCallRevertName = "bitcoin_deposit_and_call_revert" TestBitcoinDepositAndCallRevertWithDustName = "bitcoin_deposit_and_call_revert_with_dust" + TestBitcoinDepositAndWithdrawWithDustName = "bitcoin_deposit_and_withdraw_with_dust" TestBitcoinDonationName = "bitcoin_donation" TestBitcoinStdMemoDepositName = "bitcoin_std_memo_deposit" TestBitcoinStdMemoDepositAndCallName = "bitcoin_std_memo_deposit_and_call" @@ -587,16 +588,24 @@ var AllE2ETests = []runner.E2ETest{ ), runner.NewE2ETest( TestBitcoinDepositAndCallRevertName, - "deposit Bitcoin into ZEVM; expect refund", []runner.ArgDefinition{ + "deposit Bitcoin into ZEVM; expect refund", + []runner.ArgDefinition{ {Description: "amount in btc", DefaultValue: "0.1"}, }, TestBitcoinDepositAndCallRevert, ), runner.NewE2ETest( TestBitcoinDepositAndCallRevertWithDustName, - "deposit Bitcoin into ZEVM; revert with dust amount that aborts the CCTX", []runner.ArgDefinition{}, + "deposit Bitcoin into ZEVM; revert with dust amount that aborts the CCTX", + []runner.ArgDefinition{}, TestBitcoinDepositAndCallRevertWithDust, ), + runner.NewE2ETest( + TestBitcoinDepositAndWithdrawWithDustName, + "deposit Bitcoin into ZEVM and withdraw with dust amount that fails the CCTX", + []runner.ArgDefinition{}, + TestBitcoinDepositAndWithdrawWithDust, + ), runner.NewE2ETest( TestBitcoinStdMemoDepositName, "deposit Bitcoin into ZEVM with standard memo", diff --git a/e2e/e2etests/test_bitcoin_deposit_and_withdraw_with_dust.go b/e2e/e2etests/test_bitcoin_deposit_and_withdraw_with_dust.go new file mode 100644 index 0000000000..8b108f2103 --- /dev/null +++ b/e2e/e2etests/test_bitcoin_deposit_and_withdraw_with_dust.go @@ -0,0 +1,58 @@ +package e2etests + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/contracts/withdrawer" + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" +) + +// TestBitcoinDepositAndWithdrawWithDust deposits Bitcoin and call a smart contract that withdraw dust amount +// It tests the edge case where during a cross-chain call, a invaild withdraw is initiated (processLogs fails) +func TestBitcoinDepositAndWithdrawWithDust(r *runner.E2ERunner, args []string) { + // Given "Live" BTC network + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + + require.Len(r, args, 0) + + // ARRANGE + + // Deploy the withdrawer contract on ZetaChain with a withdraw amount of 100 satoshis (dust amount is 1000 satoshis) + withdrawerAddr, tx, _, err := withdrawer.DeployWithdrawer(r.ZEVMAuth, r.ZEVMClient, big.NewInt(100)) + require.NoError(r, err) + + // Wait for the transaction to be mined + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + require.Equal(r, receipt.Status, uint64(1)) + + // Given a list of UTXOs + utxos, err := r.ListDeployerUTXOs() + require.NoError(r, err) + require.NotEmpty(r, utxos) + + // ACT + // Deposit 0.01 BTC to withdrawer, this is an arbitrary amount, must be greater than dust amount + txHash, err := r.SendToTSSFromDeployerWithMemo(0.01, utxos, withdrawerAddr.Bytes()) + require.NoError(r, err) + require.NotEmpty(r, txHash) + + // ASSERT + // Now we want to make sure the cctx is reverted with expected error message + + // cctx status would be pending revert if using v21 or before + cctx := utils.WaitCctxRevertedByInboundHash(r.Ctx, r, txHash.String(), r.CctxClient) + require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) + require.Contains(r, cctx.CctxStatus.ErrorMessage, crosschaintypes.ErrCannotProcessWithdrawal.Error()) + + // check the contract has no BTC balance, this would mean the contract call state transition is not reverted + // get BTC ZRC20 balance of the withdrawer contract + bal, err := r.BTCZRC20.BalanceOf(&bind.CallOpts{}, withdrawerAddr) + require.NoError(r, err) + require.Zero(r, bal.Uint64()) +} diff --git a/x/crosschain/keeper/evm_deposit.go b/x/crosschain/keeper/evm_deposit.go index e57dc16d1b..d6c8a77a66 100644 --- a/x/crosschain/keeper/evm_deposit.go +++ b/x/crosschain/keeper/evm_deposit.go @@ -100,8 +100,13 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo return false, fmt.Errorf("HandleEVMDeposit: unable to decode address: %w", err) } + // use a temporary context to not commit any state change in case of error + // note: ZRC20DepositAndCallContract is solely responsible for calling the contract and depositing tokens if needed + // and does not include any other side effects or any logic that modifies the state directly + tmpCtx, commit := ctx.CacheContext() + evmTxResponse, contractCall, err := k.fungibleKeeper.ZRC20DepositAndCallContract( - ctx, + tmpCtx, from, to, inboundAmount, @@ -113,8 +118,11 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo cctx.InboundParams.IsCrossChainCall, ) if fungibletypes.IsContractReverted(evmTxResponse, err) || errShouldRevertCctx(err) { + // this is a contract revert, we commit the state to save the emitted logs related to revert + commit() return true, err } else if err != nil { + // this should not happen and we don't commit the state to avoid inconsistent state return false, err } @@ -123,18 +131,21 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo if !evmTxResponse.Failed() && contractCall { logs := evmtypes.LogsToEthereum(evmTxResponse.Logs) if len(logs) > 0 { - ctx = ctx.WithValue(InCCTXIndexKey, cctx.Index) + tmpCtx = tmpCtx.WithValue(InCCTXIndexKey, cctx.Index) txOrigin := cctx.InboundParams.TxOrigin if txOrigin == "" { txOrigin = inboundSender } - err = k.ProcessLogs(ctx, logs, to, txOrigin) + // process logs to process cctx events initiated during the contract call + err = k.ProcessLogs(tmpCtx, logs, to, txOrigin) if err != nil { - // ProcessLogs should not error; error indicates exception, should abort - return false, errors.Wrap(types.ErrCannotProcessWithdrawal, err.Error()) + // this happens if the cctx events are not processed correctly with invalid withdrawals + // in this situation we want the CCTX to be reverted, we don't commit the state so the contract call is not persisted + // the contract call is considered as reverted + return true, errors.Wrap(types.ErrCannotProcessWithdrawal, err.Error()) } - ctx.EventManager().EmitEvent( + tmpCtx.EventManager().EmitEvent( sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), sdk.NewAttribute("action", "DepositZRC20AndCallContract"), @@ -145,7 +156,11 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo ) } } + + // commit state change from the deposit and eventual cctx events + commit() } + return false, nil } diff --git a/x/crosschain/keeper/evm_deposit_test.go b/x/crosschain/keeper/evm_deposit_test.go index 45c0a0aecc..b65071a13f 100644 --- a/x/crosschain/keeper/evm_deposit_test.go +++ b/x/crosschain/keeper/evm_deposit_test.go @@ -98,7 +98,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { // ZRC20DepositAndCallContract(ctx, from, to, msg.Amount.BigInt(), senderChain, msg.Message, contract, data, msg.CoinType, msg.Asset) fungibleMock.On( "ZRC20DepositAndCallContract", - ctx, + mock.Anything, mock.Anything, receiver, amount, @@ -145,7 +145,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { // ZRC20DepositAndCallContract(ctx, from, to, msg.Amount.BigInt(), senderChain, msg.Message, contract, data, msg.CoinType, msg.Asset) fungibleMock.On( "ZRC20DepositAndCallContract", - ctx, + mock.Anything, mock.Anything, receiver, amount, @@ -187,7 +187,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { cctx, ) require.Error(t, err) - require.False(t, reverted) + require.True(t, reverted) fungibleMock.AssertExpectations(t) }, ) @@ -209,7 +209,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { // ZRC20DepositAndCallContract(ctx, from, to, msg.Amount.BigInt(), senderChain, msg.Message, contract, data, msg.CoinType, msg.Asset) fungibleMock.On( "ZRC20DepositAndCallContract", - ctx, + mock.Anything, mock.Anything, receiver, amount, @@ -300,7 +300,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { errDeposit := errors.New("deposit failed") fungibleMock.On( "ZRC20DepositAndCallContract", - ctx, + mock.Anything, mock.Anything, receiver, amount, @@ -346,7 +346,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { errDeposit := errors.New("deposit failed") fungibleMock.On( "ZRC20DepositAndCallContract", - ctx, + mock.Anything, mock.Anything, receiver, amount, @@ -391,7 +391,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { // ZRC20DepositAndCallContract(ctx, from, to, msg.Amount.BigInt(), senderChain, msg.Message, contract, data, msg.CoinType, msg.Asset) fungibleMock.On( "ZRC20DepositAndCallContract", - ctx, + mock.Anything, mock.Anything, receiver, amount, @@ -436,7 +436,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { // ZRC20DepositAndCallContract(ctx, from, to, msg.Amount.BigInt(), senderChain, msg.Message, contract, data, msg.CoinType, msg.Asset) fungibleMock.On( "ZRC20DepositAndCallContract", - ctx, + mock.Anything, mock.Anything, receiver, amount, @@ -481,7 +481,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { fungibleMock.On( "ZRC20DepositAndCallContract", - ctx, + mock.Anything, mock.Anything, receiver, amount, @@ -552,7 +552,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { ctx = ctx.WithTxBytes(b) fungibleMock.On( "ZRC20DepositAndCallContract", - ctx, + mock.Anything, mock.Anything, receiver, amount, @@ -596,7 +596,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { require.NoError(t, err) fungibleMock.On( "ZRC20DepositAndCallContract", - ctx, + mock.Anything, mock.Anything, receiver, amount, diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index 38726c287a..d09f771fbb 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -133,7 +133,7 @@ func (k Keeper) ProcessZEVMInboundV1( // If Validation fails, we will not process the event and return and error. This condition means that the event was correct, and emitted from a registered ZRC20 contract // But the information entered by the user is incorrect. In this case we can return an error and roll back the transaction - if err := k.ValidateZrc20WithdrawEvent(ctx, eventZRC20Withdrawal, coin.ForeignChainId); err != nil { + if err := k.ValidateZRC20WithdrawEvent(ctx, eventZRC20Withdrawal, coin.ForeignChainId); err != nil { return err } // If the event is valid, we will process it and create a new CCTX @@ -303,9 +303,9 @@ func (k Keeper) ProcessZetaSentEvent( return nil } -// ValidateZrc20WithdrawEvent checks if the ZRC20Withdrawal event is valid +// ValidateZRC20WithdrawEvent checks if the ZRC20Withdrawal event is valid // It verifies event information for BTC chains and returns an error if the event is invalid -func (k Keeper) ValidateZrc20WithdrawEvent(ctx sdk.Context, event *zrc20.ZRC20Withdrawal, chainID int64) error { +func (k Keeper) ValidateZRC20WithdrawEvent(ctx sdk.Context, event *zrc20.ZRC20Withdrawal, chainID int64) error { // The event was parsed; that means the user has deposited tokens to the contract. return k.validateZRC20Withdrawal(ctx, chainID, event.Value, event.To) } diff --git a/x/crosschain/keeper/evm_hooks_test.go b/x/crosschain/keeper/evm_hooks_test.go index fff7e31f02..e9b4b49b0c 100644 --- a/x/crosschain/keeper/evm_hooks_test.go +++ b/x/crosschain/keeper/evm_hooks_test.go @@ -161,7 +161,7 @@ func TestValidateZrc20WithdrawEvent(t *testing.T) { *sample.ValidZRC20WithdrawToBTCReceipt(t).Logs[3], ) require.NoError(t, err) - err = k.ValidateZrc20WithdrawEvent(ctx, btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) + err = k.ValidateZRC20WithdrawEvent(ctx, btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) require.NoError(t, err) }) @@ -174,12 +174,12 @@ func TestValidateZrc20WithdrawEvent(t *testing.T) { // 1000 satoshis is the minimum amount that can be withdrawn btcMainNetWithdrawalEvent.Value = big.NewInt(constant.BTCWithdrawalDustAmount) - err = k.ValidateZrc20WithdrawEvent(ctx, btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) + err = k.ValidateZRC20WithdrawEvent(ctx, btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) require.NoError(t, err) // 999 satoshis cannot be withdrawn btcMainNetWithdrawalEvent.Value = big.NewInt(constant.BTCWithdrawalDustAmount - 1) - err = k.ValidateZrc20WithdrawEvent(ctx, btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) + err = k.ValidateZRC20WithdrawEvent(ctx, btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) require.ErrorContains(t, err, "less than dust amount") }) @@ -189,7 +189,7 @@ func TestValidateZrc20WithdrawEvent(t *testing.T) { *sample.ValidZRC20WithdrawToBTCReceipt(t).Logs[3], ) require.NoError(t, err) - err = k.ValidateZrc20WithdrawEvent(ctx, btcMainNetWithdrawalEvent, chains.BitcoinTestnet.ChainId) + err = k.ValidateZRC20WithdrawEvent(ctx, btcMainNetWithdrawalEvent, chains.BitcoinTestnet.ChainId) require.ErrorContains(t, err, "invalid address") }) @@ -201,7 +201,7 @@ func TestValidateZrc20WithdrawEvent(t *testing.T) { require.NoError(t, err) btcMainNetWithdrawalEvent.To = []byte("04b2891ba8cb491828db3ebc8a780d43b169e7b3974114e6e50f9bab6ec" + "63c2f20f6d31b2025377d05c2a704d3bd799d0d56f3a8543d79a01ab6084a1cb204f260") - err = k.ValidateZrc20WithdrawEvent(ctx, btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) + err = k.ValidateZRC20WithdrawEvent(ctx, btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) require.ErrorContains(t, err, "unsupported address") }) @@ -213,7 +213,7 @@ func TestValidateZrc20WithdrawEvent(t *testing.T) { value := big.NewInt(constant.SolanaWalletRentExempt) solWithdrawalEvent := sample.ZRC20Withdrawal(to, value) - err := k.ValidateZrc20WithdrawEvent(ctx, solWithdrawalEvent, chains.SolanaMainnet.ChainId) + err := k.ValidateZRC20WithdrawEvent(ctx, solWithdrawalEvent, chains.SolanaMainnet.ChainId) require.ErrorContains(t, err, "invalid address") }) @@ -227,12 +227,12 @@ func TestValidateZrc20WithdrawEvent(t *testing.T) { solWithdrawalEvent := sample.ZRC20Withdrawal(to, value) // 1000000 lamports can be withdrawn - err := k.ValidateZrc20WithdrawEvent(ctx, solWithdrawalEvent, chainID) + err := k.ValidateZRC20WithdrawEvent(ctx, solWithdrawalEvent, chainID) require.NoError(t, err) // 999999 lamports cannot be withdrawn solWithdrawalEvent.Value = big.NewInt(constant.SolanaWalletRentExempt - 1) - err = k.ValidateZrc20WithdrawEvent(ctx, solWithdrawalEvent, chainID) + err = k.ValidateZRC20WithdrawEvent(ctx, solWithdrawalEvent, chainID) require.ErrorContains(t, err, "less than rent exempt") }) }