From b4c51d79d55411bba7f3b2afce10be4e9f6c7010 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Thu, 17 Oct 2024 18:51:40 +0200 Subject: [PATCH] feat: distribute ZRC20 rewards function --- precompiles/bank/bank.go | 34 - precompiles/bank/bank_test.go | 30 - 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 +- 21 files changed, 2125 insertions(+), 1868 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/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/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")