From afac1d1c50a4dd6b2aafdc16cd442c61435f24fc Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Thu, 17 Oct 2024 18:51:40 +0200 Subject: [PATCH 01/20] feat: distribute ZRC20 rewards function --- changelog.md | 1 + precompiles/bank/bank.go | 34 - precompiles/bank/bank_test.go | 30 - precompiles/bank/logs.go | 4 +- precompiles/bank/method_balance_of.go | 4 +- precompiles/bank/method_deposit.go | 6 +- precompiles/bank/method_withdraw.go | 8 +- precompiles/precompiles.go | 2 +- precompiles/staking/IStaking.sol | 33 +- precompiles/staking/const.go | 18 + precompiles/staking/logs.go | 36 +- precompiles/staking/method_distribute.go | 100 + .../staking/method_get_all_validators.go | 25 + precompiles/staking/method_get_shares.go | 50 + precompiles/staking/method_move_stake.go | 85 + precompiles/staking/method_stake.go | 84 + precompiles/staking/method_unstake.go | 77 + precompiles/staking/staking.go | 352 +- precompiles/staking/staking_test.go | 2946 ++++++++--------- precompiles/types/address.go | 42 + precompiles/types/address_test.go | 45 + precompiles/{bank => types}/coin.go | 12 +- precompiles/{bank => types}/coin_test.go | 4 +- 23 files changed, 2128 insertions(+), 1870 deletions(-) create mode 100644 precompiles/staking/const.go create mode 100644 precompiles/staking/method_distribute.go create mode 100644 precompiles/staking/method_get_all_validators.go create mode 100644 precompiles/staking/method_get_shares.go create mode 100644 precompiles/staking/method_move_stake.go create mode 100644 precompiles/staking/method_stake.go create mode 100644 precompiles/staking/method_unstake.go create mode 100644 precompiles/types/address.go create mode 100644 precompiles/types/address_test.go rename precompiles/{bank => types}/coin.go (83%) rename precompiles/{bank => types}/coin_test.go (93%) diff --git a/changelog.md b/changelog.md index 2dd8e75f9a..9f046c2939 100644 --- a/changelog.md +++ b/changelog.md @@ -20,6 +20,7 @@ * [2896](https://github.com/zeta-chain/node/pull/2896) - add TON inbound observation * [2987](https://github.com/zeta-chain/node/pull/2987) - add non-EVM standard inbound memo package * [2979](https://github.com/zeta-chain/node/pull/2979) - add fungible keeper ability to lock/unlock ZRC20 tokens +* [3019](https://github.com/zeta-chain/node/pull/3019) - add ditribute functions to staking precompile ### Refactor diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index 51a559edd3..9dbb23ef5b 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -180,37 +180,3 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt } } } - -// getEVMCallerAddress returns the caller address. -// Usually the caller is the contract.CallerAddress, which is the address of the contract that called the precompiled contract. -// If contract.CallerAddress != evm.Origin is true, it means the call was made through a contract, -// on which case there is a need to set the caller to the evm.Origin. -func getEVMCallerAddress(evm *vm.EVM, contract *vm.Contract) (common.Address, error) { - caller := contract.CallerAddress - if contract.CallerAddress != evm.Origin { - caller = evm.Origin - } - - return caller, nil -} - -// getCosmosAddress returns the counterpart cosmos address of the given ethereum address. -// It checks if the address is empty or blocked by the bank keeper. -func getCosmosAddress(bankKeeper bank.Keeper, addr common.Address) (sdk.AccAddress, error) { - toAddr := sdk.AccAddress(addr.Bytes()) - if toAddr.Empty() { - return nil, &ptypes.ErrInvalidAddr{ - Got: toAddr.String(), - Reason: "empty address", - } - } - - if bankKeeper.BlockedAddr(toAddr) { - return nil, &ptypes.ErrInvalidAddr{ - Got: toAddr.String(), - Reason: "destination address blocked by bank keeper", - } - } - - return toAddr, nil -} diff --git a/precompiles/bank/bank_test.go b/precompiles/bank/bank_test.go index 518c330a7f..a42bdeb88a 100644 --- a/precompiles/bank/bank_test.go +++ b/precompiles/bank/bank_test.go @@ -2,16 +2,12 @@ package bank import ( "encoding/json" - "math/big" "testing" storetypes "github.com/cosmos/cosmos-sdk/store/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" "github.com/stretchr/testify/require" ethermint "github.com/zeta-chain/ethermint/types" "github.com/zeta-chain/node/testutil/keeper" - "github.com/zeta-chain/node/testutil/sample" ) func Test_IBankContract(t *testing.T) { @@ -128,29 +124,3 @@ func Test_InvalidABI(t *testing.T) { initABI() } - -func Test_getEVMCallerAddress(t *testing.T) { - mockEVM := vm.EVM{ - TxContext: vm.TxContext{ - Origin: common.Address{}, - }, - } - - mockVMContract := vm.NewContract( - contractRef{address: common.Address{}}, - contractRef{address: ContractAddress}, - big.NewInt(0), - 0, - ) - - // When contract.CallerAddress == evm.Origin, caller is set to contract.CallerAddress. - caller, err := getEVMCallerAddress(&mockEVM, mockVMContract) - require.NoError(t, err) - require.Equal(t, common.Address{}, caller, "address shouldn be the same") - - // When contract.CallerAddress != evm.Origin, caller should be set to evm.Origin. - mockEVM.Origin = sample.EthAddress() - caller, err = getEVMCallerAddress(&mockEVM, mockVMContract) - require.NoError(t, err) - require.Equal(t, mockEVM.Origin, caller, "address should be evm.Origin") -} diff --git a/precompiles/bank/logs.go b/precompiles/bank/logs.go index 36b877dfa5..9043cc63f9 100644 --- a/precompiles/bank/logs.go +++ b/precompiles/bank/logs.go @@ -28,8 +28,8 @@ func (c *Contract) addEventLog( topics, err := logs.MakeTopics( event, - []interface{}{common.BytesToAddress(eventData.zrc20Addr.Bytes())}, - []interface{}{common.BytesToAddress(eventData.zrc20Token.Bytes())}, + []interface{}{eventData.zrc20Addr}, + []interface{}{eventData.zrc20Token}, []interface{}{eventData.cosmosCoin}, ) if err != nil { diff --git a/precompiles/bank/method_balance_of.go b/precompiles/bank/method_balance_of.go index e4bc644a2b..d5d08320f9 100644 --- a/precompiles/bank/method_balance_of.go +++ b/precompiles/bank/method_balance_of.go @@ -32,7 +32,7 @@ func (c *Contract) balanceOf( } // Get the counterpart cosmos address. - toAddr, err := getCosmosAddress(c.bankKeeper, addr) + toAddr, err := ptypes.GetCosmosAddress(c.bankKeeper, addr) if err != nil { return nil, err } @@ -49,7 +49,7 @@ func (c *Contract) balanceOf( // Bank Keeper GetBalance returns the specified Cosmos coin balance for a given address. // Check explicitly the balance is a non-negative non-nil value. - coin := c.bankKeeper.GetBalance(ctx, toAddr, ZRC20ToCosmosDenom(zrc20Addr)) + coin := c.bankKeeper.GetBalance(ctx, toAddr, ptypes.ZRC20ToCosmosDenom(zrc20Addr)) if !coin.IsValid() { return nil, &ptypes.ErrInvalidCoin{ Got: coin.GetDenom(), diff --git a/precompiles/bank/method_deposit.go b/precompiles/bank/method_deposit.go index 1d5792d8a3..b4af95ab4a 100644 --- a/precompiles/bank/method_deposit.go +++ b/precompiles/bank/method_deposit.go @@ -48,13 +48,13 @@ func (c *Contract) deposit( } // Get the correct caller address. - caller, err := getEVMCallerAddress(evm, contract) + caller, err := ptypes.GetEVMCallerAddress(evm, contract) if err != nil { return nil, err } // Get the cosmos address of the caller. - toAddr, err := getCosmosAddress(c.bankKeeper, caller) + toAddr, err := ptypes.GetCosmosAddress(c.bankKeeper, caller) if err != nil { return nil, err } @@ -94,7 +94,7 @@ func (c *Contract) deposit( // this way we map ZRC20 addresses to cosmos denoms "zevm/0x12345". // - Mint coins to the fungible module. // - Send coins from fungible to the caller. - coinSet, err := createCoinSet(ZRC20ToCosmosDenom(zrc20Addr), amount) + coinSet, err := ptypes.CreateCoinSet(ptypes.ZRC20ToCosmosDenom(zrc20Addr), amount) if err != nil { return nil, err } diff --git a/precompiles/bank/method_withdraw.go b/precompiles/bank/method_withdraw.go index e071ed04cd..5a5411503c 100644 --- a/precompiles/bank/method_withdraw.go +++ b/precompiles/bank/method_withdraw.go @@ -43,14 +43,14 @@ func (c *Contract) withdraw( } // Get the correct caller address. - caller, err := getEVMCallerAddress(evm, contract) + caller, err := ptypes.GetEVMCallerAddress(evm, contract) if err != nil { return nil, err } // Get the cosmos address of the caller. // This address should have enough cosmos coin balance as the requested amount. - fromAddr, err := getCosmosAddress(c.bankKeeper, caller) + fromAddr, err := ptypes.GetCosmosAddress(c.bankKeeper, caller) if err != nil { return nil, err } @@ -64,7 +64,7 @@ func (c *Contract) withdraw( } // Caller has to have enough cosmos coin balance to withdraw the requested amount. - coin := c.bankKeeper.GetBalance(ctx, fromAddr, ZRC20ToCosmosDenom(zrc20Addr)) + coin := c.bankKeeper.GetBalance(ctx, fromAddr, ptypes.ZRC20ToCosmosDenom(zrc20Addr)) if !coin.IsValid() { return nil, &ptypes.ErrInsufficientBalance{ Requested: amount.String(), @@ -79,7 +79,7 @@ func (c *Contract) withdraw( } } - coinSet, err := createCoinSet(ZRC20ToCosmosDenom(zrc20Addr), amount) + coinSet, err := ptypes.CreateCoinSet(ptypes.ZRC20ToCosmosDenom(zrc20Addr), amount) if err != nil { return nil, err } diff --git a/precompiles/precompiles.go b/precompiles/precompiles.go index cdd5e2ff74..93e4cdb519 100644 --- a/precompiles/precompiles.go +++ b/precompiles/precompiles.go @@ -50,7 +50,7 @@ func StatefulContracts( // Define the staking contract function. if EnabledStatefulContracts[staking.ContractAddress] { stakingContract := func(_ sdktypes.Context, _ ethparams.Rules) vm.PrecompiledContract { - return staking.NewIStakingContract(stakingKeeper, cdc, gasConfig) + return staking.NewIStakingContract(stakingKeeper, *fungibleKeeper, bankKeeper, cdc, gasConfig) } // Append the staking contract to the precompiledContracts slice. diff --git a/precompiles/staking/IStaking.sol b/precompiles/staking/IStaking.sol index c6b2642a4c..dece711d71 100644 --- a/precompiles/staking/IStaking.sol +++ b/precompiles/staking/IStaking.sol @@ -5,9 +5,7 @@ pragma solidity ^0.8.26; address constant ISTAKING_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000066; // 102 /// @dev The IStaking contract's instance. -IStaking constant ISTAKING_CONTRACT = IStaking( - ISTAKING_PRECOMPILE_ADDRESS -); +IStaking constant ISTAKING_CONTRACT = IStaking(ISTAKING_PRECOMPILE_ADDRESS); /// @notice Bond status for validator enum BondStatus { @@ -58,6 +56,16 @@ interface IStaking { uint256 amount ); + /// @notice Distributed event is emitted when distribute function is called successfully. + /// @param zrc20_distributor Distributor address. + /// @param zrc20_token ZRC20 token address. + /// @param amount Distributed amount. + event Distributed( + address indexed zrc20_distributor, + address indexed zrc20_token, + uint256 amount + ); + /// @notice Stake coins to validator /// @param staker Staker address /// @param validator Validator address @@ -95,9 +103,24 @@ interface IStaking { /// @notice Get all validators /// @return validators All validators - function getAllValidators() external view returns (Validator[] calldata validators); + function getAllValidators() + external + view + returns (Validator[] calldata validators); /// @notice Get shares for staker in validator /// @return shares Staker shares in validator - function getShares(address staker, string memory validator) external view returns (uint256 shares); + function getShares( + address staker, + string memory validator + ) external view returns (uint256 shares); + + /// @notice Distribute a ZRC20 token as staking rewards. + /// @param zrc20 The ZRC20 token address to be distributed. + /// @param amount The amount of ZRC20 tokens to distribute. + /// @return success Boolean indicating whether the distribution was successful. + function distribute( + address zrc20, + uint256 amount + ) external returns (bool success); } diff --git a/precompiles/staking/const.go b/precompiles/staking/const.go new file mode 100644 index 0000000000..e68c58057e --- /dev/null +++ b/precompiles/staking/const.go @@ -0,0 +1,18 @@ +package staking + +const ( + DistributeMethodName = "distribute" + DistributeMethodGas = 10000 + + GetAllValidatorsMethodName = "getAllValidators" + GetSharesMethodName = "getShares" + + MoveStakeMethodName = "moveStake" + MoveStakeMethodGas = 10000 + + StakeMethodName = "stake" + StakeMethodGas = 10000 + + UnstakeMethodName = "unstake" + UnstakeMethodGas = 1000 +) diff --git a/precompiles/staking/logs.go b/precompiles/staking/logs.go index ea8e51274c..bfd4a0e055 100644 --- a/precompiles/staking/logs.go +++ b/precompiles/staking/logs.go @@ -16,7 +16,7 @@ const ( MoveStakeEventName = "MoveStake" ) -func (c *Contract) AddStakeLog( +func (c *Contract) addStakeLog( ctx sdk.Context, stateDB vm.StateDB, staker common.Address, @@ -49,7 +49,7 @@ func (c *Contract) AddStakeLog( return nil } -func (c *Contract) AddUnstakeLog( +func (c *Contract) addUnstakeLog( ctx sdk.Context, stateDB vm.StateDB, staker common.Address, @@ -81,7 +81,7 @@ func (c *Contract) AddUnstakeLog( return nil } -func (c *Contract) AddMoveStakeLog( +func (c *Contract) addMoveStakeLog( ctx sdk.Context, stateDB vm.StateDB, staker common.Address, @@ -123,3 +123,33 @@ func (c *Contract) AddMoveStakeLog( return nil } + +func (c *Contract) addDistributeLog( + ctx sdk.Context, + stateDB vm.StateDB, + distributor common.Address, + zrc20Token common.Address, + amount *big.Int, +) error { + event := c.Abi().Events[MoveStakeEventName] + + topics, err := logs.MakeTopics( + event, + []interface{}{distributor}, + []interface{}{zrc20Token}, + ) + if err != nil { + return err + } + + data, err := logs.PackArguments([]logs.Argument{ + {Type: "uint256", Value: amount}, + }) + if err != nil { + return err + } + + logs.AddLog(ctx, c.Address(), stateDB, topics, data) + + return nil +} diff --git a/precompiles/staking/method_distribute.go b/precompiles/staking/method_distribute.go new file mode 100644 index 0000000000..b60467936a --- /dev/null +++ b/precompiles/staking/method_distribute.go @@ -0,0 +1,100 @@ +package staking + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + ptypes "github.com/zeta-chain/node/precompiles/types" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +// function distribute(address zrc20, uint256 amount) external returns (bool success) +func (c *Contract) distribute( + ctx sdk.Context, + evm *vm.EVM, + contract *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 2 { + return nil, &(ptypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 2, + }) + } + + // Unpack arguments and check if they are valid. + zrc20Addr, amount, err := unpackDistributeArgs(args) + if err != nil { + return nil, err + } + + // Get the original caller address. Necessary for LockZRC20 to work. + caller, err := ptypes.GetEVMCallerAddress(evm, contract) + if err != nil { + return nil, err + } + + // Create the coinSet in advance, if this step fails do not lock ZRC20. + coinSet, err := ptypes.CreateCoinSet(ptypes.ZRC20ToCosmosDenom(zrc20Addr), amount) + if err != nil { + return nil, err + } + + // LockZRC20 locks the ZRC20 under the locker address. + // It performs all the necessary checks such as allowance in order to execute a transferFrom. + if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, c.Address(), caller, c.Address(), amount); err != nil { + return nil, &ptypes.ErrUnexpected{ + When: "LockZRC20InBank", + Got: err.Error(), + } + } + + // With the ZRC20 locked, proceed to mint the cosmos coins. + if err := c.bankKeeper.MintCoins(ctx, fungibletypes.ModuleName, coinSet); err != nil { + return nil, &ptypes.ErrUnexpected{ + When: "MintCoins", + Got: err.Error(), + } + } + + // Send the coins to the FeePool. + if err := c.bankKeeper.SendCoinsFromModuleToModule(ctx, fungibletypes.ModuleName, authtypes.FeeCollectorName, coinSet); err != nil { + return nil, &ptypes.ErrUnexpected{ + When: "SendCoinsFromModuleToModule", + Got: err.Error(), + } + } + + if err := c.addDistributeLog(ctx, evm.StateDB, caller, zrc20Addr, amount); err != nil { + return nil, &ptypes.ErrUnexpected{ + When: "AddDistributeLog", + Got: err.Error(), + } + } + + return method.Outputs.Pack(true) +} + +func unpackDistributeArgs(args []interface{}) (zrc20Addr common.Address, amount *big.Int, err error) { + zrc20Addr, ok := args[0].(common.Address) + if !ok { + return common.Address{}, nil, &ptypes.ErrInvalidAddr{ + Got: zrc20Addr.String(), + } + } + + amount, ok = args[1].(*big.Int) + if !ok || amount == nil || amount.Sign() <= 0 { + return common.Address{}, nil, &ptypes.ErrInvalidAmount{ + Got: amount.String(), + } + } + + return zrc20Addr, amount, nil +} diff --git a/precompiles/staking/method_get_all_validators.go b/precompiles/staking/method_get_all_validators.go new file mode 100644 index 0000000000..61732b867b --- /dev/null +++ b/precompiles/staking/method_get_all_validators.go @@ -0,0 +1,25 @@ +package staking + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" +) + +func (c *Contract) GetAllValidators( + ctx sdk.Context, + method *abi.Method, +) ([]byte, error) { + validators := c.stakingKeeper.GetAllValidators(ctx) + + validatorsRes := make([]Validator, len(validators)) + for i, v := range validators { + validatorsRes[i] = Validator{ + OperatorAddress: v.OperatorAddress, + ConsensusPubKey: v.ConsensusPubkey.String(), + BondStatus: uint8(v.Status), + Jailed: v.Jailed, + } + } + + return method.Outputs.Pack(validatorsRes) +} diff --git a/precompiles/staking/method_get_shares.go b/precompiles/staking/method_get_shares.go new file mode 100644 index 0000000000..5af1a6da36 --- /dev/null +++ b/precompiles/staking/method_get_shares.go @@ -0,0 +1,50 @@ +package staking + +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" +) + +func (c *Contract) GetShares( + ctx sdk.Context, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 2 { + return nil, &(ptypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 2, + }) + } + stakerAddress, ok := args[0].(common.Address) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[0], + } + } + + validatorAddress, ok := args[1].(string) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[1], + } + } + + validator, err := sdk.ValAddressFromBech32(validatorAddress) + if err != nil { + return nil, err + } + + delegation := c.stakingKeeper.Delegation(ctx, sdk.AccAddress(stakerAddress.Bytes()), validator) + shares := big.NewInt(0) + if delegation != nil { + shares = delegation.GetShares().BigInt() + } + + return method.Outputs.Pack(shares) +} diff --git a/precompiles/staking/method_move_stake.go b/precompiles/staking/method_move_stake.go new file mode 100644 index 0000000000..5a0d8d74cb --- /dev/null +++ b/precompiles/staking/method_move_stake.go @@ -0,0 +1,85 @@ +package staking + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + ptypes "github.com/zeta-chain/node/precompiles/types" +) + +func (c *Contract) MoveStake( + ctx sdk.Context, + evm *vm.EVM, + contract *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 4 { + return nil, &(ptypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 4, + }) + } + + stakerAddress, ok := args[0].(common.Address) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[0], + } + } + + if contract.CallerAddress != stakerAddress { + return nil, fmt.Errorf("caller is not staker address") + } + + validatorSrcAddress, ok := args[1].(string) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[1], + } + } + + validatorDstAddress, ok := args[2].(string) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[2], + } + } + + amount, ok := args[3].(*big.Int) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[3], + } + } + + msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) + res, err := msgServer.BeginRedelegate(ctx, &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), + ValidatorSrcAddress: validatorSrcAddress, + ValidatorDstAddress: validatorDstAddress, + Amount: sdk.Coin{ + Denom: c.stakingKeeper.BondDenom(ctx), + Amount: math.NewIntFromBigInt(amount), + }, + }) + if err != nil { + return nil, err + } + + stateDB := evm.StateDB.(ptypes.ExtStateDB) + err = c.addMoveStakeLog(ctx, stateDB, stakerAddress, validatorSrcAddress, validatorDstAddress, amount) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(res.GetCompletionTime().UTC().Unix()) +} diff --git a/precompiles/staking/method_stake.go b/precompiles/staking/method_stake.go new file mode 100644 index 0000000000..857018c165 --- /dev/null +++ b/precompiles/staking/method_stake.go @@ -0,0 +1,84 @@ +package staking + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + ptypes "github.com/zeta-chain/node/precompiles/types" +) + +func (c *Contract) Stake( + ctx sdk.Context, + evm *vm.EVM, + contract *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 3 { + return nil, &(ptypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 3, + }) + } + + stakerAddress, ok := args[0].(common.Address) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[0], + } + } + + if contract.CallerAddress != stakerAddress { + return nil, fmt.Errorf("caller is not staker address") + } + + validatorAddress, ok := args[1].(string) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[1], + } + } + + amount, ok := args[2].(*big.Int) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[2], + } + } + + msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) + _, err := msgServer.Delegate(ctx, &stakingtypes.MsgDelegate{ + DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), + ValidatorAddress: validatorAddress, + Amount: sdk.Coin{ + Denom: c.stakingKeeper.BondDenom(ctx), + Amount: math.NewIntFromBigInt(amount), + }, + }) + if err != nil { + return nil, err + } + + // if caller is not the same as origin it means call is coming through smart contract, + // and because state of smart contract calling precompile might be updated as well + // manually reduce amount in stateDB, so it is properly reflected in bank module + stateDB := evm.StateDB.(ptypes.ExtStateDB) + if contract.CallerAddress != evm.Origin { + stateDB.SubBalance(stakerAddress, amount) + } + + err = c.addStakeLog(ctx, stateDB, stakerAddress, validatorAddress, amount) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} diff --git a/precompiles/staking/method_unstake.go b/precompiles/staking/method_unstake.go new file mode 100644 index 0000000000..e0be246e05 --- /dev/null +++ b/precompiles/staking/method_unstake.go @@ -0,0 +1,77 @@ +package staking + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + ptypes "github.com/zeta-chain/node/precompiles/types" +) + +func (c *Contract) Unstake( + ctx sdk.Context, + evm *vm.EVM, + contract *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 3 { + return nil, &(ptypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 3, + }) + } + + stakerAddress, ok := args[0].(common.Address) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[0], + } + } + + if contract.CallerAddress != stakerAddress { + return nil, fmt.Errorf("caller is not staker address") + } + + validatorAddress, ok := args[1].(string) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[1], + } + } + + amount, ok := args[2].(*big.Int) + if !ok { + return nil, ptypes.ErrInvalidArgument{ + Got: args[2], + } + } + + msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) + res, err := msgServer.Undelegate(ctx, &stakingtypes.MsgUndelegate{ + DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), + ValidatorAddress: validatorAddress, + Amount: sdk.Coin{ + Denom: c.stakingKeeper.BondDenom(ctx), + Amount: math.NewIntFromBigInt(amount), + }, + }) + if err != nil { + return nil, err + } + + stateDB := evm.StateDB.(ptypes.ExtStateDB) + err = c.addUnstakeLog(ctx, stateDB, stakerAddress, validatorAddress, amount) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(res.GetCompletionTime().UTC().Unix()) +} diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index fea0d0f9f2..b5b4164e5f 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -1,32 +1,18 @@ package staking import ( - "fmt" - "math/big" - - "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/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/protocol-contracts/v2/pkg/zrc20.sol" ptypes "github.com/zeta-chain/node/precompiles/types" -) - -// method names -const ( - // write - StakeMethodName = "stake" - UnstakeMethodName = "unstake" - MoveStakeMethodName = "moveStake" - - // read - GetAllValidatorsMethodName = "getAllValidators" - GetSharesMethodName = "getShares" + fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" ) var ( @@ -54,11 +40,13 @@ func initABI() { // just temporary flat values, double check these flat values // can we just use WriteCostFlat/ReadCostFlat from gas config for flat values? case StakeMethodName: - GasRequiredByMethod[methodID] = 10000 + GasRequiredByMethod[methodID] = StakeMethodGas case UnstakeMethodName: - GasRequiredByMethod[methodID] = 10000 + GasRequiredByMethod[methodID] = UnstakeMethodGas case MoveStakeMethodName: - GasRequiredByMethod[methodID] = 10000 + GasRequiredByMethod[methodID] = MoveStakeMethodGas + case DistributeMethodName: + GasRequiredByMethod[methodID] = DistributeMethodGas case GetAllValidatorsMethodName: GasRequiredByMethod[methodID] = 0 ViewMethod[methodID] = true @@ -74,21 +62,36 @@ func initABI() { type Contract struct { ptypes.BaseContract - stakingKeeper stakingkeeper.Keeper - cdc codec.Codec - kvGasConfig storetypes.GasConfig + stakingKeeper stakingkeeper.Keeper + fungibleKeeper fungiblekeeper.Keeper + bankKeeper bankkeeper.Keeper + zrc20ABI *abi.ABI + cdc codec.Codec + kvGasConfig storetypes.GasConfig } func NewIStakingContract( stakingKeeper *stakingkeeper.Keeper, + fungibleKeeper fungiblekeeper.Keeper, + bankKeeper bankkeeper.Keeper, cdc codec.Codec, kvGasConfig storetypes.GasConfig, ) *Contract { + // Instantiate the ZRC20 ABI only one time. + // This avoids instantiating it every time deposit or withdraw are called. + zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() + if err != nil { + return nil + } + return &Contract{ - BaseContract: ptypes.NewBaseContract(ContractAddress), - stakingKeeper: *stakingKeeper, - cdc: cdc, - kvGasConfig: kvGasConfig, + BaseContract: ptypes.NewBaseContract(ContractAddress), + stakingKeeper: *stakingKeeper, + fungibleKeeper: fungibleKeeper, + bankKeeper: bankKeeper, + zrc20ABI: zrc20ABI, + cdc: cdc, + kvGasConfig: kvGasConfig, } } @@ -122,262 +125,6 @@ func (c *Contract) RequiredGas(input []byte) uint64 { return 0 } -func (c *Contract) GetAllValidators( - ctx sdk.Context, - method *abi.Method, -) ([]byte, error) { - validators := c.stakingKeeper.GetAllValidators(ctx) - - validatorsRes := make([]Validator, len(validators)) - for i, v := range validators { - validatorsRes[i] = Validator{ - OperatorAddress: v.OperatorAddress, - ConsensusPubKey: v.ConsensusPubkey.String(), - BondStatus: uint8(v.Status), - Jailed: v.Jailed, - } - } - - return method.Outputs.Pack(validatorsRes) -} - -func (c *Contract) GetShares( - ctx sdk.Context, - method *abi.Method, - args []interface{}, -) ([]byte, error) { - if len(args) != 2 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ - Got: len(args), - Expect: 2, - }) - } - stakerAddress, ok := args[0].(common.Address) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[0], - } - } - - validatorAddress, ok := args[1].(string) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[1], - } - } - - validator, err := sdk.ValAddressFromBech32(validatorAddress) - if err != nil { - return nil, err - } - - delegation := c.stakingKeeper.Delegation(ctx, sdk.AccAddress(stakerAddress.Bytes()), validator) - shares := big.NewInt(0) - if delegation != nil { - shares = delegation.GetShares().BigInt() - } - - return method.Outputs.Pack(shares) -} - -func (c *Contract) Stake( - ctx sdk.Context, - evm *vm.EVM, - contract *vm.Contract, - method *abi.Method, - args []interface{}, -) ([]byte, error) { - if len(args) != 3 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ - Got: len(args), - Expect: 3, - }) - } - - stakerAddress, ok := args[0].(common.Address) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[0], - } - } - - if contract.CallerAddress != stakerAddress { - return nil, fmt.Errorf("caller is not staker address") - } - - validatorAddress, ok := args[1].(string) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[1], - } - } - - amount, ok := args[2].(*big.Int) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[2], - } - } - - msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) - _, err := msgServer.Delegate(ctx, &stakingtypes.MsgDelegate{ - DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), - ValidatorAddress: validatorAddress, - Amount: sdk.Coin{ - Denom: c.stakingKeeper.BondDenom(ctx), - Amount: math.NewIntFromBigInt(amount), - }, - }) - if err != nil { - return nil, err - } - - // if caller is not the same as origin it means call is coming through smart contract, - // and because state of smart contract calling precompile might be updated as well - // manually reduce amount in stateDB, so it is properly reflected in bank module - stateDB := evm.StateDB.(ptypes.ExtStateDB) - if contract.CallerAddress != evm.Origin { - stateDB.SubBalance(stakerAddress, amount) - } - - err = c.AddStakeLog(ctx, stateDB, stakerAddress, validatorAddress, amount) - if err != nil { - return nil, err - } - - return method.Outputs.Pack(true) -} - -func (c *Contract) Unstake( - ctx sdk.Context, - evm *vm.EVM, - contract *vm.Contract, - method *abi.Method, - args []interface{}, -) ([]byte, error) { - if len(args) != 3 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ - Got: len(args), - Expect: 3, - }) - } - - stakerAddress, ok := args[0].(common.Address) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[0], - } - } - - if contract.CallerAddress != stakerAddress { - return nil, fmt.Errorf("caller is not staker address") - } - - validatorAddress, ok := args[1].(string) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[1], - } - } - - amount, ok := args[2].(*big.Int) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[2], - } - } - - msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) - res, err := msgServer.Undelegate(ctx, &stakingtypes.MsgUndelegate{ - DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), - ValidatorAddress: validatorAddress, - Amount: sdk.Coin{ - Denom: c.stakingKeeper.BondDenom(ctx), - Amount: math.NewIntFromBigInt(amount), - }, - }) - if err != nil { - return nil, err - } - - stateDB := evm.StateDB.(ptypes.ExtStateDB) - err = c.AddUnstakeLog(ctx, stateDB, stakerAddress, validatorAddress, amount) - if err != nil { - return nil, err - } - - return method.Outputs.Pack(res.GetCompletionTime().UTC().Unix()) -} - -func (c *Contract) MoveStake( - ctx sdk.Context, - evm *vm.EVM, - contract *vm.Contract, - method *abi.Method, - args []interface{}, -) ([]byte, error) { - if len(args) != 4 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ - Got: len(args), - Expect: 4, - }) - } - - stakerAddress, ok := args[0].(common.Address) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[0], - } - } - - if contract.CallerAddress != stakerAddress { - return nil, fmt.Errorf("caller is not staker address") - } - - validatorSrcAddress, ok := args[1].(string) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[1], - } - } - - validatorDstAddress, ok := args[2].(string) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[2], - } - } - - amount, ok := args[3].(*big.Int) - if !ok { - return nil, ptypes.ErrInvalidArgument{ - Got: args[3], - } - } - - msgServer := stakingkeeper.NewMsgServerImpl(&c.stakingKeeper) - res, err := msgServer.BeginRedelegate(ctx, &stakingtypes.MsgBeginRedelegate{ - DelegatorAddress: sdk.AccAddress(stakerAddress.Bytes()).String(), - ValidatorSrcAddress: validatorSrcAddress, - ValidatorDstAddress: validatorDstAddress, - Amount: sdk.Coin{ - Denom: c.stakingKeeper.BondDenom(ctx), - Amount: math.NewIntFromBigInt(amount), - }, - }) - if err != nil { - return nil, err - } - - stateDB := evm.StateDB.(ptypes.ExtStateDB) - err = c.AddMoveStakeLog(ctx, stateDB, stakerAddress, validatorSrcAddress, validatorDstAddress, amount) - if err != nil { - return nil, err - } - - return method.Outputs.Pack(res.GetCompletionTime().UTC().Unix()) -} - // 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) { @@ -393,6 +140,13 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt stateDB := evm.StateDB.(ptypes.ExtStateDB) + // If the method is not a view method, it should not be executed in read-only mode. + if _, isViewMethod := ViewMethod[[4]byte(method.ID)]; !isViewMethod && readOnly { + return nil, ptypes.ErrWriteMethod{ + Method: method.Name, + } + } + switch method.Name { case GetAllValidatorsMethodName: var res []byte @@ -420,13 +174,6 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt Method: method.Name, } - //nolint:govet - if readOnly { - return nil, ptypes.ErrWriteMethod{ - Method: method.Name, - } - } - var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { res, err = c.Stake(ctx, evm, contract, method, args) @@ -442,13 +189,6 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt Method: method.Name, } - //nolint:govet - if readOnly { - return nil, ptypes.ErrWriteMethod{ - Method: method.Name, - } - } - var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { res, err = c.Unstake(ctx, evm, contract, method, args) @@ -464,13 +204,6 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt Method: method.Name, } - //nolint:govet - if readOnly { - return nil, ptypes.ErrWriteMethod{ - Method: method.Name, - } - } - var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { res, err = c.MoveStake(ctx, evm, contract, method, args) @@ -480,7 +213,16 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return nil, err } return res, nil - + case DistributeMethodName: + var res []byte + execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { + res, err = c.distribute(ctx, evm, contract, method, args) + return err + }) + if execErr != nil { + return nil, err + } + return res, nil default: return nil, ptypes.ErrInvalidMethod{ Method: method.Name, diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index aaea87f94d..1e292b1a35 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -1,1475 +1,1475 @@ package staking -import ( - "encoding/json" - "fmt" - "testing" - - "math/big" - "math/rand" - - tmdb "github.com/cometbft/cometbft-db" - "github.com/cosmos/cosmos-sdk/store" - - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/params" - "github.com/stretchr/testify/require" - ethermint "github.com/zeta-chain/ethermint/types" - "github.com/zeta-chain/ethermint/x/evm/statedb" - "github.com/zeta-chain/node/cmd/zetacored/config" - "github.com/zeta-chain/node/precompiles/prototype" - ptypes "github.com/zeta-chain/node/precompiles/types" - "github.com/zeta-chain/node/testutil/keeper" - - "github.com/zeta-chain/node/testutil/sample" - fungibletypes "github.com/zeta-chain/node/x/fungible/types" -) - -func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *vm.EVM, *vm.Contract) { - var encoding ethermint.EncodingConfig - appCodec := encoding.Codec - - cdc := keeper.NewCodec() - - db := tmdb.NewMemDB() - stateStore := store.NewCommitMultiStore(db) - keys, memKeys, tkeys, allKeys := keeper.StoreKeys() - sdkKeepers := keeper.NewSDKKeepersWithKeys(cdc, keys, memKeys, tkeys, allKeys) - for _, key := range keys { - stateStore.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) - } - for _, key := range tkeys { - stateStore.MountStoreWithDB(key, storetypes.StoreTypeTransient, nil) - } - for _, key := range memKeys { - stateStore.MountStoreWithDB(key, storetypes.StoreTypeMemory, nil) - } - - gasConfig := storetypes.TransientGasConfig() - ctx := keeper.NewContext(stateStore) - - require.NoError(t, stateStore.LoadLatestVersion()) - - stakingGenesisState := stakingtypes.DefaultGenesisState() - stakingGenesisState.Params.BondDenom = config.BaseDenom - sdkKeepers.StakingKeeper.InitGenesis(ctx, stakingGenesisState) - - contract := NewIStakingContract(&sdkKeepers.StakingKeeper, appCodec, gasConfig) - require.NotNil(t, contract, "NewIStakingContract() should not return a nil contract") - - abi := contract.Abi() - require.NotNil(t, abi, "contract ABI should not be nil") - - address := contract.Address() - require.NotNil(t, address, "contract address should not be nil") - - mockEVM := vm.NewEVM( - vm.BlockContext{}, - vm.TxContext{}, - statedb.New(ctx, sdkKeepers.EvmKeeper, statedb.TxConfig{}), - ¶ms.ChainConfig{}, - vm.Config{}, - ) - mockVMContract := vm.NewContract( - contractRef{address: common.Address{}}, - contractRef{address: ContractAddress}, - big.NewInt(0), - 0, - ) - return ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract -} - -func packInputArgs(t *testing.T, methodID abi.Method, args ...interface{}) []byte { - input, err := methodID.Inputs.Pack(args...) - require.NoError(t, err) - return append(methodID.ID, input...) -} - -type contractRef struct { - address common.Address -} - -func (c contractRef) Address() common.Address { - return c.address -} - -func Test_IStakingContract(t *testing.T) { - _, contract, abi, _, _, _ := setup(t) - gasConfig := storetypes.TransientGasConfig() - - t.Run("should check methods are present in ABI", func(t *testing.T) { - require.NotNil(t, abi.Methods[StakeMethodName], "stake method should be present in the ABI") - require.NotNil(t, abi.Methods[UnstakeMethodName], "unstake method should be present in the ABI") - require.NotNil( - t, - abi.Methods[MoveStakeMethodName], - "moveStake method should be present in the ABI", - ) - - require.NotNil( - t, - abi.Methods[GetAllValidatorsMethodName], - "getAllValidators method should be present in the ABI", - ) - require.NotNil(t, abi.Methods[GetSharesMethodName], "getShares method should be present in the ABI") - }) - - t.Run("should check gas requirements for methods", func(t *testing.T) { - var method [4]byte - - t.Run("stake", func(t *testing.T) { - // ACT - stake := contract.RequiredGas(abi.Methods[StakeMethodName].ID) - // ASSERT - copy(method[:], abi.Methods[StakeMethodName].ID[:4]) - baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte - require.Equal( - t, - GasRequiredByMethod[method]+baseCost, - stake, - "stake method should require %d gas, got %d", - GasRequiredByMethod[method]+baseCost, - stake, - ) - }) - - t.Run("unstake", func(t *testing.T) { - // ACT - unstake := contract.RequiredGas(abi.Methods[UnstakeMethodName].ID) - // ASSERT - copy(method[:], abi.Methods[UnstakeMethodName].ID[:4]) - baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte - require.Equal( - t, - GasRequiredByMethod[method]+baseCost, - unstake, - "unstake method should require %d gas, got %d", - GasRequiredByMethod[method]+baseCost, - unstake, - ) - }) - - t.Run("moveStake", func(t *testing.T) { - // ACT - moveStake := contract.RequiredGas(abi.Methods[MoveStakeMethodName].ID) - // ASSERT - copy(method[:], abi.Methods[MoveStakeMethodName].ID[:4]) - baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte - require.Equal( - t, - GasRequiredByMethod[method]+baseCost, - moveStake, - "moveStake method should require %d gas, got %d", - GasRequiredByMethod[method]+baseCost, - moveStake, - ) - }) - - t.Run("getAllValidators", func(t *testing.T) { - // ACT - getAllValidators := contract.RequiredGas(abi.Methods[GetAllValidatorsMethodName].ID) - // ASSERT - copy(method[:], abi.Methods[GetAllValidatorsMethodName].ID[:4]) - baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte - require.Equal( - t, - GasRequiredByMethod[method]+baseCost, - getAllValidators, - "getAllValidators method should require %d gas, got %d", - GasRequiredByMethod[method]+baseCost, - getAllValidators, - ) - }) - - t.Run("getShares", func(t *testing.T) { - // ACT - getShares := contract.RequiredGas(abi.Methods[GetSharesMethodName].ID) - // ASSERT - copy(method[:], abi.Methods[GetSharesMethodName].ID[:4]) - baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte - require.Equal( - t, - GasRequiredByMethod[method]+baseCost, - getShares, - "getShares method should require %d gas, got %d", - GasRequiredByMethod[method]+baseCost, - getShares, - ) - }) - - t.Run("invalid method", func(t *testing.T) { - // ARRANGE - invalidMethodBytes := []byte("invalidMethod") - // ACT - gasInvalidMethod := contract.RequiredGas(invalidMethodBytes) - // ASSERT - require.Equal( - t, - uint64(0), - gasInvalidMethod, - "invalid method should require %d gas, got %d", - uint64(0), - gasInvalidMethod, - ) - }) - }) -} - -func Test_InvalidMethod(t *testing.T) { - _, _, abi, _, _, _ := setup(t) - - _, doNotExist := abi.Methods["invalidMethod"] - require.False(t, doNotExist, "invalidMethod should not be present in the ABI") -} - -func Test_InvalidABI(t *testing.T) { - IStakingMetaData.ABI = "invalid json" - defer func() { - if r := recover(); r != nil { - require.IsType(t, &json.SyntaxError{}, r, "expected error type: json.SyntaxError, got: %T", r) - } - }() - - initABI() -} - -func Test_Stake(t *testing.T) { - // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - t.Run("should fail with error disabled", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[StakeMethodName] - r := rand.New(rand.NewSource(42)) - validator := sample.Validator(t, r) - - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - require.NoError(t, err) - - stakerAddr := common.BytesToAddress(staker.Bytes()) - mockVMContract.CallerAddress = stakerAddr - args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - mockVMContract.Input = packInputArgs(t, methodID, args...) - - // ACT - _, err = contract.Run(mockEVM, mockVMContract, false) - - // ASSERT - require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ - Method: StakeMethodName, - }) - }) - - // t.Run("should fail in read only mode", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, true) - - // // ASSERT - // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: StakeMethodName}) - // }) - - // t.Run("should fail if validator doesn't exist", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should stake", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.NoError(t, err) - // }) - - // t.Run("should fail if no input args", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // mockVMContract.Input = methodID.ID - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if caller is not staker", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // nonStakerAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) - // args := []interface{}{nonStakerAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.ErrorContains(t, err, "caller is not staker address") - // }) - - // t.Run("should fail if staking fails", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // // staker without funds - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err := contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if wrong args amount", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress} - - // // ACT - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if staker is not eth addr", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - - // // ACT - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if validator is not valid string", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} - - // // ACT - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if amount is not int64", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[StakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} - - // // ACT - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) -} - -func Test_Unstake(t *testing.T) { - // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - t.Run("should fail with error disabled", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[UnstakeMethodName] - r := rand.New(rand.NewSource(42)) - validator := sample.Validator(t, r) - - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - require.NoError(t, err) - - stakerAddr := common.BytesToAddress(staker.Bytes()) - mockVMContract.CallerAddress = stakerAddr - - args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - mockVMContract.Input = packInputArgs(t, methodID, args...) - - // ACT - _, err = contract.Run(mockEVM, mockVMContract, false) - - // ASSERT - require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ - Method: UnstakeMethodName, - }) - }) - - // t.Run("should fail in read only method", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, true) - - // // ASSERT - // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: UnstakeMethodName}) - // }) - - // t.Run("should fail if validator doesn't exist", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should unstake", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - - // // stake first - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // // ACT - // mockVMContract.Input = packInputArgs(t, methodID, args...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.NoError(t, err) - // }) - - // t.Run("should fail if caller is not staker", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // // stake first - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) - // mockVMContract.CallerAddress = callerEthAddr - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.ErrorContains(t, err, "caller is not staker address") - // }) - - // t.Run("should fail if no previous staking", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - // mockVMContract.Input = packInputArgs(t, methodID, args...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if wrong args amount", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress} - - // // ACT - // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if staker is not eth addr", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - - // // ACT - // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if validator is not valid string", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} - - // // ACT - // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if amount is not int64", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[UnstakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validator := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} - - // // ACT - // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - - // // ASSERT - // require.Error(t, err) - // }) -} - -func Test_MoveStake(t *testing.T) { - // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - t.Run("should fail with error disabled", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[MoveStakeMethodName] - r := rand.New(rand.NewSource(42)) - validatorSrc := sample.Validator(t, r) - sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - validatorDest := sample.Validator(t, r) - - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - require.NoError(t, err) - - stakerAddr := common.BytesToAddress(staker.Bytes()) - mockVMContract.CallerAddress = stakerAddr - - argsStake := []interface{}{ - stakerEthAddr, - validatorSrc.OperatorAddress, - coins.AmountOf(config.BaseDenom).BigInt(), - } - - // stake to validator src - stakeMethodID := abi.Methods[StakeMethodName] - mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - _, err = contract.Run(mockEVM, mockVMContract, false) - require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ - Method: StakeMethodName, - }) - - argsMoveStake := []interface{}{ - stakerEthAddr, - validatorSrc.OperatorAddress, - validatorDest.OperatorAddress, - coins.AmountOf(config.BaseDenom).BigInt(), - } - mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - - // ACT - _, err = contract.Run(mockEVM, mockVMContract, false) - - // ASSERT - require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ - Method: MoveStakeMethodName, - }) - }) - - // t.Run("should fail in read only method", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, true) - - // // ASSERT - // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: MoveStakeMethodName}) - // }) - - // t.Run("should fail if validator dest doesn't exist", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should move stake", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - - // // ACT - // // move stake to validator dest - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.NoError(t, err) - // }) - - // t.Run("should fail if staker is invalid arg", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // 42, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // ACT - // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if validator src is invalid arg", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // 42, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // ACT - // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if validator dest is invalid arg", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // 42, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // ACT - // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if amount is invalid arg", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).Uint64(), - // } - - // // ACT - // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if wrong args amount", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{stakerEthAddr, validatorSrc.OperatorAddress, validatorDest.OperatorAddress} - - // // ACT - // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - - // // ASSERT - // require.Error(t, err) - // }) - - // t.Run("should fail if caller is not staker", func(t *testing.T) { - // // ARRANGE - // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - // methodID := abi.Methods[MoveStakeMethodName] - // r := rand.New(rand.NewSource(42)) - // validatorSrc := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) - // validatorDest := sample.Validator(t, r) - // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - - // staker := sample.Bech32AccAddress() - // stakerEthAddr := common.BytesToAddress(staker.Bytes()) - // coins := sample.Coins() - // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - // require.NoError(t, err) - // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - // require.NoError(t, err) - - // stakerAddr := common.BytesToAddress(staker.Bytes()) - // mockVMContract.CallerAddress = stakerAddr - // argsStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - - // // stake to validator src - // stakeMethodID := abi.Methods[StakeMethodName] - // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - // _, err = contract.Run(mockEVM, mockVMContract, false) - // require.NoError(t, err) - - // argsMoveStake := []interface{}{ - // stakerEthAddr, - // validatorSrc.OperatorAddress, - // validatorDest.OperatorAddress, - // coins.AmountOf(config.BaseDenom).BigInt(), - // } - // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - - // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) - // mockVMContract.CallerAddress = callerEthAddr - - // // ACT - // _, err = contract.Run(mockEVM, mockVMContract, false) - - // // ASSERT - // require.ErrorContains(t, err, "caller is not staker") - // }) -} - -func Test_GetAllValidators(t *testing.T) { - t.Run("should return empty array if validators not set", func(t *testing.T) { - // ARRANGE - _, contract, abi, _, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[GetAllValidatorsMethodName] - mockVMContract.Input = methodID.ID - - // ACT - validators, err := contract.Run(mockEVM, mockVMContract, false) - - // ASSERT - require.NoError(t, err) - - res, err := methodID.Outputs.Unpack(validators) - require.NoError(t, err) - - require.Empty(t, res[0]) - }) - - t.Run("should return validators if set", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[GetAllValidatorsMethodName] - mockVMContract.Input = methodID.ID - r := rand.New(rand.NewSource(42)) - validator := sample.Validator(t, r) - sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - // ACT - validators, err := contract.Run(mockEVM, mockVMContract, false) - - // ASSERT - require.NoError(t, err) - - res, err := methodID.Outputs.Unpack(validators) - require.NoError(t, err) - - require.NotEmpty(t, res[0]) - }) -} - -func Test_GetShares(t *testing.T) { - t.Run("should return stakes", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[GetSharesMethodName] - r := rand.New(rand.NewSource(42)) - validator := sample.Validator(t, r) - sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - require.NoError(t, err) - - stakerAddr := common.BytesToAddress(staker.Bytes()) - - stakeArgs := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - - stakeMethodID := abi.Methods[StakeMethodName] - - // ACT - _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, stakeArgs) - require.NoError(t, err) - - // ASSERT - args := []interface{}{stakerEthAddr, validator.OperatorAddress} - mockVMContract.Input = packInputArgs(t, methodID, args...) - stakes, err := contract.Run(mockEVM, mockVMContract, false) - require.NoError(t, err) - - res, err := methodID.Outputs.Unpack(stakes) - require.NoError(t, err) - require.Equal( - t, - fmt.Sprintf("%d000000000000000000", coins.AmountOf(config.BaseDenom).BigInt().Int64()), - res[0].(*big.Int).String(), - ) - }) - - t.Run("should fail if wrong args amount", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - args := []interface{}{stakerEthAddr} - - // ACT - _, err := contract.GetShares(ctx, &methodID, args) - - // ASSERT - require.Error(t, err) - }) - - t.Run("should fail if invalid staker arg", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] - r := rand.New(rand.NewSource(42)) - validator := sample.Validator(t, r) - args := []interface{}{42, validator.OperatorAddress} - - // ACT - _, err := contract.GetShares(ctx, &methodID, args) - - // ASSERT - require.Error(t, err) - }) - - t.Run("should fail if invalid val address", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - args := []interface{}{stakerEthAddr, staker.String()} - - // ACT - _, err := contract.GetShares(ctx, &methodID, args) - - // ASSERT - require.Error(t, err) - }) - - t.Run("should fail if invalid val address format", func(t *testing.T) { - // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - args := []interface{}{stakerEthAddr, 42} - - // ACT - _, err := contract.GetShares(ctx, &methodID, args) - - // ASSERT - require.Error(t, err) - }) -} - -func Test_RunInvalidMethod(t *testing.T) { - // ARRANGE - _, contract, _, _, mockEVM, mockVMContract := setup(t) - k, _, _, _ := keeper.FungibleKeeper(t) - - var encoding ethermint.EncodingConfig - appCodec := encoding.Codec - gasConfig := storetypes.TransientGasConfig() - - prototype := prototype.NewIPrototypeContract(k, appCodec, gasConfig) - - prototypeAbi := prototype.Abi() - methodID := prototypeAbi.Methods["bech32ToHexAddr"] - args := []interface{}{"123"} - mockVMContract.Input = packInputArgs(t, methodID, args...) - - // ACT - _, err := contract.Run(mockEVM, mockVMContract, false) - - // ASSERT - require.Error(t, err) -} +// import ( +// "encoding/json" +// "fmt" +// "testing" + +// "math/big" +// "math/rand" + +// tmdb "github.com/cometbft/cometbft-db" +// "github.com/cosmos/cosmos-sdk/store" + +// storetypes "github.com/cosmos/cosmos-sdk/store/types" +// sdk "github.com/cosmos/cosmos-sdk/types" +// stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +// "github.com/ethereum/go-ethereum/accounts/abi" +// "github.com/ethereum/go-ethereum/common" +// "github.com/ethereum/go-ethereum/core/vm" +// "github.com/ethereum/go-ethereum/params" +// "github.com/stretchr/testify/require" +// ethermint "github.com/zeta-chain/ethermint/types" +// "github.com/zeta-chain/ethermint/x/evm/statedb" +// "github.com/zeta-chain/node/cmd/zetacored/config" +// "github.com/zeta-chain/node/precompiles/prototype" +// ptypes "github.com/zeta-chain/node/precompiles/types" +// "github.com/zeta-chain/node/testutil/keeper" + +// "github.com/zeta-chain/node/testutil/sample" +// fungibletypes "github.com/zeta-chain/node/x/fungible/types" +// ) + +// func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *vm.EVM, *vm.Contract) { +// var encoding ethermint.EncodingConfig +// appCodec := encoding.Codec + +// cdc := keeper.NewCodec() + +// db := tmdb.NewMemDB() +// stateStore := store.NewCommitMultiStore(db) +// keys, memKeys, tkeys, allKeys := keeper.StoreKeys() +// sdkKeepers := keeper.NewSDKKeepersWithKeys(cdc, keys, memKeys, tkeys, allKeys) +// for _, key := range keys { +// stateStore.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) +// } +// for _, key := range tkeys { +// stateStore.MountStoreWithDB(key, storetypes.StoreTypeTransient, nil) +// } +// for _, key := range memKeys { +// stateStore.MountStoreWithDB(key, storetypes.StoreTypeMemory, nil) +// } + +// gasConfig := storetypes.TransientGasConfig() +// ctx := keeper.NewContext(stateStore) + +// require.NoError(t, stateStore.LoadLatestVersion()) + +// stakingGenesisState := stakingtypes.DefaultGenesisState() +// stakingGenesisState.Params.BondDenom = config.BaseDenom +// sdkKeepers.StakingKeeper.InitGenesis(ctx, stakingGenesisState) + +// contract := NewIStakingContract(&sdkKeepers.StakingKeeper, appCodec, gasConfig) +// require.NotNil(t, contract, "NewIStakingContract() should not return a nil contract") + +// abi := contract.Abi() +// require.NotNil(t, abi, "contract ABI should not be nil") + +// address := contract.Address() +// require.NotNil(t, address, "contract address should not be nil") + +// mockEVM := vm.NewEVM( +// vm.BlockContext{}, +// vm.TxContext{}, +// statedb.New(ctx, sdkKeepers.EvmKeeper, statedb.TxConfig{}), +// ¶ms.ChainConfig{}, +// vm.Config{}, +// ) +// mockVMContract := vm.NewContract( +// contractRef{address: common.Address{}}, +// contractRef{address: ContractAddress}, +// big.NewInt(0), +// 0, +// ) +// return ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract +// } + +// func packInputArgs(t *testing.T, methodID abi.Method, args ...interface{}) []byte { +// input, err := methodID.Inputs.Pack(args...) +// require.NoError(t, err) +// return append(methodID.ID, input...) +// } + +// type contractRef struct { +// address common.Address +// } + +// func (c contractRef) Address() common.Address { +// return c.address +// } + +// func Test_IStakingContract(t *testing.T) { +// _, contract, abi, _, _, _ := setup(t) +// gasConfig := storetypes.TransientGasConfig() + +// t.Run("should check methods are present in ABI", func(t *testing.T) { +// require.NotNil(t, abi.Methods[StakeMethodName], "stake method should be present in the ABI") +// require.NotNil(t, abi.Methods[UnstakeMethodName], "unstake method should be present in the ABI") +// require.NotNil( +// t, +// abi.Methods[MoveStakeMethodName], +// "moveStake method should be present in the ABI", +// ) + +// require.NotNil( +// t, +// abi.Methods[GetAllValidatorsMethodName], +// "getAllValidators method should be present in the ABI", +// ) +// require.NotNil(t, abi.Methods[GetSharesMethodName], "getShares method should be present in the ABI") +// }) + +// t.Run("should check gas requirements for methods", func(t *testing.T) { +// var method [4]byte + +// t.Run("stake", func(t *testing.T) { +// // ACT +// stake := contract.RequiredGas(abi.Methods[StakeMethodName].ID) +// // ASSERT +// copy(method[:], abi.Methods[StakeMethodName].ID[:4]) +// baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte +// require.Equal( +// t, +// GasRequiredByMethod[method]+baseCost, +// stake, +// "stake method should require %d gas, got %d", +// GasRequiredByMethod[method]+baseCost, +// stake, +// ) +// }) + +// t.Run("unstake", func(t *testing.T) { +// // ACT +// unstake := contract.RequiredGas(abi.Methods[UnstakeMethodName].ID) +// // ASSERT +// copy(method[:], abi.Methods[UnstakeMethodName].ID[:4]) +// baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte +// require.Equal( +// t, +// GasRequiredByMethod[method]+baseCost, +// unstake, +// "unstake method should require %d gas, got %d", +// GasRequiredByMethod[method]+baseCost, +// unstake, +// ) +// }) + +// t.Run("moveStake", func(t *testing.T) { +// // ACT +// moveStake := contract.RequiredGas(abi.Methods[MoveStakeMethodName].ID) +// // ASSERT +// copy(method[:], abi.Methods[MoveStakeMethodName].ID[:4]) +// baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte +// require.Equal( +// t, +// GasRequiredByMethod[method]+baseCost, +// moveStake, +// "moveStake method should require %d gas, got %d", +// GasRequiredByMethod[method]+baseCost, +// moveStake, +// ) +// }) + +// t.Run("getAllValidators", func(t *testing.T) { +// // ACT +// getAllValidators := contract.RequiredGas(abi.Methods[GetAllValidatorsMethodName].ID) +// // ASSERT +// copy(method[:], abi.Methods[GetAllValidatorsMethodName].ID[:4]) +// baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte +// require.Equal( +// t, +// GasRequiredByMethod[method]+baseCost, +// getAllValidators, +// "getAllValidators method should require %d gas, got %d", +// GasRequiredByMethod[method]+baseCost, +// getAllValidators, +// ) +// }) + +// t.Run("getShares", func(t *testing.T) { +// // ACT +// getShares := contract.RequiredGas(abi.Methods[GetSharesMethodName].ID) +// // ASSERT +// copy(method[:], abi.Methods[GetSharesMethodName].ID[:4]) +// baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte +// require.Equal( +// t, +// GasRequiredByMethod[method]+baseCost, +// getShares, +// "getShares method should require %d gas, got %d", +// GasRequiredByMethod[method]+baseCost, +// getShares, +// ) +// }) + +// t.Run("invalid method", func(t *testing.T) { +// // ARRANGE +// invalidMethodBytes := []byte("invalidMethod") +// // ACT +// gasInvalidMethod := contract.RequiredGas(invalidMethodBytes) +// // ASSERT +// require.Equal( +// t, +// uint64(0), +// gasInvalidMethod, +// "invalid method should require %d gas, got %d", +// uint64(0), +// gasInvalidMethod, +// ) +// }) +// }) +// } + +// func Test_InvalidMethod(t *testing.T) { +// _, _, abi, _, _, _ := setup(t) + +// _, doNotExist := abi.Methods["invalidMethod"] +// require.False(t, doNotExist, "invalidMethod should not be present in the ABI") +// } + +// func Test_InvalidABI(t *testing.T) { +// IStakingMetaData.ABI = "invalid json" +// defer func() { +// if r := recover(); r != nil { +// require.IsType(t, &json.SyntaxError{}, r, "expected error type: json.SyntaxError, got: %T", r) +// } +// }() + +// initABI() +// } + +// func Test_Stake(t *testing.T) { +// // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. +// t.Run("should fail with error disabled", func(t *testing.T) { +// // ARRANGE +// ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// methodID := abi.Methods[StakeMethodName] +// r := rand.New(rand.NewSource(42)) +// validator := sample.Validator(t, r) + +// staker := sample.Bech32AccAddress() +// stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// coins := sample.Coins() +// err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// require.NoError(t, err) +// err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// require.NoError(t, err) + +// stakerAddr := common.BytesToAddress(staker.Bytes()) +// mockVMContract.CallerAddress = stakerAddr +// args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} +// mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // ACT +// _, err = contract.Run(mockEVM, mockVMContract, false) + +// // ASSERT +// require.Error(t, err) +// require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ +// Method: StakeMethodName, +// }) +// }) + +// // t.Run("should fail in read only mode", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[StakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} +// // mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, true) + +// // // ASSERT +// // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: StakeMethodName}) +// // }) + +// // t.Run("should fail if validator doesn't exist", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[StakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} +// // mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should stake", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[StakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} +// // mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.NoError(t, err) +// // }) + +// // t.Run("should fail if no input args", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[StakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr +// // mockVMContract.Input = methodID.ID + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if caller is not staker", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[StakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr + +// // nonStakerAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) +// // args := []interface{}{nonStakerAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} +// // mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.ErrorContains(t, err, "caller is not staker address") +// // }) + +// // t.Run("should fail if staking fails", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[StakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // // staker without funds +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr + +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} +// // mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // // ACT +// // _, err := contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if wrong args amount", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[StakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress} + +// // // ACT +// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if staker is not eth addr", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[StakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + +// // // ACT +// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if validator is not valid string", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[StakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} + +// // // ACT +// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if amount is not int64", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[StakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} + +// // // ACT +// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + +// // // ASSERT +// // require.Error(t, err) +// // }) +// } + +// func Test_Unstake(t *testing.T) { +// // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. +// t.Run("should fail with error disabled", func(t *testing.T) { +// // ARRANGE +// ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// methodID := abi.Methods[UnstakeMethodName] +// r := rand.New(rand.NewSource(42)) +// validator := sample.Validator(t, r) + +// staker := sample.Bech32AccAddress() +// stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// coins := sample.Coins() +// err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// require.NoError(t, err) +// err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// require.NoError(t, err) + +// stakerAddr := common.BytesToAddress(staker.Bytes()) +// mockVMContract.CallerAddress = stakerAddr + +// args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} +// mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // ACT +// _, err = contract.Run(mockEVM, mockVMContract, false) + +// // ASSERT +// require.Error(t, err) +// require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ +// Method: UnstakeMethodName, +// }) +// }) + +// // t.Run("should fail in read only method", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[UnstakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr + +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} +// // mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, true) + +// // // ASSERT +// // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: UnstakeMethodName}) +// // }) + +// // t.Run("should fail if validator doesn't exist", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[UnstakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr + +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} +// // mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should unstake", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[UnstakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr + +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + +// // // stake first +// // stakeMethodID := abi.Methods[StakeMethodName] +// // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) +// // _, err = contract.Run(mockEVM, mockVMContract, false) +// // require.NoError(t, err) + +// // // ACT +// // mockVMContract.Input = packInputArgs(t, methodID, args...) +// // _, err = contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.NoError(t, err) +// // }) + +// // t.Run("should fail if caller is not staker", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[UnstakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr + +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} +// // // stake first +// // stakeMethodID := abi.Methods[StakeMethodName] +// // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) +// // _, err = contract.Run(mockEVM, mockVMContract, false) +// // require.NoError(t, err) + +// // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) +// // mockVMContract.CallerAddress = callerEthAddr +// // mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.ErrorContains(t, err, "caller is not staker address") +// // }) + +// // t.Run("should fail if no previous staking", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[UnstakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr + +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} +// // mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if wrong args amount", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[UnstakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress} + +// // // ACT +// // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if staker is not eth addr", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[UnstakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + +// // // ACT +// // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if validator is not valid string", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[UnstakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} + +// // // ACT +// // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if amount is not int64", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[UnstakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validator := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} + +// // // ACT +// // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + +// // // ASSERT +// // require.Error(t, err) +// // }) +// } + +// func Test_MoveStake(t *testing.T) { +// // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. +// t.Run("should fail with error disabled", func(t *testing.T) { +// // ARRANGE +// ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// methodID := abi.Methods[MoveStakeMethodName] +// r := rand.New(rand.NewSource(42)) +// validatorSrc := sample.Validator(t, r) +// sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) +// validatorDest := sample.Validator(t, r) + +// staker := sample.Bech32AccAddress() +// stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// coins := sample.Coins() +// err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// require.NoError(t, err) +// err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// require.NoError(t, err) + +// stakerAddr := common.BytesToAddress(staker.Bytes()) +// mockVMContract.CallerAddress = stakerAddr + +// argsStake := []interface{}{ +// stakerEthAddr, +// validatorSrc.OperatorAddress, +// coins.AmountOf(config.BaseDenom).BigInt(), +// } + +// // stake to validator src +// stakeMethodID := abi.Methods[StakeMethodName] +// mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) +// _, err = contract.Run(mockEVM, mockVMContract, false) +// require.Error(t, err) +// require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ +// Method: StakeMethodName, +// }) + +// argsMoveStake := []interface{}{ +// stakerEthAddr, +// validatorSrc.OperatorAddress, +// validatorDest.OperatorAddress, +// coins.AmountOf(config.BaseDenom).BigInt(), +// } +// mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + +// // ACT +// _, err = contract.Run(mockEVM, mockVMContract, false) + +// // ASSERT +// require.Error(t, err) +// require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ +// Method: MoveStakeMethodName, +// }) +// }) + +// // t.Run("should fail in read only method", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[MoveStakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validatorSrc := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) +// // validatorDest := sample.Validator(t, r) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr + +// // argsStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // stake to validator src +// // stakeMethodID := abi.Methods[StakeMethodName] +// // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) +// // _, err = contract.Run(mockEVM, mockVMContract, false) +// // require.NoError(t, err) + +// // argsMoveStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // validatorDest.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } +// // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, true) + +// // // ASSERT +// // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: MoveStakeMethodName}) +// // }) + +// // t.Run("should fail if validator dest doesn't exist", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[MoveStakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validatorSrc := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) +// // validatorDest := sample.Validator(t, r) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr + +// // argsStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // stake to validator src +// // stakeMethodID := abi.Methods[StakeMethodName] +// // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) +// // _, err = contract.Run(mockEVM, mockVMContract, false) +// // require.NoError(t, err) + +// // argsMoveStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // validatorDest.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } +// // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should move stake", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[MoveStakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validatorSrc := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) +// // validatorDest := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr +// // argsStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // stake to validator src +// // stakeMethodID := abi.Methods[StakeMethodName] +// // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) +// // _, err = contract.Run(mockEVM, mockVMContract, false) +// // require.NoError(t, err) + +// // argsMoveStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // validatorDest.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } +// // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + +// // // ACT +// // // move stake to validator dest +// // _, err = contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.NoError(t, err) +// // }) + +// // t.Run("should fail if staker is invalid arg", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[MoveStakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validatorSrc := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) +// // validatorDest := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // argsStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // stake to validator src +// // stakeMethodID := abi.Methods[StakeMethodName] +// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) +// // require.NoError(t, err) + +// // argsMoveStake := []interface{}{ +// // 42, +// // validatorSrc.OperatorAddress, +// // validatorDest.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // ACT +// // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if validator src is invalid arg", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[MoveStakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validatorSrc := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) +// // validatorDest := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // argsStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // stake to validator src +// // stakeMethodID := abi.Methods[StakeMethodName] +// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) +// // require.NoError(t, err) + +// // argsMoveStake := []interface{}{ +// // stakerEthAddr, +// // 42, +// // validatorDest.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // ACT +// // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if validator dest is invalid arg", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[MoveStakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validatorSrc := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) +// // validatorDest := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // argsStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // stake to validator src +// // stakeMethodID := abi.Methods[StakeMethodName] +// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) +// // require.NoError(t, err) + +// // argsMoveStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // 42, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // ACT +// // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if amount is invalid arg", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[MoveStakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validatorSrc := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) +// // validatorDest := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // argsStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // stake to validator src +// // stakeMethodID := abi.Methods[StakeMethodName] +// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) +// // require.NoError(t, err) + +// // argsMoveStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // validatorDest.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).Uint64(), +// // } + +// // // ACT +// // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if wrong args amount", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) +// // methodID := abi.Methods[MoveStakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validatorSrc := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) +// // validatorDest := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) + +// // argsStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // stake to validator src +// // stakeMethodID := abi.Methods[StakeMethodName] +// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) +// // require.NoError(t, err) + +// // argsMoveStake := []interface{}{stakerEthAddr, validatorSrc.OperatorAddress, validatorDest.OperatorAddress} + +// // // ACT +// // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + +// // // ASSERT +// // require.Error(t, err) +// // }) + +// // t.Run("should fail if caller is not staker", func(t *testing.T) { +// // // ARRANGE +// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// // methodID := abi.Methods[MoveStakeMethodName] +// // r := rand.New(rand.NewSource(42)) +// // validatorSrc := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) +// // validatorDest := sample.Validator(t, r) +// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + +// // staker := sample.Bech32AccAddress() +// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// // coins := sample.Coins() +// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// // require.NoError(t, err) +// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// // require.NoError(t, err) + +// // stakerAddr := common.BytesToAddress(staker.Bytes()) +// // mockVMContract.CallerAddress = stakerAddr +// // argsStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } + +// // // stake to validator src +// // stakeMethodID := abi.Methods[StakeMethodName] +// // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) +// // _, err = contract.Run(mockEVM, mockVMContract, false) +// // require.NoError(t, err) + +// // argsMoveStake := []interface{}{ +// // stakerEthAddr, +// // validatorSrc.OperatorAddress, +// // validatorDest.OperatorAddress, +// // coins.AmountOf(config.BaseDenom).BigInt(), +// // } +// // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + +// // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) +// // mockVMContract.CallerAddress = callerEthAddr + +// // // ACT +// // _, err = contract.Run(mockEVM, mockVMContract, false) + +// // // ASSERT +// // require.ErrorContains(t, err, "caller is not staker") +// // }) +// } + +// func Test_GetAllValidators(t *testing.T) { +// t.Run("should return empty array if validators not set", func(t *testing.T) { +// // ARRANGE +// _, contract, abi, _, mockEVM, mockVMContract := setup(t) +// methodID := abi.Methods[GetAllValidatorsMethodName] +// mockVMContract.Input = methodID.ID + +// // ACT +// validators, err := contract.Run(mockEVM, mockVMContract, false) + +// // ASSERT +// require.NoError(t, err) + +// res, err := methodID.Outputs.Unpack(validators) +// require.NoError(t, err) + +// require.Empty(t, res[0]) +// }) + +// t.Run("should return validators if set", func(t *testing.T) { +// // ARRANGE +// ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// methodID := abi.Methods[GetAllValidatorsMethodName] +// mockVMContract.Input = methodID.ID +// r := rand.New(rand.NewSource(42)) +// validator := sample.Validator(t, r) +// sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// // ACT +// validators, err := contract.Run(mockEVM, mockVMContract, false) + +// // ASSERT +// require.NoError(t, err) + +// res, err := methodID.Outputs.Unpack(validators) +// require.NoError(t, err) + +// require.NotEmpty(t, res[0]) +// }) +// } + +// func Test_GetShares(t *testing.T) { +// t.Run("should return stakes", func(t *testing.T) { +// // ARRANGE +// ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) +// methodID := abi.Methods[GetSharesMethodName] +// r := rand.New(rand.NewSource(42)) +// validator := sample.Validator(t, r) +// sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + +// staker := sample.Bech32AccAddress() +// stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// coins := sample.Coins() +// err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) +// require.NoError(t, err) +// err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) +// require.NoError(t, err) + +// stakerAddr := common.BytesToAddress(staker.Bytes()) + +// stakeArgs := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + +// stakeMethodID := abi.Methods[StakeMethodName] + +// // ACT +// _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, stakeArgs) +// require.NoError(t, err) + +// // ASSERT +// args := []interface{}{stakerEthAddr, validator.OperatorAddress} +// mockVMContract.Input = packInputArgs(t, methodID, args...) +// stakes, err := contract.Run(mockEVM, mockVMContract, false) +// require.NoError(t, err) + +// res, err := methodID.Outputs.Unpack(stakes) +// require.NoError(t, err) +// require.Equal( +// t, +// fmt.Sprintf("%d000000000000000000", coins.AmountOf(config.BaseDenom).BigInt().Int64()), +// res[0].(*big.Int).String(), +// ) +// }) + +// t.Run("should fail if wrong args amount", func(t *testing.T) { +// // ARRANGE +// ctx, contract, abi, _, _, _ := setup(t) +// methodID := abi.Methods[GetSharesMethodName] +// staker := sample.Bech32AccAddress() +// stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// args := []interface{}{stakerEthAddr} + +// // ACT +// _, err := contract.GetShares(ctx, &methodID, args) + +// // ASSERT +// require.Error(t, err) +// }) + +// t.Run("should fail if invalid staker arg", func(t *testing.T) { +// // ARRANGE +// ctx, contract, abi, _, _, _ := setup(t) +// methodID := abi.Methods[GetSharesMethodName] +// r := rand.New(rand.NewSource(42)) +// validator := sample.Validator(t, r) +// args := []interface{}{42, validator.OperatorAddress} + +// // ACT +// _, err := contract.GetShares(ctx, &methodID, args) + +// // ASSERT +// require.Error(t, err) +// }) + +// t.Run("should fail if invalid val address", func(t *testing.T) { +// // ARRANGE +// ctx, contract, abi, _, _, _ := setup(t) +// methodID := abi.Methods[GetSharesMethodName] +// staker := sample.Bech32AccAddress() +// stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// args := []interface{}{stakerEthAddr, staker.String()} + +// // ACT +// _, err := contract.GetShares(ctx, &methodID, args) + +// // ASSERT +// require.Error(t, err) +// }) + +// t.Run("should fail if invalid val address format", func(t *testing.T) { +// // ARRANGE +// ctx, contract, abi, _, _, _ := setup(t) +// methodID := abi.Methods[GetSharesMethodName] +// staker := sample.Bech32AccAddress() +// stakerEthAddr := common.BytesToAddress(staker.Bytes()) +// args := []interface{}{stakerEthAddr, 42} + +// // ACT +// _, err := contract.GetShares(ctx, &methodID, args) + +// // ASSERT +// require.Error(t, err) +// }) +// } + +// func Test_RunInvalidMethod(t *testing.T) { +// // ARRANGE +// _, contract, _, _, mockEVM, mockVMContract := setup(t) +// k, _, _, _ := keeper.FungibleKeeper(t) + +// var encoding ethermint.EncodingConfig +// appCodec := encoding.Codec +// gasConfig := storetypes.TransientGasConfig() + +// prototype := prototype.NewIPrototypeContract(k, appCodec, gasConfig) + +// prototypeAbi := prototype.Abi() +// methodID := prototypeAbi.Methods["bech32ToHexAddr"] +// args := []interface{}{"123"} +// mockVMContract.Input = packInputArgs(t, methodID, args...) + +// // ACT +// _, err := contract.Run(mockEVM, mockVMContract, false) + +// // ASSERT +// require.Error(t, err) +// } diff --git a/precompiles/types/address.go b/precompiles/types/address.go new file mode 100644 index 0000000000..07eb25fb8b --- /dev/null +++ b/precompiles/types/address.go @@ -0,0 +1,42 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + bank "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" +) + +// getEVMCallerAddress returns the caller address. +// Usually the caller is the contract.CallerAddress, which is the address of the contract that called the precompiled contract. +// If contract.CallerAddress != evm.Origin is true, it means the call was made through a contract, +// on which case there is a need to set the caller to the evm.Origin. +func GetEVMCallerAddress(evm *vm.EVM, contract *vm.Contract) (common.Address, error) { + caller := contract.CallerAddress + if contract.CallerAddress != evm.Origin { + caller = evm.Origin + } + + return caller, nil +} + +// getCosmosAddress returns the counterpart cosmos address of the given ethereum address. +// It checks if the address is empty or blocked by the bank keeper. +func GetCosmosAddress(bankKeeper bank.Keeper, addr common.Address) (sdk.AccAddress, error) { + toAddr := sdk.AccAddress(addr.Bytes()) + if toAddr.Empty() { + return nil, &ErrInvalidAddr{ + Got: toAddr.String(), + Reason: "empty address", + } + } + + if bankKeeper.BlockedAddr(toAddr) { + return nil, &ErrInvalidAddr{ + Got: toAddr.String(), + Reason: "destination address blocked by bank keeper", + } + } + + return toAddr, nil +} diff --git a/precompiles/types/address_test.go b/precompiles/types/address_test.go new file mode 100644 index 0000000000..7300d5abad --- /dev/null +++ b/precompiles/types/address_test.go @@ -0,0 +1,45 @@ +package types + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/testutil/sample" +) + +func Test_getEVMCallerAddress(t *testing.T) { + mockEVM := vm.EVM{ + TxContext: vm.TxContext{ + Origin: common.Address{}, + }, + } + + mockVMContract := vm.NewContract( + contractRef{address: common.Address{}}, + contractRef{address: common.Address{}}, + big.NewInt(0), + 0, + ) + + // When contract.CallerAddress == evm.Origin, caller is set to contract.CallerAddress. + caller, err := GetEVMCallerAddress(&mockEVM, mockVMContract) + require.NoError(t, err) + require.Equal(t, common.Address{}, caller, "address shouldn be the same") + + // When contract.CallerAddress != evm.Origin, caller should be set to evm.Origin. + mockEVM.Origin = sample.EthAddress() + caller, err = GetEVMCallerAddress(&mockEVM, mockVMContract) + require.NoError(t, err) + require.Equal(t, mockEVM.Origin, caller, "address should be evm.Origin") +} + +type contractRef struct { + address common.Address +} + +func (c contractRef) Address() common.Address { + return c.address +} \ No newline at end of file diff --git a/precompiles/bank/coin.go b/precompiles/types/coin.go similarity index 83% rename from precompiles/bank/coin.go rename to precompiles/types/coin.go index 121ecdc1ee..5d1e8e598e 100644 --- a/precompiles/bank/coin.go +++ b/precompiles/types/coin.go @@ -1,4 +1,4 @@ -package bank +package types import ( "math/big" @@ -6,20 +6,20 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" - - ptypes "github.com/zeta-chain/node/precompiles/types" ) +const ZRC20DenomPrefix = "zrc20/" + // ZRC20ToCosmosDenom returns the cosmos coin address for a given ZRC20 address. // This is converted to "zevm/{ZRC20Address}". func ZRC20ToCosmosDenom(ZRC20Address common.Address) string { return ZRC20DenomPrefix + ZRC20Address.String() } -func createCoinSet(tokenDenom string, amount *big.Int) (sdk.Coins, error) { +func CreateCoinSet(tokenDenom string, amount *big.Int) (sdk.Coins, error) { coin := sdk.NewCoin(tokenDenom, math.NewIntFromBigInt(amount)) if !coin.IsValid() { - return nil, &ptypes.ErrInvalidCoin{ + return nil, &ErrInvalidCoin{ Got: coin.GetDenom(), Negative: coin.IsNegative(), Nil: coin.IsNil(), @@ -31,7 +31,7 @@ func createCoinSet(tokenDenom string, amount *big.Int) (sdk.Coins, error) { // But sdk.Coins will only contain one coin, always. coinSet := sdk.NewCoins(coin) if !coinSet.IsValid() || coinSet.Empty() || coinSet.IsAnyNil() || coinSet == nil { - return nil, &ptypes.ErrInvalidCoin{ + return nil, &ErrInvalidCoin{ Got: coinSet.String(), Negative: coinSet.IsAnyNegative(), Nil: coinSet.IsAnyNil(), diff --git a/precompiles/bank/coin_test.go b/precompiles/types/coin_test.go similarity index 93% rename from precompiles/bank/coin_test.go rename to precompiles/types/coin_test.go index 0a9669e0a6..54bffe616b 100644 --- a/precompiles/bank/coin_test.go +++ b/precompiles/types/coin_test.go @@ -1,4 +1,4 @@ -package bank +package types import ( "math/big" @@ -19,7 +19,7 @@ func Test_createCoinSet(t *testing.T) { tokenDenom := "zrc20/0x0000000000000000000000000000000000003039" amount := big.NewInt(100) - coinSet, err := createCoinSet(tokenDenom, amount) + coinSet, err := CreateCoinSet(tokenDenom, amount) require.NoError(t, err, "createCoinSet should not return an error") require.NotNil(t, coinSet, "coinSet should not be nil") From 35b1c092e1262c9d1b97e12485fb3d01c5ba5d71 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 21 Oct 2024 12:16:49 +0200 Subject: [PATCH 02/20] add unit testing first batch --- precompiles/precompiles.go | 4 +- precompiles/staking/IStaking.abi | 49 + precompiles/staking/IStaking.gen.go | 177 +- precompiles/staking/IStaking.json | 49 + precompiles/staking/method_distribute.go | 9 +- precompiles/staking/method_distribute_test.go | 509 +++++ .../staking/method_get_all_validators_test.go | 58 + precompiles/staking/method_get_shares_test.go | 118 ++ precompiles/staking/method_move_stake_test.go | 481 +++++ precompiles/staking/method_stake_test.go | 316 +++ precompiles/staking/method_unstake_test.go | 311 +++ precompiles/staking/staking.go | 17 +- precompiles/staking/staking_test.go | 1743 +++-------------- precompiles/types/address.go | 4 +- precompiles/types/address_test.go | 4 +- .../keeper/zrc20_cosmos_coins_mapping.go | 2 +- 16 files changed, 2368 insertions(+), 1483 deletions(-) create mode 100644 precompiles/staking/method_distribute_test.go create mode 100644 precompiles/staking/method_get_all_validators_test.go create mode 100644 precompiles/staking/method_get_shares_test.go create mode 100644 precompiles/staking/method_move_stake_test.go create mode 100644 precompiles/staking/method_stake_test.go create mode 100644 precompiles/staking/method_unstake_test.go diff --git a/precompiles/precompiles.go b/precompiles/precompiles.go index 93e4cdb519..b9d167dbac 100644 --- a/precompiles/precompiles.go +++ b/precompiles/precompiles.go @@ -49,8 +49,8 @@ func StatefulContracts( // Define the staking contract function. if EnabledStatefulContracts[staking.ContractAddress] { - stakingContract := func(_ sdktypes.Context, _ ethparams.Rules) vm.PrecompiledContract { - return staking.NewIStakingContract(stakingKeeper, *fungibleKeeper, bankKeeper, cdc, gasConfig) + stakingContract := func(ctx sdktypes.Context, _ ethparams.Rules) vm.PrecompiledContract { + return staking.NewIStakingContract(ctx, stakingKeeper, *fungibleKeeper, bankKeeper, cdc, gasConfig) } // Append the staking contract to the precompiledContracts slice. diff --git a/precompiles/staking/IStaking.abi b/precompiles/staking/IStaking.abi index fceba4d682..da1a9e6ffc 100644 --- a/precompiles/staking/IStaking.abi +++ b/precompiles/staking/IStaking.abi @@ -1,4 +1,29 @@ [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "zrc20_distributor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "zrc20_token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Distributed", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -80,6 +105,30 @@ "name": "Unstake", "type": "event" }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "distribute", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "getAllValidators", diff --git a/precompiles/staking/IStaking.gen.go b/precompiles/staking/IStaking.gen.go index 01226d4ecf..d4f7495d37 100644 --- a/precompiles/staking/IStaking.gen.go +++ b/precompiles/staking/IStaking.gen.go @@ -39,7 +39,7 @@ type Validator struct { // IStakingMetaData contains all meta data concerning the IStaking contract. var IStakingMetaData = &bind.MetaData{ - ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorSrc\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorDst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"MoveStake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Stake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Unstake\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"getAllValidators\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"operatorAddress\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"consensusPubKey\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"jailed\",\"type\":\"bool\"},{\"internalType\":\"enumBondStatus\",\"name\":\"bondStatus\",\"type\":\"uint8\"}],\"internalType\":\"structValidator[]\",\"name\":\"validators\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getShares\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validatorSrc\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"validatorDst\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"moveStake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"stake\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unstake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_distributor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Distributed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorSrc\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorDst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"MoveStake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Stake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Unstake\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"distribute\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllValidators\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"operatorAddress\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"consensusPubKey\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"jailed\",\"type\":\"bool\"},{\"internalType\":\"enumBondStatus\",\"name\":\"bondStatus\",\"type\":\"uint8\"}],\"internalType\":\"structValidator[]\",\"name\":\"validators\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getShares\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validatorSrc\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"validatorDst\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"moveStake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"stake\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unstake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", } // IStakingABI is the input ABI used to generate the binding from. @@ -250,6 +250,27 @@ func (_IStaking *IStakingCallerSession) GetShares(staker common.Address, validat return _IStaking.Contract.GetShares(&_IStaking.CallOpts, staker, validator) } +// Distribute is a paid mutator transaction binding the contract method 0xfb932108. +// +// Solidity: function distribute(address zrc20, uint256 amount) returns(bool success) +func (_IStaking *IStakingTransactor) Distribute(opts *bind.TransactOpts, zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IStaking.contract.Transact(opts, "distribute", zrc20, amount) +} + +// Distribute is a paid mutator transaction binding the contract method 0xfb932108. +// +// Solidity: function distribute(address zrc20, uint256 amount) returns(bool success) +func (_IStaking *IStakingSession) Distribute(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IStaking.Contract.Distribute(&_IStaking.TransactOpts, zrc20, amount) +} + +// Distribute is a paid mutator transaction binding the contract method 0xfb932108. +// +// Solidity: function distribute(address zrc20, uint256 amount) returns(bool success) +func (_IStaking *IStakingTransactorSession) Distribute(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _IStaking.Contract.Distribute(&_IStaking.TransactOpts, zrc20, amount) +} + // MoveStake is a paid mutator transaction binding the contract method 0xd11a93d0. // // Solidity: function moveStake(address staker, string validatorSrc, string validatorDst, uint256 amount) returns(int64 completionTime) @@ -313,6 +334,160 @@ func (_IStaking *IStakingTransactorSession) Unstake(staker common.Address, valid return _IStaking.Contract.Unstake(&_IStaking.TransactOpts, staker, validator, amount) } +// IStakingDistributedIterator is returned from FilterDistributed and is used to iterate over the raw logs and unpacked data for Distributed events raised by the IStaking contract. +type IStakingDistributedIterator struct { + Event *IStakingDistributed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *IStakingDistributedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(IStakingDistributed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(IStakingDistributed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *IStakingDistributedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *IStakingDistributedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// IStakingDistributed represents a Distributed event raised by the IStaking contract. +type IStakingDistributed struct { + Zrc20Distributor common.Address + Zrc20Token common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterDistributed is a free log retrieval operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_IStaking *IStakingFilterer) FilterDistributed(opts *bind.FilterOpts, zrc20_distributor []common.Address, zrc20_token []common.Address) (*IStakingDistributedIterator, error) { + + var zrc20_distributorRule []interface{} + for _, zrc20_distributorItem := range zrc20_distributor { + zrc20_distributorRule = append(zrc20_distributorRule, zrc20_distributorItem) + } + var zrc20_tokenRule []interface{} + for _, zrc20_tokenItem := range zrc20_token { + zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) + } + + logs, sub, err := _IStaking.contract.FilterLogs(opts, "Distributed", zrc20_distributorRule, zrc20_tokenRule) + if err != nil { + return nil, err + } + return &IStakingDistributedIterator{contract: _IStaking.contract, event: "Distributed", logs: logs, sub: sub}, nil +} + +// WatchDistributed is a free log subscription operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_IStaking *IStakingFilterer) WatchDistributed(opts *bind.WatchOpts, sink chan<- *IStakingDistributed, zrc20_distributor []common.Address, zrc20_token []common.Address) (event.Subscription, error) { + + var zrc20_distributorRule []interface{} + for _, zrc20_distributorItem := range zrc20_distributor { + zrc20_distributorRule = append(zrc20_distributorRule, zrc20_distributorItem) + } + var zrc20_tokenRule []interface{} + for _, zrc20_tokenItem := range zrc20_token { + zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) + } + + logs, sub, err := _IStaking.contract.WatchLogs(opts, "Distributed", zrc20_distributorRule, zrc20_tokenRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(IStakingDistributed) + if err := _IStaking.contract.UnpackLog(event, "Distributed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseDistributed is a log parse operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_IStaking *IStakingFilterer) ParseDistributed(log types.Log) (*IStakingDistributed, error) { + event := new(IStakingDistributed) + if err := _IStaking.contract.UnpackLog(event, "Distributed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + // IStakingMoveStakeIterator is returned from FilterMoveStake and is used to iterate over the raw logs and unpacked data for MoveStake events raised by the IStaking contract. type IStakingMoveStakeIterator struct { Event *IStakingMoveStake // Event containing the contract specifics and raw log diff --git a/precompiles/staking/IStaking.json b/precompiles/staking/IStaking.json index cff6a7a365..d4e0bb75f0 100644 --- a/precompiles/staking/IStaking.json +++ b/precompiles/staking/IStaking.json @@ -1,5 +1,30 @@ { "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "zrc20_distributor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "zrc20_token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Distributed", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -81,6 +106,30 @@ "name": "Unstake", "type": "event" }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "distribute", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "getAllValidators", diff --git a/precompiles/staking/method_distribute.go b/precompiles/staking/method_distribute.go index b60467936a..eea35b33f3 100644 --- a/precompiles/staking/method_distribute.go +++ b/precompiles/staking/method_distribute.go @@ -13,6 +13,10 @@ import ( fungibletypes "github.com/zeta-chain/node/x/fungible/types" ) +var ( + zrc20lockerAddress = common.HexToAddress("0x0000000000000000000000000000000000000067") +) + // function distribute(address zrc20, uint256 amount) external returns (bool success) func (c *Contract) distribute( ctx sdk.Context, @@ -48,7 +52,10 @@ func (c *Contract) distribute( // LockZRC20 locks the ZRC20 under the locker address. // It performs all the necessary checks such as allowance in order to execute a transferFrom. - if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, c.Address(), caller, c.Address(), amount); err != nil { + // - spender is the staking contract address (c.Address()). + // - owner is the caller address. + // - locker is the bank address. Assets are locked under this address to prevent liquidity fragmentation. + if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, c.Address(), caller, zrc20lockerAddress, amount); err != nil { return nil, &ptypes.ErrUnexpected{ When: "LockZRC20InBank", Got: err.Error(), diff --git a/precompiles/staking/method_distribute_test.go b/precompiles/staking/method_distribute_test.go new file mode 100644 index 0000000000..bc8dadab72 --- /dev/null +++ b/precompiles/staking/method_distribute_test.go @@ -0,0 +1,509 @@ +package staking + +import ( + "math/big" + "testing" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" + ethermint "github.com/zeta-chain/ethermint/types" + evmkeeper "github.com/zeta-chain/ethermint/x/evm/keeper" + "github.com/zeta-chain/ethermint/x/evm/statedb" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/contracts/erc1967proxy" + ptypes "github.com/zeta-chain/node/precompiles/types" + "github.com/zeta-chain/node/testutil/keeper" + "github.com/zeta-chain/node/testutil/sample" + fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" +) + +func Test_Distribute(t *testing.T) { + t.Run("should fail to run distribute as read only method", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(0)}..., + ) + + // Call method as read only. + result, err := s.contract.Run(s.mockEVM, s.mockVMContract, true) + + // Check error and result. + require.ErrorIs(t, err, ptypes.ErrWriteMethod{ + Method: DistributeMethodName, + }) + + // Result is empty as the write check is done before executing distribute() function. + // On-chain this would look like reverting, so staticcall is properly reverted. + require.Empty(t, result) + }) + + t.Run("should fail to distribute with 0 token balance", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(0)}..., + ) + + // Call method. + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.ErrorAs( + t, + ptypes.ErrInvalidAmount{ + Got: "0", + }, + err, + ) + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.False(t, ok) + }) + + t.Run("should fail to distribute with 0 allowance", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + + // Set caller balance. + s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(1000)}..., + ) + + // Call method. + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.Error(t, err) + require.Contains(t, err.Error(), "invalid allowance, got 0") + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.False(t, ok) + }) + + t.Run("should fail to distribute 0 token", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + + // Set caller balance. + s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + + // Allow staking to spend ZRC20 tokens. + allowStaking(t, s, big.NewInt(1000)) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(0)}..., + ) + + // Call method. + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.Error(t, err) + require.Contains(t, err.Error(), "invalid token amount: 0") + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.False(t, ok) + }) + + t.Run("should fail to distribute more than allowed to staking", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + + // Set caller balance. + s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + + // Allow staking to spend ZRC20 tokens. + allowStaking(t, s, big.NewInt(999)) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(1000)}..., + ) + + // Call method. + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.Error(t, err) + require.Contains(t, err.Error(), "invalid allowance, got 999, wanted 1000") + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.False(t, ok) + }) + + t.Run("should fail to distribute more than user balance", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + + // Set caller balance. + s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + + // Allow staking to spend ZRC20 tokens. + allowStaking(t, s, big.NewInt(100000)) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(1001)}..., + ) + + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.Error(t, err) + require.Contains(t, err.Error(), "execution reverted") + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.False(t, ok) + }) + + t.Run("should distribute and lock ZRC20 under the bank account", func(t *testing.T) { + // Setup test. + s := newTestSuite(t) + + // Set caller balance. + s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + + // Allow staking to spend ZRC20 tokens. + allowStaking(t, s, big.NewInt(1000)) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + s.methodID, + []interface{}{s.zrc20Address, big.NewInt(1000)}..., + ) + + success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // Check error. + require.NoError(t, err) + + // Unpack and check result boolean. + res, err := s.methodID.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.True(t, ok) + + // balance, err := s.fungibleKeeper.ZRC20BalanceOf(s.ctx, s.zrc20ABI, s.zrc20Address, s.defaultCaller) + // require.NoError(t, err) + + // check it was really distributed + }) +} + +/* + Helpers +*/ + +type testSuite struct { + ctx sdk.Context + contract *Contract + contractABI *abi.ABI + fungibleKeeper *fungiblekeeper.Keeper + sdkKeepers keeper.SDKKeepers + mockEVM *vm.EVM + mockVMContract *vm.Contract + methodID abi.Method + defaultCaller common.Address + defaultLocker common.Address + zrc20Address common.Address + zrc20ABI *abi.ABI +} + +func newTestSuite(t *testing.T) testSuite { + // Initialize basic parameters to mock the chain. + fungibleKeeper, ctx, sdkKeepers, _ := keeper.FungibleKeeper(t) + chainID := getValidChainID(t) + + // Make sure the account store is initialized. + // This is completely needed for accounts to be created in the state. + fungibleKeeper.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName) + + // Deploy system contracts in order to deploy a ZRC20 token. + deploySystemContracts(t, ctx, fungibleKeeper, *sdkKeepers.EvmKeeper) + zrc20Address := setupGasCoin(t, ctx, fungibleKeeper, sdkKeepers.EvmKeeper, chainID, "ZRC20", "ZRC20") + + // Keepers and chain configuration. + var encoding ethermint.EncodingConfig + appCodec := encoding.Codec + gasConfig := storetypes.TransientGasConfig() + + // Create the staking contract. + contract := NewIStakingContract( + ctx, + &sdkKeepers.StakingKeeper, + *fungibleKeeper, + sdkKeepers.BankKeeper, + appCodec, + gasConfig, + ) + require.NotNil(t, contract, "NewIStakingContract() should not return a nil contract") + + accAddress := sdk.AccAddress(ContractAddress.Bytes()) + fungibleKeeper.GetAuthKeeper().SetAccount(ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) + + abi := contract.Abi() + require.NotNil(t, abi, "contract ABI should not be nil") + + address := contract.Address() + require.NotNil(t, address, "contract address should not be nil") + + mockEVM := vm.NewEVM( + vm.BlockContext{}, + vm.TxContext{}, + statedb.New(ctx, sdkKeepers.EvmKeeper, statedb.TxConfig{}), + ¶ms.ChainConfig{}, + vm.Config{}, + ) + + mockVMContract := vm.NewContract( + contractRef{address: common.Address{}}, + contractRef{address: ContractAddress}, + big.NewInt(0), + 0, + ) + + zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() + require.NoError(t, err) + + // Default locker is the bank address. + locker := common.HexToAddress("0x0000000000000000000000000000000000000067") + + // Set default caller. + caller := fungibletypes.ModuleAddressEVM + mockVMContract.CallerAddress = caller + mockEVM.Origin = caller + + return testSuite{ + ctx, + contract, + &abi, + fungibleKeeper, + sdkKeepers, + mockEVM, + mockVMContract, + abi.Methods[DistributeMethodName], + caller, + locker, + zrc20Address, + zrc20ABI, + } +} + +func allowStaking(t *testing.T, ts testSuite, amount *big.Int) { + resAllowance, err := callEVM( + t, + ts.ctx, + ts.fungibleKeeper, + ts.zrc20ABI, + fungibletypes.ModuleAddressEVM, + ts.zrc20Address, + "approve", + []interface{}{ts.contract.Address(), amount}, + ) + require.NoError(t, err, "error allowing staking to spend ZRC20 tokens") + + allowed, ok := resAllowance[0].(bool) + require.True(t, ok) + require.True(t, allowed) +} + +func callEVM( + t *testing.T, + 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 + true, // noEthereumTxEvent + method, // method + args..., // args + ) + require.NoError(t, err, "CallEVM error") + require.Equal(t, "", res.VmError, "res.VmError should be empty") + + ret, err := abi.Methods[method].Outputs.Unpack(res.Ret) + require.NoError(t, err, "Unpack error") + + return ret, nil +} + +// setupGasCoin is a helper function to setup the gas coin for testing +func setupGasCoin( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + evmk *evmkeeper.Keeper, + chainID int64, + assetName string, + symbol string, +) (zrc20 common.Address) { + addr, err := k.SetupChainGasCoinAndPool( + ctx, + chainID, + assetName, + symbol, + 8, + nil, + ) + require.NoError(t, err) + assertContractDeployment(t, *evmk, ctx, addr) + return addr +} + +// get a valid chain id independently of the build flag +func getValidChainID(t *testing.T) int64 { + list := chains.DefaultChainsList() + require.NotEmpty(t, list) + require.NotNil(t, list[0]) + return list[0].ChainId +} + +// require that a contract has been deployed by checking stored code is non-empty. +func assertContractDeployment(t *testing.T, k evmkeeper.Keeper, ctx sdk.Context, contractAddress common.Address) { + acc := k.GetAccount(ctx, contractAddress) + require.NotNil(t, acc) + code := k.GetCode(ctx, common.BytesToHash(acc.CodeHash)) + require.NotEmpty(t, code) +} + +// deploySystemContracts deploys the system contracts and returns their addresses. +func deploySystemContracts( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + evmk evmkeeper.Keeper, +) (wzeta, uniswapV2Factory, uniswapV2Router, connector, systemContract common.Address) { + var err error + + wzeta, err = k.DeployWZETA(ctx) + require.NoError(t, err) + require.NotEmpty(t, wzeta) + assertContractDeployment(t, evmk, ctx, wzeta) + + uniswapV2Factory, err = k.DeployUniswapV2Factory(ctx) + require.NoError(t, err) + require.NotEmpty(t, uniswapV2Factory) + assertContractDeployment(t, evmk, ctx, uniswapV2Factory) + + uniswapV2Router, err = k.DeployUniswapV2Router02(ctx, uniswapV2Factory, wzeta) + require.NoError(t, err) + require.NotEmpty(t, uniswapV2Router) + assertContractDeployment(t, evmk, ctx, uniswapV2Router) + + connector, err = k.DeployConnectorZEVM(ctx, wzeta) + require.NoError(t, err) + require.NotEmpty(t, connector) + assertContractDeployment(t, evmk, ctx, connector) + + systemContract, err = k.DeploySystemContract(ctx, wzeta, uniswapV2Factory, uniswapV2Router) + require.NoError(t, err) + require.NotEmpty(t, systemContract) + assertContractDeployment(t, evmk, ctx, systemContract) + + // deploy the gateway contract + contract := deployGatewayContract(t, ctx, k, &evmk, wzeta, sample.EthAddress()) + require.NotEmpty(t, contract) + + return +} + +// deploy upgradable gateway contract and return its address +func deployGatewayContract( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + evmk *evmkeeper.Keeper, + wzeta, admin common.Address, +) common.Address { + // Deploy the gateway contract + implAddr, err := k.DeployContract(ctx, gatewayzevm.GatewayZEVMMetaData) + require.NoError(t, err) + require.NotEmpty(t, implAddr) + assertContractDeployment(t, *evmk, ctx, implAddr) + + // Deploy the proxy contract + gatewayABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() + require.NoError(t, err) + + // Encode the initializer data + initializerData, err := gatewayABI.Pack("initialize", wzeta, admin) + require.NoError(t, err) + + gatewayContract, err := k.DeployContract(ctx, erc1967proxy.ERC1967ProxyMetaData, implAddr, initializerData) + require.NoError(t, err) + require.NotEmpty(t, gatewayContract) + assertContractDeployment(t, *evmk, ctx, gatewayContract) + + // store the gateway in the system contract object + sys, found := k.GetSystemContract(ctx) + if !found { + sys = fungibletypes.SystemContract{} + } + sys.Gateway = gatewayContract.Hex() + k.SetSystemContract(ctx, sys) + + return gatewayContract +} diff --git a/precompiles/staking/method_get_all_validators_test.go b/precompiles/staking/method_get_all_validators_test.go new file mode 100644 index 0000000000..f09ba30166 --- /dev/null +++ b/precompiles/staking/method_get_all_validators_test.go @@ -0,0 +1,58 @@ +package staking + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/testutil/sample" +) + +func Test_GetAllValidators(t *testing.T) { + t.Run("should return empty array if validators not set", func(t *testing.T) { + // ARRANGE + ts := newTestSuite(t) + validatorsList := ts.sdkKeepers.StakingKeeper.GetAllValidators(ts.ctx) + for _, v := range validatorsList { + fmt.Println(v.OperatorAddress) + ts.sdkKeepers.StakingKeeper.RemoveValidator(ts.ctx, types.ValAddress(v.OperatorAddress)) + } + + methodID := ts.contractABI.Methods[GetAllValidatorsMethodName] + ts.mockVMContract.Input = methodID.ID + + // ACT + validators, err := ts.contract.Run(ts.mockEVM, ts.mockVMContract, false) + + // ASSERT + require.NoError(t, err) + + res, err := methodID.Outputs.Unpack(validators) + require.NoError(t, err) + + require.Empty(t, res[0]) + }) + + t.Run("should return validators if set", func(t *testing.T) { + // ARRANGE + ts := newTestSuite(t) + methodID := ts.contractABI.Methods[GetAllValidatorsMethodName] + ts.mockVMContract.Input = methodID.ID + r := rand.New(rand.NewSource(42)) + validator := sample.Validator(t, r) + ts.sdkKeepers.StakingKeeper.SetValidator(ts.ctx, validator) + + // ACT + validators, err := ts.contract.Run(ts.mockEVM, ts.mockVMContract, false) + + // ASSERT + require.NoError(t, err) + + res, err := methodID.Outputs.Unpack(validators) + require.NoError(t, err) + + require.NotEmpty(t, res[0]) + }) +} diff --git a/precompiles/staking/method_get_shares_test.go b/precompiles/staking/method_get_shares_test.go new file mode 100644 index 0000000000..f40c4c56a2 --- /dev/null +++ b/precompiles/staking/method_get_shares_test.go @@ -0,0 +1,118 @@ +package staking + +import ( + "fmt" + "math/big" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/cmd/zetacored/config" + "github.com/zeta-chain/node/testutil/sample" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +func Test_GetShares(t *testing.T) { + t.Run("should return stakes", func(t *testing.T) { + // ARRANGE + ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + methodID := abi.Methods[GetSharesMethodName] + r := rand.New(rand.NewSource(42)) + validator := sample.Validator(t, r) + sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + coins := sample.Coins() + err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + require.NoError(t, err) + err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + require.NoError(t, err) + + stakerAddr := common.BytesToAddress(staker.Bytes()) + + stakeArgs := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + + stakeMethodID := abi.Methods[StakeMethodName] + + // ACT + _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, stakeArgs) + require.NoError(t, err) + + // ASSERT + args := []interface{}{stakerEthAddr, validator.OperatorAddress} + mockVMContract.Input = packInputArgs(t, methodID, args...) + stakes, err := contract.Run(mockEVM, mockVMContract, false) + require.NoError(t, err) + + res, err := methodID.Outputs.Unpack(stakes) + require.NoError(t, err) + require.Equal( + t, + fmt.Sprintf("%d000000000000000000", coins.AmountOf(config.BaseDenom).BigInt().Int64()), + res[0].(*big.Int).String(), + ) + }) + + t.Run("should fail if wrong args amount", func(t *testing.T) { + // ARRANGE + ctx, contract, abi, _, _, _ := setup(t) + methodID := abi.Methods[GetSharesMethodName] + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + args := []interface{}{stakerEthAddr} + + // ACT + _, err := contract.GetShares(ctx, &methodID, args) + + // ASSERT + require.Error(t, err) + }) + + t.Run("should fail if invalid staker arg", func(t *testing.T) { + // ARRANGE + ctx, contract, abi, _, _, _ := setup(t) + methodID := abi.Methods[GetSharesMethodName] + r := rand.New(rand.NewSource(42)) + validator := sample.Validator(t, r) + args := []interface{}{42, validator.OperatorAddress} + + // ACT + _, err := contract.GetShares(ctx, &methodID, args) + + // ASSERT + require.Error(t, err) + }) + + t.Run("should fail if invalid val address", func(t *testing.T) { + // ARRANGE + ctx, contract, abi, _, _, _ := setup(t) + methodID := abi.Methods[GetSharesMethodName] + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + args := []interface{}{stakerEthAddr, staker.String()} + + // ACT + _, err := contract.GetShares(ctx, &methodID, args) + + // ASSERT + require.Error(t, err) + }) + + t.Run("should fail if invalid val address format", func(t *testing.T) { + // ARRANGE + ctx, contract, abi, _, _, _ := setup(t) + methodID := abi.Methods[GetSharesMethodName] + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + args := []interface{}{stakerEthAddr, 42} + + // ACT + _, err := contract.GetShares(ctx, &methodID, args) + + // ASSERT + require.Error(t, err) + }) +} diff --git a/precompiles/staking/method_move_stake_test.go b/precompiles/staking/method_move_stake_test.go new file mode 100644 index 0000000000..d7eba60429 --- /dev/null +++ b/precompiles/staking/method_move_stake_test.go @@ -0,0 +1,481 @@ +package staking + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/cmd/zetacored/config" + ptypes "github.com/zeta-chain/node/precompiles/types" + "github.com/zeta-chain/node/testutil/sample" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +func Test_MoveStake(t *testing.T) { + // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. + t.Run("should fail with error disabled", func(t *testing.T) { + // ARRANGE + ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + methodID := abi.Methods[MoveStakeMethodName] + r := rand.New(rand.NewSource(42)) + validatorSrc := sample.Validator(t, r) + sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + validatorDest := sample.Validator(t, r) + + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + coins := sample.Coins() + err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + require.NoError(t, err) + err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + require.NoError(t, err) + + stakerAddr := common.BytesToAddress(staker.Bytes()) + mockVMContract.CallerAddress = stakerAddr + + argsStake := []interface{}{ + stakerEthAddr, + validatorSrc.OperatorAddress, + coins.AmountOf(config.BaseDenom).BigInt(), + } + + // stake to validator src + stakeMethodID := abi.Methods[StakeMethodName] + mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) + _, err = contract.Run(mockEVM, mockVMContract, false) + require.Error(t, err) + require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ + Method: StakeMethodName, + }) + + argsMoveStake := []interface{}{ + stakerEthAddr, + validatorSrc.OperatorAddress, + validatorDest.OperatorAddress, + coins.AmountOf(config.BaseDenom).BigInt(), + } + mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + + // ACT + _, err = contract.Run(mockEVM, mockVMContract, false) + + // ASSERT + require.Error(t, err) + require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ + Method: MoveStakeMethodName, + }) + }) + + // t.Run("should fail in read only method", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, true) + + // // ASSERT + // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: MoveStakeMethodName}) + // }) + + // t.Run("should fail if validator dest doesn't exist", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should move stake", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + + // // ACT + // // move stake to validator dest + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.NoError(t, err) + // }) + + // t.Run("should fail if staker is invalid arg", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // 42, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // ACT + // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if validator src is invalid arg", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // 42, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // ACT + // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if validator dest is invalid arg", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // 42, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // ACT + // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if amount is invalid arg", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).Uint64(), + // } + + // // ACT + // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if wrong args amount", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{stakerEthAddr, validatorSrc.OperatorAddress, validatorDest.OperatorAddress} + + // // ACT + // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if caller is not staker", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[MoveStakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validatorSrc := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + // validatorDest := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // argsStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + + // // stake to validator src + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // argsMoveStake := []interface{}{ + // stakerEthAddr, + // validatorSrc.OperatorAddress, + // validatorDest.OperatorAddress, + // coins.AmountOf(config.BaseDenom).BigInt(), + // } + // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + + // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) + // mockVMContract.CallerAddress = callerEthAddr + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.ErrorContains(t, err, "caller is not staker") + // }) +} diff --git a/precompiles/staking/method_stake_test.go b/precompiles/staking/method_stake_test.go new file mode 100644 index 0000000000..9bf7847da0 --- /dev/null +++ b/precompiles/staking/method_stake_test.go @@ -0,0 +1,316 @@ +package staking + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/cmd/zetacored/config" + ptypes "github.com/zeta-chain/node/precompiles/types" + "github.com/zeta-chain/node/testutil/sample" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +func Test_Stake(t *testing.T) { + // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. + t.Run("should fail with error disabled", func(t *testing.T) { + // ARRANGE + ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + methodID := abi.Methods[StakeMethodName] + r := rand.New(rand.NewSource(42)) + validator := sample.Validator(t, r) + + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + coins := sample.Coins() + err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + require.NoError(t, err) + err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + require.NoError(t, err) + + stakerAddr := common.BytesToAddress(staker.Bytes()) + mockVMContract.CallerAddress = stakerAddr + args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + mockVMContract.Input = packInputArgs(t, methodID, args...) + + // ACT + _, err = contract.Run(mockEVM, mockVMContract, false) + + // ASSERT + require.Error(t, err) + require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ + Method: StakeMethodName, + }) + }) + + // t.Run("should fail in read only mode", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, true) + + // // ASSERT + // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: StakeMethodName}) + // }) + + // t.Run("should fail if validator doesn't exist", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should stake", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.NoError(t, err) + // }) + + // t.Run("should fail if no input args", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + // mockVMContract.Input = methodID.ID + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if caller is not staker", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // nonStakerAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) + // args := []interface{}{nonStakerAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.ErrorContains(t, err, "caller is not staker address") + // }) + + // t.Run("should fail if staking fails", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // // staker without funds + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err := contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if wrong args amount", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress} + + // // ACT + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if staker is not eth addr", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + + // // ACT + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if validator is not valid string", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} + + // // ACT + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if amount is not int64", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[StakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} + + // // ACT + // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) +} diff --git a/precompiles/staking/method_unstake_test.go b/precompiles/staking/method_unstake_test.go new file mode 100644 index 0000000000..e2a71846d5 --- /dev/null +++ b/precompiles/staking/method_unstake_test.go @@ -0,0 +1,311 @@ +package staking + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/cmd/zetacored/config" + ptypes "github.com/zeta-chain/node/precompiles/types" + "github.com/zeta-chain/node/testutil/sample" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +func Test_Unstake(t *testing.T) { + // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. + t.Run("should fail with error disabled", func(t *testing.T) { + // ARRANGE + ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + methodID := abi.Methods[UnstakeMethodName] + r := rand.New(rand.NewSource(42)) + validator := sample.Validator(t, r) + + staker := sample.Bech32AccAddress() + stakerEthAddr := common.BytesToAddress(staker.Bytes()) + coins := sample.Coins() + err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + require.NoError(t, err) + err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + require.NoError(t, err) + + stakerAddr := common.BytesToAddress(staker.Bytes()) + mockVMContract.CallerAddress = stakerAddr + + args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + mockVMContract.Input = packInputArgs(t, methodID, args...) + + // ACT + _, err = contract.Run(mockEVM, mockVMContract, false) + + // ASSERT + require.Error(t, err) + require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ + Method: UnstakeMethodName, + }) + }) + + // t.Run("should fail in read only method", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, true) + + // // ASSERT + // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: UnstakeMethodName}) + // }) + + // t.Run("should fail if validator doesn't exist", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should unstake", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + + // // stake first + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // // ACT + // mockVMContract.Input = packInputArgs(t, methodID, args...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.NoError(t, err) + // }) + + // t.Run("should fail if caller is not staker", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // // stake first + // stakeMethodID := abi.Methods[StakeMethodName] + // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) + // _, err = contract.Run(mockEVM, mockVMContract, false) + // require.NoError(t, err) + + // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) + // mockVMContract.CallerAddress = callerEthAddr + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.ErrorContains(t, err, "caller is not staker address") + // }) + + // t.Run("should fail if no previous staking", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + // mockVMContract.CallerAddress = stakerAddr + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + // mockVMContract.Input = packInputArgs(t, methodID, args...) + + // // ACT + // _, err = contract.Run(mockEVM, mockVMContract, false) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if wrong args amount", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress} + + // // ACT + // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if staker is not eth addr", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + + // // ACT + // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if validator is not valid string", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} + + // // ACT + // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) + + // t.Run("should fail if amount is not int64", func(t *testing.T) { + // // ARRANGE + // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) + // methodID := abi.Methods[UnstakeMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} + + // // ACT + // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) + + // // ASSERT + // require.Error(t, err) + // }) +} diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index b5b4164e5f..dbb1b6d482 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -4,6 +4,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" "github.com/ethereum/go-ethereum/accounts/abi" @@ -71,12 +72,18 @@ type Contract struct { } func NewIStakingContract( + ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper, fungibleKeeper fungiblekeeper.Keeper, bankKeeper bankkeeper.Keeper, cdc codec.Codec, kvGasConfig storetypes.GasConfig, ) *Contract { + accAddress := sdk.AccAddress(ContractAddress.Bytes()) + if fungibleKeeper.GetAuthKeeper().GetAccount(ctx, accAddress) == nil { + fungibleKeeper.GetAuthKeeper().SetAccount(ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) + } + // Instantiate the ZRC20 ABI only one time. // This avoids instantiating it every time deposit or withdraw are called. zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() @@ -174,6 +181,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt Method: method.Name, } + //nolint:govet var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { res, err = c.Stake(ctx, evm, contract, method, args) @@ -189,6 +197,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt Method: method.Name, } + //nolint:govet var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { res, err = c.Unstake(ctx, evm, contract, method, args) @@ -204,6 +213,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt Method: method.Name, } + //nolint:govet var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { res, err = c.MoveStake(ctx, evm, contract, method, args) @@ -220,7 +230,12 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return err }) if execErr != nil { - return nil, err + res, errPack := method.Outputs.Pack(false) + if errPack != nil { + return nil, errPack + } + + return res, err } return res, nil default: diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index 1e292b1a35..200e70c8a6 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -1,1475 +1,272 @@ package staking -// import ( -// "encoding/json" -// "fmt" -// "testing" - -// "math/big" -// "math/rand" - -// tmdb "github.com/cometbft/cometbft-db" -// "github.com/cosmos/cosmos-sdk/store" - -// storetypes "github.com/cosmos/cosmos-sdk/store/types" -// sdk "github.com/cosmos/cosmos-sdk/types" -// stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" -// "github.com/ethereum/go-ethereum/accounts/abi" -// "github.com/ethereum/go-ethereum/common" -// "github.com/ethereum/go-ethereum/core/vm" -// "github.com/ethereum/go-ethereum/params" -// "github.com/stretchr/testify/require" -// ethermint "github.com/zeta-chain/ethermint/types" -// "github.com/zeta-chain/ethermint/x/evm/statedb" -// "github.com/zeta-chain/node/cmd/zetacored/config" -// "github.com/zeta-chain/node/precompiles/prototype" -// ptypes "github.com/zeta-chain/node/precompiles/types" -// "github.com/zeta-chain/node/testutil/keeper" - -// "github.com/zeta-chain/node/testutil/sample" -// fungibletypes "github.com/zeta-chain/node/x/fungible/types" -// ) - -// func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *vm.EVM, *vm.Contract) { -// var encoding ethermint.EncodingConfig -// appCodec := encoding.Codec - -// cdc := keeper.NewCodec() - -// db := tmdb.NewMemDB() -// stateStore := store.NewCommitMultiStore(db) -// keys, memKeys, tkeys, allKeys := keeper.StoreKeys() -// sdkKeepers := keeper.NewSDKKeepersWithKeys(cdc, keys, memKeys, tkeys, allKeys) -// for _, key := range keys { -// stateStore.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) -// } -// for _, key := range tkeys { -// stateStore.MountStoreWithDB(key, storetypes.StoreTypeTransient, nil) -// } -// for _, key := range memKeys { -// stateStore.MountStoreWithDB(key, storetypes.StoreTypeMemory, nil) -// } - -// gasConfig := storetypes.TransientGasConfig() -// ctx := keeper.NewContext(stateStore) - -// require.NoError(t, stateStore.LoadLatestVersion()) - -// stakingGenesisState := stakingtypes.DefaultGenesisState() -// stakingGenesisState.Params.BondDenom = config.BaseDenom -// sdkKeepers.StakingKeeper.InitGenesis(ctx, stakingGenesisState) - -// contract := NewIStakingContract(&sdkKeepers.StakingKeeper, appCodec, gasConfig) -// require.NotNil(t, contract, "NewIStakingContract() should not return a nil contract") - -// abi := contract.Abi() -// require.NotNil(t, abi, "contract ABI should not be nil") - -// address := contract.Address() -// require.NotNil(t, address, "contract address should not be nil") - -// mockEVM := vm.NewEVM( -// vm.BlockContext{}, -// vm.TxContext{}, -// statedb.New(ctx, sdkKeepers.EvmKeeper, statedb.TxConfig{}), -// ¶ms.ChainConfig{}, -// vm.Config{}, -// ) -// mockVMContract := vm.NewContract( -// contractRef{address: common.Address{}}, -// contractRef{address: ContractAddress}, -// big.NewInt(0), -// 0, -// ) -// return ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract -// } - -// func packInputArgs(t *testing.T, methodID abi.Method, args ...interface{}) []byte { -// input, err := methodID.Inputs.Pack(args...) -// require.NoError(t, err) -// return append(methodID.ID, input...) -// } - -// type contractRef struct { -// address common.Address -// } - -// func (c contractRef) Address() common.Address { -// return c.address -// } - -// func Test_IStakingContract(t *testing.T) { -// _, contract, abi, _, _, _ := setup(t) -// gasConfig := storetypes.TransientGasConfig() - -// t.Run("should check methods are present in ABI", func(t *testing.T) { -// require.NotNil(t, abi.Methods[StakeMethodName], "stake method should be present in the ABI") -// require.NotNil(t, abi.Methods[UnstakeMethodName], "unstake method should be present in the ABI") -// require.NotNil( -// t, -// abi.Methods[MoveStakeMethodName], -// "moveStake method should be present in the ABI", -// ) - -// require.NotNil( -// t, -// abi.Methods[GetAllValidatorsMethodName], -// "getAllValidators method should be present in the ABI", -// ) -// require.NotNil(t, abi.Methods[GetSharesMethodName], "getShares method should be present in the ABI") -// }) - -// t.Run("should check gas requirements for methods", func(t *testing.T) { -// var method [4]byte - -// t.Run("stake", func(t *testing.T) { -// // ACT -// stake := contract.RequiredGas(abi.Methods[StakeMethodName].ID) -// // ASSERT -// copy(method[:], abi.Methods[StakeMethodName].ID[:4]) -// baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte -// require.Equal( -// t, -// GasRequiredByMethod[method]+baseCost, -// stake, -// "stake method should require %d gas, got %d", -// GasRequiredByMethod[method]+baseCost, -// stake, -// ) -// }) - -// t.Run("unstake", func(t *testing.T) { -// // ACT -// unstake := contract.RequiredGas(abi.Methods[UnstakeMethodName].ID) -// // ASSERT -// copy(method[:], abi.Methods[UnstakeMethodName].ID[:4]) -// baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte -// require.Equal( -// t, -// GasRequiredByMethod[method]+baseCost, -// unstake, -// "unstake method should require %d gas, got %d", -// GasRequiredByMethod[method]+baseCost, -// unstake, -// ) -// }) - -// t.Run("moveStake", func(t *testing.T) { -// // ACT -// moveStake := contract.RequiredGas(abi.Methods[MoveStakeMethodName].ID) -// // ASSERT -// copy(method[:], abi.Methods[MoveStakeMethodName].ID[:4]) -// baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte -// require.Equal( -// t, -// GasRequiredByMethod[method]+baseCost, -// moveStake, -// "moveStake method should require %d gas, got %d", -// GasRequiredByMethod[method]+baseCost, -// moveStake, -// ) -// }) - -// t.Run("getAllValidators", func(t *testing.T) { -// // ACT -// getAllValidators := contract.RequiredGas(abi.Methods[GetAllValidatorsMethodName].ID) -// // ASSERT -// copy(method[:], abi.Methods[GetAllValidatorsMethodName].ID[:4]) -// baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte -// require.Equal( -// t, -// GasRequiredByMethod[method]+baseCost, -// getAllValidators, -// "getAllValidators method should require %d gas, got %d", -// GasRequiredByMethod[method]+baseCost, -// getAllValidators, -// ) -// }) - -// t.Run("getShares", func(t *testing.T) { -// // ACT -// getShares := contract.RequiredGas(abi.Methods[GetSharesMethodName].ID) -// // ASSERT -// copy(method[:], abi.Methods[GetSharesMethodName].ID[:4]) -// baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte -// require.Equal( -// t, -// GasRequiredByMethod[method]+baseCost, -// getShares, -// "getShares method should require %d gas, got %d", -// GasRequiredByMethod[method]+baseCost, -// getShares, -// ) -// }) - -// t.Run("invalid method", func(t *testing.T) { -// // ARRANGE -// invalidMethodBytes := []byte("invalidMethod") -// // ACT -// gasInvalidMethod := contract.RequiredGas(invalidMethodBytes) -// // ASSERT -// require.Equal( -// t, -// uint64(0), -// gasInvalidMethod, -// "invalid method should require %d gas, got %d", -// uint64(0), -// gasInvalidMethod, -// ) -// }) -// }) -// } - -// func Test_InvalidMethod(t *testing.T) { -// _, _, abi, _, _, _ := setup(t) - -// _, doNotExist := abi.Methods["invalidMethod"] -// require.False(t, doNotExist, "invalidMethod should not be present in the ABI") -// } - -// func Test_InvalidABI(t *testing.T) { -// IStakingMetaData.ABI = "invalid json" -// defer func() { -// if r := recover(); r != nil { -// require.IsType(t, &json.SyntaxError{}, r, "expected error type: json.SyntaxError, got: %T", r) -// } -// }() - -// initABI() -// } - -// func Test_Stake(t *testing.T) { -// // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. -// t.Run("should fail with error disabled", func(t *testing.T) { -// // ARRANGE -// ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// methodID := abi.Methods[StakeMethodName] -// r := rand.New(rand.NewSource(42)) -// validator := sample.Validator(t, r) - -// staker := sample.Bech32AccAddress() -// stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// coins := sample.Coins() -// err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// require.NoError(t, err) -// err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// require.NoError(t, err) - -// stakerAddr := common.BytesToAddress(staker.Bytes()) -// mockVMContract.CallerAddress = stakerAddr -// args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} -// mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // ACT -// _, err = contract.Run(mockEVM, mockVMContract, false) - -// // ASSERT -// require.Error(t, err) -// require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ -// Method: StakeMethodName, -// }) -// }) - -// // t.Run("should fail in read only mode", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[StakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} -// // mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, true) - -// // // ASSERT -// // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: StakeMethodName}) -// // }) - -// // t.Run("should fail if validator doesn't exist", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[StakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} -// // mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should stake", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[StakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} -// // mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.NoError(t, err) -// // }) - -// // t.Run("should fail if no input args", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[StakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr -// // mockVMContract.Input = methodID.ID - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if caller is not staker", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[StakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr - -// // nonStakerAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) -// // args := []interface{}{nonStakerAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} -// // mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.ErrorContains(t, err, "caller is not staker address") -// // }) - -// // t.Run("should fail if staking fails", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[StakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // // staker without funds -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr - -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} -// // mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // // ACT -// // _, err := contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if wrong args amount", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[StakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress} - -// // // ACT -// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if staker is not eth addr", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[StakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - -// // // ACT -// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if validator is not valid string", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[StakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} - -// // // ACT -// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if amount is not int64", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[StakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} - -// // // ACT -// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - -// // // ASSERT -// // require.Error(t, err) -// // }) -// } - -// func Test_Unstake(t *testing.T) { -// // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. -// t.Run("should fail with error disabled", func(t *testing.T) { -// // ARRANGE -// ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// methodID := abi.Methods[UnstakeMethodName] -// r := rand.New(rand.NewSource(42)) -// validator := sample.Validator(t, r) - -// staker := sample.Bech32AccAddress() -// stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// coins := sample.Coins() -// err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// require.NoError(t, err) -// err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// require.NoError(t, err) - -// stakerAddr := common.BytesToAddress(staker.Bytes()) -// mockVMContract.CallerAddress = stakerAddr - -// args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} -// mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // ACT -// _, err = contract.Run(mockEVM, mockVMContract, false) - -// // ASSERT -// require.Error(t, err) -// require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ -// Method: UnstakeMethodName, -// }) -// }) - -// // t.Run("should fail in read only method", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[UnstakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr - -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} -// // mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, true) - -// // // ASSERT -// // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: UnstakeMethodName}) -// // }) - -// // t.Run("should fail if validator doesn't exist", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[UnstakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr - -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} -// // mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should unstake", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[UnstakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr - -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - -// // // stake first -// // stakeMethodID := abi.Methods[StakeMethodName] -// // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) -// // _, err = contract.Run(mockEVM, mockVMContract, false) -// // require.NoError(t, err) - -// // // ACT -// // mockVMContract.Input = packInputArgs(t, methodID, args...) -// // _, err = contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.NoError(t, err) -// // }) - -// // t.Run("should fail if caller is not staker", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[UnstakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr - -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} -// // // stake first -// // stakeMethodID := abi.Methods[StakeMethodName] -// // mockVMContract.Input = packInputArgs(t, stakeMethodID, args...) -// // _, err = contract.Run(mockEVM, mockVMContract, false) -// // require.NoError(t, err) - -// // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) -// // mockVMContract.CallerAddress = callerEthAddr -// // mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.ErrorContains(t, err, "caller is not staker address") -// // }) - -// // t.Run("should fail if no previous staking", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[UnstakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr - -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} -// // mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if wrong args amount", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[UnstakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress} - -// // // ACT -// // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if staker is not eth addr", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[UnstakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // args := []interface{}{staker, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - -// // // ACT -// // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if validator is not valid string", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[UnstakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // args := []interface{}{stakerEthAddr, 42, coins.AmountOf(config.BaseDenom).BigInt()} - -// // // ACT -// // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if amount is not int64", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[UnstakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validator := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).Uint64()} - -// // // ACT -// // _, err = contract.Unstake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, args) - -// // // ASSERT -// // require.Error(t, err) -// // }) -// } - -// func Test_MoveStake(t *testing.T) { -// // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. -// t.Run("should fail with error disabled", func(t *testing.T) { -// // ARRANGE -// ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// methodID := abi.Methods[MoveStakeMethodName] -// r := rand.New(rand.NewSource(42)) -// validatorSrc := sample.Validator(t, r) -// sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) -// validatorDest := sample.Validator(t, r) - -// staker := sample.Bech32AccAddress() -// stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// coins := sample.Coins() -// err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// require.NoError(t, err) -// err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// require.NoError(t, err) - -// stakerAddr := common.BytesToAddress(staker.Bytes()) -// mockVMContract.CallerAddress = stakerAddr - -// argsStake := []interface{}{ -// stakerEthAddr, -// validatorSrc.OperatorAddress, -// coins.AmountOf(config.BaseDenom).BigInt(), -// } - -// // stake to validator src -// stakeMethodID := abi.Methods[StakeMethodName] -// mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) -// _, err = contract.Run(mockEVM, mockVMContract, false) -// require.Error(t, err) -// require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ -// Method: StakeMethodName, -// }) - -// argsMoveStake := []interface{}{ -// stakerEthAddr, -// validatorSrc.OperatorAddress, -// validatorDest.OperatorAddress, -// coins.AmountOf(config.BaseDenom).BigInt(), -// } -// mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - -// // ACT -// _, err = contract.Run(mockEVM, mockVMContract, false) - -// // ASSERT -// require.Error(t, err) -// require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ -// Method: MoveStakeMethodName, -// }) -// }) - -// // t.Run("should fail in read only method", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[MoveStakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validatorSrc := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) -// // validatorDest := sample.Validator(t, r) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr - -// // argsStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // stake to validator src -// // stakeMethodID := abi.Methods[StakeMethodName] -// // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) -// // _, err = contract.Run(mockEVM, mockVMContract, false) -// // require.NoError(t, err) - -// // argsMoveStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // validatorDest.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } -// // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, true) - -// // // ASSERT -// // require.ErrorIs(t, err, ptypes.ErrWriteMethod{Method: MoveStakeMethodName}) -// // }) - -// // t.Run("should fail if validator dest doesn't exist", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[MoveStakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validatorSrc := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) -// // validatorDest := sample.Validator(t, r) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr - -// // argsStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // stake to validator src -// // stakeMethodID := abi.Methods[StakeMethodName] -// // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) -// // _, err = contract.Run(mockEVM, mockVMContract, false) -// // require.NoError(t, err) - -// // argsMoveStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // validatorDest.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } -// // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should move stake", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[MoveStakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validatorSrc := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) -// // validatorDest := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr -// // argsStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // stake to validator src -// // stakeMethodID := abi.Methods[StakeMethodName] -// // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) -// // _, err = contract.Run(mockEVM, mockVMContract, false) -// // require.NoError(t, err) - -// // argsMoveStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // validatorDest.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } -// // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - -// // // ACT -// // // move stake to validator dest -// // _, err = contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.NoError(t, err) -// // }) - -// // t.Run("should fail if staker is invalid arg", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[MoveStakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validatorSrc := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) -// // validatorDest := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // argsStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // stake to validator src -// // stakeMethodID := abi.Methods[StakeMethodName] -// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) -// // require.NoError(t, err) - -// // argsMoveStake := []interface{}{ -// // 42, -// // validatorSrc.OperatorAddress, -// // validatorDest.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // ACT -// // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if validator src is invalid arg", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[MoveStakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validatorSrc := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) -// // validatorDest := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // argsStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // stake to validator src -// // stakeMethodID := abi.Methods[StakeMethodName] -// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) -// // require.NoError(t, err) - -// // argsMoveStake := []interface{}{ -// // stakerEthAddr, -// // 42, -// // validatorDest.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // ACT -// // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if validator dest is invalid arg", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[MoveStakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validatorSrc := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) -// // validatorDest := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // argsStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // stake to validator src -// // stakeMethodID := abi.Methods[StakeMethodName] -// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) -// // require.NoError(t, err) - -// // argsMoveStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // 42, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // ACT -// // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if amount is invalid arg", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[MoveStakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validatorSrc := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) -// // validatorDest := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // argsStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // stake to validator src -// // stakeMethodID := abi.Methods[StakeMethodName] -// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) -// // require.NoError(t, err) - -// // argsMoveStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // validatorDest.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).Uint64(), -// // } - -// // // ACT -// // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if wrong args amount", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, _ := setup(t) -// // methodID := abi.Methods[MoveStakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validatorSrc := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) -// // validatorDest := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) - -// // argsStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // stake to validator src -// // stakeMethodID := abi.Methods[StakeMethodName] -// // _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, argsStake) -// // require.NoError(t, err) - -// // argsMoveStake := []interface{}{stakerEthAddr, validatorSrc.OperatorAddress, validatorDest.OperatorAddress} - -// // // ACT -// // _, err = contract.MoveStake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &methodID, argsMoveStake) - -// // // ASSERT -// // require.Error(t, err) -// // }) - -// // t.Run("should fail if caller is not staker", func(t *testing.T) { -// // // ARRANGE -// // ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// // methodID := abi.Methods[MoveStakeMethodName] -// // r := rand.New(rand.NewSource(42)) -// // validatorSrc := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) -// // validatorDest := sample.Validator(t, r) -// // sdkKeepers.StakingKeeper.SetValidator(ctx, validatorDest) - -// // staker := sample.Bech32AccAddress() -// // stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// // coins := sample.Coins() -// // err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// // require.NoError(t, err) -// // err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// // require.NoError(t, err) - -// // stakerAddr := common.BytesToAddress(staker.Bytes()) -// // mockVMContract.CallerAddress = stakerAddr -// // argsStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } - -// // // stake to validator src -// // stakeMethodID := abi.Methods[StakeMethodName] -// // mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) -// // _, err = contract.Run(mockEVM, mockVMContract, false) -// // require.NoError(t, err) - -// // argsMoveStake := []interface{}{ -// // stakerEthAddr, -// // validatorSrc.OperatorAddress, -// // validatorDest.OperatorAddress, -// // coins.AmountOf(config.BaseDenom).BigInt(), -// // } -// // mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) - -// // callerEthAddr := common.BytesToAddress(sample.Bech32AccAddress().Bytes()) -// // mockVMContract.CallerAddress = callerEthAddr - -// // // ACT -// // _, err = contract.Run(mockEVM, mockVMContract, false) - -// // // ASSERT -// // require.ErrorContains(t, err, "caller is not staker") -// // }) -// } - -// func Test_GetAllValidators(t *testing.T) { -// t.Run("should return empty array if validators not set", func(t *testing.T) { -// // ARRANGE -// _, contract, abi, _, mockEVM, mockVMContract := setup(t) -// methodID := abi.Methods[GetAllValidatorsMethodName] -// mockVMContract.Input = methodID.ID - -// // ACT -// validators, err := contract.Run(mockEVM, mockVMContract, false) - -// // ASSERT -// require.NoError(t, err) - -// res, err := methodID.Outputs.Unpack(validators) -// require.NoError(t, err) - -// require.Empty(t, res[0]) -// }) - -// t.Run("should return validators if set", func(t *testing.T) { -// // ARRANGE -// ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// methodID := abi.Methods[GetAllValidatorsMethodName] -// mockVMContract.Input = methodID.ID -// r := rand.New(rand.NewSource(42)) -// validator := sample.Validator(t, r) -// sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// // ACT -// validators, err := contract.Run(mockEVM, mockVMContract, false) - -// // ASSERT -// require.NoError(t, err) - -// res, err := methodID.Outputs.Unpack(validators) -// require.NoError(t, err) - -// require.NotEmpty(t, res[0]) -// }) -// } - -// func Test_GetShares(t *testing.T) { -// t.Run("should return stakes", func(t *testing.T) { -// // ARRANGE -// ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) -// methodID := abi.Methods[GetSharesMethodName] -// r := rand.New(rand.NewSource(42)) -// validator := sample.Validator(t, r) -// sdkKeepers.StakingKeeper.SetValidator(ctx, validator) - -// staker := sample.Bech32AccAddress() -// stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// coins := sample.Coins() -// err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) -// require.NoError(t, err) -// err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) -// require.NoError(t, err) - -// stakerAddr := common.BytesToAddress(staker.Bytes()) - -// stakeArgs := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - -// stakeMethodID := abi.Methods[StakeMethodName] - -// // ACT -// _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, stakeArgs) -// require.NoError(t, err) - -// // ASSERT -// args := []interface{}{stakerEthAddr, validator.OperatorAddress} -// mockVMContract.Input = packInputArgs(t, methodID, args...) -// stakes, err := contract.Run(mockEVM, mockVMContract, false) -// require.NoError(t, err) - -// res, err := methodID.Outputs.Unpack(stakes) -// require.NoError(t, err) -// require.Equal( -// t, -// fmt.Sprintf("%d000000000000000000", coins.AmountOf(config.BaseDenom).BigInt().Int64()), -// res[0].(*big.Int).String(), -// ) -// }) - -// t.Run("should fail if wrong args amount", func(t *testing.T) { -// // ARRANGE -// ctx, contract, abi, _, _, _ := setup(t) -// methodID := abi.Methods[GetSharesMethodName] -// staker := sample.Bech32AccAddress() -// stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// args := []interface{}{stakerEthAddr} - -// // ACT -// _, err := contract.GetShares(ctx, &methodID, args) - -// // ASSERT -// require.Error(t, err) -// }) - -// t.Run("should fail if invalid staker arg", func(t *testing.T) { -// // ARRANGE -// ctx, contract, abi, _, _, _ := setup(t) -// methodID := abi.Methods[GetSharesMethodName] -// r := rand.New(rand.NewSource(42)) -// validator := sample.Validator(t, r) -// args := []interface{}{42, validator.OperatorAddress} - -// // ACT -// _, err := contract.GetShares(ctx, &methodID, args) - -// // ASSERT -// require.Error(t, err) -// }) - -// t.Run("should fail if invalid val address", func(t *testing.T) { -// // ARRANGE -// ctx, contract, abi, _, _, _ := setup(t) -// methodID := abi.Methods[GetSharesMethodName] -// staker := sample.Bech32AccAddress() -// stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// args := []interface{}{stakerEthAddr, staker.String()} - -// // ACT -// _, err := contract.GetShares(ctx, &methodID, args) - -// // ASSERT -// require.Error(t, err) -// }) - -// t.Run("should fail if invalid val address format", func(t *testing.T) { -// // ARRANGE -// ctx, contract, abi, _, _, _ := setup(t) -// methodID := abi.Methods[GetSharesMethodName] -// staker := sample.Bech32AccAddress() -// stakerEthAddr := common.BytesToAddress(staker.Bytes()) -// args := []interface{}{stakerEthAddr, 42} - -// // ACT -// _, err := contract.GetShares(ctx, &methodID, args) - -// // ASSERT -// require.Error(t, err) -// }) -// } - -// func Test_RunInvalidMethod(t *testing.T) { -// // ARRANGE -// _, contract, _, _, mockEVM, mockVMContract := setup(t) -// k, _, _, _ := keeper.FungibleKeeper(t) - -// var encoding ethermint.EncodingConfig -// appCodec := encoding.Codec -// gasConfig := storetypes.TransientGasConfig() - -// prototype := prototype.NewIPrototypeContract(k, appCodec, gasConfig) - -// prototypeAbi := prototype.Abi() -// methodID := prototypeAbi.Methods["bech32ToHexAddr"] -// args := []interface{}{"123"} -// mockVMContract.Input = packInputArgs(t, methodID, args...) - -// // ACT -// _, err := contract.Run(mockEVM, mockVMContract, false) - -// // ASSERT -// require.Error(t, err) -// } +import ( + "encoding/json" + "testing" + + "math/big" + + tmdb "github.com/cometbft/cometbft-db" + "github.com/cosmos/cosmos-sdk/store" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" + ethermint "github.com/zeta-chain/ethermint/types" + "github.com/zeta-chain/ethermint/x/evm/statedb" + "github.com/zeta-chain/node/cmd/zetacored/config" + "github.com/zeta-chain/node/precompiles/prototype" + "github.com/zeta-chain/node/testutil/keeper" +) + +func Test_IStakingContract(t *testing.T) { + s := newTestSuite(t) + gasConfig := storetypes.TransientGasConfig() + + t.Run("should check methods are present in ABI", func(t *testing.T) { + require.NotNil(t, s.contractABI.Methods[StakeMethodName], "stake method should be present in the ABI") + require.NotNil(t, s.contractABI.Methods[UnstakeMethodName], "unstake method should be present in the ABI") + require.NotNil( + t, + s.contractABI.Methods[MoveStakeMethodName], + "moveStake method should be present in the ABI", + ) + + require.NotNil( + t, + s.contractABI.Methods[GetAllValidatorsMethodName], + "getAllValidators method should be present in the ABI", + ) + require.NotNil(t, s.contractABI.Methods[GetSharesMethodName], "getShares method should be present in the ABI") + }) + + t.Run("should check gas requirements for methods", func(t *testing.T) { + var method [4]byte + + t.Run("stake", func(t *testing.T) { + // ACT + stake := s.contract.RequiredGas(s.contractABI.Methods[StakeMethodName].ID) + // ASSERT + copy(method[:], s.contractABI.Methods[StakeMethodName].ID[:4]) + baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte + require.Equal( + t, + GasRequiredByMethod[method]+baseCost, + stake, + "stake method should require %d gas, got %d", + GasRequiredByMethod[method]+baseCost, + stake, + ) + }) + + t.Run("unstake", func(t *testing.T) { + // ACT + unstake := s.contract.RequiredGas(s.contractABI.Methods[UnstakeMethodName].ID) + // ASSERT + copy(method[:], s.contractABI.Methods[UnstakeMethodName].ID[:4]) + baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte + require.Equal( + t, + GasRequiredByMethod[method]+baseCost, + unstake, + "unstake method should require %d gas, got %d", + GasRequiredByMethod[method]+baseCost, + unstake, + ) + }) + + t.Run("moveStake", func(t *testing.T) { + // ACT + moveStake := s.contract.RequiredGas(s.contractABI.Methods[MoveStakeMethodName].ID) + // ASSERT + copy(method[:], s.contractABI.Methods[MoveStakeMethodName].ID[:4]) + baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte + require.Equal( + t, + GasRequiredByMethod[method]+baseCost, + moveStake, + "moveStake method should require %d gas, got %d", + GasRequiredByMethod[method]+baseCost, + moveStake, + ) + }) + + t.Run("getAllValidators", func(t *testing.T) { + // ACT + getAllValidators := s.contract.RequiredGas(s.contractABI.Methods[GetAllValidatorsMethodName].ID) + // ASSERT + copy(method[:], s.contractABI.Methods[GetAllValidatorsMethodName].ID[:4]) + baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte + require.Equal( + t, + GasRequiredByMethod[method]+baseCost, + getAllValidators, + "getAllValidators method should require %d gas, got %d", + GasRequiredByMethod[method]+baseCost, + getAllValidators, + ) + }) + + t.Run("getShares", func(t *testing.T) { + // ACT + getShares := s.contract.RequiredGas(s.contractABI.Methods[GetSharesMethodName].ID) + // ASSERT + copy(method[:], s.contractABI.Methods[GetSharesMethodName].ID[:4]) + baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte + require.Equal( + t, + GasRequiredByMethod[method]+baseCost, + getShares, + "getShares method should require %d gas, got %d", + GasRequiredByMethod[method]+baseCost, + getShares, + ) + }) + + t.Run("invalid method", func(t *testing.T) { + // ARRANGE + invalidMethodBytes := []byte("invalidMethod") + // ACT + gasInvalidMethod := s.contract.RequiredGas(invalidMethodBytes) + // ASSERT + require.Equal( + t, + uint64(0), + gasInvalidMethod, + "invalid method should require %d gas, got %d", + uint64(0), + gasInvalidMethod, + ) + }) + }) +} + +func Test_InvalidMethod(t *testing.T) { + s := newTestSuite(t) + + _, doNotExist := s.contractABI.Methods["invalidMethod"] + require.False(t, doNotExist, "invalidMethod should not be present in the ABI") +} + +func Test_InvalidABI(t *testing.T) { + IStakingMetaData.ABI = "invalid json" + defer func() { + if r := recover(); r != nil { + require.IsType(t, &json.SyntaxError{}, r, "expected error type: json.SyntaxError, got: %T", r) + } + }() + + initABI() +} + +func Test_RunInvalidMethod(t *testing.T) { + // ARRANGE + s := newTestSuite(t) + + var encoding ethermint.EncodingConfig + appCodec := encoding.Codec + gasConfig := storetypes.TransientGasConfig() + + prototype := prototype.NewIPrototypeContract(s.fungibleKeeper, appCodec, gasConfig) + + prototypeAbi := prototype.Abi() + methodID := prototypeAbi.Methods["bech32ToHexAddr"] + args := []interface{}{"123"} + s.mockVMContract.Input = packInputArgs(t, methodID, args...) + + // ACT + _, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + + // ASSERT + require.Error(t, err) +} + +func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *vm.EVM, *vm.Contract) { + // Initialize state. + // Get sdk keepers initialized with this state and the context. + cdc := keeper.NewCodec() + db := tmdb.NewMemDB() + stateStore := store.NewCommitMultiStore(db) + keys, memKeys, tkeys, allKeys := keeper.StoreKeys() + + sdkKeepers := keeper.NewSDKKeepersWithKeys(cdc, keys, memKeys, tkeys, allKeys) + + for _, key := range keys { + stateStore.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) + } + for _, key := range tkeys { + stateStore.MountStoreWithDB(key, storetypes.StoreTypeTransient, nil) + } + for _, key := range memKeys { + stateStore.MountStoreWithDB(key, storetypes.StoreTypeMemory, nil) + } + + require.NoError(t, stateStore.LoadLatestVersion()) + + ctx := keeper.NewContext(stateStore) + + // Intiliaze codecs and gas config. + var encoding ethermint.EncodingConfig + appCodec := encoding.Codec + gasConfig := storetypes.TransientGasConfig() + + stakingGenesisState := stakingtypes.DefaultGenesisState() + stakingGenesisState.Params.BondDenom = config.BaseDenom + sdkKeepers.StakingKeeper.InitGenesis(ctx, stakingGenesisState) + + // Get the fungible keeper. + fungibleKeeper, _, _, _ := keeper.FungibleKeeper(t) + + // Initialize staking contract. + contract := NewIStakingContract( + ctx, + &sdkKeepers.StakingKeeper, + *fungibleKeeper, + sdkKeepers.BankKeeper, + appCodec, + gasConfig, + ) + require.NotNil(t, contract, "NewIStakingContract() should not return a nil contract") + + abi := contract.Abi() + require.NotNil(t, abi, "contract ABI should not be nil") + + address := contract.Address() + require.NotNil(t, address, "contract address should not be nil") + + mockEVM := vm.NewEVM( + vm.BlockContext{}, + vm.TxContext{}, + statedb.New(ctx, sdkKeepers.EvmKeeper, statedb.TxConfig{}), + ¶ms.ChainConfig{}, + vm.Config{}, + ) + + mockVMContract := vm.NewContract( + contractRef{address: common.Address{}}, + contractRef{address: ContractAddress}, + big.NewInt(0), + 0, + ) + + return ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract +} + +func packInputArgs(t *testing.T, methodID abi.Method, args ...interface{}) []byte { + input, err := methodID.Inputs.Pack(args...) + require.NoError(t, err) + return append(methodID.ID, input...) +} + +type contractRef struct { + address common.Address +} + +func (c contractRef) Address() common.Address { + return c.address +} diff --git a/precompiles/types/address.go b/precompiles/types/address.go index 07eb25fb8b..d587b550fa 100644 --- a/precompiles/types/address.go +++ b/precompiles/types/address.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" ) -// getEVMCallerAddress returns the caller address. +// GetEVMCallerAddress returns the caller address. // Usually the caller is the contract.CallerAddress, which is the address of the contract that called the precompiled contract. // If contract.CallerAddress != evm.Origin is true, it means the call was made through a contract, // on which case there is a need to set the caller to the evm.Origin. @@ -20,7 +20,7 @@ func GetEVMCallerAddress(evm *vm.EVM, contract *vm.Contract) (common.Address, er return caller, nil } -// getCosmosAddress returns the counterpart cosmos address of the given ethereum address. +// GetCosmosAddress returns the counterpart cosmos address of the given ethereum address. // It checks if the address is empty or blocked by the bank keeper. func GetCosmosAddress(bankKeeper bank.Keeper, addr common.Address) (sdk.AccAddress, error) { toAddr := sdk.AccAddress(addr.Bytes()) diff --git a/precompiles/types/address_test.go b/precompiles/types/address_test.go index 7300d5abad..c329fcd6ab 100644 --- a/precompiles/types/address_test.go +++ b/precompiles/types/address_test.go @@ -10,7 +10,7 @@ import ( "github.com/zeta-chain/node/testutil/sample" ) -func Test_getEVMCallerAddress(t *testing.T) { +func Test_GetEVMCallerAddress(t *testing.T) { mockEVM := vm.EVM{ TxContext: vm.TxContext{ Origin: common.Address{}, @@ -42,4 +42,4 @@ type contractRef struct { func (c contractRef) Address() common.Address { return c.address -} \ No newline at end of file +} diff --git a/x/fungible/keeper/zrc20_cosmos_coins_mapping.go b/x/fungible/keeper/zrc20_cosmos_coins_mapping.go index f7d152f749..140dc52aab 100644 --- a/x/fungible/keeper/zrc20_cosmos_coins_mapping.go +++ b/x/fungible/keeper/zrc20_cosmos_coins_mapping.go @@ -25,7 +25,7 @@ func (k Keeper) LockZRC20( ) error { // owner is the EOA owner of the ZRC20 tokens. // locker is the address that will lock the ZRC20 tokens, i.e: bank precompile. - if err := k.CheckZRC20Allowance(ctx, zrc20ABI, owner, locker, zrc20Address, amount); err != nil { + if err := k.CheckZRC20Allowance(ctx, zrc20ABI, owner, spender, zrc20Address, amount); err != nil { return errors.Wrap(err, "failed allowance check") } From 559243233b8c2cc3d6244019018f04e142b4ef68 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 21 Oct 2024 09:38:46 -0400 Subject: [PATCH 03/20] fix should return empty array if validators not set unit test --- precompiles/staking/method_get_all_validators_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/precompiles/staking/method_get_all_validators_test.go b/precompiles/staking/method_get_all_validators_test.go index f09ba30166..b873ad2289 100644 --- a/precompiles/staking/method_get_all_validators_test.go +++ b/precompiles/staking/method_get_all_validators_test.go @@ -5,7 +5,6 @@ import ( "math/rand" "testing" - "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/testutil/sample" ) @@ -17,7 +16,7 @@ func Test_GetAllValidators(t *testing.T) { validatorsList := ts.sdkKeepers.StakingKeeper.GetAllValidators(ts.ctx) for _, v := range validatorsList { fmt.Println(v.OperatorAddress) - ts.sdkKeepers.StakingKeeper.RemoveValidator(ts.ctx, types.ValAddress(v.OperatorAddress)) + ts.sdkKeepers.StakingKeeper.RemoveValidator(ts.ctx, v.GetOperator()) } methodID := ts.contractABI.Methods[GetAllValidatorsMethodName] From 220a353a15dc7c571b735b4745833fb8ebf45867 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 21 Oct 2024 16:08:59 +0200 Subject: [PATCH 04/20] migrate to new test suite --- precompiles/staking/method_distribute_test.go | 284 ------------------ .../staking/method_get_all_validators_test.go | 28 +- precompiles/staking/method_get_shares_test.go | 42 +-- precompiles/staking/method_move_stake_test.go | 22 +- precompiles/staking/method_stake_test.go | 14 +- precompiles/staking/method_unstake_test.go | 14 +- precompiles/staking/staking_test.go | 281 ++++++++++++++++- 7 files changed, 340 insertions(+), 345 deletions(-) diff --git a/precompiles/staking/method_distribute_test.go b/precompiles/staking/method_distribute_test.go index bc8dadab72..3b91525ed6 100644 --- a/precompiles/staking/method_distribute_test.go +++ b/precompiles/staking/method_distribute_test.go @@ -4,26 +4,8 @@ import ( "math/big" "testing" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/require" - ethermint "github.com/zeta-chain/ethermint/types" - evmkeeper "github.com/zeta-chain/ethermint/x/evm/keeper" - "github.com/zeta-chain/ethermint/x/evm/statedb" - "github.com/zeta-chain/node/pkg/chains" - "github.com/zeta-chain/node/pkg/contracts/erc1967proxy" ptypes "github.com/zeta-chain/node/precompiles/types" - "github.com/zeta-chain/node/testutil/keeper" - "github.com/zeta-chain/node/testutil/sample" - fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" - fungibletypes "github.com/zeta-chain/node/x/fungible/types" - "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" - "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" ) func Test_Distribute(t *testing.T) { @@ -241,269 +223,3 @@ func Test_Distribute(t *testing.T) { // check it was really distributed }) } - -/* - Helpers -*/ - -type testSuite struct { - ctx sdk.Context - contract *Contract - contractABI *abi.ABI - fungibleKeeper *fungiblekeeper.Keeper - sdkKeepers keeper.SDKKeepers - mockEVM *vm.EVM - mockVMContract *vm.Contract - methodID abi.Method - defaultCaller common.Address - defaultLocker common.Address - zrc20Address common.Address - zrc20ABI *abi.ABI -} - -func newTestSuite(t *testing.T) testSuite { - // Initialize basic parameters to mock the chain. - fungibleKeeper, ctx, sdkKeepers, _ := keeper.FungibleKeeper(t) - chainID := getValidChainID(t) - - // Make sure the account store is initialized. - // This is completely needed for accounts to be created in the state. - fungibleKeeper.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName) - - // Deploy system contracts in order to deploy a ZRC20 token. - deploySystemContracts(t, ctx, fungibleKeeper, *sdkKeepers.EvmKeeper) - zrc20Address := setupGasCoin(t, ctx, fungibleKeeper, sdkKeepers.EvmKeeper, chainID, "ZRC20", "ZRC20") - - // Keepers and chain configuration. - var encoding ethermint.EncodingConfig - appCodec := encoding.Codec - gasConfig := storetypes.TransientGasConfig() - - // Create the staking contract. - contract := NewIStakingContract( - ctx, - &sdkKeepers.StakingKeeper, - *fungibleKeeper, - sdkKeepers.BankKeeper, - appCodec, - gasConfig, - ) - require.NotNil(t, contract, "NewIStakingContract() should not return a nil contract") - - accAddress := sdk.AccAddress(ContractAddress.Bytes()) - fungibleKeeper.GetAuthKeeper().SetAccount(ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) - - abi := contract.Abi() - require.NotNil(t, abi, "contract ABI should not be nil") - - address := contract.Address() - require.NotNil(t, address, "contract address should not be nil") - - mockEVM := vm.NewEVM( - vm.BlockContext{}, - vm.TxContext{}, - statedb.New(ctx, sdkKeepers.EvmKeeper, statedb.TxConfig{}), - ¶ms.ChainConfig{}, - vm.Config{}, - ) - - mockVMContract := vm.NewContract( - contractRef{address: common.Address{}}, - contractRef{address: ContractAddress}, - big.NewInt(0), - 0, - ) - - zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() - require.NoError(t, err) - - // Default locker is the bank address. - locker := common.HexToAddress("0x0000000000000000000000000000000000000067") - - // Set default caller. - caller := fungibletypes.ModuleAddressEVM - mockVMContract.CallerAddress = caller - mockEVM.Origin = caller - - return testSuite{ - ctx, - contract, - &abi, - fungibleKeeper, - sdkKeepers, - mockEVM, - mockVMContract, - abi.Methods[DistributeMethodName], - caller, - locker, - zrc20Address, - zrc20ABI, - } -} - -func allowStaking(t *testing.T, ts testSuite, amount *big.Int) { - resAllowance, err := callEVM( - t, - ts.ctx, - ts.fungibleKeeper, - ts.zrc20ABI, - fungibletypes.ModuleAddressEVM, - ts.zrc20Address, - "approve", - []interface{}{ts.contract.Address(), amount}, - ) - require.NoError(t, err, "error allowing staking to spend ZRC20 tokens") - - allowed, ok := resAllowance[0].(bool) - require.True(t, ok) - require.True(t, allowed) -} - -func callEVM( - t *testing.T, - 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 - true, // noEthereumTxEvent - method, // method - args..., // args - ) - require.NoError(t, err, "CallEVM error") - require.Equal(t, "", res.VmError, "res.VmError should be empty") - - ret, err := abi.Methods[method].Outputs.Unpack(res.Ret) - require.NoError(t, err, "Unpack error") - - return ret, nil -} - -// setupGasCoin is a helper function to setup the gas coin for testing -func setupGasCoin( - t *testing.T, - ctx sdk.Context, - k *fungiblekeeper.Keeper, - evmk *evmkeeper.Keeper, - chainID int64, - assetName string, - symbol string, -) (zrc20 common.Address) { - addr, err := k.SetupChainGasCoinAndPool( - ctx, - chainID, - assetName, - symbol, - 8, - nil, - ) - require.NoError(t, err) - assertContractDeployment(t, *evmk, ctx, addr) - return addr -} - -// get a valid chain id independently of the build flag -func getValidChainID(t *testing.T) int64 { - list := chains.DefaultChainsList() - require.NotEmpty(t, list) - require.NotNil(t, list[0]) - return list[0].ChainId -} - -// require that a contract has been deployed by checking stored code is non-empty. -func assertContractDeployment(t *testing.T, k evmkeeper.Keeper, ctx sdk.Context, contractAddress common.Address) { - acc := k.GetAccount(ctx, contractAddress) - require.NotNil(t, acc) - code := k.GetCode(ctx, common.BytesToHash(acc.CodeHash)) - require.NotEmpty(t, code) -} - -// deploySystemContracts deploys the system contracts and returns their addresses. -func deploySystemContracts( - t *testing.T, - ctx sdk.Context, - k *fungiblekeeper.Keeper, - evmk evmkeeper.Keeper, -) (wzeta, uniswapV2Factory, uniswapV2Router, connector, systemContract common.Address) { - var err error - - wzeta, err = k.DeployWZETA(ctx) - require.NoError(t, err) - require.NotEmpty(t, wzeta) - assertContractDeployment(t, evmk, ctx, wzeta) - - uniswapV2Factory, err = k.DeployUniswapV2Factory(ctx) - require.NoError(t, err) - require.NotEmpty(t, uniswapV2Factory) - assertContractDeployment(t, evmk, ctx, uniswapV2Factory) - - uniswapV2Router, err = k.DeployUniswapV2Router02(ctx, uniswapV2Factory, wzeta) - require.NoError(t, err) - require.NotEmpty(t, uniswapV2Router) - assertContractDeployment(t, evmk, ctx, uniswapV2Router) - - connector, err = k.DeployConnectorZEVM(ctx, wzeta) - require.NoError(t, err) - require.NotEmpty(t, connector) - assertContractDeployment(t, evmk, ctx, connector) - - systemContract, err = k.DeploySystemContract(ctx, wzeta, uniswapV2Factory, uniswapV2Router) - require.NoError(t, err) - require.NotEmpty(t, systemContract) - assertContractDeployment(t, evmk, ctx, systemContract) - - // deploy the gateway contract - contract := deployGatewayContract(t, ctx, k, &evmk, wzeta, sample.EthAddress()) - require.NotEmpty(t, contract) - - return -} - -// deploy upgradable gateway contract and return its address -func deployGatewayContract( - t *testing.T, - ctx sdk.Context, - k *fungiblekeeper.Keeper, - evmk *evmkeeper.Keeper, - wzeta, admin common.Address, -) common.Address { - // Deploy the gateway contract - implAddr, err := k.DeployContract(ctx, gatewayzevm.GatewayZEVMMetaData) - require.NoError(t, err) - require.NotEmpty(t, implAddr) - assertContractDeployment(t, *evmk, ctx, implAddr) - - // Deploy the proxy contract - gatewayABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() - require.NoError(t, err) - - // Encode the initializer data - initializerData, err := gatewayABI.Pack("initialize", wzeta, admin) - require.NoError(t, err) - - gatewayContract, err := k.DeployContract(ctx, erc1967proxy.ERC1967ProxyMetaData, implAddr, initializerData) - require.NoError(t, err) - require.NotEmpty(t, gatewayContract) - assertContractDeployment(t, *evmk, ctx, gatewayContract) - - // store the gateway in the system contract object - sys, found := k.GetSystemContract(ctx) - if !found { - sys = fungibletypes.SystemContract{} - } - sys.Gateway = gatewayContract.Hex() - k.SetSystemContract(ctx, sys) - - return gatewayContract -} diff --git a/precompiles/staking/method_get_all_validators_test.go b/precompiles/staking/method_get_all_validators_test.go index b873ad2289..3e26ab01d0 100644 --- a/precompiles/staking/method_get_all_validators_test.go +++ b/precompiles/staking/method_get_all_validators_test.go @@ -1,7 +1,6 @@ package staking import ( - "fmt" "math/rand" "testing" @@ -12,18 +11,19 @@ import ( func Test_GetAllValidators(t *testing.T) { t.Run("should return empty array if validators not set", func(t *testing.T) { // ARRANGE - ts := newTestSuite(t) - validatorsList := ts.sdkKeepers.StakingKeeper.GetAllValidators(ts.ctx) + s := newTestSuite(t) + + // Clean all validators. + validatorsList := s.sdkKeepers.StakingKeeper.GetAllValidators(s.ctx) for _, v := range validatorsList { - fmt.Println(v.OperatorAddress) - ts.sdkKeepers.StakingKeeper.RemoveValidator(ts.ctx, v.GetOperator()) + s.sdkKeepers.StakingKeeper.RemoveValidator(s.ctx, v.GetOperator()) } - methodID := ts.contractABI.Methods[GetAllValidatorsMethodName] - ts.mockVMContract.Input = methodID.ID + methodID := s.contractABI.Methods[GetAllValidatorsMethodName] + s.mockVMContract.Input = methodID.ID // ACT - validators, err := ts.contract.Run(ts.mockEVM, ts.mockVMContract, false) + validators, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) // ASSERT require.NoError(t, err) @@ -36,15 +36,15 @@ func Test_GetAllValidators(t *testing.T) { t.Run("should return validators if set", func(t *testing.T) { // ARRANGE - ts := newTestSuite(t) - methodID := ts.contractABI.Methods[GetAllValidatorsMethodName] - ts.mockVMContract.Input = methodID.ID + s := newTestSuite(t) + methodID := s.contractABI.Methods[GetAllValidatorsMethodName] + s.mockVMContract.Input = methodID.ID r := rand.New(rand.NewSource(42)) validator := sample.Validator(t, r) - ts.sdkKeepers.StakingKeeper.SetValidator(ts.ctx, validator) + s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) // ACT - validators, err := ts.contract.Run(ts.mockEVM, ts.mockVMContract, false) + validators, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) // ASSERT require.NoError(t, err) @@ -54,4 +54,4 @@ func Test_GetAllValidators(t *testing.T) { require.NotEmpty(t, res[0]) }) -} +} \ No newline at end of file diff --git a/precompiles/staking/method_get_shares_test.go b/precompiles/staking/method_get_shares_test.go index f40c4c56a2..1d87ef4df2 100644 --- a/precompiles/staking/method_get_shares_test.go +++ b/precompiles/staking/method_get_shares_test.go @@ -17,34 +17,34 @@ import ( func Test_GetShares(t *testing.T) { t.Run("should return stakes", func(t *testing.T) { // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[GetSharesMethodName] + s := newTestSuite(t) + methodID := s.contractABI.Methods[GetSharesMethodName] r := rand.New(rand.NewSource(42)) validator := sample.Validator(t, r) - sdkKeepers.StakingKeeper.SetValidator(ctx, validator) + s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) staker := sample.Bech32AccAddress() stakerEthAddr := common.BytesToAddress(staker.Bytes()) coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + err := s.sdkKeepers.BankKeeper.MintCoins(s.ctx, fungibletypes.ModuleName, sample.Coins()) require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + err = s.sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(s.ctx, fungibletypes.ModuleName, staker, coins) require.NoError(t, err) stakerAddr := common.BytesToAddress(staker.Bytes()) stakeArgs := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - stakeMethodID := abi.Methods[StakeMethodName] + stakeMethodID := s.contractABI.Methods[StakeMethodName] // ACT - _, err = contract.Stake(ctx, mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, stakeArgs) + _, err = s.contract.Stake(s.ctx, s.mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, stakeArgs) require.NoError(t, err) // ASSERT args := []interface{}{stakerEthAddr, validator.OperatorAddress} - mockVMContract.Input = packInputArgs(t, methodID, args...) - stakes, err := contract.Run(mockEVM, mockVMContract, false) + s.mockVMContract.Input = packInputArgs(t, methodID, args...) + stakes, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) require.NoError(t, err) res, err := methodID.Outputs.Unpack(stakes) @@ -58,14 +58,14 @@ func Test_GetShares(t *testing.T) { t.Run("should fail if wrong args amount", func(t *testing.T) { // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] + s := newTestSuite(t) + methodID := s.contractABI.Methods[GetSharesMethodName] staker := sample.Bech32AccAddress() stakerEthAddr := common.BytesToAddress(staker.Bytes()) args := []interface{}{stakerEthAddr} // ACT - _, err := contract.GetShares(ctx, &methodID, args) + _, err := s.contract.GetShares(s.ctx, &methodID, args) // ASSERT require.Error(t, err) @@ -73,14 +73,14 @@ func Test_GetShares(t *testing.T) { t.Run("should fail if invalid staker arg", func(t *testing.T) { // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] + s := newTestSuite(t) + methodID := s.contractABI.Methods[GetSharesMethodName] r := rand.New(rand.NewSource(42)) validator := sample.Validator(t, r) args := []interface{}{42, validator.OperatorAddress} // ACT - _, err := contract.GetShares(ctx, &methodID, args) + _, err := s.contract.GetShares(s.ctx, &methodID, args) // ASSERT require.Error(t, err) @@ -88,14 +88,14 @@ func Test_GetShares(t *testing.T) { t.Run("should fail if invalid val address", func(t *testing.T) { // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] + s := newTestSuite(t) + methodID := s.contractABI.Methods[GetSharesMethodName] staker := sample.Bech32AccAddress() stakerEthAddr := common.BytesToAddress(staker.Bytes()) args := []interface{}{stakerEthAddr, staker.String()} // ACT - _, err := contract.GetShares(ctx, &methodID, args) + _, err := s.contract.GetShares(s.ctx, &methodID, args) // ASSERT require.Error(t, err) @@ -103,14 +103,14 @@ func Test_GetShares(t *testing.T) { t.Run("should fail if invalid val address format", func(t *testing.T) { // ARRANGE - ctx, contract, abi, _, _, _ := setup(t) - methodID := abi.Methods[GetSharesMethodName] + s := newTestSuite(t) + methodID := s.contractABI.Methods[GetSharesMethodName] staker := sample.Bech32AccAddress() stakerEthAddr := common.BytesToAddress(staker.Bytes()) args := []interface{}{stakerEthAddr, 42} // ACT - _, err := contract.GetShares(ctx, &methodID, args) + _, err := s.contract.GetShares(s.ctx, &methodID, args) // ASSERT require.Error(t, err) diff --git a/precompiles/staking/method_move_stake_test.go b/precompiles/staking/method_move_stake_test.go index d7eba60429..2d2c8e3a0f 100644 --- a/precompiles/staking/method_move_stake_test.go +++ b/precompiles/staking/method_move_stake_test.go @@ -16,23 +16,23 @@ func Test_MoveStake(t *testing.T) { // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. t.Run("should fail with error disabled", func(t *testing.T) { // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[MoveStakeMethodName] + s := newTestSuite(t) + methodID := s.contractABI.Methods[MoveStakeMethodName] r := rand.New(rand.NewSource(42)) validatorSrc := sample.Validator(t, r) - sdkKeepers.StakingKeeper.SetValidator(ctx, validatorSrc) + s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validatorSrc) validatorDest := sample.Validator(t, r) staker := sample.Bech32AccAddress() stakerEthAddr := common.BytesToAddress(staker.Bytes()) coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + err := s.sdkKeepers.BankKeeper.MintCoins(s.ctx, fungibletypes.ModuleName, sample.Coins()) require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + err = s.sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(s.ctx, fungibletypes.ModuleName, staker, coins) require.NoError(t, err) stakerAddr := common.BytesToAddress(staker.Bytes()) - mockVMContract.CallerAddress = stakerAddr + s.mockVMContract.CallerAddress = stakerAddr argsStake := []interface{}{ stakerEthAddr, @@ -41,9 +41,9 @@ func Test_MoveStake(t *testing.T) { } // stake to validator src - stakeMethodID := abi.Methods[StakeMethodName] - mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - _, err = contract.Run(mockEVM, mockVMContract, false) + stakeMethodID := s.contractABI.Methods[StakeMethodName] + s.mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) + _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) require.Error(t, err) require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ Method: StakeMethodName, @@ -55,10 +55,10 @@ func Test_MoveStake(t *testing.T) { validatorDest.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt(), } - mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) + s.mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) // ACT - _, err = contract.Run(mockEVM, mockVMContract, false) + _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) // ASSERT require.Error(t, err) diff --git a/precompiles/staking/method_stake_test.go b/precompiles/staking/method_stake_test.go index 9bf7847da0..f41ca310cd 100644 --- a/precompiles/staking/method_stake_test.go +++ b/precompiles/staking/method_stake_test.go @@ -16,26 +16,26 @@ func Test_Stake(t *testing.T) { // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. t.Run("should fail with error disabled", func(t *testing.T) { // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[StakeMethodName] + s := newTestSuite(t) + methodID := s.contractABI.Methods[StakeMethodName] r := rand.New(rand.NewSource(42)) validator := sample.Validator(t, r) staker := sample.Bech32AccAddress() stakerEthAddr := common.BytesToAddress(staker.Bytes()) coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + err := s.sdkKeepers.BankKeeper.MintCoins(s.ctx, fungibletypes.ModuleName, sample.Coins()) require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + err = s.sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(s.ctx, fungibletypes.ModuleName, staker, coins) require.NoError(t, err) stakerAddr := common.BytesToAddress(staker.Bytes()) - mockVMContract.CallerAddress = stakerAddr + s.mockVMContract.CallerAddress = stakerAddr args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - mockVMContract.Input = packInputArgs(t, methodID, args...) + s.mockVMContract.Input = packInputArgs(t, methodID, args...) // ACT - _, err = contract.Run(mockEVM, mockVMContract, false) + _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) // ASSERT require.Error(t, err) diff --git a/precompiles/staking/method_unstake_test.go b/precompiles/staking/method_unstake_test.go index e2a71846d5..13f1f2bd8f 100644 --- a/precompiles/staking/method_unstake_test.go +++ b/precompiles/staking/method_unstake_test.go @@ -16,27 +16,27 @@ func Test_Unstake(t *testing.T) { // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. t.Run("should fail with error disabled", func(t *testing.T) { // ARRANGE - ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract := setup(t) - methodID := abi.Methods[UnstakeMethodName] + s := newTestSuite(t) + methodID := s.contractABI.Methods[UnstakeMethodName] r := rand.New(rand.NewSource(42)) validator := sample.Validator(t, r) staker := sample.Bech32AccAddress() stakerEthAddr := common.BytesToAddress(staker.Bytes()) coins := sample.Coins() - err := sdkKeepers.BankKeeper.MintCoins(ctx, fungibletypes.ModuleName, sample.Coins()) + err := s.sdkKeepers.BankKeeper.MintCoins(s.ctx, fungibletypes.ModuleName, sample.Coins()) require.NoError(t, err) - err = sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + err = s.sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(s.ctx, fungibletypes.ModuleName, staker, coins) require.NoError(t, err) stakerAddr := common.BytesToAddress(staker.Bytes()) - mockVMContract.CallerAddress = stakerAddr + s.mockVMContract.CallerAddress = stakerAddr args := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - mockVMContract.Input = packInputArgs(t, methodID, args...) + s.mockVMContract.Input = packInputArgs(t, methodID, args...) // ACT - _, err = contract.Run(mockEVM, mockVMContract, false) + _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) // ASSERT require.Error(t, err) diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index 200e70c8a6..93c7e4e88f 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -11,6 +11,7 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -18,10 +19,18 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/require" ethermint "github.com/zeta-chain/ethermint/types" + evmkeeper "github.com/zeta-chain/ethermint/x/evm/keeper" "github.com/zeta-chain/ethermint/x/evm/statedb" "github.com/zeta-chain/node/cmd/zetacored/config" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/contracts/erc1967proxy" "github.com/zeta-chain/node/precompiles/prototype" "github.com/zeta-chain/node/testutil/keeper" + "github.com/zeta-chain/node/testutil/sample" + fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" ) func Test_IStakingContract(t *testing.T) { @@ -214,7 +223,7 @@ func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *v var encoding ethermint.EncodingConfig appCodec := encoding.Codec gasConfig := storetypes.TransientGasConfig() - + stakingGenesisState := stakingtypes.DefaultGenesisState() stakingGenesisState.Params.BondDenom = config.BaseDenom sdkKeepers.StakingKeeper.InitGenesis(ctx, stakingGenesisState) @@ -222,6 +231,9 @@ func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *v // Get the fungible keeper. fungibleKeeper, _, _, _ := keeper.FungibleKeeper(t) + accAddress := sdk.AccAddress(ContractAddress.Bytes()) + fungibleKeeper.GetAuthKeeper().SetAccount(ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) + // Initialize staking contract. contract := NewIStakingContract( ctx, @@ -257,12 +269,279 @@ func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *v return ctx, contract, abi, sdkKeepers, mockEVM, mockVMContract } +/* + Complete Test Suite + TODO: Migrate all staking tests to this suite. +*/ + +type testSuite struct { + ctx sdk.Context + contract *Contract + contractABI *abi.ABI + fungibleKeeper *fungiblekeeper.Keeper + sdkKeepers keeper.SDKKeepers + mockEVM *vm.EVM + mockVMContract *vm.Contract + methodID abi.Method + defaultCaller common.Address + defaultLocker common.Address + zrc20Address common.Address + zrc20ABI *abi.ABI +} + +func newTestSuite(t *testing.T) testSuite { + // Initialize basic parameters to mock the chain. + fungibleKeeper, ctx, sdkKeepers, _ := keeper.FungibleKeeper(t) + chainID := getValidChainID(t) + + // Make sure the account store is initialized. + // This is completely needed for accounts to be created in the state. + fungibleKeeper.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName) + + // Deploy system contracts in order to deploy a ZRC20 token. + deploySystemContracts(t, ctx, fungibleKeeper, *sdkKeepers.EvmKeeper) + zrc20Address := setupGasCoin(t, ctx, fungibleKeeper, sdkKeepers.EvmKeeper, chainID, "ZRC20", "ZRC20") + + // Keepers and chain configuration. + var encoding ethermint.EncodingConfig + appCodec := encoding.Codec + gasConfig := storetypes.TransientGasConfig() + + // Create the staking contract. + contract := NewIStakingContract( + ctx, + &sdkKeepers.StakingKeeper, + *fungibleKeeper, + sdkKeepers.BankKeeper, + appCodec, + gasConfig, + ) + require.NotNil(t, contract, "NewIStakingContract() should not return a nil contract") + + accAddress := sdk.AccAddress(ContractAddress.Bytes()) + fungibleKeeper.GetAuthKeeper().SetAccount(ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) + + abi := contract.Abi() + require.NotNil(t, abi, "contract ABI should not be nil") + + address := contract.Address() + require.NotNil(t, address, "contract address should not be nil") + + mockEVM := vm.NewEVM( + vm.BlockContext{}, + vm.TxContext{}, + statedb.New(ctx, sdkKeepers.EvmKeeper, statedb.TxConfig{}), + ¶ms.ChainConfig{}, + vm.Config{}, + ) + + mockVMContract := vm.NewContract( + contractRef{address: common.Address{}}, + contractRef{address: ContractAddress}, + big.NewInt(0), + 0, + ) + + zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() + require.NoError(t, err) + + // Default locker is the bank address. + locker := common.HexToAddress("0x0000000000000000000000000000000000000067") + + // Set default caller. + caller := fungibletypes.ModuleAddressEVM + mockVMContract.CallerAddress = caller + mockEVM.Origin = caller + + return testSuite{ + ctx, + contract, + &abi, + fungibleKeeper, + sdkKeepers, + mockEVM, + mockVMContract, + abi.Methods[DistributeMethodName], + caller, + locker, + zrc20Address, + zrc20ABI, + } +} + func packInputArgs(t *testing.T, methodID abi.Method, args ...interface{}) []byte { input, err := methodID.Inputs.Pack(args...) require.NoError(t, err) return append(methodID.ID, input...) } +func allowStaking(t *testing.T, ts testSuite, amount *big.Int) { + resAllowance, err := callEVM( + t, + ts.ctx, + ts.fungibleKeeper, + ts.zrc20ABI, + fungibletypes.ModuleAddressEVM, + ts.zrc20Address, + "approve", + []interface{}{ts.contract.Address(), amount}, + ) + require.NoError(t, err, "error allowing staking to spend ZRC20 tokens") + + allowed, ok := resAllowance[0].(bool) + require.True(t, ok) + require.True(t, allowed) +} + +func callEVM( + t *testing.T, + 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 + true, // noEthereumTxEvent + method, // method + args..., // args + ) + require.NoError(t, err, "CallEVM error") + require.Equal(t, "", res.VmError, "res.VmError should be empty") + + ret, err := abi.Methods[method].Outputs.Unpack(res.Ret) + require.NoError(t, err, "Unpack error") + + return ret, nil +} + +// setupGasCoin is a helper function to setup the gas coin for testing +func setupGasCoin( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + evmk *evmkeeper.Keeper, + chainID int64, + assetName string, + symbol string, +) (zrc20 common.Address) { + addr, err := k.SetupChainGasCoinAndPool( + ctx, + chainID, + assetName, + symbol, + 8, + nil, + ) + require.NoError(t, err) + assertContractDeployment(t, *evmk, ctx, addr) + return addr +} + +// get a valid chain id independently of the build flag +func getValidChainID(t *testing.T) int64 { + list := chains.DefaultChainsList() + require.NotEmpty(t, list) + require.NotNil(t, list[0]) + return list[0].ChainId +} + +// require that a contract has been deployed by checking stored code is non-empty. +func assertContractDeployment(t *testing.T, k evmkeeper.Keeper, ctx sdk.Context, contractAddress common.Address) { + acc := k.GetAccount(ctx, contractAddress) + require.NotNil(t, acc) + code := k.GetCode(ctx, common.BytesToHash(acc.CodeHash)) + require.NotEmpty(t, code) +} + +// deploySystemContracts deploys the system contracts and returns their addresses. +func deploySystemContracts( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + evmk evmkeeper.Keeper, +) (wzeta, uniswapV2Factory, uniswapV2Router, connector, systemContract common.Address) { + var err error + + wzeta, err = k.DeployWZETA(ctx) + require.NoError(t, err) + require.NotEmpty(t, wzeta) + assertContractDeployment(t, evmk, ctx, wzeta) + + uniswapV2Factory, err = k.DeployUniswapV2Factory(ctx) + require.NoError(t, err) + require.NotEmpty(t, uniswapV2Factory) + assertContractDeployment(t, evmk, ctx, uniswapV2Factory) + + uniswapV2Router, err = k.DeployUniswapV2Router02(ctx, uniswapV2Factory, wzeta) + require.NoError(t, err) + require.NotEmpty(t, uniswapV2Router) + assertContractDeployment(t, evmk, ctx, uniswapV2Router) + + connector, err = k.DeployConnectorZEVM(ctx, wzeta) + require.NoError(t, err) + require.NotEmpty(t, connector) + assertContractDeployment(t, evmk, ctx, connector) + + systemContract, err = k.DeploySystemContract(ctx, wzeta, uniswapV2Factory, uniswapV2Router) + require.NoError(t, err) + require.NotEmpty(t, systemContract) + assertContractDeployment(t, evmk, ctx, systemContract) + + // deploy the gateway contract + contract := deployGatewayContract(t, ctx, k, &evmk, wzeta, sample.EthAddress()) + require.NotEmpty(t, contract) + + return +} + +// deploy upgradable gateway contract and return its address +func deployGatewayContract( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + evmk *evmkeeper.Keeper, + wzeta, admin common.Address, +) common.Address { + // Deploy the gateway contract + implAddr, err := k.DeployContract(ctx, gatewayzevm.GatewayZEVMMetaData) + require.NoError(t, err) + require.NotEmpty(t, implAddr) + assertContractDeployment(t, *evmk, ctx, implAddr) + + // Deploy the proxy contract + gatewayABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() + require.NoError(t, err) + + // Encode the initializer data + initializerData, err := gatewayABI.Pack("initialize", wzeta, admin) + require.NoError(t, err) + + gatewayContract, err := k.DeployContract(ctx, erc1967proxy.ERC1967ProxyMetaData, implAddr, initializerData) + require.NoError(t, err) + require.NotEmpty(t, gatewayContract) + assertContractDeployment(t, *evmk, ctx, gatewayContract) + + // store the gateway in the system contract object + sys, found := k.GetSystemContract(ctx) + if !found { + sys = fungibletypes.SystemContract{} + } + sys.Gateway = gatewayContract.Hex() + k.SetSystemContract(ctx, sys) + + return gatewayContract +} + type contractRef struct { address common.Address } From b174dc8e68c9274b2bdbfa661cefee364f812663 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 21 Oct 2024 17:27:16 +0200 Subject: [PATCH 05/20] remove function depending on staking precompile --- precompiles/staking/method_get_shares_test.go | 87 +++++++++---------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/precompiles/staking/method_get_shares_test.go b/precompiles/staking/method_get_shares_test.go index 1d87ef4df2..d0038886f4 100644 --- a/precompiles/staking/method_get_shares_test.go +++ b/precompiles/staking/method_get_shares_test.go @@ -1,60 +1,57 @@ package staking import ( - "fmt" - "math/big" "math/rand" "testing" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" "github.com/stretchr/testify/require" - "github.com/zeta-chain/node/cmd/zetacored/config" "github.com/zeta-chain/node/testutil/sample" - fungibletypes "github.com/zeta-chain/node/x/fungible/types" ) func Test_GetShares(t *testing.T) { - t.Run("should return stakes", func(t *testing.T) { - // ARRANGE - s := newTestSuite(t) - methodID := s.contractABI.Methods[GetSharesMethodName] - r := rand.New(rand.NewSource(42)) - validator := sample.Validator(t, r) - s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) - - staker := sample.Bech32AccAddress() - stakerEthAddr := common.BytesToAddress(staker.Bytes()) - coins := sample.Coins() - err := s.sdkKeepers.BankKeeper.MintCoins(s.ctx, fungibletypes.ModuleName, sample.Coins()) - require.NoError(t, err) - err = s.sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(s.ctx, fungibletypes.ModuleName, staker, coins) - require.NoError(t, err) - - stakerAddr := common.BytesToAddress(staker.Bytes()) - - stakeArgs := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} - - stakeMethodID := s.contractABI.Methods[StakeMethodName] - - // ACT - _, err = s.contract.Stake(s.ctx, s.mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, stakeArgs) - require.NoError(t, err) - - // ASSERT - args := []interface{}{stakerEthAddr, validator.OperatorAddress} - s.mockVMContract.Input = packInputArgs(t, methodID, args...) - stakes, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) - require.NoError(t, err) - - res, err := methodID.Outputs.Unpack(stakes) - require.NoError(t, err) - require.Equal( - t, - fmt.Sprintf("%d000000000000000000", coins.AmountOf(config.BaseDenom).BigInt().Int64()), - res[0].(*big.Int).String(), - ) - }) + // Disabled temporarily because the staking functions were disabled. + // Issue: https://github.com/zeta-chain/node/issues/3009 + // t.Run("should return stakes", func(t *testing.T) { + // // ARRANGE + // s := newTestSuite(t) + // methodID := s.contractABI.Methods[GetSharesMethodName] + // r := rand.New(rand.NewSource(42)) + // validator := sample.Validator(t, r) + // s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) + + // staker := sample.Bech32AccAddress() + // stakerEthAddr := common.BytesToAddress(staker.Bytes()) + // coins := sample.Coins() + // err := s.sdkKeepers.BankKeeper.MintCoins(s.ctx, fungibletypes.ModuleName, sample.Coins()) + // require.NoError(t, err) + // err = s.sdkKeepers.BankKeeper.SendCoinsFromModuleToAccount(s.ctx, fungibletypes.ModuleName, staker, coins) + // require.NoError(t, err) + + // stakerAddr := common.BytesToAddress(staker.Bytes()) + + // stakeArgs := []interface{}{stakerEthAddr, validator.OperatorAddress, coins.AmountOf(config.BaseDenom).BigInt()} + + // stakeMethodID := s.contractABI.Methods[StakeMethodName] + + // // ACT + // _, err = s.contract.Stake(s.ctx, s.mockEVM, &vm.Contract{CallerAddress: stakerAddr}, &stakeMethodID, stakeArgs) + // require.NoError(t, err) + + // // ASSERT + // args := []interface{}{stakerEthAddr, validator.OperatorAddress} + // s.mockVMContract.Input = packInputArgs(t, methodID, args...) + // stakes, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + // require.NoError(t, err) + + // res, err := methodID.Outputs.Unpack(stakes) + // require.NoError(t, err) + // require.Equal( + // t, + // fmt.Sprintf("%d000000000000000000", coins.AmountOf(config.BaseDenom).BigInt().Int64()), + // res[0].(*big.Int).String(), + // ) + // }) t.Run("should fail if wrong args amount", func(t *testing.T) { // ARRANGE From 4728c10e7c15153bc0224bab5e3ff96639dc57ff Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 21 Oct 2024 18:28:18 +0200 Subject: [PATCH 06/20] commit last unit test changes --- precompiles/staking/method_distribute_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/precompiles/staking/method_distribute_test.go b/precompiles/staking/method_distribute_test.go index 3b91525ed6..4d5f5c3411 100644 --- a/precompiles/staking/method_distribute_test.go +++ b/precompiles/staking/method_distribute_test.go @@ -188,7 +188,7 @@ func Test_Distribute(t *testing.T) { require.False(t, ok) }) - t.Run("should distribute and lock ZRC20 under the bank account", func(t *testing.T) { + t.Run("should distribute and lock ZRC20", func(t *testing.T) { // Setup test. s := newTestSuite(t) @@ -217,9 +217,8 @@ func Test_Distribute(t *testing.T) { ok := res[0].(bool) require.True(t, ok) - // balance, err := s.fungibleKeeper.ZRC20BalanceOf(s.ctx, s.zrc20ABI, s.zrc20Address, s.defaultCaller) - // require.NoError(t, err) - - // check it was really distributed + // feeCollectorAddress := s.sdkKeepers.AuthKeeper.GetModuleAccount(s.ctx, types.FeeCollectorName).GetAddress() + // coin := s.sdkKeepers.BankKeeper.GetBalance(s.ctx, feeCollectorAddress, ptypes.ZRC20ToCosmosDenom(s.zrc20Address)) + // fmt.Println(coin) }) } From eae55f246de8ba94338d97915f295e5e719077f4 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Thu, 24 Oct 2024 14:59:49 +0200 Subject: [PATCH 07/20] add e2e base --- .../testdistribute/TestDistribute.abi | 64 +++ .../testdistribute/TestDistribute.bin | 1 + .../testdistribute/TestDistribute.go | 420 ++++++++++++++++++ .../testdistribute/TestDistribute.json | 67 +++ .../testdistribute/TestDistribute.sol | 43 ++ e2e/contracts/testdistribute/bindings.go | 8 + e2e/contracts/teststaking/TestStaking.sol | 42 +- e2e/e2etests/e2etests.go | 28 +- .../test_precompiles_bank_through_contract.go | 20 +- e2e/e2etests/test_precompiles_distribute.go | 155 +++++++ ...precompiles_distribute_through_contract.go | 132 ++++++ precompiles/staking/method_distribute.go | 9 + precompiles/staking/method_distribute_test.go | 4 - .../staking/method_get_all_validators_test.go | 2 +- precompiles/staking/staking_test.go | 2 +- 15 files changed, 968 insertions(+), 29 deletions(-) create mode 100644 e2e/contracts/testdistribute/TestDistribute.abi create mode 100644 e2e/contracts/testdistribute/TestDistribute.bin create mode 100644 e2e/contracts/testdistribute/TestDistribute.go create mode 100644 e2e/contracts/testdistribute/TestDistribute.json create mode 100644 e2e/contracts/testdistribute/TestDistribute.sol create mode 100644 e2e/contracts/testdistribute/bindings.go create mode 100644 e2e/e2etests/test_precompiles_distribute.go create mode 100644 e2e/e2etests/test_precompiles_distribute_through_contract.go diff --git a/e2e/contracts/testdistribute/TestDistribute.abi b/e2e/contracts/testdistribute/TestDistribute.abi new file mode 100644 index 0000000000..26aa07af4e --- /dev/null +++ b/e2e/contracts/testdistribute/TestDistribute.abi @@ -0,0 +1,64 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "zrc20_distributor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "zrc20_token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Distributed", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "distributeThroughContract", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/e2e/contracts/testdistribute/TestDistribute.bin b/e2e/contracts/testdistribute/TestDistribute.bin new file mode 100644 index 0000000000..5840bf96e5 --- /dev/null +++ b/e2e/contracts/testdistribute/TestDistribute.bin @@ -0,0 +1 @@ +60a060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b503373ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff168152505060805161034d6100a06000396000606c015261034d6000f3fe6080604052600436106100225760003560e01c806350b54e841461002b57610029565b3661002957005b005b34801561003757600080fd5b50610052600480360381019061004d9190610201565b610068565b60405161005f919061025c565b60405180910390f35b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146100c257600080fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b815260040161011d929190610295565b6020604051808303816000875af115801561013c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016091906102ea565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101988261016d565b9050919050565b6101a88161018d565b81146101b357600080fd5b50565b6000813590506101c58161019f565b92915050565b6000819050919050565b6101de816101cb565b81146101e957600080fd5b50565b6000813590506101fb816101d5565b92915050565b6000806040838503121561021857610217610168565b5b6000610226858286016101b6565b9250506020610237858286016101ec565b9150509250929050565b60008115159050919050565b61025681610241565b82525050565b6000602082019050610271600083018461024d565b92915050565b6102808161018d565b82525050565b61028f816101cb565b82525050565b60006040820190506102aa6000830185610277565b6102b76020830184610286565b9392505050565b6102c781610241565b81146102d257600080fd5b50565b6000815190506102e4816102be565b92915050565b600060208284031215610300576102ff610168565b5b600061030e848285016102d5565b9150509291505056fea26469706673582212205443ec313ecb8c2e08ca8a30687daed4c3b666f9318ae72ccbe9033479c8b8be64736f6c634300080a0033 diff --git a/e2e/contracts/testdistribute/TestDistribute.go b/e2e/contracts/testdistribute/TestDistribute.go new file mode 100644 index 0000000000..18b4201b1a --- /dev/null +++ b/e2e/contracts/testdistribute/TestDistribute.go @@ -0,0 +1,420 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package testdistribute + +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 +) + +// TestDistributeMetaData contains all meta data concerning the TestDistribute contract. +var TestDistributeMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_distributor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Distributed\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"distributeThroughContract\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", + Bin: "0x60a060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b503373ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff168152505060805161034d6100a06000396000606c015261034d6000f3fe6080604052600436106100225760003560e01c806350b54e841461002b57610029565b3661002957005b005b34801561003757600080fd5b50610052600480360381019061004d9190610201565b610068565b60405161005f919061025c565b60405180910390f35b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146100c257600080fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b815260040161011d929190610295565b6020604051808303816000875af115801561013c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016091906102ea565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101988261016d565b9050919050565b6101a88161018d565b81146101b357600080fd5b50565b6000813590506101c58161019f565b92915050565b6000819050919050565b6101de816101cb565b81146101e957600080fd5b50565b6000813590506101fb816101d5565b92915050565b6000806040838503121561021857610217610168565b5b6000610226858286016101b6565b9250506020610237858286016101ec565b9150509250929050565b60008115159050919050565b61025681610241565b82525050565b6000602082019050610271600083018461024d565b92915050565b6102808161018d565b82525050565b61028f816101cb565b82525050565b60006040820190506102aa6000830185610277565b6102b76020830184610286565b9392505050565b6102c781610241565b81146102d257600080fd5b50565b6000815190506102e4816102be565b92915050565b600060208284031215610300576102ff610168565b5b600061030e848285016102d5565b9150509291505056fea26469706673582212205443ec313ecb8c2e08ca8a30687daed4c3b666f9318ae72ccbe9033479c8b8be64736f6c634300080a0033", +} + +// TestDistributeABI is the input ABI used to generate the binding from. +// Deprecated: Use TestDistributeMetaData.ABI instead. +var TestDistributeABI = TestDistributeMetaData.ABI + +// TestDistributeBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use TestDistributeMetaData.Bin instead. +var TestDistributeBin = TestDistributeMetaData.Bin + +// DeployTestDistribute deploys a new Ethereum contract, binding an instance of TestDistribute to it. +func DeployTestDistribute(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *TestDistribute, error) { + parsed, err := TestDistributeMetaData.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(TestDistributeBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &TestDistribute{TestDistributeCaller: TestDistributeCaller{contract: contract}, TestDistributeTransactor: TestDistributeTransactor{contract: contract}, TestDistributeFilterer: TestDistributeFilterer{contract: contract}}, nil +} + +// TestDistribute is an auto generated Go binding around an Ethereum contract. +type TestDistribute struct { + TestDistributeCaller // Read-only binding to the contract + TestDistributeTransactor // Write-only binding to the contract + TestDistributeFilterer // Log filterer for contract events +} + +// TestDistributeCaller is an auto generated read-only Go binding around an Ethereum contract. +type TestDistributeCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TestDistributeTransactor is an auto generated write-only Go binding around an Ethereum contract. +type TestDistributeTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TestDistributeFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type TestDistributeFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TestDistributeSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type TestDistributeSession struct { + Contract *TestDistribute // 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 +} + +// TestDistributeCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type TestDistributeCallerSession struct { + Contract *TestDistributeCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// TestDistributeTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type TestDistributeTransactorSession struct { + Contract *TestDistributeTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// TestDistributeRaw is an auto generated low-level Go binding around an Ethereum contract. +type TestDistributeRaw struct { + Contract *TestDistribute // Generic contract binding to access the raw methods on +} + +// TestDistributeCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type TestDistributeCallerRaw struct { + Contract *TestDistributeCaller // Generic read-only contract binding to access the raw methods on +} + +// TestDistributeTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type TestDistributeTransactorRaw struct { + Contract *TestDistributeTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewTestDistribute creates a new instance of TestDistribute, bound to a specific deployed contract. +func NewTestDistribute(address common.Address, backend bind.ContractBackend) (*TestDistribute, error) { + contract, err := bindTestDistribute(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &TestDistribute{TestDistributeCaller: TestDistributeCaller{contract: contract}, TestDistributeTransactor: TestDistributeTransactor{contract: contract}, TestDistributeFilterer: TestDistributeFilterer{contract: contract}}, nil +} + +// NewTestDistributeCaller creates a new read-only instance of TestDistribute, bound to a specific deployed contract. +func NewTestDistributeCaller(address common.Address, caller bind.ContractCaller) (*TestDistributeCaller, error) { + contract, err := bindTestDistribute(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &TestDistributeCaller{contract: contract}, nil +} + +// NewTestDistributeTransactor creates a new write-only instance of TestDistribute, bound to a specific deployed contract. +func NewTestDistributeTransactor(address common.Address, transactor bind.ContractTransactor) (*TestDistributeTransactor, error) { + contract, err := bindTestDistribute(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &TestDistributeTransactor{contract: contract}, nil +} + +// NewTestDistributeFilterer creates a new log filterer instance of TestDistribute, bound to a specific deployed contract. +func NewTestDistributeFilterer(address common.Address, filterer bind.ContractFilterer) (*TestDistributeFilterer, error) { + contract, err := bindTestDistribute(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &TestDistributeFilterer{contract: contract}, nil +} + +// bindTestDistribute binds a generic wrapper to an already deployed contract. +func bindTestDistribute(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := TestDistributeMetaData.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 (_TestDistribute *TestDistributeRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _TestDistribute.Contract.TestDistributeCaller.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 (_TestDistribute *TestDistributeRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TestDistribute.Contract.TestDistributeTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_TestDistribute *TestDistributeRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _TestDistribute.Contract.TestDistributeTransactor.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 (_TestDistribute *TestDistributeCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _TestDistribute.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 (_TestDistribute *TestDistributeTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TestDistribute.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_TestDistribute *TestDistributeTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _TestDistribute.Contract.contract.Transact(opts, method, params...) +} + +// DistributeThroughContract is a paid mutator transaction binding the contract method 0x50b54e84. +// +// Solidity: function distributeThroughContract(address zrc20, uint256 amount) returns(bool) +func (_TestDistribute *TestDistributeTransactor) DistributeThroughContract(opts *bind.TransactOpts, zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _TestDistribute.contract.Transact(opts, "distributeThroughContract", zrc20, amount) +} + +// DistributeThroughContract is a paid mutator transaction binding the contract method 0x50b54e84. +// +// Solidity: function distributeThroughContract(address zrc20, uint256 amount) returns(bool) +func (_TestDistribute *TestDistributeSession) DistributeThroughContract(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _TestDistribute.Contract.DistributeThroughContract(&_TestDistribute.TransactOpts, zrc20, amount) +} + +// DistributeThroughContract is a paid mutator transaction binding the contract method 0x50b54e84. +// +// Solidity: function distributeThroughContract(address zrc20, uint256 amount) returns(bool) +func (_TestDistribute *TestDistributeTransactorSession) DistributeThroughContract(zrc20 common.Address, amount *big.Int) (*types.Transaction, error) { + return _TestDistribute.Contract.DistributeThroughContract(&_TestDistribute.TransactOpts, zrc20, amount) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_TestDistribute *TestDistributeTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { + return _TestDistribute.contract.RawTransact(opts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_TestDistribute *TestDistributeSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _TestDistribute.Contract.Fallback(&_TestDistribute.TransactOpts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_TestDistribute *TestDistributeTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _TestDistribute.Contract.Fallback(&_TestDistribute.TransactOpts, calldata) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_TestDistribute *TestDistributeTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TestDistribute.contract.RawTransact(opts, nil) // calldata is disallowed for receive function +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_TestDistribute *TestDistributeSession) Receive() (*types.Transaction, error) { + return _TestDistribute.Contract.Receive(&_TestDistribute.TransactOpts) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_TestDistribute *TestDistributeTransactorSession) Receive() (*types.Transaction, error) { + return _TestDistribute.Contract.Receive(&_TestDistribute.TransactOpts) +} + +// TestDistributeDistributedIterator is returned from FilterDistributed and is used to iterate over the raw logs and unpacked data for Distributed events raised by the TestDistribute contract. +type TestDistributeDistributedIterator struct { + Event *TestDistributeDistributed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TestDistributeDistributedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TestDistributeDistributed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TestDistributeDistributed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TestDistributeDistributedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TestDistributeDistributedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TestDistributeDistributed represents a Distributed event raised by the TestDistribute contract. +type TestDistributeDistributed struct { + Zrc20Distributor common.Address + Zrc20Token common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterDistributed is a free log retrieval operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_TestDistribute *TestDistributeFilterer) FilterDistributed(opts *bind.FilterOpts, zrc20_distributor []common.Address, zrc20_token []common.Address) (*TestDistributeDistributedIterator, error) { + + var zrc20_distributorRule []interface{} + for _, zrc20_distributorItem := range zrc20_distributor { + zrc20_distributorRule = append(zrc20_distributorRule, zrc20_distributorItem) + } + var zrc20_tokenRule []interface{} + for _, zrc20_tokenItem := range zrc20_token { + zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) + } + + logs, sub, err := _TestDistribute.contract.FilterLogs(opts, "Distributed", zrc20_distributorRule, zrc20_tokenRule) + if err != nil { + return nil, err + } + return &TestDistributeDistributedIterator{contract: _TestDistribute.contract, event: "Distributed", logs: logs, sub: sub}, nil +} + +// WatchDistributed is a free log subscription operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_TestDistribute *TestDistributeFilterer) WatchDistributed(opts *bind.WatchOpts, sink chan<- *TestDistributeDistributed, zrc20_distributor []common.Address, zrc20_token []common.Address) (event.Subscription, error) { + + var zrc20_distributorRule []interface{} + for _, zrc20_distributorItem := range zrc20_distributor { + zrc20_distributorRule = append(zrc20_distributorRule, zrc20_distributorItem) + } + var zrc20_tokenRule []interface{} + for _, zrc20_tokenItem := range zrc20_token { + zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) + } + + logs, sub, err := _TestDistribute.contract.WatchLogs(opts, "Distributed", zrc20_distributorRule, zrc20_tokenRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TestDistributeDistributed) + if err := _TestDistribute.contract.UnpackLog(event, "Distributed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseDistributed is a log parse operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. +// +// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) +func (_TestDistribute *TestDistributeFilterer) ParseDistributed(log types.Log) (*TestDistributeDistributed, error) { + event := new(TestDistributeDistributed) + if err := _TestDistribute.contract.UnpackLog(event, "Distributed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/e2e/contracts/testdistribute/TestDistribute.json b/e2e/contracts/testdistribute/TestDistribute.json new file mode 100644 index 0000000000..05ee369e1c --- /dev/null +++ b/e2e/contracts/testdistribute/TestDistribute.json @@ -0,0 +1,67 @@ +{ + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "zrc20_distributor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "zrc20_token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Distributed", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "distributeThroughContract", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "bin": "60a060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b503373ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff168152505060805161034d6100a06000396000606c015261034d6000f3fe6080604052600436106100225760003560e01c806350b54e841461002b57610029565b3661002957005b005b34801561003757600080fd5b50610052600480360381019061004d9190610201565b610068565b60405161005f919061025c565b60405180910390f35b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146100c257600080fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b815260040161011d929190610295565b6020604051808303816000875af115801561013c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016091906102ea565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101988261016d565b9050919050565b6101a88161018d565b81146101b357600080fd5b50565b6000813590506101c58161019f565b92915050565b6000819050919050565b6101de816101cb565b81146101e957600080fd5b50565b6000813590506101fb816101d5565b92915050565b6000806040838503121561021857610217610168565b5b6000610226858286016101b6565b9250506020610237858286016101ec565b9150509250929050565b60008115159050919050565b61025681610241565b82525050565b6000602082019050610271600083018461024d565b92915050565b6102808161018d565b82525050565b61028f816101cb565b82525050565b60006040820190506102aa6000830185610277565b6102b76020830184610286565b9392505050565b6102c781610241565b81146102d257600080fd5b50565b6000815190506102e4816102be565b92915050565b600060208284031215610300576102ff610168565b5b600061030e848285016102d5565b9150509291505056fea26469706673582212205443ec313ecb8c2e08ca8a30687daed4c3b666f9318ae72ccbe9033479c8b8be64736f6c634300080a0033" +} diff --git a/e2e/contracts/testdistribute/TestDistribute.sol b/e2e/contracts/testdistribute/TestDistribute.sol new file mode 100644 index 0000000000..5cf2277b88 --- /dev/null +++ b/e2e/contracts/testdistribute/TestDistribute.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.10; + +// @dev Interface to interact with distribute. +interface IDistribute { + function distribute( + address zrc20, + uint256 amount + ) external returns (bool success); +} + +// @dev Call IBank contract functions +contract TestDistribute { + event Distributed( + address indexed zrc20_distributor, + address indexed zrc20_token, + uint256 amount + ); + + IDistribute distr = IDistribute(0x0000000000000000000000000000000000000066); + + address immutable owner; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + function distributeThroughContract( + address zrc20, + uint256 amount + ) external onlyOwner returns (bool) { + return distr.distribute(zrc20, amount); + } + + fallback() external payable {} + + receive() external payable {} +} diff --git a/e2e/contracts/testdistribute/bindings.go b/e2e/contracts/testdistribute/bindings.go new file mode 100644 index 0000000000..765dfb5a8a --- /dev/null +++ b/e2e/contracts/testdistribute/bindings.go @@ -0,0 +1,8 @@ +//go:generate sh -c "solc TestDistribute.sol --combined-json abi,bin | jq '.contracts.\"TestDistribute.sol:TestDistribute\"' > TestDistribute.json" +//go:generate sh -c "cat TestDistribute.json | jq .abi > TestDistribute.abi" +//go:generate sh -c "cat TestDistribute.json | jq .bin | tr -d '\"' > TestDistribute.bin" +//go:generate sh -c "abigen --abi TestDistribute.abi --bin TestDistribute.bin --pkg testdistribute --type TestDistribute --out TestDistribute.go" + +package testdistribute + +var _ TestDistribute diff --git a/e2e/contracts/teststaking/TestStaking.sol b/e2e/contracts/teststaking/TestStaking.sol index 48a3837100..b6235ae658 100644 --- a/e2e/contracts/teststaking/TestStaking.sol +++ b/e2e/contracts/teststaking/TestStaking.sol @@ -36,13 +36,20 @@ interface IStaking { uint256 amount ) external returns (int64 completionTime); - function getAllValidators() external view returns (Validator[] calldata validators); + function getAllValidators() + external + view + returns (Validator[] calldata validators); - function getShares(address staker, string memory validator) external view returns (uint256 shares); + function getShares( + address staker, + string memory validator + ) external view returns (uint256 shares); } interface WZETA { function deposit() external payable; + function withdraw(uint256 wad) external; } @@ -94,18 +101,30 @@ contract TestStaking { wzeta.withdraw(wad); } - function stake(address staker, string memory validator, uint256 amount) external onlyOwner returns (bool) { + function stake( + address staker, + string memory validator, + uint256 amount + ) external onlyOwner returns (bool) { return staking.stake(staker, validator, amount); } - function stakeWithStateUpdate(address staker, string memory validator, uint256 amount) external onlyOwner returns (bool) { + function stakeWithStateUpdate( + address staker, + string memory validator, + uint256 amount + ) external onlyOwner returns (bool) { counter = counter + 1; bool success = staking.stake(staker, validator, amount); counter = counter + 1; return success; } - function stakeAndRevert(address staker, string memory validator, uint256 amount) external onlyOwner returns (bool) { + function stakeAndRevert( + address staker, + string memory validator, + uint256 amount + ) external onlyOwner returns (bool) { counter = counter + 1; staking.stake(staker, validator, amount); counter = counter + 1; @@ -129,15 +148,22 @@ contract TestStaking { return staking.moveStake(staker, validatorSrc, validatorDst, amount); } - function getShares(address staker, string memory validator) external view returns(uint256 shares) { + function getShares( + address staker, + string memory validator + ) external view returns (uint256 shares) { return staking.getShares(staker, validator); } - function getAllValidators() external view returns (Validator[] memory validators) { + function getAllValidators() + external + view + returns (Validator[] memory validators) + { return staking.getAllValidators(); } fallback() external payable {} receive() external payable {} -} \ No newline at end of file +} diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index fe6428385d..c0dff3e1a0 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -162,13 +162,15 @@ const ( /* Stateful precompiled contracts tests */ - TestPrecompilesPrototypeName = "precompile_contracts_prototype" - TestPrecompilesPrototypeThroughContractName = "precompile_contracts_prototype_through_contract" - TestPrecompilesStakingName = "precompile_contracts_staking" - TestPrecompilesStakingThroughContractName = "precompile_contracts_staking_through_contract" - TestPrecompilesBankName = "precompile_contracts_bank" - TestPrecompilesBankFailName = "precompile_contracts_bank_fail" - TestPrecompilesBankThroughContractName = "precompile_contracts_bank_through_contract" + TestPrecompilesPrototypeName = "precompile_contracts_prototype" + TestPrecompilesPrototypeThroughContractName = "precompile_contracts_prototype_through_contract" + TestPrecompilesStakingName = "precompile_contracts_staking" + TestPrecompilesStakingThroughContractName = "precompile_contracts_staking_through_contract" + TestPrecompilesBankName = "precompile_contracts_bank" + TestPrecompilesBankFailName = "precompile_contracts_bank_fail" + TestPrecompilesBankThroughContractName = "precompile_contracts_bank_through_contract" + TestPrecompilesDistributeName = "precompile_contracts_distribute" + TestPrecompilesDistributeThroughContractName = "precompile_contracts_distribute_through_contract" ) // AllE2ETests is an ordered list of all e2e tests @@ -956,4 +958,16 @@ var AllE2ETests = []runner.E2ETest{ []runner.ArgDefinition{}, TestPrecompilesBankThroughContract, ), + runner.NewE2ETest( + TestPrecompilesDistributeName, + "test stateful precompiled contracts distribute", + []runner.ArgDefinition{}, + TestPrecompilesDistribute, + ), + runner.NewE2ETest( + TestPrecompilesDistributeThroughContractName, + "test stateful precompiled contracts distribute through contract", + []runner.ArgDefinition{}, + TestPrecompilesDistributeThroughContract, + ), } diff --git a/e2e/e2etests/test_precompiles_bank_through_contract.go b/e2e/e2etests/test_precompiles_bank_through_contract.go index 6d6384fd9e..4baa6fefb5 100644 --- a/e2e/e2etests/test_precompiles_bank_through_contract.go +++ b/e2e/e2etests/test_precompiles_bank_through_contract.go @@ -58,7 +58,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { }() // Check initial balances. - balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) @@ -67,7 +67,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequiredTxFailed(r, receipt, "Deposit ERC20ZRC20 without allowance should fail") // Check balances, should be the same. - balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) @@ -80,7 +80,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequiredTxFailed(r, receipt, "Depositting an amount higher than allowed should fail") // Balances shouldn't change. - balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) @@ -93,7 +93,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequiredTxFailed(r, receipt, "Depositting an amount higher than balance should fail") // Balances shouldn't change. - balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) @@ -102,7 +102,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequireTxSuccessful(r, receipt, "Depositting a correct amount should pass") // Balances should be transferred. Bank now locks 500 ZRC20 tokens. - balanceShouldBe(r, 500, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 500, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 500, checkZRC20Balance(r, spender)) balanceShouldBe(r, 500, checkZRC20Balance(r, bankAddress)) @@ -118,7 +118,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequiredTxFailed(r, receipt, "Withdrawing an amount higher than balance should fail") // Balances shouldn't change. - balanceShouldBe(r, 500, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 500, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 500, checkZRC20Balance(r, spender)) balanceShouldBe(r, 500, checkZRC20Balance(r, bankAddress)) @@ -127,7 +127,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequireTxSuccessful(r, receipt, "Withdraw correct amount should pass") // Balances should be reverted to initial state. - balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) @@ -156,7 +156,11 @@ func checkZRC20Balance(r *runner.E2ERunner, target common.Address) *big.Int { return bankZRC20Balance } -func checkCosmosBalance(r *runner.E2ERunner, bank *testbank.TestBank, zrc20, target common.Address) *big.Int { +func checkCosmosBalanceThroughBank( + r *runner.E2ERunner, + bank *testbank.TestBank, + zrc20, target common.Address, +) *big.Int { balance, err := bank.BalanceOf(&bind.CallOpts{Context: r.Ctx, From: r.ZEVMAuth.From}, zrc20, target) require.NoError(r, err) return balance diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go new file mode 100644 index 0000000000..ea0b491da8 --- /dev/null +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -0,0 +1,155 @@ +package e2etests + +import ( + "fmt" + "math/big" + + "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/precompiles/bank" + "github.com/zeta-chain/node/precompiles/staking" + ptypes "github.com/zeta-chain/node/precompiles/types" +) + +func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { + require.Len(r, args, 0, "No arguments expected") + + var ( + spenderAddress = r.EVMAddress() + distributeContractAddress = staking.ContractAddress + feeCollectorAddress = authtypes.NewModuleAddress("fee_collector") + lockerAddress = bank.ContractAddress + + zrc20Address = r.ERC20ZRC20Addr + zrc20Denom = ptypes.ZRC20ToCosmosDenom(zrc20Address) + + oneThousand = big.NewInt(1e3) + oneThousandOne = big.NewInt(1001) + fiveHundred = big.NewInt(500) + fiveHundredOne = big.NewInt(501) + + previousGasLimit = r.ZEVMAuth.GasLimit + ) + + // Set new gas limit to avoid out of gas errors. + r.ZEVMAuth.GasLimit = 10_000_000 + + // Set the test to reset the state after it finishes. + defer resetTest(r, lockerAddress, previousGasLimit) + + // Get ERC20ZRC20. + txHash := r.DepositERC20WithAmountAndMessage(spenderAddress, oneThousand, []byte{}) + utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + + dstrContract, err := staking.NewIStaking(distributeContractAddress, r.ZEVMClient) + require.NoError(r, err, "failed to create distribute contract caller") + + validators, err := dstrContract.GetAllValidators(&bind.CallOpts{}) + require.NoError(r, err) + fmt.Println(validators) + + // Check initial balances. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + + tx, err := dstrContract.Distribute(r.ZEVMAuth, zrc20Address, oneThousand) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequiredTxFailed(r, receipt, "distribute should fail when there's no allowance") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + + // Allow 500. + approveAllowance(r, distributeContractAddress, fiveHundred) + + // Shouldn't be able to distribute more than allowed. + tx, err = dstrContract.Distribute(r.ZEVMAuth, zrc20Address, fiveHundredOne) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than allowed") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + + // Raise the allowance to 1000. + approveAllowance(r, distributeContractAddress, oneThousand) + + // Shouldn't be able to distribute more than owned balance. + tx, err = dstrContract.Distribute(r.ZEVMAuth, zrc20Address, oneThousandOne) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than owned balance") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + + // Should be able to distribute 500, which is within balance and allowance. + tx, err = dstrContract.Distribute(r.ZEVMAuth, zrc20Address, fiveHundred) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "distribute should succeed when distributing within balance and allowance") + + balanceShouldBe(r, 500, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, 500, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + + // After one block the rewards should have been distributed and fee collector should have 0 ZRC20 balance. + r.WaitForBlocks(1) + balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + + accAddress := types.AccAddress("zetavaloper16vuh496n7wahw6m8dmzc0p5meymmctleuaz562") + valBal := checkCosmosBalance(r, accAddress, zrc20Denom) + fmt.Println(valBal) + + accAddress = types.AccAddress(validators[0].OperatorAddress) + valBal = checkCosmosBalance(r, accAddress, zrc20Denom) + fmt.Println(valBal) + + coins := checkAllCosmosBalance(r, accAddress) + fmt.Printf("Coins: %+v\n", coins) +} + +func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom string) *big.Int { + bal, err := r.BankClient.Balance( + r.Ctx, + &banktypes.QueryBalanceRequest{Address: address.String(), Denom: denom}, + ) + require.NoError(r, err) + + return bal.Balance.Amount.BigInt() +} + +func checkAllCosmosBalance(r *runner.E2ERunner, address types.AccAddress) types.Coins { + bal, err := r.BankClient.AllBalances( + r.Ctx, + &banktypes.QueryAllBalancesRequest{Address: address.String()}, + ) + require.NoError(r, err) + + return bal.Balances +} + +func resetTest(r *runner.E2ERunner, lockerAddress common.Address, previousGasLimit uint64) { + r.ZEVMAuth.GasLimit = previousGasLimit + + // Reset the allowance to 0; this is needed when running upgrade tests where this test runs twice. + tx, err := r.ERC20ZRC20.Approve(r.ZEVMAuth, lockerAddress, big.NewInt(0)) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "Resetting allowance failed") +} diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_through_contract.go new file mode 100644 index 0000000000..ab8bdbffc6 --- /dev/null +++ b/e2e/e2etests/test_precompiles_distribute_through_contract.go @@ -0,0 +1,132 @@ +package e2etests + +import ( + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" +) + +func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string) { + require.Len(r, args, 0, "No arguments expected") + + // spender := r.EVMAddress() + // bankAddress := bank.ContractAddress + // zrc20Address := r.ERC20ZRC20Addr + // oneThousand := big.NewInt(1e3) + // oneThousandOne := big.NewInt(1001) + // fiveHundred := big.NewInt(500) + // fiveHundredOne := big.NewInt(501) + + // // Get ERC20ZRC20. + // txHash := r.DepositERC20WithAmountAndMessage(r.EVMAddress(), oneThousand, []byte{}) + // utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + + // bankPrecompileCaller, err := bank.NewIBank(bank.ContractAddress, r.ZEVMClient) + // require.NoError(r, err, "Failed to create bank precompile caller") + + // // Deploy the TestBank. Ensure the transaction is successful. + // _, tx, testBank, err := testbank.DeployTestBank(r.ZEVMAuth, r.ZEVMClient) + // require.NoError(r, err) + // receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + // utils.RequireTxSuccessful(r, receipt, "Deployment of TestBank contract failed") + + // previousGasLimit := r.ZEVMAuth.GasLimit + // r.ZEVMAuth.GasLimit = 10_000_000 + // defer func() { + // r.ZEVMAuth.GasLimit = previousGasLimit + + // // Reset the allowance to 0; this is needed when running upgrade tests where this test runs twice. + // approveAllowance(r, bank.ContractAddress, big.NewInt(0)) + + // // Reset balance to 0; this is needed when running upgrade tests where this test runs twice. + // tx, err = r.ERC20ZRC20.Transfer( + // r.ZEVMAuth, + // common.HexToAddress("0x000000000000000000000000000000000000dEaD"), + // oneThousand, + // ) + // require.NoError(r, err) + // receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + // utils.RequireTxSuccessful(r, receipt, "Resetting balance failed") + // }() + + // // Check initial balances. + // balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + // balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) + // balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) + + // // Deposit without previous alllowance should fail. + // receipt = depositThroughTestBank(r, testBank, zrc20Address, oneThousand) + // utils.RequiredTxFailed(r, receipt, "Deposit ERC20ZRC20 without allowance should fail") + + // // Check balances, should be the same. + // balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + // balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) + // balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) + + // // Allow 500 ZRC20 to bank precompile. + // approveAllowance(r, bankAddress, fiveHundred) + + // // Deposit 501 ERC20ZRC20 tokens to the bank contract, through TestBank. + // // It's higher than allowance but lower than balance, should fail. + // receipt = depositThroughTestBank(r, testBank, zrc20Address, fiveHundredOne) + // utils.RequiredTxFailed(r, receipt, "Depositting an amount higher than allowed should fail") + + // // Balances shouldn't change. + // balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + // balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) + // balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) + + // // Allow 1000 ZRC20 to bank precompile. + // approveAllowance(r, bankAddress, oneThousand) + + // // Deposit 1001 ERC20ZRC20 tokens to the bank contract. + // // It's higher than spender balance but within approved allowance, should fail. + // receipt = depositThroughTestBank(r, testBank, zrc20Address, oneThousandOne) + // utils.RequiredTxFailed(r, receipt, "Depositting an amount higher than balance should fail") + + // // Balances shouldn't change. + // balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + // balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) + // balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) + + // // Deposit 500 ERC20ZRC20 tokens to the bank contract, it's within allowance and balance. Should pass. + // receipt = depositThroughTestBank(r, testBank, zrc20Address, fiveHundred) + // utils.RequireTxSuccessful(r, receipt, "Depositting a correct amount should pass") + + // // Balances should be transferred. Bank now locks 500 ZRC20 tokens. + // balanceShouldBe(r, 500, checkCosmosBalance(r, testBank, zrc20Address, spender)) + // balanceShouldBe(r, 500, checkZRC20Balance(r, spender)) + // balanceShouldBe(r, 500, checkZRC20Balance(r, bankAddress)) + + // // Check the deposit event. + // eventDeposit, err := bankPrecompileCaller.ParseDeposit(*receipt.Logs[0]) + // require.NoError(r, err, "Parse Deposit event") + // require.Equal(r, r.EVMAddress(), eventDeposit.Zrc20Depositor, "Deposit event token should be r.EVMAddress()") + // require.Equal(r, r.ERC20ZRC20Addr, eventDeposit.Zrc20Token, "Deposit event token should be ERC20ZRC20Addr") + // require.Equal(r, fiveHundred, eventDeposit.Amount, "Deposit event amount should be 500") + + // // Should faild to withdraw more than cosmos balance. + // receipt = withdrawThroughTestBank(r, testBank, zrc20Address, fiveHundredOne) + // utils.RequiredTxFailed(r, receipt, "Withdrawing an amount higher than balance should fail") + + // // Balances shouldn't change. + // balanceShouldBe(r, 500, checkCosmosBalance(r, testBank, zrc20Address, spender)) + // balanceShouldBe(r, 500, checkZRC20Balance(r, spender)) + // balanceShouldBe(r, 500, checkZRC20Balance(r, bankAddress)) + + // // Try to withdraw 500 ERC20ZRC20 tokens. Should pass. + // receipt = withdrawThroughTestBank(r, testBank, zrc20Address, fiveHundred) + // utils.RequireTxSuccessful(r, receipt, "Withdraw correct amount should pass") + + // // Balances should be reverted to initial state. + // balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) + // balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) + // balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) + + // // Check the withdraw event. + // eventWithdraw, err := bankPrecompileCaller.ParseWithdraw(*receipt.Logs[0]) + // require.NoError(r, err, "Parse Withdraw event") + // require.Equal(r, r.EVMAddress(), eventWithdraw.Zrc20Withdrawer, "Withdrawer should be r.EVMAddress()") + // require.Equal(r, r.ERC20ZRC20Addr, eventWithdraw.Zrc20Token, "Withdraw event token should be ERC20ZRC20Addr") + // require.Equal(r, fiveHundred, eventWithdraw.Amount, "Withdraw event amount should be 500") +} diff --git a/precompiles/staking/method_distribute.go b/precompiles/staking/method_distribute.go index eea35b33f3..84e966d936 100644 --- a/precompiles/staking/method_distribute.go +++ b/precompiles/staking/method_distribute.go @@ -1,6 +1,7 @@ package staking import ( + "fmt" "math/big" sdk "github.com/cosmos/cosmos-sdk/types" @@ -78,6 +79,14 @@ func (c *Contract) distribute( } } + // Log similar message as in abci DistributeValidatorRewards function. + ctx.Logger().Info( + fmt.Sprintf("Distributing ZRC20 Validator Rewards Total:%s To FeeCollector : %s, Denom: %s", + amount.String(), + authtypes.FeeCollectorName, + ptypes.ZRC20ToCosmosDenom(zrc20Addr), + )) + if err := c.addDistributeLog(ctx, evm.StateDB, caller, zrc20Addr, amount); err != nil { return nil, &ptypes.ErrUnexpected{ When: "AddDistributeLog", diff --git a/precompiles/staking/method_distribute_test.go b/precompiles/staking/method_distribute_test.go index 4d5f5c3411..73de0cdc36 100644 --- a/precompiles/staking/method_distribute_test.go +++ b/precompiles/staking/method_distribute_test.go @@ -216,9 +216,5 @@ func Test_Distribute(t *testing.T) { ok := res[0].(bool) require.True(t, ok) - - // feeCollectorAddress := s.sdkKeepers.AuthKeeper.GetModuleAccount(s.ctx, types.FeeCollectorName).GetAddress() - // coin := s.sdkKeepers.BankKeeper.GetBalance(s.ctx, feeCollectorAddress, ptypes.ZRC20ToCosmosDenom(s.zrc20Address)) - // fmt.Println(coin) }) } diff --git a/precompiles/staking/method_get_all_validators_test.go b/precompiles/staking/method_get_all_validators_test.go index 3e26ab01d0..8a80793de3 100644 --- a/precompiles/staking/method_get_all_validators_test.go +++ b/precompiles/staking/method_get_all_validators_test.go @@ -54,4 +54,4 @@ func Test_GetAllValidators(t *testing.T) { require.NotEmpty(t, res[0]) }) -} \ No newline at end of file +} diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index 93c7e4e88f..b7b00ede0d 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -223,7 +223,7 @@ func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *v var encoding ethermint.EncodingConfig appCodec := encoding.Codec gasConfig := storetypes.TransientGasConfig() - + stakingGenesisState := stakingtypes.DefaultGenesisState() stakingGenesisState.Params.BondDenom = config.BaseDenom sdkKeepers.StakingKeeper.InitGenesis(ctx, stakingGenesisState) From 5ea0fac2b7dcf7a30e0e497cc8b5274c73fc55c6 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Thu, 24 Oct 2024 18:27:06 +0200 Subject: [PATCH 08/20] add distribution query client to e2e --- e2e/e2etests/test_precompiles_distribute.go | 64 ++++++--- ...precompiles_distribute_through_contract.go | 121 ------------------ e2e/runner/runner.go | 39 +++--- pkg/rpc/clients.go | 14 +- precompiles/staking/const.go | 4 + precompiles/staking/logs.go | 8 +- 6 files changed, 81 insertions(+), 169 deletions(-) diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index ea0b491da8..2450bc0714 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -108,40 +109,67 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) balanceShouldBe(r, 500, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + eventDitributed, err := dstrContract.ParseDistributed(*receipt.Logs[0]) + require.NoError(r, err) + require.Equal(r, zrc20Address, eventDitributed.Zrc20Token) + require.Equal(r, spenderAddress, eventDitributed.Zrc20Distributor) + require.Equal(r, fiveHundred.Uint64(), eventDitributed.Amount.Uint64()) + // After one block the rewards should have been distributed and fee collector should have 0 ZRC20 balance. r.WaitForBlocks(1) balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + res, err := r.DistributionClient.ValidatorDistributionInfo( + r.Ctx, + &distributiontypes.QueryValidatorDistributionInfoRequest{ + ValidatorAddress: validators[0].OperatorAddress, + }, + ) + require.NoError(r, err) + fmt.Printf("Validator 0 distribution info: %+v\n", res) - accAddress := types.AccAddress("zetavaloper16vuh496n7wahw6m8dmzc0p5meymmctleuaz562") - valBal := checkCosmosBalance(r, accAddress, zrc20Denom) - fmt.Println(valBal) - - accAddress = types.AccAddress(validators[0].OperatorAddress) - valBal = checkCosmosBalance(r, accAddress, zrc20Denom) - fmt.Println(valBal) + res2, err := r.DistributionClient.ValidatorOutstandingRewards(r.Ctx, &distributiontypes.QueryValidatorOutstandingRewardsRequest{ + ValidatorAddress: validators[0].OperatorAddress, + }) + require.NoError(r, err) + fmt.Printf("Validator 0 outstanding rewards: %+v\n", res2) - coins := checkAllCosmosBalance(r, accAddress) - fmt.Printf("Coins: %+v\n", coins) -} + res3, err := r.DistributionClient.ValidatorCommission(r.Ctx, &distributiontypes.QueryValidatorCommissionRequest{ + ValidatorAddress: validators[0].OperatorAddress, + }) + require.NoError(r, err) + fmt.Printf("Validator 0 commission: %+v\n", res3) -func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom string) *big.Int { - bal, err := r.BankClient.Balance( + // Validator 1 + res, err = r.DistributionClient.ValidatorDistributionInfo( r.Ctx, - &banktypes.QueryBalanceRequest{Address: address.String(), Denom: denom}, + &distributiontypes.QueryValidatorDistributionInfoRequest{ + ValidatorAddress: validators[1].OperatorAddress, + }, ) require.NoError(r, err) + fmt.Printf("Validator 1 distribution info: %+v\n", res) - return bal.Balance.Amount.BigInt() + res2, err = r.DistributionClient.ValidatorOutstandingRewards(r.Ctx, &distributiontypes.QueryValidatorOutstandingRewardsRequest{ + ValidatorAddress: validators[1].OperatorAddress, + }) + require.NoError(r, err) + fmt.Printf("Validator 1 outstanding rewards: %+v\n", res2) + + res3, err = r.DistributionClient.ValidatorCommission(r.Ctx, &distributiontypes.QueryValidatorCommissionRequest{ + ValidatorAddress: validators[1].OperatorAddress, + }) + require.NoError(r, err) + fmt.Printf("Validator 1 commission: %+v\n", res3) } -func checkAllCosmosBalance(r *runner.E2ERunner, address types.AccAddress) types.Coins { - bal, err := r.BankClient.AllBalances( +func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom string) *big.Int { + bal, err := r.BankClient.Balance( r.Ctx, - &banktypes.QueryAllBalancesRequest{Address: address.String()}, + &banktypes.QueryBalanceRequest{Address: address.String(), Denom: denom}, ) require.NoError(r, err) - return bal.Balances + return bal.Balance.Amount.BigInt() } func resetTest(r *runner.E2ERunner, lockerAddress common.Address, previousGasLimit uint64) { diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_through_contract.go index ab8bdbffc6..f4d6c0f816 100644 --- a/e2e/e2etests/test_precompiles_distribute_through_contract.go +++ b/e2e/e2etests/test_precompiles_distribute_through_contract.go @@ -8,125 +8,4 @@ import ( func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string) { require.Len(r, args, 0, "No arguments expected") - - // spender := r.EVMAddress() - // bankAddress := bank.ContractAddress - // zrc20Address := r.ERC20ZRC20Addr - // oneThousand := big.NewInt(1e3) - // oneThousandOne := big.NewInt(1001) - // fiveHundred := big.NewInt(500) - // fiveHundredOne := big.NewInt(501) - - // // Get ERC20ZRC20. - // txHash := r.DepositERC20WithAmountAndMessage(r.EVMAddress(), oneThousand, []byte{}) - // utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) - - // bankPrecompileCaller, err := bank.NewIBank(bank.ContractAddress, r.ZEVMClient) - // require.NoError(r, err, "Failed to create bank precompile caller") - - // // Deploy the TestBank. Ensure the transaction is successful. - // _, tx, testBank, err := testbank.DeployTestBank(r.ZEVMAuth, r.ZEVMClient) - // require.NoError(r, err) - // receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - // utils.RequireTxSuccessful(r, receipt, "Deployment of TestBank contract failed") - - // previousGasLimit := r.ZEVMAuth.GasLimit - // r.ZEVMAuth.GasLimit = 10_000_000 - // defer func() { - // r.ZEVMAuth.GasLimit = previousGasLimit - - // // Reset the allowance to 0; this is needed when running upgrade tests where this test runs twice. - // approveAllowance(r, bank.ContractAddress, big.NewInt(0)) - - // // Reset balance to 0; this is needed when running upgrade tests where this test runs twice. - // tx, err = r.ERC20ZRC20.Transfer( - // r.ZEVMAuth, - // common.HexToAddress("0x000000000000000000000000000000000000dEaD"), - // oneThousand, - // ) - // require.NoError(r, err) - // receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - // utils.RequireTxSuccessful(r, receipt, "Resetting balance failed") - // }() - - // // Check initial balances. - // balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) - // balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) - // balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) - - // // Deposit without previous alllowance should fail. - // receipt = depositThroughTestBank(r, testBank, zrc20Address, oneThousand) - // utils.RequiredTxFailed(r, receipt, "Deposit ERC20ZRC20 without allowance should fail") - - // // Check balances, should be the same. - // balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) - // balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) - // balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) - - // // Allow 500 ZRC20 to bank precompile. - // approveAllowance(r, bankAddress, fiveHundred) - - // // Deposit 501 ERC20ZRC20 tokens to the bank contract, through TestBank. - // // It's higher than allowance but lower than balance, should fail. - // receipt = depositThroughTestBank(r, testBank, zrc20Address, fiveHundredOne) - // utils.RequiredTxFailed(r, receipt, "Depositting an amount higher than allowed should fail") - - // // Balances shouldn't change. - // balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) - // balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) - // balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) - - // // Allow 1000 ZRC20 to bank precompile. - // approveAllowance(r, bankAddress, oneThousand) - - // // Deposit 1001 ERC20ZRC20 tokens to the bank contract. - // // It's higher than spender balance but within approved allowance, should fail. - // receipt = depositThroughTestBank(r, testBank, zrc20Address, oneThousandOne) - // utils.RequiredTxFailed(r, receipt, "Depositting an amount higher than balance should fail") - - // // Balances shouldn't change. - // balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) - // balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) - // balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) - - // // Deposit 500 ERC20ZRC20 tokens to the bank contract, it's within allowance and balance. Should pass. - // receipt = depositThroughTestBank(r, testBank, zrc20Address, fiveHundred) - // utils.RequireTxSuccessful(r, receipt, "Depositting a correct amount should pass") - - // // Balances should be transferred. Bank now locks 500 ZRC20 tokens. - // balanceShouldBe(r, 500, checkCosmosBalance(r, testBank, zrc20Address, spender)) - // balanceShouldBe(r, 500, checkZRC20Balance(r, spender)) - // balanceShouldBe(r, 500, checkZRC20Balance(r, bankAddress)) - - // // Check the deposit event. - // eventDeposit, err := bankPrecompileCaller.ParseDeposit(*receipt.Logs[0]) - // require.NoError(r, err, "Parse Deposit event") - // require.Equal(r, r.EVMAddress(), eventDeposit.Zrc20Depositor, "Deposit event token should be r.EVMAddress()") - // require.Equal(r, r.ERC20ZRC20Addr, eventDeposit.Zrc20Token, "Deposit event token should be ERC20ZRC20Addr") - // require.Equal(r, fiveHundred, eventDeposit.Amount, "Deposit event amount should be 500") - - // // Should faild to withdraw more than cosmos balance. - // receipt = withdrawThroughTestBank(r, testBank, zrc20Address, fiveHundredOne) - // utils.RequiredTxFailed(r, receipt, "Withdrawing an amount higher than balance should fail") - - // // Balances shouldn't change. - // balanceShouldBe(r, 500, checkCosmosBalance(r, testBank, zrc20Address, spender)) - // balanceShouldBe(r, 500, checkZRC20Balance(r, spender)) - // balanceShouldBe(r, 500, checkZRC20Balance(r, bankAddress)) - - // // Try to withdraw 500 ERC20ZRC20 tokens. Should pass. - // receipt = withdrawThroughTestBank(r, testBank, zrc20Address, fiveHundred) - // utils.RequireTxSuccessful(r, receipt, "Withdraw correct amount should pass") - - // // Balances should be reverted to initial state. - // balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender)) - // balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) - // balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) - - // // Check the withdraw event. - // eventWithdraw, err := bankPrecompileCaller.ParseWithdraw(*receipt.Logs[0]) - // require.NoError(r, err, "Parse Withdraw event") - // require.Equal(r, r.EVMAddress(), eventWithdraw.Zrc20Withdrawer, "Withdrawer should be r.EVMAddress()") - // require.Equal(r, r.ERC20ZRC20Addr, eventWithdraw.Zrc20Token, "Withdraw event token should be ERC20ZRC20Addr") - // require.Equal(r, fiveHundred, eventWithdraw.Amount, "Withdraw event amount should be 500") } diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index 03cafb6fc4..c3dcb74637 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -11,6 +11,7 @@ import ( "github.com/btcsuite/btcd/rpcclient" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcommon "github.com/ethereum/go-ethereum/common" @@ -86,14 +87,15 @@ type E2ERunner struct { SolanaClient *rpc.Client // zetacored grpc clients - AuthorityClient authoritytypes.QueryClient - CctxClient crosschaintypes.QueryClient - FungibleClient fungibletypes.QueryClient - AuthClient authtypes.QueryClient - BankClient banktypes.QueryClient - StakingClient stakingtypes.QueryClient - ObserverClient observertypes.QueryClient - LightclientClient lightclienttypes.QueryClient + AuthorityClient authoritytypes.QueryClient + CctxClient crosschaintypes.QueryClient + FungibleClient fungibletypes.QueryClient + AuthClient authtypes.QueryClient + BankClient banktypes.QueryClient + StakingClient stakingtypes.QueryClient + ObserverClient observertypes.QueryClient + LightclientClient lightclienttypes.QueryClient + DistributionClient distributiontypes.QueryClient // optional zeta (cosmos) client // typically only in test runners that need it @@ -189,16 +191,17 @@ func NewE2ERunner( Clients: clients, - ZEVMClient: clients.Zevm, - EVMClient: clients.Evm, - AuthorityClient: clients.Zetacore.Authority, - CctxClient: clients.Zetacore.Crosschain, - FungibleClient: clients.Zetacore.Fungible, - AuthClient: clients.Zetacore.Auth, - BankClient: clients.Zetacore.Bank, - StakingClient: clients.Zetacore.Staking, - ObserverClient: clients.Zetacore.Observer, - LightclientClient: clients.Zetacore.Lightclient, + ZEVMClient: clients.Zevm, + EVMClient: clients.Evm, + AuthorityClient: clients.Zetacore.Authority, + CctxClient: clients.Zetacore.Crosschain, + FungibleClient: clients.Zetacore.Fungible, + AuthClient: clients.Zetacore.Auth, + BankClient: clients.Zetacore.Bank, + StakingClient: clients.Zetacore.Staking, + ObserverClient: clients.Zetacore.Observer, + LightclientClient: clients.Zetacore.Lightclient, + DistributionClient: clients.Zetacore.Distribution, EVMAuth: clients.EvmAuth, ZEVMAuth: clients.ZevmAuth, diff --git a/pkg/rpc/clients.go b/pkg/rpc/clients.go index 26ecbe729f..1dc0d7314e 100644 --- a/pkg/rpc/clients.go +++ b/pkg/rpc/clients.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" feemarkettypes "github.com/zeta-chain/ethermint/x/feemarket/types" @@ -35,6 +36,8 @@ type Clients struct { Staking stakingtypes.QueryClient // Upgrade is a github.com/cosmos/cosmos-sdk/x/upgrade/types QueryClient Upgrade upgradetypes.QueryClient + // Distribution is a "github.com/cosmos/cosmos-sdk/x/distribution/types" QueryClient + Distribution distributiontypes.QueryClient // ZetaCore specific clients @@ -65,11 +68,12 @@ type Clients struct { func newClients(ctx client.Context) (Clients, error) { return Clients{ // Cosmos SDK clients - Auth: authtypes.NewQueryClient(ctx), - Bank: banktypes.NewQueryClient(ctx), - Staking: stakingtypes.NewQueryClient(ctx), - Upgrade: upgradetypes.NewQueryClient(ctx), - Authority: authoritytypes.NewQueryClient(ctx), + Auth: authtypes.NewQueryClient(ctx), + Bank: banktypes.NewQueryClient(ctx), + Staking: stakingtypes.NewQueryClient(ctx), + Upgrade: upgradetypes.NewQueryClient(ctx), + Authority: authoritytypes.NewQueryClient(ctx), + Distribution: distributiontypes.NewQueryClient(ctx), // ZetaCore specific clients Crosschain: crosschaintypes.NewQueryClient(ctx), Fungible: fungibletypes.NewQueryClient(ctx), diff --git a/precompiles/staking/const.go b/precompiles/staking/const.go index e68c58057e..f18b072bbe 100644 --- a/precompiles/staking/const.go +++ b/precompiles/staking/const.go @@ -2,17 +2,21 @@ package staking const ( DistributeMethodName = "distribute" + DisitributeEventName = "Distributed" DistributeMethodGas = 10000 GetAllValidatorsMethodName = "getAllValidators" GetSharesMethodName = "getShares" MoveStakeMethodName = "moveStake" + MoveStakeEventName = "MoveStake" MoveStakeMethodGas = 10000 StakeMethodName = "stake" + StakeEventName = "Stake" StakeMethodGas = 10000 UnstakeMethodName = "unstake" + UnstakeEventName = "Unstake" UnstakeMethodGas = 1000 ) diff --git a/precompiles/staking/logs.go b/precompiles/staking/logs.go index bfd4a0e055..9154b9a23d 100644 --- a/precompiles/staking/logs.go +++ b/precompiles/staking/logs.go @@ -10,12 +10,6 @@ import ( "github.com/zeta-chain/node/precompiles/logs" ) -const ( - StakeEventName = "Stake" - UnstakeEventName = "Unstake" - MoveStakeEventName = "MoveStake" -) - func (c *Contract) addStakeLog( ctx sdk.Context, stateDB vm.StateDB, @@ -131,7 +125,7 @@ func (c *Contract) addDistributeLog( zrc20Token common.Address, amount *big.Int, ) error { - event := c.Abi().Events[MoveStakeEventName] + event := c.Abi().Events[DisitributeEventName] topics, err := logs.MakeTopics( event, From 7da0e3ad8ef1e3c557054d70c6300ba1cf363727 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Thu, 24 Oct 2024 20:19:47 +0200 Subject: [PATCH 09/20] e2e: finish disitribute tests --- cmd/zetae2e/local/local.go | 2 + e2e/e2etests/test_precompiles_distribute.go | 111 ++++++++++-------- ...precompiles_distribute_through_contract.go | 111 ++++++++++++++++++ e2e/utils/require.go | 2 +- 4 files changed, 176 insertions(+), 50 deletions(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index de46edf450..8db1d7ea02 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -335,6 +335,8 @@ func localE2ETest(cmd *cobra.Command, _ []string) { e2etests.TestPrecompilesBankName, e2etests.TestPrecompilesBankFailName, e2etests.TestPrecompilesBankThroughContractName, + e2etests.TestPrecompilesDistributeName, + e2etests.TestPrecompilesDistributeThroughContractName, } } diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index 2450bc0714..7e2daa581b 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -1,14 +1,11 @@ package e2etests import ( - "fmt" "math/big" "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -43,7 +40,7 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { r.ZEVMAuth.GasLimit = 10_000_000 // Set the test to reset the state after it finishes. - defer resetTest(r, lockerAddress, previousGasLimit) + defer resetTest(r, lockerAddress, previousGasLimit, fiveHundred) // Get ERC20ZRC20. txHash := r.DepositERC20WithAmountAndMessage(spenderAddress, oneThousand, []byte{}) @@ -52,9 +49,10 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { dstrContract, err := staking.NewIStaking(distributeContractAddress, r.ZEVMClient) require.NoError(r, err, "failed to create distribute contract caller") - validators, err := dstrContract.GetAllValidators(&bind.CallOpts{}) - require.NoError(r, err) - fmt.Println(validators) + // DO NOT REMOVE - will be used in a subsequent PR when the ability to withdraw delegator rewards is introduced. + // Get validators through staking contract. + // validators, err := dstrContract.GetAllValidators(&bind.CallOpts{}) + // require.NoError(r, err) // Check initial balances. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) @@ -118,48 +116,53 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { // After one block the rewards should have been distributed and fee collector should have 0 ZRC20 balance. r.WaitForBlocks(1) balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) - res, err := r.DistributionClient.ValidatorDistributionInfo( - r.Ctx, - &distributiontypes.QueryValidatorDistributionInfoRequest{ - ValidatorAddress: validators[0].OperatorAddress, - }, - ) - require.NoError(r, err) - fmt.Printf("Validator 0 distribution info: %+v\n", res) - - res2, err := r.DistributionClient.ValidatorOutstandingRewards(r.Ctx, &distributiontypes.QueryValidatorOutstandingRewardsRequest{ - ValidatorAddress: validators[0].OperatorAddress, - }) - require.NoError(r, err) - fmt.Printf("Validator 0 outstanding rewards: %+v\n", res2) - - res3, err := r.DistributionClient.ValidatorCommission(r.Ctx, &distributiontypes.QueryValidatorCommissionRequest{ - ValidatorAddress: validators[0].OperatorAddress, - }) - require.NoError(r, err) - fmt.Printf("Validator 0 commission: %+v\n", res3) - // Validator 1 - res, err = r.DistributionClient.ValidatorDistributionInfo( - r.Ctx, - &distributiontypes.QueryValidatorDistributionInfoRequest{ - ValidatorAddress: validators[1].OperatorAddress, - }, - ) - require.NoError(r, err) - fmt.Printf("Validator 1 distribution info: %+v\n", res) - - res2, err = r.DistributionClient.ValidatorOutstandingRewards(r.Ctx, &distributiontypes.QueryValidatorOutstandingRewardsRequest{ - ValidatorAddress: validators[1].OperatorAddress, - }) - require.NoError(r, err) - fmt.Printf("Validator 1 outstanding rewards: %+v\n", res2) - - res3, err = r.DistributionClient.ValidatorCommission(r.Ctx, &distributiontypes.QueryValidatorCommissionRequest{ - ValidatorAddress: validators[1].OperatorAddress, - }) - require.NoError(r, err) - fmt.Printf("Validator 1 commission: %+v\n", res3) + // DO NOT REMOVE THE FOLLOWING CODE + // This section is commented until a following PR introduces the ability to withdraw delegator rewards. + // This validator checks will be used then to complete the whole e2e. + + // res, err := r.DistributionClient.ValidatorDistributionInfo( + // r.Ctx, + // &distributiontypes.QueryValidatorDistributionInfoRequest{ + // ValidatorAddress: validators[0].OperatorAddress, + // }, + // ) + // require.NoError(r, err) + // fmt.Printf("Validator 0 distribution info: %+v\n", res) + + // res2, err := r.DistributionClient.ValidatorOutstandingRewards(r.Ctx, &distributiontypes.QueryValidatorOutstandingRewardsRequest{ + // ValidatorAddress: validators[0].OperatorAddress, + // }) + // require.NoError(r, err) + // fmt.Printf("Validator 0 outstanding rewards: %+v\n", res2) + + // res3, err := r.DistributionClient.ValidatorCommission(r.Ctx, &distributiontypes.QueryValidatorCommissionRequest{ + // ValidatorAddress: validators[0].OperatorAddress, + // }) + // require.NoError(r, err) + // fmt.Printf("Validator 0 commission: %+v\n", res3) + + // // Validator 1 + // res, err = r.DistributionClient.ValidatorDistributionInfo( + // r.Ctx, + // &distributiontypes.QueryValidatorDistributionInfoRequest{ + // ValidatorAddress: validators[1].OperatorAddress, + // }, + // ) + // require.NoError(r, err) + // fmt.Printf("Validator 1 distribution info: %+v\n", res) + + // res2, err = r.DistributionClient.ValidatorOutstandingRewards(r.Ctx, &distributiontypes.QueryValidatorOutstandingRewardsRequest{ + // ValidatorAddress: validators[1].OperatorAddress, + // }) + // require.NoError(r, err) + // fmt.Printf("Validator 1 outstanding rewards: %+v\n", res2) + + // res3, err = r.DistributionClient.ValidatorCommission(r.Ctx, &distributiontypes.QueryValidatorCommissionRequest{ + // ValidatorAddress: validators[1].OperatorAddress, + // }) + // require.NoError(r, err) + // fmt.Printf("Validator 1 commission: %+v\n", res3) } func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom string) *big.Int { @@ -172,7 +175,7 @@ func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom str return bal.Balance.Amount.BigInt() } -func resetTest(r *runner.E2ERunner, lockerAddress common.Address, previousGasLimit uint64) { +func resetTest(r *runner.E2ERunner, lockerAddress common.Address, previousGasLimit uint64, amount *big.Int) { r.ZEVMAuth.GasLimit = previousGasLimit // Reset the allowance to 0; this is needed when running upgrade tests where this test runs twice. @@ -180,4 +183,14 @@ func resetTest(r *runner.E2ERunner, lockerAddress common.Address, previousGasLim require.NoError(r, err) receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequireTxSuccessful(r, receipt, "Resetting allowance failed") + + // Reset balance to 0 for spender; this is needed when running upgrade tests where this test runs twice. + tx, err = r.ERC20ZRC20.Transfer( + r.ZEVMAuth, + common.HexToAddress("0x000000000000000000000000000000000000dEaD"), + amount, + ) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "Resetting balance failed") } diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_through_contract.go index f4d6c0f816..dd4366d2e2 100644 --- a/e2e/e2etests/test_precompiles_distribute_through_contract.go +++ b/e2e/e2etests/test_precompiles_distribute_through_contract.go @@ -1,11 +1,122 @@ package e2etests import ( + "math/big" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/e2e/contracts/testdistribute" "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/precompiles/bank" + "github.com/zeta-chain/node/precompiles/staking" + ptypes "github.com/zeta-chain/node/precompiles/types" ) func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string) { require.Len(r, args, 0, "No arguments expected") + + var ( + spenderAddress = r.EVMAddress() + distributeContractAddress = staking.ContractAddress + feeCollectorAddress = authtypes.NewModuleAddress("fee_collector") + lockerAddress = bank.ContractAddress + + zrc20Address = r.ERC20ZRC20Addr + zrc20Denom = ptypes.ZRC20ToCosmosDenom(zrc20Address) + + oneThousand = big.NewInt(1e3) + oneThousandOne = big.NewInt(1001) + fiveHundred = big.NewInt(500) + fiveHundredOne = big.NewInt(501) + + previousGasLimit = r.ZEVMAuth.GasLimit + ) + + // Get ERC20ZRC20. + txHash := r.DepositERC20WithAmountAndMessage(spenderAddress, oneThousand, []byte{}) + utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + + dstrContract, err := staking.NewIStaking(distributeContractAddress, r.ZEVMClient) + require.NoError(r, err, "failed to create distribute contract caller") + + _, tx, testDstrContract, err := testdistribute.DeployTestDistribute(r.ZEVMAuth, r.ZEVMClient) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "deployment of disitributor caller contract failed") + + // Set new gas limit to avoid out of gas errors. + r.ZEVMAuth.GasLimit = 10_000_000 + + // Set the test to reset the state after it finishes. + defer resetTest(r, lockerAddress, previousGasLimit, fiveHundred) + + // Check initial balances. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + + receipt = distributeThroughContract(r, testDstrContract, zrc20Address, oneThousand) + utils.RequiredTxFailed(r, receipt, "distribute should fail when there's no allowance") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + + // Allow 500. + approveAllowance(r, distributeContractAddress, fiveHundred) + + receipt = distributeThroughContract(r, testDstrContract, zrc20Address, fiveHundredOne) + utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than allowed") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + + // Raise the allowance to 1000. + approveAllowance(r, distributeContractAddress, oneThousand) + + // Shouldn't be able to distribute more than owned balance. + receipt = distributeThroughContract(r, testDstrContract, zrc20Address, oneThousandOne) + utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than owned balance") + + // Balances shouldn't change after a failed attempt. + balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + + // Should be able to distribute 500, which is within balance and allowance. + receipt = distributeThroughContract(r, testDstrContract, zrc20Address, fiveHundred) + utils.RequireTxSuccessful(r, receipt, "distribute should succeed when distributing within balance and allowance") + + balanceShouldBe(r, 500, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, 1000, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, 500, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + + eventDitributed, err := dstrContract.ParseDistributed(*receipt.Logs[0]) + require.NoError(r, err) + require.Equal(r, zrc20Address, eventDitributed.Zrc20Token) + require.Equal(r, spenderAddress, eventDitributed.Zrc20Distributor) + require.Equal(r, fiveHundred.Uint64(), eventDitributed.Amount.Uint64()) + + // After one block the rewards should have been distributed and fee collector should have 0 ZRC20 balance. + r.WaitForBlocks(1) + balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) +} + +func distributeThroughContract( + r *runner.E2ERunner, + dstr *testdistribute.TestDistribute, + zrc20Address common.Address, + amount *big.Int, +) *types.Receipt { + tx, err := dstr.DistributeThroughContract(r.ZEVMAuth, zrc20Address, amount) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + return receipt } diff --git a/e2e/utils/require.go b/e2e/utils/require.go index 3dedb7dd26..8bf9c5f5d0 100644 --- a/e2e/utils/require.go +++ b/e2e/utils/require.go @@ -38,7 +38,7 @@ func RequireTxSuccessful(t require.TestingT, receipt *ethtypes.Receipt, msgAndAr // RequiredTxFailed checks if the receipt status is failed. // Currently, it accepts eth receipt, but we can make this more generic by using type assertion. func RequiredTxFailed(t require.TestingT, receipt *ethtypes.Receipt, msgAndArgs ...any) { - msg := "receipt status is not successful: %s" + msg := "receipt status is not failed: %s" require.Equal( t, ethtypes.ReceiptStatusFailed, From 884598c261c506953bf2e0f441ee70fb073cb649 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Thu, 24 Oct 2024 20:46:04 +0200 Subject: [PATCH 10/20] fix event name typo --- precompiles/staking/const.go | 2 +- precompiles/staking/logs.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/precompiles/staking/const.go b/precompiles/staking/const.go index f18b072bbe..8500e723f4 100644 --- a/precompiles/staking/const.go +++ b/precompiles/staking/const.go @@ -2,7 +2,7 @@ package staking const ( DistributeMethodName = "distribute" - DisitributeEventName = "Distributed" + DistributeEventName = "Distributed" DistributeMethodGas = 10000 GetAllValidatorsMethodName = "getAllValidators" diff --git a/precompiles/staking/logs.go b/precompiles/staking/logs.go index 9154b9a23d..c8d1db24e2 100644 --- a/precompiles/staking/logs.go +++ b/precompiles/staking/logs.go @@ -125,7 +125,7 @@ func (c *Contract) addDistributeLog( zrc20Token common.Address, amount *big.Int, ) error { - event := c.Abi().Events[DisitributeEventName] + event := c.Abi().Events[DistributeEventName] topics, err := logs.MakeTopics( event, From 7243c80e07a339a2e2b8c5153cae1e3e183efb6f Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Fri, 25 Oct 2024 13:18:43 +0200 Subject: [PATCH 11/20] Update precompiles/types/coin.go Co-authored-by: Tanmay --- precompiles/types/coin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/types/coin.go b/precompiles/types/coin.go index 5d1e8e598e..16037e0a94 100644 --- a/precompiles/types/coin.go +++ b/precompiles/types/coin.go @@ -28,7 +28,7 @@ func CreateCoinSet(tokenDenom string, amount *big.Int) (sdk.Coins, error) { // A sdk.Coins (type []sdk.Coin) has to be created because it's the type expected by MintCoins // and SendCoinsFromModuleToAccount. - // But sdk.Coins will only contain one coin, always. + // But coinSet will only contain one coin, always. coinSet := sdk.NewCoins(coin) if !coinSet.IsValid() || coinSet.Empty() || coinSet.IsAnyNil() || coinSet == nil { return nil, &ErrInvalidCoin{ From 95c92370a0a2983384cf317779b6c302e8b6f5c7 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Fri, 25 Oct 2024 17:23:43 +0200 Subject: [PATCH 12/20] first batch of reviews --- precompiles/types/address.go | 6 +++++ precompiles/types/address_test.go | 45 +++++++++++++++++++++++-------- precompiles/types/coin.go | 8 +++++- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/precompiles/types/address.go b/precompiles/types/address.go index d587b550fa..5e6654cc5b 100644 --- a/precompiles/types/address.go +++ b/precompiles/types/address.go @@ -1,6 +1,8 @@ package types import ( + "errors" + sdk "github.com/cosmos/cosmos-sdk/types" bank "github.com/cosmos/cosmos-sdk/x/bank/keeper" "github.com/ethereum/go-ethereum/common" @@ -12,6 +14,10 @@ import ( // If contract.CallerAddress != evm.Origin is true, it means the call was made through a contract, // on which case there is a need to set the caller to the evm.Origin. func GetEVMCallerAddress(evm *vm.EVM, contract *vm.Contract) (common.Address, error) { + if evm == nil || contract == nil { + return common.Address{}, errors.New("invalid input: evm or contract is nil") + } + caller := contract.CallerAddress if contract.CallerAddress != evm.Origin { caller = evm.Origin diff --git a/precompiles/types/address_test.go b/precompiles/types/address_test.go index c329fcd6ab..801df4f8d4 100644 --- a/precompiles/types/address_test.go +++ b/precompiles/types/address_test.go @@ -11,9 +11,41 @@ import ( ) func Test_GetEVMCallerAddress(t *testing.T) { + t.Run("should raise error when evm is nil", func(t *testing.T) { + _, mockVMContract := setupMockEVMAndContract(common.Address{}) + caller, err := GetEVMCallerAddress(nil, &mockVMContract) + require.Error(t, err) + require.Equal(t, common.Address{}, caller, "address should be zeroed") + }) + + t.Run("should raise error when contract is nil", func(t *testing.T) { + mockEVM, _ := setupMockEVMAndContract(common.Address{}) + caller, err := GetEVMCallerAddress(&mockEVM, nil) + require.Error(t, err) + require.Equal(t, common.Address{}, caller, "address should be zeroed") + }) + + // When contract.CallerAddress == evm.Origin, caller is set to contract.CallerAddress. + t.Run("when caller address equals origin", func(t *testing.T) { + mockEVM, mockVMContract := setupMockEVMAndContract(common.Address{}) + caller, err := GetEVMCallerAddress(&mockEVM, &mockVMContract) + require.NoError(t, err) + require.Equal(t, common.Address{}, caller, "address should be the same") + }) + + // When contract.CallerAddress != evm.Origin, caller should be set to evm.Origin. + t.Run("when caller address equals origin", func(t *testing.T) { + mockEVM, mockVMContract := setupMockEVMAndContract(sample.EthAddress()) + caller, err := GetEVMCallerAddress(&mockEVM, &mockVMContract) + require.NoError(t, err) + require.Equal(t, mockEVM.Origin, caller, "address should be evm.Origin") + }) +} + +func setupMockEVMAndContract(address common.Address) (vm.EVM, vm.Contract) { mockEVM := vm.EVM{ TxContext: vm.TxContext{ - Origin: common.Address{}, + Origin: address, }, } @@ -24,16 +56,7 @@ func Test_GetEVMCallerAddress(t *testing.T) { 0, ) - // When contract.CallerAddress == evm.Origin, caller is set to contract.CallerAddress. - caller, err := GetEVMCallerAddress(&mockEVM, mockVMContract) - require.NoError(t, err) - require.Equal(t, common.Address{}, caller, "address shouldn be the same") - - // When contract.CallerAddress != evm.Origin, caller should be set to evm.Origin. - mockEVM.Origin = sample.EthAddress() - caller, err = GetEVMCallerAddress(&mockEVM, mockVMContract) - require.NoError(t, err) - require.Equal(t, mockEVM.Origin, caller, "address should be evm.Origin") + return mockEVM, *mockVMContract } type contractRef struct { diff --git a/precompiles/types/coin.go b/precompiles/types/coin.go index 16037e0a94..0c95eb108b 100644 --- a/precompiles/types/coin.go +++ b/precompiles/types/coin.go @@ -11,12 +11,18 @@ import ( const ZRC20DenomPrefix = "zrc20/" // ZRC20ToCosmosDenom returns the cosmos coin address for a given ZRC20 address. -// This is converted to "zevm/{ZRC20Address}". +// This is converted to "zrc20/{ZRC20Address}". func ZRC20ToCosmosDenom(ZRC20Address common.Address) string { return ZRC20DenomPrefix + ZRC20Address.String() } func CreateCoinSet(tokenDenom string, amount *big.Int) (sdk.Coins, error) { + defer func() { + if r := recover(); r != nil { + return + } + }() + coin := sdk.NewCoin(tokenDenom, math.NewIntFromBigInt(amount)) if !coin.IsValid() { return nil, &ErrInvalidCoin{ From 2f35ee767344b8ca968d0b45356a02c51cd7a8bf Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 28 Oct 2024 18:56:25 +0100 Subject: [PATCH 13/20] apply reviews --- cmd/zetacored/config/prefixes.go | 3 +++ e2e/e2etests/test_precompiles_distribute.go | 19 ++++++++-------- ...precompiles_distribute_through_contract.go | 16 ++++++-------- e2e/runner/runner.go | 4 ++++ precompiles/bank/bank.go | 1 + precompiles/staking/method_distribute.go | 22 ++++++++----------- precompiles/staking/staking.go | 1 + precompiles/types/coin.go | 6 ++--- .../keeper/zrc20_cosmos_coins_mapping.go | 1 + 9 files changed, 38 insertions(+), 35 deletions(-) diff --git a/cmd/zetacored/config/prefixes.go b/cmd/zetacored/config/prefixes.go index a96a4c57bc..5fcd9218a5 100644 --- a/cmd/zetacored/config/prefixes.go +++ b/cmd/zetacored/config/prefixes.go @@ -6,6 +6,9 @@ const ( // Bech32Prefix defines the Bech32 prefix used for Cronos Accounts Bech32Prefix = "zeta" + // ZRC20DenomPrefix defines the prefix for ZRC20 tokens when converted to sdk.Coin. + ZRC20DenomPrefix = "zrc20/" + // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address Bech32PrefixAccAddr = Bech32Prefix // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index 7e2daa581b..7f98518f23 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -4,7 +4,6 @@ import ( "math/big" "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -22,7 +21,6 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { var ( spenderAddress = r.EVMAddress() distributeContractAddress = staking.ContractAddress - feeCollectorAddress = authtypes.NewModuleAddress("fee_collector") lockerAddress = bank.ContractAddress zrc20Address = r.ERC20ZRC20Addr @@ -40,7 +38,7 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { r.ZEVMAuth.GasLimit = 10_000_000 // Set the test to reset the state after it finishes. - defer resetTest(r, lockerAddress, previousGasLimit, fiveHundred) + defer resetDistributionTest(r, lockerAddress, previousGasLimit, fiveHundred) // Get ERC20ZRC20. txHash := r.DepositERC20WithAmountAndMessage(spenderAddress, oneThousand, []byte{}) @@ -57,7 +55,7 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { // Check initial balances. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) - balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) tx, err := dstrContract.Distribute(r.ZEVMAuth, zrc20Address, oneThousand) require.NoError(r, err) @@ -67,7 +65,7 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { // Balances shouldn't change after a failed attempt. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) - balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Allow 500. approveAllowance(r, distributeContractAddress, fiveHundred) @@ -81,7 +79,7 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { // Balances shouldn't change after a failed attempt. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) - balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Raise the allowance to 1000. approveAllowance(r, distributeContractAddress, oneThousand) @@ -95,7 +93,7 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { // Balances shouldn't change after a failed attempt. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) - balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Should be able to distribute 500, which is within balance and allowance. tx, err = dstrContract.Distribute(r.ZEVMAuth, zrc20Address, fiveHundred) @@ -105,7 +103,7 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { balanceShouldBe(r, 500, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) - balanceShouldBe(r, 500, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 500, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) eventDitributed, err := dstrContract.ParseDistributed(*receipt.Logs[0]) require.NoError(r, err) @@ -115,7 +113,7 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { // After one block the rewards should have been distributed and fee collector should have 0 ZRC20 balance. r.WaitForBlocks(1) - balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // DO NOT REMOVE THE FOLLOWING CODE // This section is commented until a following PR introduces the ability to withdraw delegator rewards. @@ -165,6 +163,7 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { // fmt.Printf("Validator 1 commission: %+v\n", res3) } +// checkCosmosBalance checks the cosmos coin balance for an address. The coin is specified by its denom. func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom string) *big.Int { bal, err := r.BankClient.Balance( r.Ctx, @@ -175,7 +174,7 @@ func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom str return bal.Balance.Amount.BigInt() } -func resetTest(r *runner.E2ERunner, lockerAddress common.Address, previousGasLimit uint64, amount *big.Int) { +func resetDistributionTest(r *runner.E2ERunner, lockerAddress common.Address, previousGasLimit uint64, amount *big.Int) { r.ZEVMAuth.GasLimit = previousGasLimit // Reset the allowance to 0; this is needed when running upgrade tests where this test runs twice. diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_through_contract.go index dd4366d2e2..0fccf7ce21 100644 --- a/e2e/e2etests/test_precompiles_distribute_through_contract.go +++ b/e2e/e2etests/test_precompiles_distribute_through_contract.go @@ -3,7 +3,6 @@ package e2etests import ( "math/big" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" @@ -22,7 +21,6 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string var ( spenderAddress = r.EVMAddress() distributeContractAddress = staking.ContractAddress - feeCollectorAddress = authtypes.NewModuleAddress("fee_collector") lockerAddress = bank.ContractAddress zrc20Address = r.ERC20ZRC20Addr @@ -52,12 +50,12 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string r.ZEVMAuth.GasLimit = 10_000_000 // Set the test to reset the state after it finishes. - defer resetTest(r, lockerAddress, previousGasLimit, fiveHundred) + defer resetDistributionTest(r, lockerAddress, previousGasLimit, fiveHundred) // Check initial balances. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. - balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) receipt = distributeThroughContract(r, testDstrContract, zrc20Address, oneThousand) utils.RequiredTxFailed(r, receipt, "distribute should fail when there's no allowance") @@ -65,7 +63,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string // Balances shouldn't change after a failed attempt. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. - balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Allow 500. approveAllowance(r, distributeContractAddress, fiveHundred) @@ -76,7 +74,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string // Balances shouldn't change after a failed attempt. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. - balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Raise the allowance to 1000. approveAllowance(r, distributeContractAddress, oneThousand) @@ -88,7 +86,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string // Balances shouldn't change after a failed attempt. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. - balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Should be able to distribute 500, which is within balance and allowance. receipt = distributeThroughContract(r, testDstrContract, zrc20Address, fiveHundred) @@ -96,7 +94,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string balanceShouldBe(r, 500, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 1000, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. - balanceShouldBe(r, 500, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 500, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) eventDitributed, err := dstrContract.ParseDistributed(*receipt.Logs[0]) require.NoError(r, err) @@ -106,7 +104,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string // After one block the rewards should have been distributed and fee collector should have 0 ZRC20 balance. r.WaitForBlocks(1) - balanceShouldBe(r, 0, checkCosmosBalance(r, feeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) } func distributeThroughContract( diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index c3dcb74637..cf7a8e6f02 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/rpcclient" + "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" @@ -75,6 +76,7 @@ type E2ERunner struct { SolanaDeployerAddress solana.PublicKey TONDeployer *tonrunner.Deployer TONGateway *toncontracts.Gateway + FeeCollectorAddress types.AccAddress // all clients. // a reference to this type is required to enable creating a new E2ERunner. @@ -189,6 +191,8 @@ func NewE2ERunner( Account: account, + FeeCollectorAddress: authtypes.NewModuleAddress(authtypes.FeeCollectorName), + Clients: clients, ZEVMClient: clients.Zevm, diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index 9dbb23ef5b..22f2258928 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -74,6 +74,7 @@ func NewIBankContract( // This avoids instantiating it every time deposit or withdraw are called. zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() if err != nil { + ctx.Logger().Error("bank contract failed to get ZRC20 ABI", "error", err) return nil } diff --git a/precompiles/staking/method_distribute.go b/precompiles/staking/method_distribute.go index 84e966d936..3fd282a8cf 100644 --- a/precompiles/staking/method_distribute.go +++ b/precompiles/staking/method_distribute.go @@ -1,7 +1,6 @@ package staking import ( - "fmt" "math/big" sdk "github.com/cosmos/cosmos-sdk/types" @@ -10,14 +9,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" + "github.com/zeta-chain/node/precompiles/bank" ptypes "github.com/zeta-chain/node/precompiles/types" fungibletypes "github.com/zeta-chain/node/x/fungible/types" ) -var ( - zrc20lockerAddress = common.HexToAddress("0x0000000000000000000000000000000000000067") -) - // function distribute(address zrc20, uint256 amount) external returns (bool success) func (c *Contract) distribute( ctx sdk.Context, @@ -27,10 +23,10 @@ func (c *Contract) distribute( args []interface{}, ) ([]byte, error) { if len(args) != 2 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &ptypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 2, - }) + } } // Unpack arguments and check if they are valid. @@ -56,7 +52,7 @@ func (c *Contract) distribute( // - spender is the staking contract address (c.Address()). // - owner is the caller address. // - locker is the bank address. Assets are locked under this address to prevent liquidity fragmentation. - if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, c.Address(), caller, zrc20lockerAddress, amount); err != nil { + if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, c.Address(), caller, bank.ContractAddress, amount); err != nil { return nil, &ptypes.ErrUnexpected{ When: "LockZRC20InBank", Got: err.Error(), @@ -81,11 +77,11 @@ func (c *Contract) distribute( // Log similar message as in abci DistributeValidatorRewards function. ctx.Logger().Info( - fmt.Sprintf("Distributing ZRC20 Validator Rewards Total:%s To FeeCollector : %s, Denom: %s", - amount.String(), - authtypes.FeeCollectorName, - ptypes.ZRC20ToCosmosDenom(zrc20Addr), - )) + "Distributing ZRC20 Validator Rewards", + "Total", amount.String(), + "Fee_collector", authtypes.FeeCollectorName, + "Denom", ptypes.ZRC20ToCosmosDenom(zrc20Addr), + ) if err := c.addDistributeLog(ctx, evm.StateDB, caller, zrc20Addr, amount); err != nil { return nil, &ptypes.ErrUnexpected{ diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index dbb1b6d482..16fc2f1f98 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -88,6 +88,7 @@ func NewIStakingContract( // This avoids instantiating it every time deposit or withdraw are called. zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() if err != nil { + ctx.Logger().Error("staking contract failed to get ZRC20 ABI", "error", err) return nil } diff --git a/precompiles/types/coin.go b/precompiles/types/coin.go index 0c95eb108b..7c0bc25317 100644 --- a/precompiles/types/coin.go +++ b/precompiles/types/coin.go @@ -6,14 +6,14 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" -) -const ZRC20DenomPrefix = "zrc20/" + "github.com/zeta-chain/node/cmd/zetacored/config" +) // ZRC20ToCosmosDenom returns the cosmos coin address for a given ZRC20 address. // This is converted to "zrc20/{ZRC20Address}". func ZRC20ToCosmosDenom(ZRC20Address common.Address) string { - return ZRC20DenomPrefix + ZRC20Address.String() + return config.ZRC20DenomPrefix + ZRC20Address.String() } func CreateCoinSet(tokenDenom string, amount *big.Int) (sdk.Coins, error) { diff --git a/x/fungible/keeper/zrc20_cosmos_coins_mapping.go b/x/fungible/keeper/zrc20_cosmos_coins_mapping.go index 140dc52aab..7ac3d7ef22 100644 --- a/x/fungible/keeper/zrc20_cosmos_coins_mapping.go +++ b/x/fungible/keeper/zrc20_cosmos_coins_mapping.go @@ -24,6 +24,7 @@ func (k Keeper) LockZRC20( amount *big.Int, ) error { // owner is the EOA owner of the ZRC20 tokens. + // spender is the EOA allowed to spend ZRC20 on owner's behalf. // locker is the address that will lock the ZRC20 tokens, i.e: bank precompile. if err := k.CheckZRC20Allowance(ctx, zrc20ABI, owner, spender, zrc20Address, amount); err != nil { return errors.Wrap(err, "failed allowance check") From d851eaff3f29be6599c914f714194dc3833d2865 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Tue, 29 Oct 2024 10:22:13 +0100 Subject: [PATCH 14/20] add nonZRC20 token e2e test --- cmd/zetae2e/local/local.go | 17 ++++----- e2e/e2etests/e2etests.go | 7 ++++ e2e/e2etests/test_precompiles_distribute.go | 39 +++++++++++++++++++++ 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 8db1d7ea02..0b83daf5fc 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -327,15 +327,16 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if !skipPrecompiles { precompiledContractTests = []string{ - e2etests.TestPrecompilesPrototypeName, - e2etests.TestPrecompilesPrototypeThroughContractName, - e2etests.TestPrecompilesStakingName, - // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - // e2etests.TestPrecompilesStakingThroughContractName, - e2etests.TestPrecompilesBankName, - e2etests.TestPrecompilesBankFailName, - e2etests.TestPrecompilesBankThroughContractName, + // e2etests.TestPrecompilesPrototypeName, + // e2etests.TestPrecompilesPrototypeThroughContractName, + // e2etests.TestPrecompilesStakingName, + // // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. + // // e2etests.TestPrecompilesStakingThroughContractName, + // e2etests.TestPrecompilesBankName, + // e2etests.TestPrecompilesBankFailName, + // e2etests.TestPrecompilesBankThroughContractName, e2etests.TestPrecompilesDistributeName, + e2etests.TestPrecompilesDistributeNonZRC20Name, e2etests.TestPrecompilesDistributeThroughContractName, } } diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 308fc4b829..d530ddb220 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -175,6 +175,7 @@ const ( TestPrecompilesBankFailName = "precompile_contracts_bank_fail" TestPrecompilesBankThroughContractName = "precompile_contracts_bank_through_contract" TestPrecompilesDistributeName = "precompile_contracts_distribute" + TestPrecompilesDistributeNonZRC20Name = "precompile_contracts_distribute_non_zrc20" TestPrecompilesDistributeThroughContractName = "precompile_contracts_distribute_through_contract" ) @@ -1005,6 +1006,12 @@ var AllE2ETests = []runner.E2ETest{ []runner.ArgDefinition{}, TestPrecompilesDistribute, ), + runner.NewE2ETest( + TestPrecompilesDistributeNonZRC20Name, + "test stateful precompiled contracts distribute with non ZRC20 tokens", + []runner.ArgDefinition{}, + TestPrecompilesDistributeNonZRC20, + ), runner.NewE2ETest( TestPrecompilesDistributeThroughContractName, "test stateful precompiled contracts distribute through contract", diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index 7f98518f23..69c4dbb9e9 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -5,6 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -163,6 +164,44 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { // fmt.Printf("Validator 1 commission: %+v\n", res3) } +func TestPrecompilesDistributeNonZRC20(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 + }() + + spender, dstrAddress := r.EVMAddress(), staking.ContractAddress + + // Create a staking contract caller. + dstrContract, err := staking.NewIStaking(dstrAddress, r.ZEVMClient) + require.NoError(r, err, "Failed to create staking contract caller") + + // Deposit and approve 50 WZETA for the test. + approveAmount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(50)) + r.DepositAndApproveWZeta(approveAmount) + + // Allow the staking contract to spend 25 WZeta tokens. + tx, err := r.WZeta.Approve(r.ZEVMAuth, dstrAddress, big.NewInt(25)) + require.NoError(r, err, "Error approving allowance for staking contract") + 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 staking in WZeta tokens. Should be 25. + allowance, err := r.WZeta.Allowance(&bind.CallOpts{Context: r.Ctx}, spender, dstrAddress) + require.NoError(r, err, "Error retrieving staking allowance") + require.EqualValues(r, uint64(25), allowance.Uint64(), "Error allowance for staking contract") + + // Call Distribute with 25 Non ZRC20 tokens. Should fail. + tx, err = dstrContract.Distribute(r.ZEVMAuth, r.WZetaAddr, big.NewInt(25)) + require.NoError(r, err, "Error calling staking.distribute()") + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + require.Equal(r, uint64(0), receipt.Status, "Non ZRC20 deposit should fail") +} + // checkCosmosBalance checks the cosmos coin balance for an address. The coin is specified by its denom. func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom string) *big.Int { bal, err := r.BankClient.Balance( From 4eae5e2fd58f8c7f16e324ea8321dd12944c563b Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Tue, 29 Oct 2024 10:40:31 +0100 Subject: [PATCH 15/20] add fee collector balance check --- e2e/e2etests/test_precompiles_distribute.go | 4 +- ...precompiles_distribute_through_contract.go | 4 +- precompiles/bank/bank.go | 12 ++-- precompiles/bank/method_balance_of.go | 16 ++--- precompiles/bank/method_deposit.go | 28 ++++---- precompiles/bank/method_test.go | 12 ++-- precompiles/bank/method_withdraw.go | 32 +++++----- precompiles/prototype/prototype.go | 18 +++--- precompiles/staking/method_distribute.go | 22 +++---- precompiles/staking/method_distribute_test.go | 64 ++++++++++++++++++- precompiles/staking/method_get_shares.go | 8 +-- precompiles/staking/method_move_stake.go | 14 ++-- precompiles/staking/method_move_stake_test.go | 6 +- precompiles/staking/method_stake.go | 12 ++-- precompiles/staking/method_stake_test.go | 4 +- precompiles/staking/method_unstake.go | 12 ++-- precompiles/staking/method_unstake_test.go | 4 +- precompiles/staking/staking.go | 18 +++--- 18 files changed, 174 insertions(+), 116 deletions(-) diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index 69c4dbb9e9..e5b5ca7fcb 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -13,7 +13,7 @@ import ( "github.com/zeta-chain/node/e2e/utils" "github.com/zeta-chain/node/precompiles/bank" "github.com/zeta-chain/node/precompiles/staking" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" ) func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { @@ -25,7 +25,7 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { lockerAddress = bank.ContractAddress zrc20Address = r.ERC20ZRC20Addr - zrc20Denom = ptypes.ZRC20ToCosmosDenom(zrc20Address) + zrc20Denom = precompiletypes.ZRC20ToCosmosDenom(zrc20Address) oneThousand = big.NewInt(1e3) oneThousandOne = big.NewInt(1001) diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_through_contract.go index 0fccf7ce21..444d8e6a59 100644 --- a/e2e/e2etests/test_precompiles_distribute_through_contract.go +++ b/e2e/e2etests/test_precompiles_distribute_through_contract.go @@ -12,7 +12,7 @@ import ( "github.com/zeta-chain/node/e2e/utils" "github.com/zeta-chain/node/precompiles/bank" "github.com/zeta-chain/node/precompiles/staking" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" ) func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string) { @@ -24,7 +24,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string lockerAddress = bank.ContractAddress zrc20Address = r.ERC20ZRC20Addr - zrc20Denom = ptypes.ZRC20ToCosmosDenom(zrc20Address) + zrc20Denom = precompiletypes.ZRC20ToCosmosDenom(zrc20Address) oneThousand = big.NewInt(1e3) oneThousandOne = big.NewInt(1001) diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index 22f2258928..b92b4a8217 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -11,7 +11,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" ) @@ -49,7 +49,7 @@ func initABI() { } type Contract struct { - ptypes.BaseContract + precompiletypes.BaseContract bankKeeper bank.Keeper fungibleKeeper fungiblekeeper.Keeper @@ -79,7 +79,7 @@ func NewIBankContract( } return &Contract{ - BaseContract: ptypes.NewBaseContract(ContractAddress), + BaseContract: precompiletypes.NewBaseContract(ContractAddress), bankKeeper: bankKeeper, fungibleKeeper: fungibleKeeper, zrc20ABI: zrc20ABI, @@ -131,13 +131,13 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return nil, err } - stateDB := evm.StateDB.(ptypes.ExtStateDB) + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) switch method.Name { // Deposit and Withdraw methods are both not allowed in read-only mode. case DepositMethodName, WithdrawMethodName: if readOnly { - return nil, ptypes.ErrWriteMethod{ + return nil, precompiletypes.ErrWriteMethod{ Method: method.Name, } } @@ -176,7 +176,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return res, nil default: - return nil, ptypes.ErrInvalidMethod{ + return nil, precompiletypes.ErrInvalidMethod{ Method: method.Name, } } diff --git a/precompiles/bank/method_balance_of.go b/precompiles/bank/method_balance_of.go index d5d08320f9..85765d2ab8 100644 --- a/precompiles/bank/method_balance_of.go +++ b/precompiles/bank/method_balance_of.go @@ -5,7 +5,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" ) // balanceOf returns the balance of cosmos coins minted by the bank's deposit function, @@ -19,7 +19,7 @@ func (c *Contract) balanceOf( args []interface{}, ) (result []byte, err error) { if len(args) != 2 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 2, }) @@ -32,7 +32,7 @@ func (c *Contract) balanceOf( } // Get the counterpart cosmos address. - toAddr, err := ptypes.GetCosmosAddress(c.bankKeeper, addr) + toAddr, err := precompiletypes.GetCosmosAddress(c.bankKeeper, addr) if err != nil { return nil, err } @@ -41,7 +41,7 @@ func (c *Contract) balanceOf( // Do not check for t.Paused, as the balance is read only the EOA won't be able to operate. _, found := c.fungibleKeeper.GetForeignCoins(ctx, zrc20Addr.String()) if !found { - return nil, &ptypes.ErrInvalidToken{ + return nil, &precompiletypes.ErrInvalidToken{ Got: zrc20Addr.String(), Reason: "token is not a whitelisted ZRC20", } @@ -49,9 +49,9 @@ func (c *Contract) balanceOf( // Bank Keeper GetBalance returns the specified Cosmos coin balance for a given address. // Check explicitly the balance is a non-negative non-nil value. - coin := c.bankKeeper.GetBalance(ctx, toAddr, ptypes.ZRC20ToCosmosDenom(zrc20Addr)) + coin := c.bankKeeper.GetBalance(ctx, toAddr, precompiletypes.ZRC20ToCosmosDenom(zrc20Addr)) if !coin.IsValid() { - return nil, &ptypes.ErrInvalidCoin{ + return nil, &precompiletypes.ErrInvalidCoin{ Got: coin.GetDenom(), Negative: coin.IsNegative(), Nil: coin.IsNil(), @@ -64,14 +64,14 @@ func (c *Contract) balanceOf( func unpackBalanceOfArgs(args []interface{}) (zrc20Addr common.Address, addr common.Address, err error) { zrc20Addr, ok := args[0].(common.Address) if !ok { - return common.Address{}, common.Address{}, &ptypes.ErrInvalidAddr{ + return common.Address{}, common.Address{}, &precompiletypes.ErrInvalidAddr{ Got: zrc20Addr.String(), } } addr, ok = args[1].(common.Address) if !ok { - return common.Address{}, common.Address{}, &ptypes.ErrInvalidAddr{ + return common.Address{}, common.Address{}, &precompiletypes.ErrInvalidAddr{ Got: addr.String(), } } diff --git a/precompiles/bank/method_deposit.go b/precompiles/bank/method_deposit.go index b4af95ab4a..e98a9c8208 100644 --- a/precompiles/bank/method_deposit.go +++ b/precompiles/bank/method_deposit.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/x/fungible/types" ) @@ -34,7 +34,7 @@ func (c *Contract) deposit( // This function is developed using the Check - Effects - Interactions pattern: // 1. Check everything is correct. if len(args) != 2 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 2, }) @@ -48,13 +48,13 @@ func (c *Contract) deposit( } // Get the correct caller address. - caller, err := ptypes.GetEVMCallerAddress(evm, contract) + caller, err := precompiletypes.GetEVMCallerAddress(evm, contract) if err != nil { return nil, err } // Get the cosmos address of the caller. - toAddr, err := ptypes.GetCosmosAddress(c.bankKeeper, caller) + toAddr, err := precompiletypes.GetCosmosAddress(c.bankKeeper, caller) if err != nil { return nil, err } @@ -70,7 +70,7 @@ func (c *Contract) deposit( []interface{}{caller}, ) if err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "balanceOf", Got: err.Error(), } @@ -78,13 +78,13 @@ func (c *Contract) deposit( balance, ok := resBalanceOf[0].(*big.Int) if !ok { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ Got: "ZRC20 balanceOf returned an unexpected type", } } if balance.Cmp(amount) < 0 || balance.Cmp(big.NewInt(0)) <= 0 { - return nil, &ptypes.ErrInvalidAmount{ + return nil, &precompiletypes.ErrInvalidAmount{ Got: balance.String(), } } @@ -94,14 +94,14 @@ func (c *Contract) deposit( // this way we map ZRC20 addresses to cosmos denoms "zevm/0x12345". // - Mint coins to the fungible module. // - Send coins from fungible to the caller. - coinSet, err := ptypes.CreateCoinSet(ptypes.ZRC20ToCosmosDenom(zrc20Addr), amount) + coinSet, err := precompiletypes.CreateCoinSet(precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), amount) if err != nil { return nil, err } // 2. Effect: subtract balance. if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, c.Address(), caller, c.Address(), amount); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "LockZRC20InBank", Got: err.Error(), } @@ -109,7 +109,7 @@ func (c *Contract) deposit( // 3. Interactions: create cosmos coin and send. if err := c.bankKeeper.MintCoins(ctx, types.ModuleName, coinSet); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "MintCoins", Got: err.Error(), } @@ -117,14 +117,14 @@ func (c *Contract) deposit( err = c.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, toAddr, coinSet) if err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "SendCoinsFromModuleToAccount", Got: err.Error(), } } if err := c.addEventLog(ctx, evm.StateDB, DepositEventName, eventData{caller, zrc20Addr, toAddr.String(), coinSet.Denoms()[0], amount}); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "AddDepositLog", Got: err.Error(), } @@ -136,14 +136,14 @@ func (c *Contract) deposit( func unpackDepositArgs(args []interface{}) (zrc20Addr common.Address, amount *big.Int, err error) { zrc20Addr, ok := args[0].(common.Address) if !ok { - return common.Address{}, nil, &ptypes.ErrInvalidAddr{ + return common.Address{}, nil, &precompiletypes.ErrInvalidAddr{ Got: zrc20Addr.String(), } } amount, ok = args[1].(*big.Int) if !ok || amount == nil || amount.Sign() <= 0 { - return common.Address{}, nil, &ptypes.ErrInvalidAmount{ + return common.Address{}, nil, &precompiletypes.ErrInvalidAmount{ Got: amount.String(), } } diff --git a/precompiles/bank/method_test.go b/precompiles/bank/method_test.go index e416187490..38fc715af6 100644 --- a/precompiles/bank/method_test.go +++ b/precompiles/bank/method_test.go @@ -16,7 +16,7 @@ import ( "github.com/zeta-chain/ethermint/x/evm/statedb" "github.com/zeta-chain/node/pkg/chains" erc1967proxy "github.com/zeta-chain/node/pkg/contracts/erc1967proxy" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/testutil/keeper" "github.com/zeta-chain/node/testutil/sample" fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" @@ -46,7 +46,7 @@ func Test_Methods(t *testing.T) { success, err := ts.bankContract.Run(ts.mockEVM, ts.mockVMContract, true) require.ErrorIs( t, - ptypes.ErrWriteMethod{ + precompiletypes.ErrWriteMethod{ Method: "deposit", }, err) @@ -73,7 +73,7 @@ func Test_Methods(t *testing.T) { success, err := ts.bankContract.Run(ts.mockEVM, ts.mockVMContract, true) require.ErrorIs( t, - ptypes.ErrWriteMethod{ + precompiletypes.ErrWriteMethod{ Method: "withdraw", }, err) @@ -101,7 +101,7 @@ func Test_Methods(t *testing.T) { require.Error(t, err) require.ErrorAs( t, - ptypes.ErrInvalidAmount{ + precompiletypes.ErrInvalidAmount{ Got: "0", }, err, @@ -248,7 +248,7 @@ func Test_Methods(t *testing.T) { require.Error(t, err) require.ErrorAs( t, - ptypes.ErrInvalidAmount{ + precompiletypes.ErrInvalidAmount{ Got: "1000", }, err, @@ -459,7 +459,7 @@ func Test_Methods(t *testing.T) { require.Error(t, err) require.ErrorAs( t, - ptypes.ErrInsufficientBalance{ + precompiletypes.ErrInsufficientBalance{ Requested: "501", Got: "500", }, diff --git a/precompiles/bank/method_withdraw.go b/precompiles/bank/method_withdraw.go index 5a5411503c..0072619160 100644 --- a/precompiles/bank/method_withdraw.go +++ b/precompiles/bank/method_withdraw.go @@ -9,7 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/x/fungible/types" ) @@ -29,7 +29,7 @@ func (c *Contract) withdraw( ) (result []byte, err error) { // 1. Check everything is correct. if len(args) != 2 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 2, }) @@ -43,50 +43,50 @@ func (c *Contract) withdraw( } // Get the correct caller address. - caller, err := ptypes.GetEVMCallerAddress(evm, contract) + caller, err := precompiletypes.GetEVMCallerAddress(evm, contract) if err != nil { return nil, err } // Get the cosmos address of the caller. // This address should have enough cosmos coin balance as the requested amount. - fromAddr, err := ptypes.GetCosmosAddress(c.bankKeeper, caller) + fromAddr, err := precompiletypes.GetCosmosAddress(c.bankKeeper, caller) if err != nil { return nil, err } // Safety check: token has to be a non-paused whitelisted ZRC20. if err := c.fungibleKeeper.IsValidZRC20(ctx, zrc20Addr); err != nil { - return nil, &ptypes.ErrInvalidToken{ + return nil, &precompiletypes.ErrInvalidToken{ Got: zrc20Addr.String(), Reason: err.Error(), } } // Caller has to have enough cosmos coin balance to withdraw the requested amount. - coin := c.bankKeeper.GetBalance(ctx, fromAddr, ptypes.ZRC20ToCosmosDenom(zrc20Addr)) + coin := c.bankKeeper.GetBalance(ctx, fromAddr, precompiletypes.ZRC20ToCosmosDenom(zrc20Addr)) if !coin.IsValid() { - return nil, &ptypes.ErrInsufficientBalance{ + return nil, &precompiletypes.ErrInsufficientBalance{ Requested: amount.String(), Got: "invalid coin", } } if coin.Amount.LT(math.NewIntFromBigInt(amount)) { - return nil, &ptypes.ErrInsufficientBalance{ + return nil, &precompiletypes.ErrInsufficientBalance{ Requested: amount.String(), Got: coin.Amount.String(), } } - coinSet, err := ptypes.CreateCoinSet(ptypes.ZRC20ToCosmosDenom(zrc20Addr), amount) + coinSet, err := precompiletypes.CreateCoinSet(precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), amount) if err != nil { return nil, err } // Check if bank address has enough ZRC20 balance. if err := c.fungibleKeeper.CheckZRC20Balance(ctx, c.zrc20ABI, zrc20Addr, c.Address(), amount); err != nil { - return nil, &ptypes.ErrInsufficientBalance{ + return nil, &precompiletypes.ErrInsufficientBalance{ Requested: amount.String(), Got: err.Error(), } @@ -94,14 +94,14 @@ func (c *Contract) withdraw( // 2. Effect: burn cosmos coin balance. if err := c.bankKeeper.SendCoinsFromAccountToModule(ctx, fromAddr, types.ModuleName, coinSet); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "SendCoinsFromAccountToModule", Got: err.Error(), } } if err := c.bankKeeper.BurnCoins(ctx, types.ModuleName, coinSet); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "BurnCoins", Got: err.Error(), } @@ -109,14 +109,14 @@ func (c *Contract) withdraw( // 3. Interactions: send ZRC20. if err := c.fungibleKeeper.UnlockZRC20(ctx, c.zrc20ABI, zrc20Addr, caller, c.Address(), amount); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "UnlockZRC20InBank", Got: err.Error(), } } if err := c.addEventLog(ctx, evm.StateDB, WithdrawEventName, eventData{caller, zrc20Addr, fromAddr.String(), coinSet.Denoms()[0], amount}); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "AddWithdrawLog", Got: err.Error(), } @@ -128,14 +128,14 @@ func (c *Contract) withdraw( func unpackWithdrawArgs(args []interface{}) (zrc20Addr common.Address, amount *big.Int, err error) { zrc20Addr, ok := args[0].(common.Address) if !ok { - return common.Address{}, nil, &ptypes.ErrInvalidAddr{ + return common.Address{}, nil, &precompiletypes.ErrInvalidAddr{ Got: zrc20Addr.String(), } } amount, ok = args[1].(*big.Int) if !ok || amount == nil || amount.Sign() <= 0 { - return common.Address{}, nil, &ptypes.ErrInvalidAmount{ + return common.Address{}, nil, &precompiletypes.ErrInvalidAmount{ Got: amount.String(), } } diff --git a/precompiles/prototype/prototype.go b/precompiles/prototype/prototype.go index 123885ccb4..31f1bcaaec 100644 --- a/precompiles/prototype/prototype.go +++ b/precompiles/prototype/prototype.go @@ -11,7 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" ) @@ -55,7 +55,7 @@ func initABI() { } type Contract struct { - ptypes.BaseContract + precompiletypes.BaseContract fungibleKeeper fungiblekeeper.Keeper cdc codec.Codec @@ -68,7 +68,7 @@ func NewIPrototypeContract( kvGasConfig storetypes.GasConfig, ) *Contract { return &Contract{ - BaseContract: ptypes.NewBaseContract(ContractAddress), + BaseContract: precompiletypes.NewBaseContract(ContractAddress), fungibleKeeper: *fungibleKeeper, cdc: cdc, kvGasConfig: kvGasConfig, @@ -106,7 +106,7 @@ func (c *Contract) RequiredGas(input []byte) uint64 { // Bech32ToHexAddr converts a bech32 address to a hex address. func (c *Contract) Bech32ToHexAddr(method *abi.Method, args []interface{}) ([]byte, error) { if len(args) != 1 { - return nil, &ptypes.ErrInvalidNumberOfArgs{ + return nil, &precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 1, } @@ -144,7 +144,7 @@ func (c *Contract) Bech32ToHexAddr(method *abi.Method, args []interface{}) ([]by // Bech32ify converts a hex address to a bech32 address. func (c *Contract) Bech32ify(method *abi.Method, args []interface{}) ([]byte, error) { if len(args) != 2 { - return nil, &ptypes.ErrInvalidNumberOfArgs{ + return nil, &precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 2, } @@ -198,7 +198,7 @@ func (c *Contract) GetGasStabilityPoolBalance( args []interface{}, ) ([]byte, error) { if len(args) != 1 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 1, }) @@ -207,7 +207,7 @@ func (c *Contract) GetGasStabilityPoolBalance( // Unwrap arguments. The chainID is the first and unique argument. chainID, ok := args[0].(int64) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: chainID, } } @@ -233,7 +233,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, _ bool) ([]byte, erro return nil, err } - stateDB := evm.StateDB.(ptypes.ExtStateDB) + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) switch method.Name { case GetGasStabilityPoolBalanceName: @@ -252,7 +252,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, _ bool) ([]byte, erro case Bech32ifyMethodName: return c.Bech32ify(method, args) default: - return nil, ptypes.ErrInvalidMethod{ + return nil, precompiletypes.ErrInvalidMethod{ Method: method.Name, } } diff --git a/precompiles/staking/method_distribute.go b/precompiles/staking/method_distribute.go index 3fd282a8cf..51aa25154a 100644 --- a/precompiles/staking/method_distribute.go +++ b/precompiles/staking/method_distribute.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/zeta-chain/node/precompiles/bank" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" fungibletypes "github.com/zeta-chain/node/x/fungible/types" ) @@ -23,7 +23,7 @@ func (c *Contract) distribute( args []interface{}, ) ([]byte, error) { if len(args) != 2 { - return nil, &ptypes.ErrInvalidNumberOfArgs{ + return nil, &precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 2, } @@ -36,13 +36,13 @@ func (c *Contract) distribute( } // Get the original caller address. Necessary for LockZRC20 to work. - caller, err := ptypes.GetEVMCallerAddress(evm, contract) + caller, err := precompiletypes.GetEVMCallerAddress(evm, contract) if err != nil { return nil, err } // Create the coinSet in advance, if this step fails do not lock ZRC20. - coinSet, err := ptypes.CreateCoinSet(ptypes.ZRC20ToCosmosDenom(zrc20Addr), amount) + coinSet, err := precompiletypes.CreateCoinSet(precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), amount) if err != nil { return nil, err } @@ -53,7 +53,7 @@ func (c *Contract) distribute( // - owner is the caller address. // - locker is the bank address. Assets are locked under this address to prevent liquidity fragmentation. if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, c.Address(), caller, bank.ContractAddress, amount); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "LockZRC20InBank", Got: err.Error(), } @@ -61,7 +61,7 @@ func (c *Contract) distribute( // With the ZRC20 locked, proceed to mint the cosmos coins. if err := c.bankKeeper.MintCoins(ctx, fungibletypes.ModuleName, coinSet); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "MintCoins", Got: err.Error(), } @@ -69,7 +69,7 @@ func (c *Contract) distribute( // Send the coins to the FeePool. if err := c.bankKeeper.SendCoinsFromModuleToModule(ctx, fungibletypes.ModuleName, authtypes.FeeCollectorName, coinSet); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "SendCoinsFromModuleToModule", Got: err.Error(), } @@ -80,11 +80,11 @@ func (c *Contract) distribute( "Distributing ZRC20 Validator Rewards", "Total", amount.String(), "Fee_collector", authtypes.FeeCollectorName, - "Denom", ptypes.ZRC20ToCosmosDenom(zrc20Addr), + "Denom", precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), ) if err := c.addDistributeLog(ctx, evm.StateDB, caller, zrc20Addr, amount); err != nil { - return nil, &ptypes.ErrUnexpected{ + return nil, &precompiletypes.ErrUnexpected{ When: "AddDistributeLog", Got: err.Error(), } @@ -96,14 +96,14 @@ func (c *Contract) distribute( func unpackDistributeArgs(args []interface{}) (zrc20Addr common.Address, amount *big.Int, err error) { zrc20Addr, ok := args[0].(common.Address) if !ok { - return common.Address{}, nil, &ptypes.ErrInvalidAddr{ + return common.Address{}, nil, &precompiletypes.ErrInvalidAddr{ Got: zrc20Addr.String(), } } amount, ok = args[1].(*big.Int) if !ok || amount == nil || amount.Sign() <= 0 { - return common.Address{}, nil, &ptypes.ErrInvalidAmount{ + return common.Address{}, nil, &precompiletypes.ErrInvalidAmount{ Got: amount.String(), } } diff --git a/precompiles/staking/method_distribute_test.go b/precompiles/staking/method_distribute_test.go index 73de0cdc36..9e4ade948e 100644 --- a/precompiles/staking/method_distribute_test.go +++ b/precompiles/staking/method_distribute_test.go @@ -4,14 +4,19 @@ import ( "math/big" "testing" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/stretchr/testify/require" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" ) func Test_Distribute(t *testing.T) { + feeCollectorAddress := authtypes.NewModuleAddress(authtypes.FeeCollectorName).String() + t.Run("should fail to run distribute as read only method", func(t *testing.T) { // Setup test. s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Setup method input. s.mockVMContract.Input = packInputArgs( @@ -24,18 +29,27 @@ func Test_Distribute(t *testing.T) { result, err := s.contract.Run(s.mockEVM, s.mockVMContract, true) // Check error and result. - require.ErrorIs(t, err, ptypes.ErrWriteMethod{ + require.ErrorIs(t, err, precompiletypes.ErrWriteMethod{ Method: DistributeMethodName, }) // Result is empty as the write check is done before executing distribute() function. // On-chain this would look like reverting, so staticcall is properly reverted. require.Empty(t, result) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) }) t.Run("should fail to distribute with 0 token balance", func(t *testing.T) { // Setup test. s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Setup method input. s.mockVMContract.Input = packInputArgs( @@ -50,7 +64,7 @@ func Test_Distribute(t *testing.T) { // Check error. require.ErrorAs( t, - ptypes.ErrInvalidAmount{ + precompiletypes.ErrInvalidAmount{ Got: "0", }, err, @@ -62,11 +76,20 @@ func Test_Distribute(t *testing.T) { ok := res[0].(bool) require.False(t, ok) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) }) t.Run("should fail to distribute with 0 allowance", func(t *testing.T) { // Setup test. s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) @@ -91,11 +114,20 @@ func Test_Distribute(t *testing.T) { ok := res[0].(bool) require.False(t, ok) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) }) t.Run("should fail to distribute 0 token", func(t *testing.T) { // Setup test. s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) @@ -123,11 +155,20 @@ func Test_Distribute(t *testing.T) { ok := res[0].(bool) require.False(t, ok) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) }) t.Run("should fail to distribute more than allowed to staking", func(t *testing.T) { // Setup test. s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) @@ -155,11 +196,20 @@ func Test_Distribute(t *testing.T) { ok := res[0].(bool) require.False(t, ok) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) }) t.Run("should fail to distribute more than user balance", func(t *testing.T) { // Setup test. s := newTestSuite(t) + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) @@ -186,6 +236,14 @@ func Test_Distribute(t *testing.T) { ok := res[0].(bool) require.False(t, ok) + + // End fee collector balance should be 0. + balance, err := s.sdkKeepers.BankKeeper.Balance(s.ctx, &banktypes.QueryBalanceRequest{ + Address: feeCollectorAddress, + Denom: zrc20Denom, + }) + require.NoError(t, err) + require.Equal(t, uint64(0), balance.Balance.Amount.Uint64()) }) t.Run("should distribute and lock ZRC20", func(t *testing.T) { diff --git a/precompiles/staking/method_get_shares.go b/precompiles/staking/method_get_shares.go index 5af1a6da36..fcf6ce7d1f 100644 --- a/precompiles/staking/method_get_shares.go +++ b/precompiles/staking/method_get_shares.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" ) func (c *Contract) GetShares( @@ -16,21 +16,21 @@ func (c *Contract) GetShares( args []interface{}, ) ([]byte, error) { if len(args) != 2 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 2, }) } stakerAddress, ok := args[0].(common.Address) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[0], } } validatorAddress, ok := args[1].(string) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[1], } } diff --git a/precompiles/staking/method_move_stake.go b/precompiles/staking/method_move_stake.go index 5a0d8d74cb..71c43bdd7d 100644 --- a/precompiles/staking/method_move_stake.go +++ b/precompiles/staking/method_move_stake.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" ) func (c *Contract) MoveStake( @@ -23,7 +23,7 @@ func (c *Contract) MoveStake( args []interface{}, ) ([]byte, error) { if len(args) != 4 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 4, }) @@ -31,7 +31,7 @@ func (c *Contract) MoveStake( stakerAddress, ok := args[0].(common.Address) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[0], } } @@ -42,21 +42,21 @@ func (c *Contract) MoveStake( validatorSrcAddress, ok := args[1].(string) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[1], } } validatorDstAddress, ok := args[2].(string) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[2], } } amount, ok := args[3].(*big.Int) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[3], } } @@ -75,7 +75,7 @@ func (c *Contract) MoveStake( return nil, err } - stateDB := evm.StateDB.(ptypes.ExtStateDB) + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) err = c.addMoveStakeLog(ctx, stateDB, stakerAddress, validatorSrcAddress, validatorDstAddress, amount) if err != nil { return nil, err diff --git a/precompiles/staking/method_move_stake_test.go b/precompiles/staking/method_move_stake_test.go index 2d2c8e3a0f..8882442069 100644 --- a/precompiles/staking/method_move_stake_test.go +++ b/precompiles/staking/method_move_stake_test.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/cmd/zetacored/config" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/testutil/sample" fungibletypes "github.com/zeta-chain/node/x/fungible/types" ) @@ -45,7 +45,7 @@ func Test_MoveStake(t *testing.T) { s.mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ + require.ErrorIs(t, err, precompiletypes.ErrDisabledMethod{ Method: StakeMethodName, }) @@ -62,7 +62,7 @@ func Test_MoveStake(t *testing.T) { // ASSERT require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ + require.ErrorIs(t, err, precompiletypes.ErrDisabledMethod{ Method: MoveStakeMethodName, }) }) diff --git a/precompiles/staking/method_stake.go b/precompiles/staking/method_stake.go index 857018c165..c19fa6c618 100644 --- a/precompiles/staking/method_stake.go +++ b/precompiles/staking/method_stake.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" ) func (c *Contract) Stake( @@ -23,7 +23,7 @@ func (c *Contract) Stake( args []interface{}, ) ([]byte, error) { if len(args) != 3 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 3, }) @@ -31,7 +31,7 @@ func (c *Contract) Stake( stakerAddress, ok := args[0].(common.Address) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[0], } } @@ -42,14 +42,14 @@ func (c *Contract) Stake( validatorAddress, ok := args[1].(string) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[1], } } amount, ok := args[2].(*big.Int) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[2], } } @@ -70,7 +70,7 @@ func (c *Contract) Stake( // if caller is not the same as origin it means call is coming through smart contract, // and because state of smart contract calling precompile might be updated as well // manually reduce amount in stateDB, so it is properly reflected in bank module - stateDB := evm.StateDB.(ptypes.ExtStateDB) + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) if contract.CallerAddress != evm.Origin { stateDB.SubBalance(stakerAddress, amount) } diff --git a/precompiles/staking/method_stake_test.go b/precompiles/staking/method_stake_test.go index f41ca310cd..bd5558abdb 100644 --- a/precompiles/staking/method_stake_test.go +++ b/precompiles/staking/method_stake_test.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/cmd/zetacored/config" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/testutil/sample" fungibletypes "github.com/zeta-chain/node/x/fungible/types" ) @@ -39,7 +39,7 @@ func Test_Stake(t *testing.T) { // ASSERT require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ + require.ErrorIs(t, err, precompiletypes.ErrDisabledMethod{ Method: StakeMethodName, }) }) diff --git a/precompiles/staking/method_unstake.go b/precompiles/staking/method_unstake.go index e0be246e05..4efb41993b 100644 --- a/precompiles/staking/method_unstake.go +++ b/precompiles/staking/method_unstake.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" ) func (c *Contract) Unstake( @@ -23,7 +23,7 @@ func (c *Contract) Unstake( args []interface{}, ) ([]byte, error) { if len(args) != 3 { - return nil, &(ptypes.ErrInvalidNumberOfArgs{ + return nil, &(precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), Expect: 3, }) @@ -31,7 +31,7 @@ func (c *Contract) Unstake( stakerAddress, ok := args[0].(common.Address) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[0], } } @@ -42,14 +42,14 @@ func (c *Contract) Unstake( validatorAddress, ok := args[1].(string) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[1], } } amount, ok := args[2].(*big.Int) if !ok { - return nil, ptypes.ErrInvalidArgument{ + return nil, precompiletypes.ErrInvalidArgument{ Got: args[2], } } @@ -67,7 +67,7 @@ func (c *Contract) Unstake( return nil, err } - stateDB := evm.StateDB.(ptypes.ExtStateDB) + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) err = c.addUnstakeLog(ctx, stateDB, stakerAddress, validatorAddress, amount) if err != nil { return nil, err diff --git a/precompiles/staking/method_unstake_test.go b/precompiles/staking/method_unstake_test.go index 13f1f2bd8f..e770020946 100644 --- a/precompiles/staking/method_unstake_test.go +++ b/precompiles/staking/method_unstake_test.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/cmd/zetacored/config" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/testutil/sample" fungibletypes "github.com/zeta-chain/node/x/fungible/types" ) @@ -40,7 +40,7 @@ func Test_Unstake(t *testing.T) { // ASSERT require.Error(t, err) - require.ErrorIs(t, err, ptypes.ErrDisabledMethod{ + require.ErrorIs(t, err, precompiletypes.ErrDisabledMethod{ Method: UnstakeMethodName, }) }) diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 16fc2f1f98..3408a2b09e 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" - ptypes "github.com/zeta-chain/node/precompiles/types" + precompiletypes "github.com/zeta-chain/node/precompiles/types" fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" ) @@ -61,7 +61,7 @@ func initABI() { } type Contract struct { - ptypes.BaseContract + precompiletypes.BaseContract stakingKeeper stakingkeeper.Keeper fungibleKeeper fungiblekeeper.Keeper @@ -93,7 +93,7 @@ func NewIStakingContract( } return &Contract{ - BaseContract: ptypes.NewBaseContract(ContractAddress), + BaseContract: precompiletypes.NewBaseContract(ContractAddress), stakingKeeper: *stakingKeeper, fungibleKeeper: fungibleKeeper, bankKeeper: bankKeeper, @@ -146,11 +146,11 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return nil, err } - stateDB := evm.StateDB.(ptypes.ExtStateDB) + stateDB := evm.StateDB.(precompiletypes.ExtStateDB) // If the method is not a view method, it should not be executed in read-only mode. if _, isViewMethod := ViewMethod[[4]byte(method.ID)]; !isViewMethod && readOnly { - return nil, ptypes.ErrWriteMethod{ + return nil, precompiletypes.ErrWriteMethod{ Method: method.Name, } } @@ -178,7 +178,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return res, nil case StakeMethodName: // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - return nil, ptypes.ErrDisabledMethod{ + return nil, precompiletypes.ErrDisabledMethod{ Method: method.Name, } @@ -194,7 +194,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return res, nil case UnstakeMethodName: // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - return nil, ptypes.ErrDisabledMethod{ + return nil, precompiletypes.ErrDisabledMethod{ Method: method.Name, } @@ -210,7 +210,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return res, nil case MoveStakeMethodName: // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - return nil, ptypes.ErrDisabledMethod{ + return nil, precompiletypes.ErrDisabledMethod{ Method: method.Name, } @@ -240,7 +240,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt } return res, nil default: - return nil, ptypes.ErrInvalidMethod{ + return nil, precompiletypes.ErrInvalidMethod{ Method: method.Name, } } From f206701e3453bfa9750518d3ba81597fec38c9f9 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Tue, 29 Oct 2024 16:03:58 +0100 Subject: [PATCH 16/20] check for err while DepositZRC20 --- precompiles/staking/method_distribute_test.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/precompiles/staking/method_distribute_test.go b/precompiles/staking/method_distribute_test.go index 9e4ade948e..dddb467b74 100644 --- a/precompiles/staking/method_distribute_test.go +++ b/precompiles/staking/method_distribute_test.go @@ -92,7 +92,8 @@ func Test_Distribute(t *testing.T) { zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. - s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + require.NoError(t, err) // Setup method input. s.mockVMContract.Input = packInputArgs( @@ -130,7 +131,8 @@ func Test_Distribute(t *testing.T) { zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. - s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + require.NoError(t, err) // Allow staking to spend ZRC20 tokens. allowStaking(t, s, big.NewInt(1000)) @@ -171,7 +173,8 @@ func Test_Distribute(t *testing.T) { zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. - s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + require.NoError(t, err) // Allow staking to spend ZRC20 tokens. allowStaking(t, s, big.NewInt(999)) @@ -212,7 +215,8 @@ func Test_Distribute(t *testing.T) { zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. - s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + require.NoError(t, err) // Allow staking to spend ZRC20 tokens. allowStaking(t, s, big.NewInt(100000)) @@ -251,7 +255,8 @@ func Test_Distribute(t *testing.T) { s := newTestSuite(t) // Set caller balance. - s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) + require.NoError(t, err) // Allow staking to spend ZRC20 tokens. allowStaking(t, s, big.NewInt(1000)) From 9584d0353be964791943ae3027710036c325a3b6 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Tue, 29 Oct 2024 23:01:03 +0100 Subject: [PATCH 17/20] fix CI --- e2e/e2etests/test_precompiles_distribute.go | 7 ++++++- precompiles/staking/method_get_all_validators.go | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index e5b5ca7fcb..36870e090c 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -213,7 +213,12 @@ func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom str return bal.Balance.Amount.BigInt() } -func resetDistributionTest(r *runner.E2ERunner, lockerAddress common.Address, previousGasLimit uint64, amount *big.Int) { +func resetDistributionTest( + r *runner.E2ERunner, + lockerAddress common.Address, + previousGasLimit uint64, + amount *big.Int, +) { r.ZEVMAuth.GasLimit = previousGasLimit // Reset the allowance to 0; this is needed when running upgrade tests where this test runs twice. diff --git a/precompiles/staking/method_get_all_validators.go b/precompiles/staking/method_get_all_validators.go index 61732b867b..976942632f 100644 --- a/precompiles/staking/method_get_all_validators.go +++ b/precompiles/staking/method_get_all_validators.go @@ -16,8 +16,10 @@ func (c *Contract) GetAllValidators( validatorsRes[i] = Validator{ OperatorAddress: v.OperatorAddress, ConsensusPubKey: v.ConsensusPubkey.String(), - BondStatus: uint8(v.Status), - Jailed: v.Jailed, + // Safe casting from int32 to uint8, as BondStatus is an enum. + //nolint:gosec + BondStatus: uint8(v.Status), + Jailed: v.Jailed, } } From dfd077e605273071659e046b3f25a6a67f7ae6b0 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Wed, 30 Oct 2024 10:51:54 +0100 Subject: [PATCH 18/20] fix gosec --- precompiles/staking/method_get_all_validators.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/precompiles/staking/method_get_all_validators.go b/precompiles/staking/method_get_all_validators.go index 976942632f..c834d8e01d 100644 --- a/precompiles/staking/method_get_all_validators.go +++ b/precompiles/staking/method_get_all_validators.go @@ -5,6 +5,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" ) +// Safe casting from int32 to uint8, as BondStatus is an enum. +// +//nolint:gosec func (c *Contract) GetAllValidators( ctx sdk.Context, method *abi.Method, @@ -16,10 +19,8 @@ func (c *Contract) GetAllValidators( validatorsRes[i] = Validator{ OperatorAddress: v.OperatorAddress, ConsensusPubKey: v.ConsensusPubkey.String(), - // Safe casting from int32 to uint8, as BondStatus is an enum. - //nolint:gosec - BondStatus: uint8(v.Status), - Jailed: v.Jailed, + BondStatus: uint8(v.Status), + Jailed: v.Jailed, } } From 3b9903f37f2868ba12e0957a3d8a3ff653dfe5b8 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Wed, 30 Oct 2024 11:01:48 +0100 Subject: [PATCH 19/20] modify CreateCoinSet to accept a common.Address --- precompiles/bank/method_deposit.go | 2 +- precompiles/bank/method_withdraw.go | 2 +- precompiles/staking/method_distribute.go | 2 +- precompiles/types/coin.go | 6 ++++-- precompiles/types/coin_test.go | 5 +++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/precompiles/bank/method_deposit.go b/precompiles/bank/method_deposit.go index e98a9c8208..76c21d926a 100644 --- a/precompiles/bank/method_deposit.go +++ b/precompiles/bank/method_deposit.go @@ -94,7 +94,7 @@ func (c *Contract) deposit( // this way we map ZRC20 addresses to cosmos denoms "zevm/0x12345". // - Mint coins to the fungible module. // - Send coins from fungible to the caller. - coinSet, err := precompiletypes.CreateCoinSet(precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), amount) + coinSet, err := precompiletypes.CreateCoinSet(zrc20Addr, amount) if err != nil { return nil, err } diff --git a/precompiles/bank/method_withdraw.go b/precompiles/bank/method_withdraw.go index 0072619160..a1308146c1 100644 --- a/precompiles/bank/method_withdraw.go +++ b/precompiles/bank/method_withdraw.go @@ -79,7 +79,7 @@ func (c *Contract) withdraw( } } - coinSet, err := precompiletypes.CreateCoinSet(precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), amount) + coinSet, err := precompiletypes.CreateCoinSet(zrc20Addr, amount) if err != nil { return nil, err } diff --git a/precompiles/staking/method_distribute.go b/precompiles/staking/method_distribute.go index 51aa25154a..1fa7351505 100644 --- a/precompiles/staking/method_distribute.go +++ b/precompiles/staking/method_distribute.go @@ -42,7 +42,7 @@ func (c *Contract) distribute( } // Create the coinSet in advance, if this step fails do not lock ZRC20. - coinSet, err := precompiletypes.CreateCoinSet(precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), amount) + coinSet, err := precompiletypes.CreateCoinSet(zrc20Addr, amount) if err != nil { return nil, err } diff --git a/precompiles/types/coin.go b/precompiles/types/coin.go index 7c0bc25317..4219040d42 100644 --- a/precompiles/types/coin.go +++ b/precompiles/types/coin.go @@ -16,14 +16,16 @@ func ZRC20ToCosmosDenom(ZRC20Address common.Address) string { return config.ZRC20DenomPrefix + ZRC20Address.String() } -func CreateCoinSet(tokenDenom string, amount *big.Int) (sdk.Coins, error) { +func CreateCoinSet(zrc20address common.Address, amount *big.Int) (sdk.Coins, error) { defer func() { if r := recover(); r != nil { return } }() - coin := sdk.NewCoin(tokenDenom, math.NewIntFromBigInt(amount)) + denom := ZRC20ToCosmosDenom(zrc20address) + + coin := sdk.NewCoin(denom, math.NewIntFromBigInt(amount)) if !coin.IsValid() { return nil, &ErrInvalidCoin{ Got: coin.GetDenom(), diff --git a/precompiles/types/coin_test.go b/precompiles/types/coin_test.go index 54bffe616b..6a8aac7d46 100644 --- a/precompiles/types/coin_test.go +++ b/precompiles/types/coin_test.go @@ -16,10 +16,11 @@ func Test_ZRC20ToCosmosDenom(t *testing.T) { } func Test_createCoinSet(t *testing.T) { - tokenDenom := "zrc20/0x0000000000000000000000000000000000003039" + tokenAddr := common.HexToAddress("0x0000000000000000000000000000000000003039") + tokenDenom := ZRC20ToCosmosDenom(tokenAddr) amount := big.NewInt(100) - coinSet, err := CreateCoinSet(tokenDenom, amount) + coinSet, err := CreateCoinSet(tokenAddr, amount) require.NoError(t, err, "createCoinSet should not return an error") require.NotNil(t, coinSet, "coinSet should not be nil") From 14289d668e757e7017244e8eb8e206c5efcecf17 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Wed, 30 Oct 2024 11:16:37 +0100 Subject: [PATCH 20/20] fix lint --- precompiles/staking/method_get_all_validators.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/precompiles/staking/method_get_all_validators.go b/precompiles/staking/method_get_all_validators.go index c834d8e01d..2187f2858a 100644 --- a/precompiles/staking/method_get_all_validators.go +++ b/precompiles/staking/method_get_all_validators.go @@ -5,9 +5,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" ) -// Safe casting from int32 to uint8, as BondStatus is an enum. -// -//nolint:gosec func (c *Contract) GetAllValidators( ctx sdk.Context, method *abi.Method, @@ -19,8 +16,10 @@ func (c *Contract) GetAllValidators( validatorsRes[i] = Validator{ OperatorAddress: v.OperatorAddress, ConsensusPubKey: v.ConsensusPubkey.String(), - BondStatus: uint8(v.Status), - Jailed: v.Jailed, + // Safe casting from int32 to uint8, as BondStatus is an enum. + // #nosec G115 always in range + BondStatus: uint8(v.Status), + Jailed: v.Jailed, } }