From 16c9f6d5eccd34c344650940891a8a8b68e5b394 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 4 Nov 2024 18:47:07 +0100 Subject: [PATCH 01/20] feat: add function to withdraw delegator rewards --- app/app.go | 1 + precompiles/precompiles.go | 12 +- precompiles/precompiles_test.go | 9 +- precompiles/staking/IStaking.abi | 104 ++++++++ precompiles/staking/IStaking.gen.go | 245 ++++++++++++++++++- precompiles/staking/IStaking.json | 104 ++++++++ precompiles/staking/IStaking.sol | 41 ++++ precompiles/staking/const.go | 22 +- precompiles/staking/logs.go | 30 +++ precompiles/staking/method_claim_rewards.go | 152 ++++++++++++ precompiles/staking/method_get_rewards.go | 85 +++++++ precompiles/staking/method_get_validators.go | 64 +++++ precompiles/staking/staking.go | 69 +++++- precompiles/staking/staking_test.go | 2 + precompiles/types/coin.go | 6 + testutil/keeper/keeper.go | 51 +++- 16 files changed, 968 insertions(+), 29 deletions(-) create mode 100644 precompiles/staking/method_claim_rewards.go create mode 100644 precompiles/staking/method_get_rewards.go create mode 100644 precompiles/staking/method_get_validators.go diff --git a/app/app.go b/app/app.go index 3a42d51aff..1c84e937c0 100644 --- a/app/app.go +++ b/app/app.go @@ -541,6 +541,7 @@ func New( &app.FungibleKeeper, app.StakingKeeper, app.BankKeeper, + app.DistrKeeper, appCodec, storetypes.TransientGasConfig(), ), diff --git a/precompiles/precompiles.go b/precompiles/precompiles.go index b9d167dbac..4e7778bec2 100644 --- a/precompiles/precompiles.go +++ b/precompiles/precompiles.go @@ -5,6 +5,7 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdktypes "github.com/cosmos/cosmos-sdk/types" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" @@ -31,6 +32,7 @@ func StatefulContracts( fungibleKeeper *fungiblekeeper.Keeper, stakingKeeper *stakingkeeper.Keeper, bankKeeper bankkeeper.Keeper, + distributionKeeper distrkeeper.Keeper, cdc codec.Codec, gasConfig storetypes.GasConfig, ) (precompiledContracts []evmkeeper.CustomContractFn) { @@ -50,7 +52,15 @@ func StatefulContracts( // Define the staking contract function. if EnabledStatefulContracts[staking.ContractAddress] { stakingContract := func(ctx sdktypes.Context, _ ethparams.Rules) vm.PrecompiledContract { - return staking.NewIStakingContract(ctx, stakingKeeper, *fungibleKeeper, bankKeeper, cdc, gasConfig) + return staking.NewIStakingContract( + ctx, + stakingKeeper, + *fungibleKeeper, + bankKeeper, + distributionKeeper, + cdc, + gasConfig, + ) } // Append the staking contract to the precompiledContracts slice. diff --git a/precompiles/precompiles_test.go b/precompiles/precompiles_test.go index a0b55572ea..b60d65c314 100644 --- a/precompiles/precompiles_test.go +++ b/precompiles/precompiles_test.go @@ -25,7 +25,14 @@ func Test_StatefulContracts(t *testing.T) { } // StatefulContracts() should return all the enabled contracts. - contracts := StatefulContracts(k, &sdkk.StakingKeeper, sdkk.BankKeeper, appCodec, gasConfig) + contracts := StatefulContracts( + k, + &sdkk.StakingKeeper, + sdkk.BankKeeper, + sdkk.DistributionKeeper, + appCodec, + gasConfig, + ) require.NotNil(t, contracts, "StatefulContracts() should not return a nil slice") require.Len(t, contracts, expectedContracts, "StatefulContracts() should return all the enabled contracts") diff --git a/precompiles/staking/IStaking.abi b/precompiles/staking/IStaking.abi index da1a9e6ffc..1573fcc82a 100644 --- a/precompiles/staking/IStaking.abi +++ b/precompiles/staking/IStaking.abi @@ -1,4 +1,29 @@ [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "claim_address", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "zrc20_token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ClaimedRewards", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -105,6 +130,30 @@ "name": "Unstake", "type": "event" }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "string", + "name": "validator", + "type": "string" + } + ], + "name": "claimRewards", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -164,6 +213,61 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "getDelegatorValidators", + "outputs": [ + { + "internalType": "string[]", + "name": "validators", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "string", + "name": "validator", + "type": "string" + } + ], + "name": "getRewards", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct DecCoin[]", + "name": "rewards", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/precompiles/staking/IStaking.gen.go b/precompiles/staking/IStaking.gen.go index d4f7495d37..8c40b7e634 100644 --- a/precompiles/staking/IStaking.gen.go +++ b/precompiles/staking/IStaking.gen.go @@ -29,6 +29,12 @@ var ( _ = abi.ConvertType ) +// DecCoin is an auto generated low-level Go binding around an user-defined struct. +type DecCoin struct { + Denom string + Amount *big.Int +} + // Validator is an auto generated low-level Go binding around an user-defined struct. type Validator struct { OperatorAddress string @@ -39,7 +45,7 @@ type Validator struct { // IStakingMetaData contains all meta data concerning the IStaking contract. var IStakingMetaData = &bind.MetaData{ - ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_distributor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Distributed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorSrc\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorDst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"MoveStake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Stake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Unstake\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"distribute\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllValidators\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"operatorAddress\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"consensusPubKey\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"jailed\",\"type\":\"bool\"},{\"internalType\":\"enumBondStatus\",\"name\":\"bondStatus\",\"type\":\"uint8\"}],\"internalType\":\"structValidator[]\",\"name\":\"validators\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getShares\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validatorSrc\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"validatorDst\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"moveStake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"stake\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unstake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"claim_address\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"ClaimedRewards\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_distributor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Distributed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorSrc\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorDst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"MoveStake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Stake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Unstake\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"claimRewards\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"distribute\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllValidators\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"operatorAddress\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"consensusPubKey\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"jailed\",\"type\":\"bool\"},{\"internalType\":\"enumBondStatus\",\"name\":\"bondStatus\",\"type\":\"uint8\"}],\"internalType\":\"structValidator[]\",\"name\":\"validators\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"}],\"name\":\"getDelegatorValidators\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"validators\",\"type\":\"string[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getRewards\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"denom\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structDecCoin[]\",\"name\":\"rewards\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getShares\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validatorSrc\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"validatorDst\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"moveStake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"stake\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unstake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", } // IStakingABI is the input ABI used to generate the binding from. @@ -219,6 +225,68 @@ func (_IStaking *IStakingCallerSession) GetAllValidators() ([]Validator, error) return _IStaking.Contract.GetAllValidators(&_IStaking.CallOpts) } +// GetDelegatorValidators is a free data retrieval call binding the contract method 0xb6a216ae. +// +// Solidity: function getDelegatorValidators(address delegator) view returns(string[] validators) +func (_IStaking *IStakingCaller) GetDelegatorValidators(opts *bind.CallOpts, delegator common.Address) ([]string, error) { + var out []interface{} + err := _IStaking.contract.Call(opts, &out, "getDelegatorValidators", delegator) + + if err != nil { + return *new([]string), err + } + + out0 := *abi.ConvertType(out[0], new([]string)).(*[]string) + + return out0, err + +} + +// GetDelegatorValidators is a free data retrieval call binding the contract method 0xb6a216ae. +// +// Solidity: function getDelegatorValidators(address delegator) view returns(string[] validators) +func (_IStaking *IStakingSession) GetDelegatorValidators(delegator common.Address) ([]string, error) { + return _IStaking.Contract.GetDelegatorValidators(&_IStaking.CallOpts, delegator) +} + +// GetDelegatorValidators is a free data retrieval call binding the contract method 0xb6a216ae. +// +// Solidity: function getDelegatorValidators(address delegator) view returns(string[] validators) +func (_IStaking *IStakingCallerSession) GetDelegatorValidators(delegator common.Address) ([]string, error) { + return _IStaking.Contract.GetDelegatorValidators(&_IStaking.CallOpts, delegator) +} + +// GetRewards is a free data retrieval call binding the contract method 0x93428792. +// +// Solidity: function getRewards(address delegator, string validator) view returns((string,uint256)[] rewards) +func (_IStaking *IStakingCaller) GetRewards(opts *bind.CallOpts, delegator common.Address, validator string) ([]DecCoin, error) { + var out []interface{} + err := _IStaking.contract.Call(opts, &out, "getRewards", delegator, validator) + + if err != nil { + return *new([]DecCoin), err + } + + out0 := *abi.ConvertType(out[0], new([]DecCoin)).(*[]DecCoin) + + return out0, err + +} + +// GetRewards is a free data retrieval call binding the contract method 0x93428792. +// +// Solidity: function getRewards(address delegator, string validator) view returns((string,uint256)[] rewards) +func (_IStaking *IStakingSession) GetRewards(delegator common.Address, validator string) ([]DecCoin, error) { + return _IStaking.Contract.GetRewards(&_IStaking.CallOpts, delegator, validator) +} + +// GetRewards is a free data retrieval call binding the contract method 0x93428792. +// +// Solidity: function getRewards(address delegator, string validator) view returns((string,uint256)[] rewards) +func (_IStaking *IStakingCallerSession) GetRewards(delegator common.Address, validator string) ([]DecCoin, error) { + return _IStaking.Contract.GetRewards(&_IStaking.CallOpts, delegator, validator) +} + // GetShares is a free data retrieval call binding the contract method 0x0d1b3daf. // // Solidity: function getShares(address staker, string validator) view returns(uint256 shares) @@ -250,6 +318,27 @@ func (_IStaking *IStakingCallerSession) GetShares(staker common.Address, validat return _IStaking.Contract.GetShares(&_IStaking.CallOpts, staker, validator) } +// ClaimRewards is a paid mutator transaction binding the contract method 0x54dbdc38. +// +// Solidity: function claimRewards(address delegator, string validator) returns(bool success) +func (_IStaking *IStakingTransactor) ClaimRewards(opts *bind.TransactOpts, delegator common.Address, validator string) (*types.Transaction, error) { + return _IStaking.contract.Transact(opts, "claimRewards", delegator, validator) +} + +// ClaimRewards is a paid mutator transaction binding the contract method 0x54dbdc38. +// +// Solidity: function claimRewards(address delegator, string validator) returns(bool success) +func (_IStaking *IStakingSession) ClaimRewards(delegator common.Address, validator string) (*types.Transaction, error) { + return _IStaking.Contract.ClaimRewards(&_IStaking.TransactOpts, delegator, validator) +} + +// ClaimRewards is a paid mutator transaction binding the contract method 0x54dbdc38. +// +// Solidity: function claimRewards(address delegator, string validator) returns(bool success) +func (_IStaking *IStakingTransactorSession) ClaimRewards(delegator common.Address, validator string) (*types.Transaction, error) { + return _IStaking.Contract.ClaimRewards(&_IStaking.TransactOpts, delegator, validator) +} + // Distribute is a paid mutator transaction binding the contract method 0xfb932108. // // Solidity: function distribute(address zrc20, uint256 amount) returns(bool success) @@ -334,6 +423,160 @@ func (_IStaking *IStakingTransactorSession) Unstake(staker common.Address, valid return _IStaking.Contract.Unstake(&_IStaking.TransactOpts, staker, validator, amount) } +// IStakingClaimedRewardsIterator is returned from FilterClaimedRewards and is used to iterate over the raw logs and unpacked data for ClaimedRewards events raised by the IStaking contract. +type IStakingClaimedRewardsIterator struct { + Event *IStakingClaimedRewards // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *IStakingClaimedRewardsIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(IStakingClaimedRewards) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(IStakingClaimedRewards) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *IStakingClaimedRewardsIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *IStakingClaimedRewardsIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// IStakingClaimedRewards represents a ClaimedRewards event raised by the IStaking contract. +type IStakingClaimedRewards struct { + ClaimAddress common.Address + Zrc20Token common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterClaimedRewards is a free log retrieval operation binding the contract event 0x2ef606d064225d24c1514dc94907c134faee1237445c2f63f410cce0852b2054. +// +// Solidity: event ClaimedRewards(address indexed claim_address, address indexed zrc20_token, uint256 amount) +func (_IStaking *IStakingFilterer) FilterClaimedRewards(opts *bind.FilterOpts, claim_address []common.Address, zrc20_token []common.Address) (*IStakingClaimedRewardsIterator, error) { + + var claim_addressRule []interface{} + for _, claim_addressItem := range claim_address { + claim_addressRule = append(claim_addressRule, claim_addressItem) + } + var zrc20_tokenRule []interface{} + for _, zrc20_tokenItem := range zrc20_token { + zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) + } + + logs, sub, err := _IStaking.contract.FilterLogs(opts, "ClaimedRewards", claim_addressRule, zrc20_tokenRule) + if err != nil { + return nil, err + } + return &IStakingClaimedRewardsIterator{contract: _IStaking.contract, event: "ClaimedRewards", logs: logs, sub: sub}, nil +} + +// WatchClaimedRewards is a free log subscription operation binding the contract event 0x2ef606d064225d24c1514dc94907c134faee1237445c2f63f410cce0852b2054. +// +// Solidity: event ClaimedRewards(address indexed claim_address, address indexed zrc20_token, uint256 amount) +func (_IStaking *IStakingFilterer) WatchClaimedRewards(opts *bind.WatchOpts, sink chan<- *IStakingClaimedRewards, claim_address []common.Address, zrc20_token []common.Address) (event.Subscription, error) { + + var claim_addressRule []interface{} + for _, claim_addressItem := range claim_address { + claim_addressRule = append(claim_addressRule, claim_addressItem) + } + var zrc20_tokenRule []interface{} + for _, zrc20_tokenItem := range zrc20_token { + zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) + } + + logs, sub, err := _IStaking.contract.WatchLogs(opts, "ClaimedRewards", claim_addressRule, zrc20_tokenRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(IStakingClaimedRewards) + if err := _IStaking.contract.UnpackLog(event, "ClaimedRewards", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseClaimedRewards is a log parse operation binding the contract event 0x2ef606d064225d24c1514dc94907c134faee1237445c2f63f410cce0852b2054. +// +// Solidity: event ClaimedRewards(address indexed claim_address, address indexed zrc20_token, uint256 amount) +func (_IStaking *IStakingFilterer) ParseClaimedRewards(log types.Log) (*IStakingClaimedRewards, error) { + event := new(IStakingClaimedRewards) + if err := _IStaking.contract.UnpackLog(event, "ClaimedRewards", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + // IStakingDistributedIterator is returned from FilterDistributed and is used to iterate over the raw logs and unpacked data for Distributed events raised by the IStaking contract. type IStakingDistributedIterator struct { Event *IStakingDistributed // Event containing the contract specifics and raw log diff --git a/precompiles/staking/IStaking.json b/precompiles/staking/IStaking.json index d4e0bb75f0..e4e3111f7e 100644 --- a/precompiles/staking/IStaking.json +++ b/precompiles/staking/IStaking.json @@ -1,5 +1,30 @@ { "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "claim_address", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "zrc20_token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ClaimedRewards", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -106,6 +131,30 @@ "name": "Unstake", "type": "event" }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "string", + "name": "validator", + "type": "string" + } + ], + "name": "claimRewards", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -165,6 +214,61 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "getDelegatorValidators", + "outputs": [ + { + "internalType": "string[]", + "name": "validators", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "string", + "name": "validator", + "type": "string" + } + ], + "name": "getRewards", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct DecCoin[]", + "name": "rewards", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/precompiles/staking/IStaking.sol b/precompiles/staking/IStaking.sol index dece711d71..1b433303cf 100644 --- a/precompiles/staking/IStaking.sol +++ b/precompiles/staking/IStaking.sol @@ -23,6 +23,13 @@ struct Validator { BondStatus bondStatus; } +/// @notice Cosmos coin representation.abi +/// ref: https://github.com/cosmos/cosmos-sdk/blob/470e0859462b28a53adb411843539561d11d7bf5/x/distribution/README.md?plain=1#L139 +struct DecCoin { + string denom; + uint256 amount; +} + interface IStaking { /// @notice Stake event is emitted when stake function is called /// @param staker Staker address @@ -66,6 +73,16 @@ interface IStaking { uint256 amount ); + /// @notice ClaimedRewards is emitted when a delegator claims ZRC20. + /// @param claim_address Distributor address. + /// @param zrc20_token ZRC20 token address. + /// @param amount Claimed amount. + event ClaimedRewards( + address indexed claim_address, + address indexed zrc20_token, + uint256 amount + ); + /// @notice Stake coins to validator /// @param staker Staker address /// @param validator Validator address @@ -123,4 +140,28 @@ interface IStaking { address zrc20, uint256 amount ) external returns (bool success); + + /// @notice Claim ZRC20 staking rewards. + /// @param validator The validator address to claim rewards from. + /// @return success Boolean indicating whether the claim was successful. + function claimRewards( + address delegator, + string memory validator + ) external returns (bool success); + + /// @dev Queries all validators the delegator has delegated to. + /// @param delegator The delegator address to query rewards from. + /// @return validators List of the validators the caller has delegated to. + function getDelegatorValidators( + address delegator + ) external view returns (string[] calldata validators); + + /// @notice Query ZRC20 outstanding staking rewards. + /// @param delegator The delegator address to query rewards from. + /// @param validator The validator address to query rewards from. + /// @return rewards The list of coins rewarded on the validator. + function getRewards( + address delegator, + string memory validator + ) external view returns (DecCoin[] calldata rewards); } diff --git a/precompiles/staking/const.go b/precompiles/staking/const.go index 8500e723f4..a12d7070e1 100644 --- a/precompiles/staking/const.go +++ b/precompiles/staking/const.go @@ -1,17 +1,15 @@ package staking const ( + // State changing methods. + ClaimRewardsMethodName = "claimRewards" + ClaimRewardsEventName = "ClaimedRewards" + ClaimRewardsMethodGas = 10000 + DistributeMethodName = "distribute" DistributeEventName = "Distributed" DistributeMethodGas = 10000 - GetAllValidatorsMethodName = "getAllValidators" - GetSharesMethodName = "getShares" - - MoveStakeMethodName = "moveStake" - MoveStakeEventName = "MoveStake" - MoveStakeMethodGas = 10000 - StakeMethodName = "stake" StakeEventName = "Stake" StakeMethodGas = 10000 @@ -19,4 +17,14 @@ const ( UnstakeMethodName = "unstake" UnstakeEventName = "Unstake" UnstakeMethodGas = 1000 + + MoveStakeMethodName = "moveStake" + MoveStakeEventName = "MoveStake" + MoveStakeMethodGas = 10000 + + // Query methods. + GetAllValidatorsMethodName = "getAllValidators" + GetSharesMethodName = "getShares" + GetRewardsMethodName = "getRewards" + GetValidatorsMethodName = "getDelegatorValidators" ) diff --git a/precompiles/staking/logs.go b/precompiles/staking/logs.go index c8d1db24e2..db6d628968 100644 --- a/precompiles/staking/logs.go +++ b/precompiles/staking/logs.go @@ -147,3 +147,33 @@ func (c *Contract) addDistributeLog( return nil } + +func (c *Contract) addClaimRewardsLog( + ctx sdk.Context, + stateDB vm.StateDB, + delegator common.Address, + zrc20Token common.Address, + amount *big.Int, +) error { + event := c.Abi().Events[ClaimRewardsEventName] + + topics, err := logs.MakeTopics( + event, + []interface{}{delegator}, + []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_claim_rewards.go b/precompiles/staking/method_claim_rewards.go new file mode 100644 index 0000000000..1899dabccd --- /dev/null +++ b/precompiles/staking/method_claim_rewards.go @@ -0,0 +1,152 @@ +package staking + +import ( + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + "github.com/zeta-chain/node/cmd/zetacored/config" + "github.com/zeta-chain/node/precompiles/bank" + precompiletypes "github.com/zeta-chain/node/precompiles/types" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +// claimRewards claims all the rewards for a delegator from a validator. +// As F1 Cosmos distribution scheme implements an all or nothing withdrawal, the precompile will +// withdraw all the rewards for the delegator, filter ZRC20 and unlock them to the delegator EVM address. +func (c *Contract) claimRewards( + ctx sdk.Context, + evm *vm.EVM, + contract *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 2 { + return nil, &precompiletypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 2, + } + } + + delegatorAddr, validatorAddr, err := unpackClaimRewardsArgs(args) + if err != nil { + return nil, err + } + + var ( + // This represents the delegator calling directly the precompile. + callerIsDelegator = contract.CallerAddress == delegatorAddr + + // This represents the delegator calling the precompile through a contract. + originIsDelegator = evm.Origin == delegatorAddr + ) + + // If the delegator is not the origin nor the caller, it's an unauthorized operation. + if !callerIsDelegator && !originIsDelegator { + return nil, precompiletypes.ErrInvalidAddr{ + Got: delegatorAddr.String(), + Reason: "unauthorized to withdraw the delegation rewards for delegator", + } + } + + // Get delegator Cosmos address. + delegatorCosmosAddr, err := precompiletypes.GetCosmosAddress(c.bankKeeper, delegatorAddr) + if err != nil { + return nil, err + } + + // Get validator Cosmos address. + validatorCosmosAddr, err := sdk.ValAddressFromBech32(validatorAddr) + if err != nil { + return nil, err + } + + // Withdraw all the delegation rewards. + // The F1 Cosmos distribution scheme implements an all or nothing withdrawal. + // The coins could be of multiple denomination, and a mix of ZRC20 and Cosmos coins. + coins, err := c.distributionKeeper.WithdrawDelegationRewards(ctx, delegatorCosmosAddr, validatorCosmosAddr) + if err != nil { + return nil, precompiletypes.ErrUnexpected{ + When: "WithdrawDelegationRewards", + Got: err.Error(), + } + } + + // For all the ZRC20 coins withdrawed: + // - Check the amount to unlock is valid. + // - Burn the Cosmos coins. + // - Unlock the ZRC20 coins. + for _, coin := range coins { + // Filter out invalid coins. + if !coin.IsValid() || !coin.Amount.IsPositive() { + continue + } + + // Filter out non-ZRC20 coins. + if !precompiletypes.CoinIsZRC20(coin.Denom) { + continue + } + + // Notice that instead of returning errors we just skip the coin. This is because there might be + // more than one ZRC20 coin in the delegation rewards, and we want to unlock as many as possible. + // Coins are locked in the bank precompile, so it should be possible to unlock them afterwards. + var ( + zrc20Addr = common.HexToAddress(strings.TrimPrefix(coin.Denom, config.ZRC20DenomPrefix)) + zrc20Amount = coin.Amount.BigInt() + ) + + // Check if bank address has enough ZRC20 balance. + // This check is also made inside UnlockZRC20, but repeat it here to avoid burning the coins. + if err := c.fungibleKeeper.CheckZRC20Balance(ctx, zrc20Addr, bank.ContractAddress, zrc20Amount); err != nil { + continue + } + + coinSet := sdk.NewCoins(coin) + + // Send the coins to the fungible module to burn them. + if err := c.bankKeeper.SendCoinsFromAccountToModule(ctx, delegatorCosmosAddr, fungibletypes.ModuleName, coinSet); err != nil { + continue + } + + if err := c.bankKeeper.BurnCoins(ctx, fungibletypes.ModuleName, coinSet); err != nil { + continue + } + + // Finally, unlock the ZRC20 coins. + if err := c.fungibleKeeper.UnlockZRC20(ctx, zrc20Addr, delegatorAddr, bank.ContractAddress, zrc20Amount); err != nil { + continue + } + + // Emit an event per ZRC20 coin unlocked. + // This keeps events as granular and deterministic as possible. + if err := c.addClaimRewardsLog(ctx, evm.StateDB, delegatorAddr, zrc20Addr, zrc20Amount); err != nil { + return nil, &precompiletypes.ErrUnexpected{ + When: "AddClaimRewardLog", + Got: err.Error(), + } + } + } + + return method.Outputs.Pack(true) +} + +func unpackClaimRewardsArgs(args []interface{}) (delegator common.Address, validator string, err error) { + delegator, ok := args[0].(common.Address) + if !ok { + return common.Address{}, "", &precompiletypes.ErrInvalidAddr{ + Got: delegator.String(), + } + } + + validator, ok = args[1].(string) + if !ok { + return common.Address{}, "", &precompiletypes.ErrInvalidAddr{ + Got: validator, + } + } + + return delegator, validator, nil +} diff --git a/precompiles/staking/method_get_rewards.go b/precompiles/staking/method_get_rewards.go new file mode 100644 index 0000000000..dbbbfff5e4 --- /dev/null +++ b/precompiles/staking/method_get_rewards.go @@ -0,0 +1,85 @@ +package staking + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + dstrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + precompiletypes "github.com/zeta-chain/node/precompiles/types" +) + +// getRewards returns the list of ZRC20 cosmos coins, available for withdrawal by the delegator. +func (c *Contract) getRewards( + ctx sdk.Context, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 2 { + return nil, &precompiletypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 2, + } + } + + delegatorAddr, validatorAddr, err := unpackGetRewardsArgs(args) + if err != nil { + return nil, err + } + + // Get delegator Cosmos address. + delegatorCosmosAddr, err := precompiletypes.GetCosmosAddress(c.bankKeeper, delegatorAddr) + if err != nil { + return nil, err + } + + // Query the delegation rewards through the distribution keeper querier. + dstrClient := distrkeeper.Querier{Keeper: c.distributionKeeper} + + res, err := dstrClient.DelegationRewards(ctx, &dstrtypes.QueryDelegationRewardsRequest{ + DelegatorAddress: delegatorCosmosAddr.String(), + ValidatorAddress: validatorAddr, + }) + if err != nil { + return nil, precompiletypes.ErrUnexpected{ + When: "DelegationRewards", + Got: err.Error(), + } + } + + coins := res.GetRewards() + if !coins.IsValid() { + return nil, precompiletypes.ErrUnexpected{ + When: "GetRewards", + Got: "invalid coins", + } + } + + zrc20Coins := make([]sdk.DecCoin, 0) + for _, coin := range coins { + if precompiletypes.CoinIsZRC20(coin.Denom) { + zrc20Coins = append(zrc20Coins, coin) + } + } + + return method.Outputs.Pack(zrc20Coins) +} + +func unpackGetRewardsArgs(args []interface{}) (delegator common.Address, validator string, err error) { + delegator, ok := args[0].(common.Address) + if !ok { + return common.Address{}, "", &precompiletypes.ErrInvalidAddr{ + Got: delegator.String(), + } + } + + validator, ok = args[1].(string) + if !ok { + return common.Address{}, "", &precompiletypes.ErrInvalidAddr{ + Got: validator, + } + } + + return delegator, validator, nil +} diff --git a/precompiles/staking/method_get_validators.go b/precompiles/staking/method_get_validators.go new file mode 100644 index 0000000000..1351d8c62f --- /dev/null +++ b/precompiles/staking/method_get_validators.go @@ -0,0 +1,64 @@ +package staking + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + dstrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + precompiletypes "github.com/zeta-chain/node/precompiles/types" +) + +// getValidators queries the list of validators for a given delegator. +func (c *Contract) getDelegatorValidators( + ctx sdk.Context, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + if len(args) != 0 { + return nil, &precompiletypes.ErrInvalidNumberOfArgs{ + Got: len(args), + Expect: 0, + } + } + + delegatorAddr, err := unpackGetValidatorsArgs(args) + if err != nil { + return nil, err + } + + // Get the cosmos address of the caller. + delegatorCosmosAddr, err := precompiletypes.GetCosmosAddress(c.bankKeeper, delegatorAddr) + if err != nil { + return nil, err + } + + // Query the validator list of the given delegator. + dstrClient := distrkeeper.Querier{Keeper: c.distributionKeeper} + + res, err := dstrClient.DelegatorValidators(ctx, &dstrtypes.QueryDelegatorValidatorsRequest{ + DelegatorAddress: delegatorCosmosAddr.String(), + }) + if err != nil { + return nil, precompiletypes.ErrUnexpected{ + When: "DelegatorValidators", + Got: err.Error(), + } + } + + // Return immediately, no need to check the slice. + // If there are no validators we simply return an empty array to calling contracts. + return method.Outputs.Pack(res.Validators) +} + +func unpackGetValidatorsArgs(args []interface{}) (delegator common.Address, err error) { + delegator, ok := args[0].(common.Address) + if !ok { + return common.Address{}, &precompiletypes.ErrInvalidAddr{ + Got: delegator.String(), + } + } + + return delegator, nil +} diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 4d8115336a..1dd2f1f654 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -47,12 +48,20 @@ func initABI() { GasRequiredByMethod[methodID] = MoveStakeMethodGas case DistributeMethodName: GasRequiredByMethod[methodID] = DistributeMethodGas + case ClaimRewardsMethodName: + GasRequiredByMethod[methodID] = ClaimRewardsMethodGas case GetAllValidatorsMethodName: GasRequiredByMethod[methodID] = 0 ViewMethod[methodID] = true case GetSharesMethodName: GasRequiredByMethod[methodID] = 0 ViewMethod[methodID] = true + case GetRewardsMethodName: + GasRequiredByMethod[methodID] = 0 + ViewMethod[methodID] = true + case GetValidatorsMethodName: + GasRequiredByMethod[methodID] = 0 + ViewMethod[methodID] = true default: GasRequiredByMethod[methodID] = 0 } @@ -62,11 +71,12 @@ func initABI() { type Contract struct { precompiletypes.BaseContract - stakingKeeper stakingkeeper.Keeper - fungibleKeeper fungiblekeeper.Keeper - bankKeeper bankkeeper.Keeper - cdc codec.Codec - kvGasConfig storetypes.GasConfig + stakingKeeper stakingkeeper.Keeper + fungibleKeeper fungiblekeeper.Keeper + bankKeeper bankkeeper.Keeper + distributionKeeper distrkeeper.Keeper + cdc codec.Codec + kvGasConfig storetypes.GasConfig } func NewIStakingContract( @@ -74,6 +84,7 @@ func NewIStakingContract( stakingKeeper *stakingkeeper.Keeper, fungibleKeeper fungiblekeeper.Keeper, bankKeeper bankkeeper.Keeper, + distributionKeeper distrkeeper.Keeper, cdc codec.Codec, kvGasConfig storetypes.GasConfig, ) *Contract { @@ -83,12 +94,13 @@ func NewIStakingContract( } return &Contract{ - BaseContract: precompiletypes.NewBaseContract(ContractAddress), - stakingKeeper: *stakingKeeper, - fungibleKeeper: fungibleKeeper, - bankKeeper: bankKeeper, - cdc: cdc, - kvGasConfig: kvGasConfig, + BaseContract: precompiletypes.NewBaseContract(ContractAddress), + stakingKeeper: *stakingKeeper, + fungibleKeeper: fungibleKeeper, + bankKeeper: bankKeeper, + distributionKeeper: distributionKeeper, + cdc: cdc, + kvGasConfig: kvGasConfig, } } @@ -228,6 +240,41 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt return res, err } return res, nil + case GetRewardsMethodName: + var res []byte + execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { + res, err = c.getRewards(ctx, method, args) + return err + }) + if execErr != nil { + return nil, err + } + return res, nil + case GetValidatorsMethodName: + var res []byte + execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { + res, err = c.getDelegatorValidators(ctx, method, args) + return err + }) + if execErr != nil { + return nil, err + } + return res, nil + case ClaimRewardsMethodName: + var res []byte + execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { + res, err = c.claimRewards(ctx, evm, contract, method, args) + return err + }) + if execErr != nil { + res, errPack := method.Outputs.Pack(false) + if errPack != nil { + return nil, errPack + } + + return res, err + } + return res, nil default: return nil, precompiletypes.ErrInvalidMethod{ Method: method.Name, diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index b7b00ede0d..4c58df2fb9 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -240,6 +240,7 @@ func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *v &sdkKeepers.StakingKeeper, *fungibleKeeper, sdkKeepers.BankKeeper, + sdkKeepers.DistributionKeeper, appCodec, gasConfig, ) @@ -313,6 +314,7 @@ func newTestSuite(t *testing.T) testSuite { &sdkKeepers.StakingKeeper, *fungibleKeeper, sdkKeepers.BankKeeper, + sdkKeepers.DistributionKeeper, appCodec, gasConfig, ) diff --git a/precompiles/types/coin.go b/precompiles/types/coin.go index 4219040d42..f8f41eb766 100644 --- a/precompiles/types/coin.go +++ b/precompiles/types/coin.go @@ -2,6 +2,7 @@ package types import ( "math/big" + "strings" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" @@ -49,3 +50,8 @@ func CreateCoinSet(zrc20address common.Address, amount *big.Int) (sdk.Coins, err return coinSet, nil } + +// CoinIsZRC20 checks if a given coin is a ZRC20 coin based on its denomination. +func CoinIsZRC20(denom string) bool { + return strings.HasPrefix(denom, config.ZRC20DenomPrefix) +} diff --git a/testutil/keeper/keeper.go b/testutil/keeper/keeper.go index ed3a107dac..f3c91910b9 100644 --- a/testutil/keeper/keeper.go +++ b/testutil/keeper/keeper.go @@ -111,6 +111,7 @@ type SDKKeepers struct { TransferKeeper ibctransferkeeper.Keeper ScopedIBCKeeper capabilitykeeper.ScopedKeeper ScopedTransferKeeper capabilitykeeper.ScopedKeeper + DistributionKeeper distrkeeper.Keeper IBCRouter *porttypes.Router } @@ -150,6 +151,22 @@ var ( ) testTransientKeys = sdk.NewTransientStoreKeys(evmtypes.TransientKey) testMemKeys = sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) + + maccPerms = map[string][]string{ + authtypes.FeeCollectorName: nil, + distrtypes.ModuleName: nil, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + govtypes.ModuleName: {authtypes.Burner}, + //ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + crosschaintypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + //ibccrosschaintypes.ModuleName: nil, + evmtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + fungibletypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + emissionstypes.ModuleName: nil, + emissionstypes.UndistributedObserverRewardsPool: nil, + emissionstypes.UndistributedTSSRewardsPool: nil, + } ) // ModuleAccountAddrs returns all the app's module account addresses. @@ -401,6 +418,14 @@ func NewSDKKeepersWithKeys( tKeys map[string]*storetypes.TransientStoreKey, allKeys map[string]storetypes.StoreKey, ) SDKKeepers { + accountKeeper := authkeeper.NewAccountKeeper( + cdc, + keys[authtypes.StoreKey], + ethermint.ProtoAccount, + maccPerms, + sdk.GetConfig().GetBech32AccountAddrPrefix(), + authtypes.NewModuleAddress(authtypes.ModuleName).String(), + ) paramsKeeper := paramskeeper.NewKeeper( cdc, fungibletypes.Amino, @@ -470,16 +495,26 @@ func NewSDKKeepersWithKeys( keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey], ) + dstrKeeper := distrkeeper.NewKeeper( + cdc, + keys[distrtypes.StoreKey], + accountKeeper, + bankKeeper, + stakingKeeper, + authtypes.FeeCollectorName, + authtypes.NewModuleAddress(distrtypes.ModuleName).String(), + ) return SDKKeepers{ - ParamsKeeper: paramsKeeper, - AuthKeeper: authKeeper, - BankKeeper: bankKeeper, - StakingKeeper: stakingKeeper, - FeeMarketKeeper: feeMarketKeeper, - EvmKeeper: evmKeeper, - SlashingKeeper: slashingKeeper, - CapabilityKeeper: capabilityKeeper, + ParamsKeeper: paramsKeeper, + AuthKeeper: authKeeper, + BankKeeper: bankKeeper, + StakingKeeper: stakingKeeper, + FeeMarketKeeper: feeMarketKeeper, + EvmKeeper: evmKeeper, + SlashingKeeper: slashingKeeper, + CapabilityKeeper: capabilityKeeper, + DistributionKeeper: dstrKeeper, } } From 69d00bdcda60c7ef25c39b6524d77a5338dca5f3 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 4 Nov 2024 18:53:58 +0100 Subject: [PATCH 02/20] add changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 4e58815e99..30444f13ee 100644 --- a/changelog.md +++ b/changelog.md @@ -27,6 +27,7 @@ * [3028](https://github.com/zeta-chain/node/pull/3028) - whitelist connection gater * [3019](https://github.com/zeta-chain/node/pull/3019) - add ditribute functions to staking precompile * [3020](https://github.com/zeta-chain/node/pull/3020) - add support for TON withdrawals +* [3088](https://github.com/zeta-chain/node/pull/3088) - add functions to check and withdraw delegator rewards ### Refactor From 4c22fa9b57edaa3879cc50516448915b29d8363e Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 4 Nov 2024 19:56:33 +0100 Subject: [PATCH 03/20] add minor fixes --- precompiles/staking/IStaking.sol | 4 ++-- precompiles/staking/method_claim_rewards.go | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/precompiles/staking/IStaking.sol b/precompiles/staking/IStaking.sol index 1b433303cf..11c9ebdfa8 100644 --- a/precompiles/staking/IStaking.sol +++ b/precompiles/staking/IStaking.sol @@ -23,7 +23,7 @@ struct Validator { BondStatus bondStatus; } -/// @notice Cosmos coin representation.abi +/// @notice Cosmos coin representation. /// ref: https://github.com/cosmos/cosmos-sdk/blob/470e0859462b28a53adb411843539561d11d7bf5/x/distribution/README.md?plain=1#L139 struct DecCoin { string denom; @@ -74,7 +74,7 @@ interface IStaking { ); /// @notice ClaimedRewards is emitted when a delegator claims ZRC20. - /// @param claim_address Distributor address. + /// @param claim_address Delegator address where the funds were withdrawed. /// @param zrc20_token ZRC20 token address. /// @param amount Claimed amount. event ClaimedRewards( diff --git a/precompiles/staking/method_claim_rewards.go b/precompiles/staking/method_claim_rewards.go index 1899dabccd..ac9dce8116 100644 --- a/precompiles/staking/method_claim_rewards.go +++ b/precompiles/staking/method_claim_rewards.go @@ -81,12 +81,7 @@ func (c *Contract) claimRewards( // - Unlock the ZRC20 coins. for _, coin := range coins { // Filter out invalid coins. - if !coin.IsValid() || !coin.Amount.IsPositive() { - continue - } - - // Filter out non-ZRC20 coins. - if !precompiletypes.CoinIsZRC20(coin.Denom) { + if !coin.IsValid() || !coin.Amount.IsPositive() || !precompiletypes.CoinIsZRC20(coin.Denom) { continue } From 92d9df6c1b6ec861a5efaf3e27ec1de6754d5610 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Thu, 7 Nov 2024 20:46:39 +0100 Subject: [PATCH 04/20] add unit test --- cmd/zetae2e/local/local.go | 26 ++-- e2e/e2etests/test_precompiles_distribute.go | 97 +++++------- ...precompiles_distribute_through_contract.go | 2 +- precompiles/staking/method_distribute_test.go | 48 +++--- .../staking/method_get_all_validators_test.go | 8 +- precompiles/staking/method_get_rewards.go | 4 +- .../staking/method_get_rewards_test.go | 142 ++++++++++++++++++ precompiles/staking/method_get_shares_test.go | 16 +- precompiles/staking/method_get_validators.go | 4 +- precompiles/staking/method_move_stake_test.go | 8 +- precompiles/staking/method_stake_test.go | 4 +- precompiles/staking/method_unstake_test.go | 4 +- precompiles/staking/staking_test.go | 48 +++--- precompiles/types/coin_test.go | 21 +++ testutil/keeper/keeper.go | 35 +++++ 15 files changed, 330 insertions(+), 137 deletions(-) create mode 100644 precompiles/staking/method_get_rewards_test.go diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 7cde1a92ad..b6381d1660 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -108,14 +108,14 @@ func localE2ETest(cmd *cobra.Command, _ []string) { light = must(cmd.Flags().GetBool(flagLight)) setupOnly = must(cmd.Flags().GetBool(flagSetupOnly)) skipSetup = must(cmd.Flags().GetBool(flagSkipSetup)) - skipBitcoinSetup = must(cmd.Flags().GetBool(flagSkipBitcoinSetup)) - skipHeaderProof = must(cmd.Flags().GetBool(flagSkipHeaderProof)) - skipTrackerCheck = must(cmd.Flags().GetBool(flagSkipTrackerCheck)) - testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration)) - testV2 = must(cmd.Flags().GetBool(flagTestV2)) - testV2Migration = must(cmd.Flags().GetBool(flagTestV2Migration)) - skipPrecompiles = must(cmd.Flags().GetBool(flagSkipPrecompiles)) - upgradeContracts = must(cmd.Flags().GetBool(flagUpgradeContracts)) + //skipBitcoinSetup = must(cmd.Flags().GetBool(flagSkipBitcoinSetup)) + skipHeaderProof = must(cmd.Flags().GetBool(flagSkipHeaderProof)) + skipTrackerCheck = must(cmd.Flags().GetBool(flagSkipTrackerCheck)) + testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration)) + testV2 = must(cmd.Flags().GetBool(flagTestV2)) + testV2Migration = must(cmd.Flags().GetBool(flagTestV2Migration)) + skipPrecompiles = must(cmd.Flags().GetBool(flagSkipPrecompiles)) + upgradeContracts = must(cmd.Flags().GetBool(flagUpgradeContracts)) ) logger := runner.NewLogger(verbose, color.FgWhite, "setup") @@ -355,11 +355,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } eg.Go(statefulPrecompilesTestRoutine(conf, deployerRunner, verbose, precompiledContractTests...)) - eg.Go(erc20TestRoutine(conf, deployerRunner, verbose, erc20Tests...)) - eg.Go(zetaTestRoutine(conf, deployerRunner, verbose, zetaTests...)) - eg.Go(zevmMPTestRoutine(conf, deployerRunner, verbose, zevmMPTests...)) - eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, bitcoinTests...)) - eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, ethereumTests...)) + // eg.Go(erc20TestRoutine(conf, deployerRunner, verbose, erc20Tests...)) + // eg.Go(zetaTestRoutine(conf, deployerRunner, verbose, zetaTests...)) + // eg.Go(zevmMPTestRoutine(conf, deployerRunner, verbose, zevmMPTests...)) + // eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, bitcoinTests...)) + // eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, ethereumTests...)) } if testAdmin { diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index 36870e090c..7ee3df568d 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -3,8 +3,10 @@ package e2etests import ( "math/big" - "github.com/cosmos/cosmos-sdk/types" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -39,7 +41,7 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { r.ZEVMAuth.GasLimit = 10_000_000 // Set the test to reset the state after it finishes. - defer resetDistributionTest(r, lockerAddress, previousGasLimit, fiveHundred) + defer resetDistributionTest(r, lockerAddress, previousGasLimit) // Get ERC20ZRC20. txHash := r.DepositERC20WithAmountAndMessage(spenderAddress, oneThousand, []byte{}) @@ -48,11 +50,6 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { dstrContract, err := staking.NewIStaking(distributeContractAddress, r.ZEVMClient) require.NoError(r, err, "failed to create distribute contract caller") - // DO NOT REMOVE - will be used in a subsequent PR when the ability to withdraw delegator rewards is introduced. - // Get validators through staking contract. - // validators, err := dstrContract.GetAllValidators(&bind.CallOpts{}) - // require.NoError(r, err) - // Check initial balances. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) @@ -116,52 +113,14 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { r.WaitForBlocks(1) balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) - // DO NOT REMOVE THE FOLLOWING CODE - // This section is commented until a following PR introduces the ability to withdraw delegator rewards. - // This validator checks will be used then to complete the whole e2e. - - // res, err := r.DistributionClient.ValidatorDistributionInfo( - // r.Ctx, - // &distributiontypes.QueryValidatorDistributionInfoRequest{ - // ValidatorAddress: validators[0].OperatorAddress, - // }, - // ) - // require.NoError(r, err) - // fmt.Printf("Validator 0 distribution info: %+v\n", res) - - // res2, err := r.DistributionClient.ValidatorOutstandingRewards(r.Ctx, &distributiontypes.QueryValidatorOutstandingRewardsRequest{ - // ValidatorAddress: validators[0].OperatorAddress, - // }) - // require.NoError(r, err) - // fmt.Printf("Validator 0 outstanding rewards: %+v\n", res2) - - // res3, err := r.DistributionClient.ValidatorCommission(r.Ctx, &distributiontypes.QueryValidatorCommissionRequest{ - // ValidatorAddress: validators[0].OperatorAddress, - // }) - // require.NoError(r, err) - // fmt.Printf("Validator 0 commission: %+v\n", res3) - - // // Validator 1 - // res, err = r.DistributionClient.ValidatorDistributionInfo( - // r.Ctx, - // &distributiontypes.QueryValidatorDistributionInfoRequest{ - // ValidatorAddress: validators[1].OperatorAddress, - // }, - // ) - // require.NoError(r, err) - // fmt.Printf("Validator 1 distribution info: %+v\n", res) - - // res2, err = r.DistributionClient.ValidatorOutstandingRewards(r.Ctx, &distributiontypes.QueryValidatorOutstandingRewardsRequest{ - // ValidatorAddress: validators[1].OperatorAddress, - // }) - // require.NoError(r, err) - // fmt.Printf("Validator 1 outstanding rewards: %+v\n", res2) - - // res3, err = r.DistributionClient.ValidatorCommission(r.Ctx, &distributiontypes.QueryValidatorCommissionRequest{ - // ValidatorAddress: validators[1].OperatorAddress, - // }) - // require.NoError(r, err) - // fmt.Printf("Validator 1 commission: %+v\n", res3) + validators, err := dstrContract.GetAllValidators(&bind.CallOpts{}) + require.NoError(r, err) + require.GreaterOrEqual(r, len(validators), 2) + + err = stakeThroughCosmosAPI(r, sdk.ValAddress(validators[0].OperatorAddress), spenderAddress, "azeta", oneThousand) + require.NoError(r, err) + + r.WaitForBlocks(10) } func TestPrecompilesDistributeNonZRC20(r *runner.E2ERunner, args []string) { @@ -203,7 +162,7 @@ func TestPrecompilesDistributeNonZRC20(r *runner.E2ERunner, args []string) { } // checkCosmosBalance checks the cosmos coin balance for an address. The coin is specified by its denom. -func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom string) *big.Int { +func checkCosmosBalance(r *runner.E2ERunner, address sdk.AccAddress, denom string) *big.Int { bal, err := r.BankClient.Balance( r.Ctx, &banktypes.QueryBalanceRequest{Address: address.String(), Denom: denom}, @@ -213,11 +172,34 @@ func checkCosmosBalance(r *runner.E2ERunner, address types.AccAddress, denom str return bal.Balance.Amount.BigInt() } +func stakeThroughCosmosAPI( + r *runner.E2ERunner, + validator sdk.ValAddress, + staker common.Address, + denom string, + amount *big.Int, +) error { + msg := stakingtypes.NewMsgDelegate( + sdk.AccAddress(staker.Bytes()), + validator, + sdk.Coin{ + Denom: denom, + Amount: math.NewIntFromBigInt(amount), + }, + ) + + _, err := r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msg) + if err != nil { + return err + } + + return nil +} + func resetDistributionTest( r *runner.E2ERunner, lockerAddress common.Address, previousGasLimit uint64, - amount *big.Int, ) { r.ZEVMAuth.GasLimit = previousGasLimit @@ -228,10 +210,13 @@ func resetDistributionTest( utils.RequireTxSuccessful(r, receipt, "Resetting allowance failed") // Reset balance to 0 for spender; this is needed when running upgrade tests where this test runs twice. + balance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) + require.NoError(r, err) + tx, err = r.ERC20ZRC20.Transfer( r.ZEVMAuth, common.HexToAddress("0x000000000000000000000000000000000000dEaD"), - amount, + balance, ) require.NoError(r, err) receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_through_contract.go index 444d8e6a59..c9f6a8bb8c 100644 --- a/e2e/e2etests/test_precompiles_distribute_through_contract.go +++ b/e2e/e2etests/test_precompiles_distribute_through_contract.go @@ -50,7 +50,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string r.ZEVMAuth.GasLimit = 10_000_000 // Set the test to reset the state after it finishes. - defer resetDistributionTest(r, lockerAddress, previousGasLimit, fiveHundred) + defer resetDistributionTest(r, lockerAddress, previousGasLimit) // Check initial balances. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) diff --git a/precompiles/staking/method_distribute_test.go b/precompiles/staking/method_distribute_test.go index dddb467b74..93df75157b 100644 --- a/precompiles/staking/method_distribute_test.go +++ b/precompiles/staking/method_distribute_test.go @@ -16,17 +16,19 @@ func Test_Distribute(t *testing.T) { t.Run("should fail to run distribute as read only method", func(t *testing.T) { // Setup test. s := newTestSuite(t) + distributeMethod := s.stkContractABI.Methods[DistributeMethodName] + zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Setup method input. s.mockVMContract.Input = packInputArgs( t, - s.methodID, + distributeMethod, []interface{}{s.zrc20Address, big.NewInt(0)}..., ) // Call method as read only. - result, err := s.contract.Run(s.mockEVM, s.mockVMContract, true) + result, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, true) // Check error and result. require.ErrorIs(t, err, precompiletypes.ErrWriteMethod{ @@ -49,17 +51,18 @@ func Test_Distribute(t *testing.T) { t.Run("should fail to distribute with 0 token balance", func(t *testing.T) { // Setup test. s := newTestSuite(t) + distributeMethod := s.stkContractABI.Methods[DistributeMethodName] zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Setup method input. s.mockVMContract.Input = packInputArgs( t, - s.methodID, + distributeMethod, []interface{}{s.zrc20Address, big.NewInt(0)}..., ) // Call method. - success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + success, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // Check error. require.ErrorAs( @@ -71,7 +74,7 @@ func Test_Distribute(t *testing.T) { ) // Unpack and check result boolean. - res, err := s.methodID.Outputs.Unpack(success) + res, err := distributeMethod.Outputs.Unpack(success) require.NoError(t, err) ok := res[0].(bool) @@ -89,6 +92,7 @@ func Test_Distribute(t *testing.T) { t.Run("should fail to distribute with 0 allowance", func(t *testing.T) { // Setup test. s := newTestSuite(t) + distributeMethod := s.stkContractABI.Methods[DistributeMethodName] zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. @@ -98,19 +102,19 @@ func Test_Distribute(t *testing.T) { // Setup method input. s.mockVMContract.Input = packInputArgs( t, - s.methodID, + distributeMethod, []interface{}{s.zrc20Address, big.NewInt(1000)}..., ) // Call method. - success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + success, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // Check error. require.Error(t, err) require.Contains(t, err.Error(), "invalid allowance, got 0") // Unpack and check result boolean. - res, err := s.methodID.Outputs.Unpack(success) + res, err := distributeMethod.Outputs.Unpack(success) require.NoError(t, err) ok := res[0].(bool) @@ -128,6 +132,7 @@ func Test_Distribute(t *testing.T) { t.Run("should fail to distribute 0 token", func(t *testing.T) { // Setup test. s := newTestSuite(t) + distributeMethod := s.stkContractABI.Methods[DistributeMethodName] zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. @@ -140,19 +145,19 @@ func Test_Distribute(t *testing.T) { // Setup method input. s.mockVMContract.Input = packInputArgs( t, - s.methodID, + distributeMethod, []interface{}{s.zrc20Address, big.NewInt(0)}..., ) // Call method. - success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + success, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // Check error. require.Error(t, err) require.Contains(t, err.Error(), "invalid token amount: 0") // Unpack and check result boolean. - res, err := s.methodID.Outputs.Unpack(success) + res, err := distributeMethod.Outputs.Unpack(success) require.NoError(t, err) ok := res[0].(bool) @@ -170,6 +175,7 @@ func Test_Distribute(t *testing.T) { t.Run("should fail to distribute more than allowed to staking", func(t *testing.T) { // Setup test. s := newTestSuite(t) + distributeMethod := s.stkContractABI.Methods[DistributeMethodName] zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. @@ -182,19 +188,19 @@ func Test_Distribute(t *testing.T) { // Setup method input. s.mockVMContract.Input = packInputArgs( t, - s.methodID, + distributeMethod, []interface{}{s.zrc20Address, big.NewInt(1000)}..., ) // Call method. - success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + success, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // Check error. require.Error(t, err) require.Contains(t, err.Error(), "invalid allowance, got 999, wanted 1000") // Unpack and check result boolean. - res, err := s.methodID.Outputs.Unpack(success) + res, err := distributeMethod.Outputs.Unpack(success) require.NoError(t, err) ok := res[0].(bool) @@ -212,6 +218,7 @@ func Test_Distribute(t *testing.T) { t.Run("should fail to distribute more than user balance", func(t *testing.T) { // Setup test. s := newTestSuite(t) + distributeMethod := s.stkContractABI.Methods[DistributeMethodName] zrc20Denom := precompiletypes.ZRC20ToCosmosDenom(s.zrc20Address) // Set caller balance. @@ -224,18 +231,18 @@ func Test_Distribute(t *testing.T) { // Setup method input. s.mockVMContract.Input = packInputArgs( t, - s.methodID, + distributeMethod, []interface{}{s.zrc20Address, big.NewInt(1001)}..., ) - success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + success, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // Check error. require.Error(t, err) require.Contains(t, err.Error(), "execution reverted") // Unpack and check result boolean. - res, err := s.methodID.Outputs.Unpack(success) + res, err := distributeMethod.Outputs.Unpack(success) require.NoError(t, err) ok := res[0].(bool) @@ -253,6 +260,7 @@ func Test_Distribute(t *testing.T) { t.Run("should distribute and lock ZRC20", func(t *testing.T) { // Setup test. s := newTestSuite(t) + distributeMethod := s.stkContractABI.Methods[DistributeMethodName] // Set caller balance. _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, big.NewInt(1000)) @@ -264,17 +272,17 @@ func Test_Distribute(t *testing.T) { // Setup method input. s.mockVMContract.Input = packInputArgs( t, - s.methodID, + distributeMethod, []interface{}{s.zrc20Address, big.NewInt(1000)}..., ) - success, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + success, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // Check error. require.NoError(t, err) // Unpack and check result boolean. - res, err := s.methodID.Outputs.Unpack(success) + res, err := distributeMethod.Outputs.Unpack(success) require.NoError(t, err) ok := res[0].(bool) diff --git a/precompiles/staking/method_get_all_validators_test.go b/precompiles/staking/method_get_all_validators_test.go index 8a80793de3..433c0f1cb4 100644 --- a/precompiles/staking/method_get_all_validators_test.go +++ b/precompiles/staking/method_get_all_validators_test.go @@ -19,11 +19,11 @@ func Test_GetAllValidators(t *testing.T) { s.sdkKeepers.StakingKeeper.RemoveValidator(s.ctx, v.GetOperator()) } - methodID := s.contractABI.Methods[GetAllValidatorsMethodName] + methodID := s.stkContractABI.Methods[GetAllValidatorsMethodName] s.mockVMContract.Input = methodID.ID // ACT - validators, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + validators, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // ASSERT require.NoError(t, err) @@ -37,14 +37,14 @@ func Test_GetAllValidators(t *testing.T) { t.Run("should return validators if set", func(t *testing.T) { // ARRANGE s := newTestSuite(t) - methodID := s.contractABI.Methods[GetAllValidatorsMethodName] + methodID := s.stkContractABI.Methods[GetAllValidatorsMethodName] s.mockVMContract.Input = methodID.ID r := rand.New(rand.NewSource(42)) validator := sample.Validator(t, r) s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) // ACT - validators, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + validators, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // ASSERT require.NoError(t, err) diff --git a/precompiles/staking/method_get_rewards.go b/precompiles/staking/method_get_rewards.go index dbbbfff5e4..52ababffde 100644 --- a/precompiles/staking/method_get_rewards.go +++ b/precompiles/staking/method_get_rewards.go @@ -35,9 +35,9 @@ func (c *Contract) getRewards( } // Query the delegation rewards through the distribution keeper querier. - dstrClient := distrkeeper.Querier{Keeper: c.distributionKeeper} + dstrQuerier := distrkeeper.NewQuerier(c.distributionKeeper) - res, err := dstrClient.DelegationRewards(ctx, &dstrtypes.QueryDelegationRewardsRequest{ + res, err := dstrQuerier.DelegationRewards(ctx, &dstrtypes.QueryDelegationRewardsRequest{ DelegatorAddress: delegatorCosmosAddr.String(), ValidatorAddress: validatorAddr, }) diff --git a/precompiles/staking/method_get_rewards_test.go b/precompiles/staking/method_get_rewards_test.go new file mode 100644 index 0000000000..5f751980eb --- /dev/null +++ b/precompiles/staking/method_get_rewards_test.go @@ -0,0 +1,142 @@ +package staking + +import ( + "fmt" + "math/big" + "math/rand" + "testing" + + "cosmossdk.io/math" + 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/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/emissions" + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +func Test_GetRewards(t *testing.T) { + t.Run("become azeta staker, distribute ZRC20, get rewards", func(t *testing.T) { + /* ARRANGE */ + s := newTestSuite(t) + + // Create validator. + validator := sample.Validator(t, rand.New(rand.NewSource(42))) + s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) + + // Create staker. + staker := sample.Bech32AccAddress() + + // Become a staker. + stakeThroughCosmosAPI( + t, + s.ctx, + s.sdkKeepers.BankKeeper, + s.sdkKeepers.StakingKeeper, + validator, + staker, + math.NewInt(100), + ) + + /* Distribute 1000 ZRC20 tokens to the staking contract */ + distributeZRC20(t, s, big.NewInt(1000)) + + // Produce blocks. + for i := 0; i < 10; i++ { + // produce a block + emissions.BeginBlocker(s.ctx, *s.sdkKeepers.EmissionsKeeper) + s.ctx = s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 1) + } + + /* ACT */ + // Call getRewards. + getRewardsMethod := s.stkContractABI.Methods[GetRewardsMethodName] + + fmt.Println(common.HexToAddress(staker.String())) + fmt.Println(validator.GetOperator().String()) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + getRewardsMethod, + []interface{}{common.HexToAddress(staker.String()), validator.GetOperator().String()}..., + ) + + bytes, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.NoError(t, err) + + res, err := s.stkContractABI.Methods[DistributeMethodName].Outputs.Unpack(bytes) + require.NoError(t, err) + fmt.Println(res) + + /* ASSERT */ + }) +} + +func stakeThroughCosmosAPI( + t *testing.T, + ctx sdk.Context, + bankKeeper bankkeeper.Keeper, + stakingKeeper stakingkeeper.Keeper, + validator stakingtypes.Validator, + staker sdk.AccAddress, + amount math.Int, +) { + // Coins to stake with default cosmos denom. + coins := sdk.NewCoins(sdk.NewCoin("stake", amount)) + + err := bankKeeper.MintCoins(ctx, fungibletypes.ModuleName, coins) + require.NoError(t, err) + + err = bankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + require.NoError(t, err) + + b := bankKeeper.GetAllBalances(ctx, staker) + fmt.Println(b) + + shares, err := stakingKeeper.Delegate( + ctx, + staker, + coins.AmountOf(coins.Denoms()[0]), + validator.Status, + validator, + true, + ) + require.NoError(t, err) + require.Equal(t, amount.Uint64(), shares.TruncateInt().Uint64()) + b = bankKeeper.GetAllBalances(ctx, staker) + + del, found := stakingKeeper.GetDelegation(ctx, staker, validator.GetOperator()) + fmt.Println(found) + fmt.Println(del) +} + +func distributeZRC20( + t *testing.T, + s testSuite, + amount *big.Int, +) { + distributeMethod := s.stkContractABI.Methods[DistributeMethodName] + + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, amount) + require.NoError(t, err) + allowStaking(t, s, amount) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + distributeMethod, + []interface{}{s.zrc20Address, amount}..., + ) + + // Call distribute method. + success, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.NoError(t, err) + res, err := distributeMethod.Outputs.Unpack(success) + require.NoError(t, err) + ok := res[0].(bool) + require.True(t, ok) +} diff --git a/precompiles/staking/method_get_shares_test.go b/precompiles/staking/method_get_shares_test.go index d0038886f4..df3f132610 100644 --- a/precompiles/staking/method_get_shares_test.go +++ b/precompiles/staking/method_get_shares_test.go @@ -56,13 +56,13 @@ func Test_GetShares(t *testing.T) { t.Run("should fail if wrong args amount", func(t *testing.T) { // ARRANGE s := newTestSuite(t) - methodID := s.contractABI.Methods[GetSharesMethodName] + methodID := s.stkContractABI.Methods[GetSharesMethodName] staker := sample.Bech32AccAddress() stakerEthAddr := common.BytesToAddress(staker.Bytes()) args := []interface{}{stakerEthAddr} // ACT - _, err := s.contract.GetShares(s.ctx, &methodID, args) + _, err := s.stkContract.GetShares(s.ctx, &methodID, args) // ASSERT require.Error(t, err) @@ -71,13 +71,13 @@ func Test_GetShares(t *testing.T) { t.Run("should fail if invalid staker arg", func(t *testing.T) { // ARRANGE s := newTestSuite(t) - methodID := s.contractABI.Methods[GetSharesMethodName] + methodID := s.stkContractABI.Methods[GetSharesMethodName] r := rand.New(rand.NewSource(42)) validator := sample.Validator(t, r) args := []interface{}{42, validator.OperatorAddress} // ACT - _, err := s.contract.GetShares(s.ctx, &methodID, args) + _, err := s.stkContract.GetShares(s.ctx, &methodID, args) // ASSERT require.Error(t, err) @@ -86,13 +86,13 @@ func Test_GetShares(t *testing.T) { t.Run("should fail if invalid val address", func(t *testing.T) { // ARRANGE s := newTestSuite(t) - methodID := s.contractABI.Methods[GetSharesMethodName] + methodID := s.stkContractABI.Methods[GetSharesMethodName] staker := sample.Bech32AccAddress() stakerEthAddr := common.BytesToAddress(staker.Bytes()) args := []interface{}{stakerEthAddr, staker.String()} // ACT - _, err := s.contract.GetShares(s.ctx, &methodID, args) + _, err := s.stkContract.GetShares(s.ctx, &methodID, args) // ASSERT require.Error(t, err) @@ -101,13 +101,13 @@ func Test_GetShares(t *testing.T) { t.Run("should fail if invalid val address format", func(t *testing.T) { // ARRANGE s := newTestSuite(t) - methodID := s.contractABI.Methods[GetSharesMethodName] + methodID := s.stkContractABI.Methods[GetSharesMethodName] staker := sample.Bech32AccAddress() stakerEthAddr := common.BytesToAddress(staker.Bytes()) args := []interface{}{stakerEthAddr, 42} // ACT - _, err := s.contract.GetShares(s.ctx, &methodID, args) + _, err := s.stkContract.GetShares(s.ctx, &methodID, args) // ASSERT require.Error(t, err) diff --git a/precompiles/staking/method_get_validators.go b/precompiles/staking/method_get_validators.go index 1351d8c62f..6c41bdc8ff 100644 --- a/precompiles/staking/method_get_validators.go +++ b/precompiles/staking/method_get_validators.go @@ -35,9 +35,9 @@ func (c *Contract) getDelegatorValidators( } // Query the validator list of the given delegator. - dstrClient := distrkeeper.Querier{Keeper: c.distributionKeeper} + dstrQuerier := distrkeeper.NewQuerier(c.distributionKeeper) - res, err := dstrClient.DelegatorValidators(ctx, &dstrtypes.QueryDelegatorValidatorsRequest{ + res, err := dstrQuerier.DelegatorValidators(ctx, &dstrtypes.QueryDelegatorValidatorsRequest{ DelegatorAddress: delegatorCosmosAddr.String(), }) if err != nil { diff --git a/precompiles/staking/method_move_stake_test.go b/precompiles/staking/method_move_stake_test.go index 8882442069..32ec0c1b92 100644 --- a/precompiles/staking/method_move_stake_test.go +++ b/precompiles/staking/method_move_stake_test.go @@ -17,7 +17,7 @@ func Test_MoveStake(t *testing.T) { t.Run("should fail with error disabled", func(t *testing.T) { // ARRANGE s := newTestSuite(t) - methodID := s.contractABI.Methods[MoveStakeMethodName] + methodID := s.stkContractABI.Methods[MoveStakeMethodName] r := rand.New(rand.NewSource(42)) validatorSrc := sample.Validator(t, r) s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validatorSrc) @@ -41,9 +41,9 @@ func Test_MoveStake(t *testing.T) { } // stake to validator src - stakeMethodID := s.contractABI.Methods[StakeMethodName] + stakeMethodID := s.stkContractABI.Methods[StakeMethodName] s.mockVMContract.Input = packInputArgs(t, stakeMethodID, argsStake...) - _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) + _, err = s.stkContract.Run(s.mockEVM, s.mockVMContract, false) require.Error(t, err) require.ErrorIs(t, err, precompiletypes.ErrDisabledMethod{ Method: StakeMethodName, @@ -58,7 +58,7 @@ func Test_MoveStake(t *testing.T) { s.mockVMContract.Input = packInputArgs(t, methodID, argsMoveStake...) // ACT - _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) + _, err = s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // ASSERT require.Error(t, err) diff --git a/precompiles/staking/method_stake_test.go b/precompiles/staking/method_stake_test.go index bd5558abdb..16f10db4a7 100644 --- a/precompiles/staking/method_stake_test.go +++ b/precompiles/staking/method_stake_test.go @@ -17,7 +17,7 @@ func Test_Stake(t *testing.T) { t.Run("should fail with error disabled", func(t *testing.T) { // ARRANGE s := newTestSuite(t) - methodID := s.contractABI.Methods[StakeMethodName] + methodID := s.stkContractABI.Methods[StakeMethodName] r := rand.New(rand.NewSource(42)) validator := sample.Validator(t, r) @@ -35,7 +35,7 @@ func Test_Stake(t *testing.T) { s.mockVMContract.Input = packInputArgs(t, methodID, args...) // ACT - _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) + _, err = s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // ASSERT require.Error(t, err) diff --git a/precompiles/staking/method_unstake_test.go b/precompiles/staking/method_unstake_test.go index e770020946..2789bd67d9 100644 --- a/precompiles/staking/method_unstake_test.go +++ b/precompiles/staking/method_unstake_test.go @@ -17,7 +17,7 @@ func Test_Unstake(t *testing.T) { t.Run("should fail with error disabled", func(t *testing.T) { // ARRANGE s := newTestSuite(t) - methodID := s.contractABI.Methods[UnstakeMethodName] + methodID := s.stkContractABI.Methods[UnstakeMethodName] r := rand.New(rand.NewSource(42)) validator := sample.Validator(t, r) @@ -36,7 +36,7 @@ func Test_Unstake(t *testing.T) { s.mockVMContract.Input = packInputArgs(t, methodID, args...) // ACT - _, err = s.contract.Run(s.mockEVM, s.mockVMContract, false) + _, err = s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // ASSERT require.Error(t, err) diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index 4c58df2fb9..8fdfd74264 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -38,20 +38,24 @@ func Test_IStakingContract(t *testing.T) { gasConfig := storetypes.TransientGasConfig() t.Run("should check methods are present in ABI", func(t *testing.T) { - require.NotNil(t, s.contractABI.Methods[StakeMethodName], "stake method should be present in the ABI") - require.NotNil(t, s.contractABI.Methods[UnstakeMethodName], "unstake method should be present in the ABI") + require.NotNil(t, s.stkContractABI.Methods[StakeMethodName], "stake method should be present in the ABI") + require.NotNil(t, s.stkContractABI.Methods[UnstakeMethodName], "unstake method should be present in the ABI") require.NotNil( t, - s.contractABI.Methods[MoveStakeMethodName], + s.stkContractABI.Methods[MoveStakeMethodName], "moveStake method should be present in the ABI", ) require.NotNil( t, - s.contractABI.Methods[GetAllValidatorsMethodName], + s.stkContractABI.Methods[GetAllValidatorsMethodName], "getAllValidators method should be present in the ABI", ) - require.NotNil(t, s.contractABI.Methods[GetSharesMethodName], "getShares method should be present in the ABI") + require.NotNil( + t, + s.stkContractABI.Methods[GetSharesMethodName], + "getShares method should be present in the ABI", + ) }) t.Run("should check gas requirements for methods", func(t *testing.T) { @@ -59,9 +63,9 @@ func Test_IStakingContract(t *testing.T) { t.Run("stake", func(t *testing.T) { // ACT - stake := s.contract.RequiredGas(s.contractABI.Methods[StakeMethodName].ID) + stake := s.stkContract.RequiredGas(s.stkContractABI.Methods[StakeMethodName].ID) // ASSERT - copy(method[:], s.contractABI.Methods[StakeMethodName].ID[:4]) + copy(method[:], s.stkContractABI.Methods[StakeMethodName].ID[:4]) baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte require.Equal( t, @@ -75,9 +79,9 @@ func Test_IStakingContract(t *testing.T) { t.Run("unstake", func(t *testing.T) { // ACT - unstake := s.contract.RequiredGas(s.contractABI.Methods[UnstakeMethodName].ID) + unstake := s.stkContract.RequiredGas(s.stkContractABI.Methods[UnstakeMethodName].ID) // ASSERT - copy(method[:], s.contractABI.Methods[UnstakeMethodName].ID[:4]) + copy(method[:], s.stkContractABI.Methods[UnstakeMethodName].ID[:4]) baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte require.Equal( t, @@ -91,9 +95,9 @@ func Test_IStakingContract(t *testing.T) { t.Run("moveStake", func(t *testing.T) { // ACT - moveStake := s.contract.RequiredGas(s.contractABI.Methods[MoveStakeMethodName].ID) + moveStake := s.stkContract.RequiredGas(s.stkContractABI.Methods[MoveStakeMethodName].ID) // ASSERT - copy(method[:], s.contractABI.Methods[MoveStakeMethodName].ID[:4]) + copy(method[:], s.stkContractABI.Methods[MoveStakeMethodName].ID[:4]) baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte require.Equal( t, @@ -107,9 +111,9 @@ func Test_IStakingContract(t *testing.T) { t.Run("getAllValidators", func(t *testing.T) { // ACT - getAllValidators := s.contract.RequiredGas(s.contractABI.Methods[GetAllValidatorsMethodName].ID) + getAllValidators := s.stkContract.RequiredGas(s.stkContractABI.Methods[GetAllValidatorsMethodName].ID) // ASSERT - copy(method[:], s.contractABI.Methods[GetAllValidatorsMethodName].ID[:4]) + copy(method[:], s.stkContractABI.Methods[GetAllValidatorsMethodName].ID[:4]) baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte require.Equal( t, @@ -123,9 +127,9 @@ func Test_IStakingContract(t *testing.T) { t.Run("getShares", func(t *testing.T) { // ACT - getShares := s.contract.RequiredGas(s.contractABI.Methods[GetSharesMethodName].ID) + getShares := s.stkContract.RequiredGas(s.stkContractABI.Methods[GetSharesMethodName].ID) // ASSERT - copy(method[:], s.contractABI.Methods[GetSharesMethodName].ID[:4]) + copy(method[:], s.stkContractABI.Methods[GetSharesMethodName].ID[:4]) baseCost := uint64(len(method)) * gasConfig.ReadCostPerByte require.Equal( t, @@ -141,7 +145,7 @@ func Test_IStakingContract(t *testing.T) { // ARRANGE invalidMethodBytes := []byte("invalidMethod") // ACT - gasInvalidMethod := s.contract.RequiredGas(invalidMethodBytes) + gasInvalidMethod := s.stkContract.RequiredGas(invalidMethodBytes) // ASSERT require.Equal( t, @@ -158,7 +162,7 @@ func Test_IStakingContract(t *testing.T) { func Test_InvalidMethod(t *testing.T) { s := newTestSuite(t) - _, doNotExist := s.contractABI.Methods["invalidMethod"] + _, doNotExist := s.stkContractABI.Methods["invalidMethod"] require.False(t, doNotExist, "invalidMethod should not be present in the ABI") } @@ -189,7 +193,7 @@ func Test_RunInvalidMethod(t *testing.T) { s.mockVMContract.Input = packInputArgs(t, methodID, args...) // ACT - _, err := s.contract.Run(s.mockEVM, s.mockVMContract, false) + _, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) // ASSERT require.Error(t, err) @@ -277,13 +281,12 @@ func setup(t *testing.T) (sdk.Context, *Contract, abi.ABI, keeper.SDKKeepers, *v type testSuite struct { ctx sdk.Context - contract *Contract - contractABI *abi.ABI + stkContract *Contract + stkContractABI *abi.ABI fungibleKeeper *fungiblekeeper.Keeper sdkKeepers keeper.SDKKeepers mockEVM *vm.EVM mockVMContract *vm.Contract - methodID abi.Method defaultCaller common.Address defaultLocker common.Address zrc20Address common.Address @@ -363,7 +366,6 @@ func newTestSuite(t *testing.T) testSuite { sdkKeepers, mockEVM, mockVMContract, - abi.Methods[DistributeMethodName], caller, locker, zrc20Address, @@ -386,7 +388,7 @@ func allowStaking(t *testing.T, ts testSuite, amount *big.Int) { fungibletypes.ModuleAddressEVM, ts.zrc20Address, "approve", - []interface{}{ts.contract.Address(), amount}, + []interface{}{ts.stkContract.Address(), amount}, ) require.NoError(t, err, "error allowing staking to spend ZRC20 tokens") diff --git a/precompiles/types/coin_test.go b/precompiles/types/coin_test.go index 6a8aac7d46..9566662dfe 100644 --- a/precompiles/types/coin_test.go +++ b/precompiles/types/coin_test.go @@ -28,3 +28,24 @@ func Test_createCoinSet(t *testing.T) { require.Equal(t, tokenDenom, coin.Denom, "coin denom should be %s, got %s", tokenDenom, coin.Denom) require.Equal(t, amount, coin.Amount.BigInt(), "coin amount should be %s, got %s", amount, coin.Amount.BigInt()) } + +func Test_CoinIsZRC20(t *testing.T) { + test := []struct { + denom string + expected bool + }{ + {"zrc20/0x0123456789abcdef", true}, + {"zrc20/0xabcdef0123456789", true}, + {"zrc200xabcdef", false}, + {"foo/0x0123456789", false}, + } + + for _, tt := range test { + t.Run(tt.denom, func(t *testing.T) { + result := CoinIsZRC20(tt.denom) + if result != tt.expected { + t.Errorf("got %v, want %v", result, tt.expected) + } + }) + } +} diff --git a/testutil/keeper/keeper.go b/testutil/keeper/keeper.go index f3c91910b9..5819c6b74b 100644 --- a/testutil/keeper/keeper.go +++ b/testutil/keeper/keeper.go @@ -112,6 +112,7 @@ type SDKKeepers struct { ScopedIBCKeeper capabilitykeeper.ScopedKeeper ScopedTransferKeeper capabilitykeeper.ScopedKeeper DistributionKeeper distrkeeper.Keeper + EmissionsKeeper *emissionskeeper.Keeper IBCRouter *porttypes.Router } @@ -418,6 +419,12 @@ func NewSDKKeepersWithKeys( tKeys map[string]*storetypes.TransientStoreKey, allKeys map[string]storetypes.StoreKey, ) SDKKeepers { + authorityKeeper := authoritykeeper.NewKeeper( + cdc, + keys[authoritytypes.StoreKey], + memKeys[authoritytypes.MemStoreKey], + AuthorityGovAddress, + ) accountKeeper := authkeeper.NewAccountKeeper( cdc, keys[authtypes.StoreKey], @@ -504,6 +511,33 @@ func NewSDKKeepersWithKeys( authtypes.FeeCollectorName, authtypes.NewModuleAddress(distrtypes.ModuleName).String(), ) + lightclientKeeper := lightclientkeeper.NewKeeper( + cdc, + keys[lightclienttypes.StoreKey], + memKeys[lightclienttypes.MemStoreKey], + authorityKeeper, + ) + observerKeeper := observerkeeper.NewKeeper( + cdc, + keys[observertypes.StoreKey], + memKeys[observertypes.MemStoreKey], + stakingKeeper, + slashingKeeper, + authorityKeeper, + lightclientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + emissionsKeeper := emissionskeeper.NewKeeper( + cdc, + keys[emissionstypes.StoreKey], + memKeys[emissionstypes.MemStoreKey], + authtypes.FeeCollectorName, + bankKeeper, + stakingKeeper, + observerKeeper, + authKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) return SDKKeepers{ ParamsKeeper: paramsKeeper, @@ -515,6 +549,7 @@ func NewSDKKeepersWithKeys( SlashingKeeper: slashingKeeper, CapabilityKeeper: capabilityKeeper, DistributionKeeper: dstrKeeper, + EmissionsKeeper: emissionsKeeper, } } From 6afbaec527c1bc5ec3e1dcf532aea5cae3494b03 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Fri, 8 Nov 2024 13:20:18 +0100 Subject: [PATCH 05/20] add unit tests --- cmd/zetae2e/local/local.go | 26 ++-- .../staking/method_get_rewards_test.go | 137 +++++++----------- precompiles/staking/method_get_validators.go | 4 +- .../staking/method_get_validators_test.go | 93 ++++++++++++ precompiles/staking/staking_test.go | 62 ++++++++ 5 files changed, 219 insertions(+), 103 deletions(-) create mode 100644 precompiles/staking/method_get_validators_test.go diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index b6381d1660..7cde1a92ad 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -108,14 +108,14 @@ func localE2ETest(cmd *cobra.Command, _ []string) { light = must(cmd.Flags().GetBool(flagLight)) setupOnly = must(cmd.Flags().GetBool(flagSetupOnly)) skipSetup = must(cmd.Flags().GetBool(flagSkipSetup)) - //skipBitcoinSetup = must(cmd.Flags().GetBool(flagSkipBitcoinSetup)) - skipHeaderProof = must(cmd.Flags().GetBool(flagSkipHeaderProof)) - skipTrackerCheck = must(cmd.Flags().GetBool(flagSkipTrackerCheck)) - testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration)) - testV2 = must(cmd.Flags().GetBool(flagTestV2)) - testV2Migration = must(cmd.Flags().GetBool(flagTestV2Migration)) - skipPrecompiles = must(cmd.Flags().GetBool(flagSkipPrecompiles)) - upgradeContracts = must(cmd.Flags().GetBool(flagUpgradeContracts)) + skipBitcoinSetup = must(cmd.Flags().GetBool(flagSkipBitcoinSetup)) + skipHeaderProof = must(cmd.Flags().GetBool(flagSkipHeaderProof)) + skipTrackerCheck = must(cmd.Flags().GetBool(flagSkipTrackerCheck)) + testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration)) + testV2 = must(cmd.Flags().GetBool(flagTestV2)) + testV2Migration = must(cmd.Flags().GetBool(flagTestV2Migration)) + skipPrecompiles = must(cmd.Flags().GetBool(flagSkipPrecompiles)) + upgradeContracts = must(cmd.Flags().GetBool(flagUpgradeContracts)) ) logger := runner.NewLogger(verbose, color.FgWhite, "setup") @@ -355,11 +355,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } eg.Go(statefulPrecompilesTestRoutine(conf, deployerRunner, verbose, precompiledContractTests...)) - // eg.Go(erc20TestRoutine(conf, deployerRunner, verbose, erc20Tests...)) - // eg.Go(zetaTestRoutine(conf, deployerRunner, verbose, zetaTests...)) - // eg.Go(zevmMPTestRoutine(conf, deployerRunner, verbose, zevmMPTests...)) - // eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, bitcoinTests...)) - // eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, ethereumTests...)) + eg.Go(erc20TestRoutine(conf, deployerRunner, verbose, erc20Tests...)) + eg.Go(zetaTestRoutine(conf, deployerRunner, verbose, zetaTests...)) + eg.Go(zevmMPTestRoutine(conf, deployerRunner, verbose, zevmMPTests...)) + eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, bitcoinTests...)) + eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, ethereumTests...)) } if testAdmin { diff --git a/precompiles/staking/method_get_rewards_test.go b/precompiles/staking/method_get_rewards_test.go index 5f751980eb..75d9c41afb 100644 --- a/precompiles/staking/method_get_rewards_test.go +++ b/precompiles/staking/method_get_rewards_test.go @@ -7,19 +7,15 @@ import ( "testing" "cosmossdk.io/math" - 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/common" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/stretchr/testify/require" + precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/testutil/sample" "github.com/zeta-chain/node/x/emissions" - fungibletypes "github.com/zeta-chain/node/x/fungible/types" ) func Test_GetRewards(t *testing.T) { - t.Run("become azeta staker, distribute ZRC20, get rewards", func(t *testing.T) { + t.Run("should return empty rewards list to a non staker", func(t *testing.T) { /* ARRANGE */ s := newTestSuite(t) @@ -28,7 +24,38 @@ func Test_GetRewards(t *testing.T) { s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) // Create staker. - staker := sample.Bech32AccAddress() + stakerEVMAddr := sample.EthAddress() + + /* ACT */ + // Call getRewards. + getRewardsMethod := s.stkContractABI.Methods[GetRewardsMethodName] + + s.mockVMContract.Input = packInputArgs( + t, + getRewardsMethod, + []interface{}{stakerEVMAddr, validator.GetOperator().String()}..., + ) + + /* ASSERT */ + bytes, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.Error(t, err) + require.Contains(t, err.Error(), "delegation does not exist") + require.Empty(t, bytes) + }) + + t.Run("should return the zrc20 rewards list for a staker", func(t *testing.T) { + /* ARRANGE */ + s := newTestSuite(t) + s.sdkKeepers.DistributionKeeper.SetFeePool(s.ctx, distrtypes.InitialFeePool()) + + // Create validator. + validator := sample.Validator(t, rand.New(rand.NewSource(42))) + s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) + + // Create staker. + stakerEVMAddr := sample.EthAddress() + stakerCosmosAddr, err := precompiletypes.GetCosmosAddress(s.sdkKeepers.BankKeeper, stakerEVMAddr) + require.NoError(t, err) // Become a staker. stakeThroughCosmosAPI( @@ -37,106 +64,40 @@ func Test_GetRewards(t *testing.T) { s.sdkKeepers.BankKeeper, s.sdkKeepers.StakingKeeper, validator, - staker, + stakerCosmosAddr, math.NewInt(100), ) + err = s.sdkKeepers.DistributionKeeper.Hooks(). + AfterDelegationModified(s.ctx, stakerCosmosAddr, validator.GetOperator()) + require.NoError(t, err) + + // DEBUG CALL + del := s.sdkKeepers.StakingKeeper.GetAllDelegations(s.ctx) + fmt.Println(del) + /* Distribute 1000 ZRC20 tokens to the staking contract */ distributeZRC20(t, s, big.NewInt(1000)) - // Produce blocks. - for i := 0; i < 10; i++ { - // produce a block - emissions.BeginBlocker(s.ctx, *s.sdkKeepers.EmissionsKeeper) - s.ctx = s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 1) - } + emissions.BeginBlocker(s.ctx, *s.sdkKeepers.EmissionsKeeper) + s.ctx = s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 1) /* ACT */ // Call getRewards. getRewardsMethod := s.stkContractABI.Methods[GetRewardsMethodName] - - fmt.Println(common.HexToAddress(staker.String())) - fmt.Println(validator.GetOperator().String()) - // Setup method input. s.mockVMContract.Input = packInputArgs( t, getRewardsMethod, - []interface{}{common.HexToAddress(staker.String()), validator.GetOperator().String()}..., + []interface{}{stakerEVMAddr, validator.GetOperator().String()}..., ) bytes, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) require.NoError(t, err) - res, err := s.stkContractABI.Methods[DistributeMethodName].Outputs.Unpack(bytes) + /* ASSERT */ + res, err := getRewardsMethod.Outputs.Unpack(bytes) require.NoError(t, err) fmt.Println(res) - - /* ASSERT */ }) } - -func stakeThroughCosmosAPI( - t *testing.T, - ctx sdk.Context, - bankKeeper bankkeeper.Keeper, - stakingKeeper stakingkeeper.Keeper, - validator stakingtypes.Validator, - staker sdk.AccAddress, - amount math.Int, -) { - // Coins to stake with default cosmos denom. - coins := sdk.NewCoins(sdk.NewCoin("stake", amount)) - - err := bankKeeper.MintCoins(ctx, fungibletypes.ModuleName, coins) - require.NoError(t, err) - - err = bankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) - require.NoError(t, err) - - b := bankKeeper.GetAllBalances(ctx, staker) - fmt.Println(b) - - shares, err := stakingKeeper.Delegate( - ctx, - staker, - coins.AmountOf(coins.Denoms()[0]), - validator.Status, - validator, - true, - ) - require.NoError(t, err) - require.Equal(t, amount.Uint64(), shares.TruncateInt().Uint64()) - b = bankKeeper.GetAllBalances(ctx, staker) - - del, found := stakingKeeper.GetDelegation(ctx, staker, validator.GetOperator()) - fmt.Println(found) - fmt.Println(del) -} - -func distributeZRC20( - t *testing.T, - s testSuite, - amount *big.Int, -) { - distributeMethod := s.stkContractABI.Methods[DistributeMethodName] - - _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, amount) - require.NoError(t, err) - allowStaking(t, s, amount) - - // Setup method input. - s.mockVMContract.Input = packInputArgs( - t, - distributeMethod, - []interface{}{s.zrc20Address, amount}..., - ) - - // Call distribute method. - success, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) - require.NoError(t, err) - res, err := distributeMethod.Outputs.Unpack(success) - require.NoError(t, err) - ok := res[0].(bool) - require.True(t, ok) -} diff --git a/precompiles/staking/method_get_validators.go b/precompiles/staking/method_get_validators.go index 6c41bdc8ff..fbf133ffb9 100644 --- a/precompiles/staking/method_get_validators.go +++ b/precompiles/staking/method_get_validators.go @@ -16,10 +16,10 @@ func (c *Contract) getDelegatorValidators( method *abi.Method, args []interface{}, ) ([]byte, error) { - if len(args) != 0 { + if len(args) != 1 { return nil, &precompiletypes.ErrInvalidNumberOfArgs{ Got: len(args), - Expect: 0, + Expect: 1, } } diff --git a/precompiles/staking/method_get_validators_test.go b/precompiles/staking/method_get_validators_test.go new file mode 100644 index 0000000000..6fd3957c96 --- /dev/null +++ b/precompiles/staking/method_get_validators_test.go @@ -0,0 +1,93 @@ +package staking + +import ( + "math/rand" + "testing" + + "cosmossdk.io/math" + "github.com/stretchr/testify/require" + precompiletypes "github.com/zeta-chain/node/precompiles/types" + "github.com/zeta-chain/node/testutil/sample" +) + +func Test_GetValidators(t *testing.T) { + t.Run("should return an empty list for a non staker address", func(t *testing.T) { + /* ARRANGE */ + s := newTestSuite(t) + + // Create validator. + validator := sample.Validator(t, rand.New(rand.NewSource(42))) + s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) + + // Create staker. + stakerEVMAddr := sample.EthAddress() + + /* ACT */ + // Call getRewards. + getValidatorsMethod := s.stkContractABI.Methods[GetValidatorsMethodName] + + s.mockVMContract.Input = packInputArgs( + t, + getValidatorsMethod, + []interface{}{stakerEVMAddr}..., + ) + + bytes, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.NoError(t, err) + + res, err := getValidatorsMethod.Outputs.Unpack(bytes) + require.NoError(t, err) + require.NotEmpty(t, res) + + list, ok := res[0].([]string) + require.True(t, ok) + require.Len(t, list, 0) + }) + + t.Run("should return staker's validator list", func(t *testing.T) { + /* ARRANGE */ + s := newTestSuite(t) + + // Create validator. + validator := sample.Validator(t, rand.New(rand.NewSource(42))) + s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) + + // Create staker. + stakerEVMAddr := sample.EthAddress() + stakerCosmosAddr, err := precompiletypes.GetCosmosAddress(s.sdkKeepers.BankKeeper, stakerEVMAddr) + require.NoError(t, err) + + // Become a staker. + stakeThroughCosmosAPI( + t, + s.ctx, + s.sdkKeepers.BankKeeper, + s.sdkKeepers.StakingKeeper, + validator, + stakerCosmosAddr, + math.NewInt(100), + ) + + /* ACT */ + // Call getRewards. + getValidatorsMethod := s.stkContractABI.Methods[GetValidatorsMethodName] + + s.mockVMContract.Input = packInputArgs( + t, + getValidatorsMethod, + []interface{}{stakerEVMAddr}..., + ) + + bytes, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.NoError(t, err) + + res, err := getValidatorsMethod.Outputs.Unpack(bytes) + require.NoError(t, err) + require.NotEmpty(t, res) + + list, ok := res[0].([]string) + require.True(t, ok) + require.Len(t, list, 1) + require.Equal(t, validator.GetOperator().String(), list[0]) + }) +} diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index 8fdfd74264..f7569f4e1a 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -6,12 +6,15 @@ import ( "math/big" + "cosmossdk.io/math" 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" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -397,6 +400,65 @@ func allowStaking(t *testing.T, ts testSuite, amount *big.Int) { require.True(t, allowed) } +func stakeThroughCosmosAPI( + t *testing.T, + ctx sdk.Context, + bankKeeper bankkeeper.Keeper, + stakingKeeper stakingkeeper.Keeper, + validator stakingtypes.Validator, + staker sdk.AccAddress, + amount math.Int, +) { + // Coins to stake with default cosmos denom. + coins := sdk.NewCoins(sdk.NewCoin("stake", amount)) + + err := bankKeeper.MintCoins(ctx, fungibletypes.ModuleName, coins) + require.NoError(t, err) + + err = bankKeeper.SendCoinsFromModuleToAccount(ctx, fungibletypes.ModuleName, staker, coins) + require.NoError(t, err) + + shares, err := stakingKeeper.Delegate( + ctx, + staker, + coins.AmountOf(coins.Denoms()[0]), + validator.Status, + validator, + true, + ) + require.NoError(t, err) + require.Equal(t, amount.Uint64(), shares.TruncateInt().Uint64()) +} + +func distributeZRC20( + t *testing.T, + s testSuite, + amount *big.Int, +) { + distributeMethod := s.stkContractABI.Methods[DistributeMethodName] + + _, err := s.fungibleKeeper.DepositZRC20(s.ctx, s.zrc20Address, s.defaultCaller, amount) + require.NoError(t, err) + allowStaking(t, s, amount) + + // Setup method input. + s.mockVMContract.Input = packInputArgs( + t, + distributeMethod, + []interface{}{s.zrc20Address, amount}..., + ) + + // Call distribute method. + success, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.NoError(t, err) + + res, err := distributeMethod.Outputs.Unpack(success) + require.NoError(t, err) + + ok := res[0].(bool) + require.True(t, ok) +} + func callEVM( t *testing.T, ctx sdk.Context, From 125a645c0694d536f8e3e5508c9c0ef91676e31c Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Wed, 13 Nov 2024 23:09:03 +0100 Subject: [PATCH 06/20] test: zetaTxServer modifications --- cmd/zetae2e/local/local.go | 25 ++++++---- cmd/zetae2e/local/precompiles.go | 1 + e2e/e2etests/test_precompiles_distribute.go | 49 ++++++++++++++++--- precompiles/staking/method_get_rewards.go | 15 +++--- .../staking/method_get_rewards_test.go | 17 +++---- precompiles/staking/method_get_validators.go | 4 +- 6 files changed, 77 insertions(+), 34 deletions(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 7cde1a92ad..f5e299b98a 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -11,6 +11,7 @@ import ( "github.com/spf13/cobra" "golang.org/x/sync/errgroup" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/node/app" zetae2econfig "github.com/zeta-chain/node/cmd/zetae2e/config" "github.com/zeta-chain/node/e2e/config" @@ -160,11 +161,17 @@ func localE2ETest(cmd *cobra.Command, _ []string) { zetaTxServer, err := txserver.NewZetaTxServer( conf.RPCs.ZetaCoreRPC, - []string{utils.EmergencyPolicyName, utils.OperationalPolicyName, utils.AdminPolicyName}, + []string{ + utils.EmergencyPolicyName, + utils.OperationalPolicyName, + utils.AdminPolicyName, + sdk.AccAddress(conf.AdditionalAccounts.UserPrecompile.EVMAddress().Bytes()).String(), + }, []string{ conf.PolicyAccounts.EmergencyPolicyAccount.RawPrivateKey.String(), conf.PolicyAccounts.OperationalPolicyAccount.RawPrivateKey.String(), conf.PolicyAccounts.AdminPolicyAccount.RawPrivateKey.String(), + conf.AdditionalAccounts.UserPrecompile.RawPrivateKey.String(), }, conf.ZetaChainID, ) @@ -332,14 +339,14 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if !skipPrecompiles { precompiledContractTests = []string{ - // e2etests.TestPrecompilesPrototypeName, - // e2etests.TestPrecompilesPrototypeThroughContractName, - // e2etests.TestPrecompilesStakingName, - // // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. - // // e2etests.TestPrecompilesStakingThroughContractName, - // e2etests.TestPrecompilesBankName, - // e2etests.TestPrecompilesBankFailName, - // e2etests.TestPrecompilesBankThroughContractName, + e2etests.TestPrecompilesPrototypeName, + e2etests.TestPrecompilesPrototypeThroughContractName, + e2etests.TestPrecompilesStakingName, + // Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005. + // e2etests.TestPrecompilesStakingThroughContractName, + e2etests.TestPrecompilesBankName, + e2etests.TestPrecompilesBankFailName, + e2etests.TestPrecompilesBankThroughContractName, e2etests.TestPrecompilesDistributeName, e2etests.TestPrecompilesDistributeNonZRC20Name, e2etests.TestPrecompilesDistributeThroughContractName, diff --git a/cmd/zetae2e/local/precompiles.go b/cmd/zetae2e/local/precompiles.go index 46668c9f44..97c1ff4230 100644 --- a/cmd/zetae2e/local/precompiles.go +++ b/cmd/zetae2e/local/precompiles.go @@ -27,6 +27,7 @@ func statefulPrecompilesTestRoutine( deployerRunner, account, runner.NewLogger(verbose, color.FgRed, "precompiles"), + runner.WithZetaTxServer(deployerRunner.ZetaTxServer), ) if err != nil { return err diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index 7ee3df568d..1f8a02cf67 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -1,6 +1,7 @@ package e2etests import ( + "fmt" "math/big" "cosmossdk.io/math" @@ -50,6 +51,39 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { dstrContract, err := staking.NewIStaking(distributeContractAddress, r.ZEVMClient) require.NoError(r, err, "failed to create distribute contract caller") + // Retrieve the list of validators. + validators, err := dstrContract.GetAllValidators(&bind.CallOpts{}) + require.NoError(r, err) + require.GreaterOrEqual(r, len(validators), 2) + + // Save first validators as it will be used through the test. + validator := validators[0] + valAddr, err := sdk.ValAddressFromBech32(validators[0].OperatorAddress) + require.NoError(r, err) + + // There is no delegation, so the response should be empty. + dv, err := dstrContract.GetDelegatorValidators(&bind.CallOpts{}, spenderAddress) + require.NoError(r, err) + require.Empty(r, dv, "DelegatorValidators response should be empty") + + // Shares at this point should be 0. + sharesBeforeVal1, err := dstrContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validator.OperatorAddress) + require.NoError(r, err) + require.Equal(r, int64(0), sharesBeforeVal1.Int64()) + + // Stake with spender so it's registered as a delegator. + err = stakeThroughCosmosAPI(r, valAddr, spenderAddress, "azeta", big.NewInt(1)) + require.NoError(r, err) + + // check shares are set to 1. + sharesAfterVal1, err := dstrContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validator.OperatorAddress) + require.NoError(r, err) + require.Equal(r, big.NewInt(1e18).String(), sharesAfterVal1.String()) + + dv, err = dstrContract.GetDelegatorValidators(&bind.CallOpts{}, spenderAddress) + require.NoError(r, err) + require.Contains(r, dv, validator.OperatorAddress, "DelegatorValidators response should include validator address") + // Check initial balances. balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) @@ -113,14 +147,17 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { r.WaitForBlocks(1) balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) - validators, err := dstrContract.GetAllValidators(&bind.CallOpts{}) - require.NoError(r, err) - require.GreaterOrEqual(r, len(validators), 2) + rewards, _ := dstrContract.GetRewards(&bind.CallOpts{}, spenderAddress, validator.OperatorAddress) + fmt.Println("rewards: ", rewards) - err = stakeThroughCosmosAPI(r, sdk.ValAddress(validators[0].OperatorAddress), spenderAddress, "azeta", oneThousand) + fmt.Println("before: ", checkZRC20Balance(r, spenderAddress)) + + tx, err = dstrContract.ClaimRewards(r.ZEVMAuth, spenderAddress, validator.OperatorAddress) require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "claim rewards should succeed") - r.WaitForBlocks(10) + fmt.Println("after: ", checkZRC20Balance(r, spenderAddress)) } func TestPrecompilesDistributeNonZRC20(r *runner.E2ERunner, args []string) { @@ -188,7 +225,7 @@ func stakeThroughCosmosAPI( }, ) - _, err := r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msg) + _, err := r.ZetaTxServer.BroadcastTx(sdk.AccAddress(staker.Bytes()).String(), msg) if err != nil { return err } diff --git a/precompiles/staking/method_get_rewards.go b/precompiles/staking/method_get_rewards.go index 52ababffde..92337d41ea 100644 --- a/precompiles/staking/method_get_rewards.go +++ b/precompiles/staking/method_get_rewards.go @@ -3,7 +3,7 @@ package staking import ( sdk "github.com/cosmos/cosmos-sdk/types" distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" - dstrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -37,7 +37,7 @@ func (c *Contract) getRewards( // Query the delegation rewards through the distribution keeper querier. dstrQuerier := distrkeeper.NewQuerier(c.distributionKeeper) - res, err := dstrQuerier.DelegationRewards(ctx, &dstrtypes.QueryDelegationRewardsRequest{ + res, err := dstrQuerier.DelegationRewards(ctx, &distrtypes.QueryDelegationRewardsRequest{ DelegatorAddress: delegatorCosmosAddr.String(), ValidatorAddress: validatorAddr, }) @@ -56,14 +56,15 @@ func (c *Contract) getRewards( } } - zrc20Coins := make([]sdk.DecCoin, 0) + rewards := make([]DecCoin, 0) for _, coin := range coins { - if precompiletypes.CoinIsZRC20(coin.Denom) { - zrc20Coins = append(zrc20Coins, coin) - } + rewards = append(rewards, DecCoin{ + Denom: coin.Denom, + Amount: coin.Amount.BigInt(), + }) } - return method.Outputs.Pack(zrc20Coins) + return method.Outputs.Pack(rewards) } func unpackGetRewardsArgs(args []interface{}) (delegator common.Address, validator string, err error) { diff --git a/precompiles/staking/method_get_rewards_test.go b/precompiles/staking/method_get_rewards_test.go index 75d9c41afb..e40c5a4453 100644 --- a/precompiles/staking/method_get_rewards_test.go +++ b/precompiles/staking/method_get_rewards_test.go @@ -1,7 +1,6 @@ package staking import ( - "fmt" "math/big" "math/rand" "testing" @@ -11,7 +10,6 @@ import ( "github.com/stretchr/testify/require" precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/testutil/sample" - "github.com/zeta-chain/node/x/emissions" ) func Test_GetRewards(t *testing.T) { @@ -72,15 +70,14 @@ func Test_GetRewards(t *testing.T) { AfterDelegationModified(s.ctx, stakerCosmosAddr, validator.GetOperator()) require.NoError(t, err) - // DEBUG CALL - del := s.sdkKeepers.StakingKeeper.GetAllDelegations(s.ctx) - fmt.Println(del) - /* Distribute 1000 ZRC20 tokens to the staking contract */ distributeZRC20(t, s, big.NewInt(1000)) - emissions.BeginBlocker(s.ctx, *s.sdkKeepers.EmissionsKeeper) - s.ctx = s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 1) + // TODO: Simulate a distribution of rewards. + // emissions.BeginBlocker(s.ctx, *s.sdkKeepers.EmissionsKeeper) + // staking.BeginBlocker(s.ctx, &s.sdkKeepers.StakingKeeper) + // distribution.BeginBlocker(s.ctx, abci.RequestBeginBlock{}, s.sdkKeepers.DistributionKeeper) + // s.ctx = s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 1) /* ACT */ // Call getRewards. @@ -96,8 +93,8 @@ func Test_GetRewards(t *testing.T) { require.NoError(t, err) /* ASSERT */ - res, err := getRewardsMethod.Outputs.Unpack(bytes) + _, err = getRewardsMethod.Outputs.Unpack(bytes) require.NoError(t, err) - fmt.Println(res) + //fmt.Println("getRewards response: ", res) }) } diff --git a/precompiles/staking/method_get_validators.go b/precompiles/staking/method_get_validators.go index fbf133ffb9..b4633872e4 100644 --- a/precompiles/staking/method_get_validators.go +++ b/precompiles/staking/method_get_validators.go @@ -3,7 +3,7 @@ package staking import ( sdk "github.com/cosmos/cosmos-sdk/types" distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" - dstrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -37,7 +37,7 @@ func (c *Contract) getDelegatorValidators( // Query the validator list of the given delegator. dstrQuerier := distrkeeper.NewQuerier(c.distributionKeeper) - res, err := dstrQuerier.DelegatorValidators(ctx, &dstrtypes.QueryDelegatorValidatorsRequest{ + res, err := dstrQuerier.DelegatorValidators(ctx, &distrtypes.QueryDelegatorValidatorsRequest{ DelegatorAddress: delegatorCosmosAddr.String(), }) if err != nil { From af1360fe80a6dcf62ada86d1cbd355a9d1f8617b Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Fri, 15 Nov 2024 18:08:16 +0100 Subject: [PATCH 07/20] e2e: finish distribute test --- cmd/zetae2e/local/local.go | 5 +- cmd/zetae2e/local/precompiles.go | 24 ++- .../test_precompiles_bank_through_contract.go | 63 +++--- e2e/e2etests/test_precompiles_distribute.go | 189 +++++++++++------- ...precompiles_distribute_through_contract.go | 37 ++-- precompiles/staking/method_get_rewards.go | 17 +- 6 files changed, 210 insertions(+), 125 deletions(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index f5e299b98a..87034a34c5 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -11,7 +11,6 @@ import ( "github.com/spf13/cobra" "golang.org/x/sync/errgroup" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/node/app" zetae2econfig "github.com/zeta-chain/node/cmd/zetae2e/config" "github.com/zeta-chain/node/e2e/config" @@ -165,13 +164,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { utils.EmergencyPolicyName, utils.OperationalPolicyName, utils.AdminPolicyName, - sdk.AccAddress(conf.AdditionalAccounts.UserPrecompile.EVMAddress().Bytes()).String(), }, []string{ conf.PolicyAccounts.EmergencyPolicyAccount.RawPrivateKey.String(), conf.PolicyAccounts.OperationalPolicyAccount.RawPrivateKey.String(), conf.PolicyAccounts.AdminPolicyAccount.RawPrivateKey.String(), - conf.AdditionalAccounts.UserPrecompile.RawPrivateKey.String(), }, conf.ZetaChainID, ) @@ -238,7 +235,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } noError(deployerRunner.FundEmissionsPool()) - deployerRunner.MintERC20OnEvm(1000000) + deployerRunner.MintERC20OnEvm(1e10) logger.Print("✅ setup completed in %s", time.Since(startTime)) } diff --git a/cmd/zetae2e/local/precompiles.go b/cmd/zetae2e/local/precompiles.go index 97c1ff4230..090a51da4d 100644 --- a/cmd/zetae2e/local/precompiles.go +++ b/cmd/zetae2e/local/precompiles.go @@ -4,11 +4,13 @@ import ( "fmt" "time" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/fatih/color" "github.com/zeta-chain/node/e2e/config" "github.com/zeta-chain/node/e2e/e2etests" "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/txserver" ) // statefulPrecompilesTestRoutine runs steateful precompiles related e2e tests @@ -27,17 +29,35 @@ func statefulPrecompilesTestRoutine( deployerRunner, account, runner.NewLogger(verbose, color.FgRed, "precompiles"), - runner.WithZetaTxServer(deployerRunner.ZetaTxServer), + //runner.WithZetaTxServer(deployerRunner.ZetaTxServer), ) if err != nil { return err } + // Initialize a ZetaTxServer with the precompile user account. + // It's needed to send messages on behalf of the precompile user. + zetaTxServer, err := txserver.NewZetaTxServer( + conf.RPCs.ZetaCoreRPC, + []string{ + sdk.AccAddress(conf.AdditionalAccounts.UserPrecompile.EVMAddress().Bytes()).String(), + }, + []string{ + conf.AdditionalAccounts.UserPrecompile.RawPrivateKey.String(), + }, + conf.ZetaChainID, + ) + if err != nil { + return err + } + + precompileRunner.ZetaTxServer = zetaTxServer + precompileRunner.Logger.Print("🏃 starting stateful precompiled contracts tests") startTime := time.Now() // Send ERC20 that will be depositted into ERC20ZRC20 tokens. - txERC20Send := deployerRunner.SendERC20OnEvm(account.EVMAddress(), 10000) + txERC20Send := deployerRunner.SendERC20OnEvm(account.EVMAddress(), 1e7) precompileRunner.WaitForTxReceiptOnEvm(txERC20Send) testsToRun, err := precompileRunner.GetE2ETestsToRunByName( diff --git a/e2e/e2etests/test_precompiles_bank_through_contract.go b/e2e/e2etests/test_precompiles_bank_through_contract.go index 4baa6fefb5..18532f2bd6 100644 --- a/e2e/e2etests/test_precompiles_bank_through_contract.go +++ b/e2e/e2etests/test_precompiles_bank_through_contract.go @@ -17,13 +17,16 @@ import ( func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { require.Len(r, args, 0, "No arguments expected") - spender := r.EVMAddress() - bankAddress := bank.ContractAddress - zrc20Address := r.ERC20ZRC20Addr - oneThousand := big.NewInt(1e3) - oneThousandOne := big.NewInt(1001) - fiveHundred := big.NewInt(500) - fiveHundredOne := big.NewInt(501) + var ( + spender = r.EVMAddress() + bankAddress = bank.ContractAddress + zrc20Address = r.ERC20ZRC20Addr + oneThousand = big.NewInt(1e3) + oneThousandOne = big.NewInt(1001) + fiveHundred = big.NewInt(500) + fiveHundredOne = big.NewInt(501) + zero = big.NewInt(0) + ) // Get ERC20ZRC20. txHash := r.DepositERC20WithAmountAndMessage(r.EVMAddress(), oneThousand, []byte{}) @@ -58,18 +61,18 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { }() // Check initial balances. - balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) - balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) - balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) + balanceShouldBe(r, zero, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spender)) + balanceShouldBe(r, zero, checkZRC20Balance(r, bankAddress)) // Deposit without previous alllowance should fail. receipt = depositThroughTestBank(r, testBank, zrc20Address, oneThousand) utils.RequiredTxFailed(r, receipt, "Deposit ERC20ZRC20 without allowance should fail") // Check balances, should be the same. - balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) - balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) - balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) + balanceShouldBe(r, zero, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spender)) + balanceShouldBe(r, zero, checkZRC20Balance(r, bankAddress)) // Allow 500 ZRC20 to bank precompile. approveAllowance(r, bankAddress, fiveHundred) @@ -80,9 +83,9 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequiredTxFailed(r, receipt, "Depositting an amount higher than allowed should fail") // Balances shouldn't change. - balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) - balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) - balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) + balanceShouldBe(r, zero, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spender)) + balanceShouldBe(r, zero, checkZRC20Balance(r, bankAddress)) // Allow 1000 ZRC20 to bank precompile. approveAllowance(r, bankAddress, oneThousand) @@ -93,18 +96,18 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequiredTxFailed(r, receipt, "Depositting an amount higher than balance should fail") // Balances shouldn't change. - balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) - balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) - balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) + balanceShouldBe(r, zero, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spender)) + balanceShouldBe(r, zero, checkZRC20Balance(r, bankAddress)) // Deposit 500 ERC20ZRC20 tokens to the bank contract, it's within allowance and balance. Should pass. receipt = depositThroughTestBank(r, testBank, zrc20Address, fiveHundred) utils.RequireTxSuccessful(r, receipt, "Depositting a correct amount should pass") // Balances should be transferred. Bank now locks 500 ZRC20 tokens. - balanceShouldBe(r, 500, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) - balanceShouldBe(r, 500, checkZRC20Balance(r, spender)) - balanceShouldBe(r, 500, checkZRC20Balance(r, bankAddress)) + balanceShouldBe(r, fiveHundred, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, spender)) + balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, bankAddress)) // Check the deposit event. eventDeposit, err := bankPrecompileCaller.ParseDeposit(*receipt.Logs[0]) @@ -118,18 +121,18 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) { utils.RequiredTxFailed(r, receipt, "Withdrawing an amount higher than balance should fail") // Balances shouldn't change. - balanceShouldBe(r, 500, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) - balanceShouldBe(r, 500, checkZRC20Balance(r, spender)) - balanceShouldBe(r, 500, checkZRC20Balance(r, bankAddress)) + balanceShouldBe(r, fiveHundred, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, spender)) + balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, bankAddress)) // Try to withdraw 500 ERC20ZRC20 tokens. Should pass. receipt = withdrawThroughTestBank(r, testBank, zrc20Address, fiveHundred) utils.RequireTxSuccessful(r, receipt, "Withdraw correct amount should pass") // Balances should be reverted to initial state. - balanceShouldBe(r, 0, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) - balanceShouldBe(r, 1000, checkZRC20Balance(r, spender)) - balanceShouldBe(r, 0, checkZRC20Balance(r, bankAddress)) + balanceShouldBe(r, zero, checkCosmosBalanceThroughBank(r, testBank, zrc20Address, spender)) + balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spender)) + balanceShouldBe(r, zero, checkZRC20Balance(r, bankAddress)) // Check the withdraw event. eventWithdraw, err := bankPrecompileCaller.ParseWithdraw(*receipt.Logs[0]) @@ -146,8 +149,8 @@ func approveAllowance(r *runner.E2ERunner, target common.Address, amount *big.In utils.RequireTxSuccessful(r, receipt, "Approve ERC20ZRC20 allowance tx failed") } -func balanceShouldBe(r *runner.E2ERunner, expected uint64, balance *big.Int) { - require.Equal(r, expected, balance.Uint64(), "Balance should be %d, got: %d", expected, balance.Uint64()) +func balanceShouldBe(r *runner.E2ERunner, expected *big.Int, balance *big.Int) { + require.Equal(r, expected.Uint64(), balance.Uint64(), "Balance should be %d, got: %d", expected, balance.Uint64()) } func checkZRC20Balance(r *runner.E2ERunner, target common.Address) *big.Int { diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index 1f8a02cf67..ed9eb3ecb8 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -1,8 +1,8 @@ package e2etests import ( - "fmt" "math/big" + "strings" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/cmd/zetacored/config" "github.com/zeta-chain/node/e2e/runner" "github.com/zeta-chain/node/e2e/utils" "github.com/zeta-chain/node/precompiles/bank" @@ -23,141 +24,175 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { require.Len(r, args, 0, "No arguments expected") var ( - spenderAddress = r.EVMAddress() - distributeContractAddress = staking.ContractAddress - lockerAddress = bank.ContractAddress + // Addresses. + staker = r.EVMAddress() + distrContractAddress = staking.ContractAddress + lockerAddress = bank.ContractAddress - zrc20Address = r.ERC20ZRC20Addr - zrc20Denom = precompiletypes.ZRC20ToCosmosDenom(zrc20Address) + // Stake amount. + stakeAmt = new(big.Int) - oneThousand = big.NewInt(1e3) - oneThousandOne = big.NewInt(1001) - fiveHundred = big.NewInt(500) - fiveHundredOne = big.NewInt(501) + // ZRC20 distribution. + zrc20Address = r.ERC20ZRC20Addr + zrc20Denom = precompiletypes.ZRC20ToCosmosDenom(zrc20Address) + zrc20DistrAmt = big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1e6)) + + // Amounts to test with. + higherThanBalance = big.NewInt(0).Add(zrc20DistrAmt, big.NewInt(1)) + fiveHundred = big.NewInt(500) + fiveHundredOne = big.NewInt(501) + zero = big.NewInt(0) previousGasLimit = r.ZEVMAuth.GasLimit ) + // stakeAmt has to be as big as the validator self delegation. + // This way the rewards will be distributed 50%. + _, ok := stakeAmt.SetString("1000000000000000000000", 10) + require.True(r, ok) + // Set new gas limit to avoid out of gas errors. r.ZEVMAuth.GasLimit = 10_000_000 - // Set the test to reset the state after it finishes. - defer resetDistributionTest(r, lockerAddress, previousGasLimit) - - // Get ERC20ZRC20. - txHash := r.DepositERC20WithAmountAndMessage(spenderAddress, oneThousand, []byte{}) - utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) - - dstrContract, err := staking.NewIStaking(distributeContractAddress, r.ZEVMClient) + distrContract, err := staking.NewIStaking(distrContractAddress, r.ZEVMClient) require.NoError(r, err, "failed to create distribute contract caller") // Retrieve the list of validators. - validators, err := dstrContract.GetAllValidators(&bind.CallOpts{}) + validators, err := distrContract.GetAllValidators(&bind.CallOpts{}) require.NoError(r, err) require.GreaterOrEqual(r, len(validators), 2) // Save first validators as it will be used through the test. validator := validators[0] - valAddr, err := sdk.ValAddressFromBech32(validators[0].OperatorAddress) + validatorAddr, err := sdk.ValAddressFromBech32(validators[0].OperatorAddress) require.NoError(r, err) + // Get ERC20ZRC20. + txHash := r.DepositERC20WithAmountAndMessage(staker, zrc20DistrAmt, []byte{}) + utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + // There is no delegation, so the response should be empty. - dv, err := dstrContract.GetDelegatorValidators(&bind.CallOpts{}, spenderAddress) + dv, err := distrContract.GetDelegatorValidators(&bind.CallOpts{}, staker) require.NoError(r, err) require.Empty(r, dv, "DelegatorValidators response should be empty") // Shares at this point should be 0. - sharesBeforeVal1, err := dstrContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validator.OperatorAddress) + sharesBefore, err := distrContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validator.OperatorAddress) require.NoError(r, err) - require.Equal(r, int64(0), sharesBeforeVal1.Int64()) + require.Equal(r, int64(0), sharesBefore.Int64(), "shares should be 0 when there are no delegations") - // Stake with spender so it's registered as a delegator. - err = stakeThroughCosmosAPI(r, valAddr, spenderAddress, "azeta", big.NewInt(1)) + // There should be no rewards. + rewards, err := distrContract.GetRewards(&bind.CallOpts{}, staker, validator.OperatorAddress) require.NoError(r, err) + require.Empty(r, rewards, "rewards should be empty when there are no delegations") - // check shares are set to 1. - sharesAfterVal1, err := dstrContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validator.OperatorAddress) + // Stake with spender so it's registered as a delegator. + err = stakeThroughCosmosAPI(r, validatorAddr, staker, stakeAmt) require.NoError(r, err) - require.Equal(r, big.NewInt(1e18).String(), sharesAfterVal1.String()) - - dv, err = dstrContract.GetDelegatorValidators(&bind.CallOpts{}, spenderAddress) - require.NoError(r, err) - require.Contains(r, dv, validator.OperatorAddress, "DelegatorValidators response should include validator address") // Check initial balances. - balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) - balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, zrc20DistrAmt, checkZRC20Balance(r, staker)) + balanceShouldBe(r, zero, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) - tx, err := dstrContract.Distribute(r.ZEVMAuth, zrc20Address, oneThousand) + // Failed attempt! + tx, err := distrContract.Distribute(r.ZEVMAuth, zrc20Address, zrc20DistrAmt) require.NoError(r, err) receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequiredTxFailed(r, receipt, "distribute should fail when there's no allowance") // Balances shouldn't change after a failed attempt. - balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) - balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, zrc20DistrAmt, checkZRC20Balance(r, staker)) + balanceShouldBe(r, zero, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Allow 500. - approveAllowance(r, distributeContractAddress, fiveHundred) + approveAllowance(r, distrContractAddress, fiveHundred) - // Shouldn't be able to distribute more than allowed. - tx, err = dstrContract.Distribute(r.ZEVMAuth, zrc20Address, fiveHundredOne) + // Failed attempt! Shouldn't be able to distribute more than allowed. + tx, err = distrContract.Distribute(r.ZEVMAuth, zrc20Address, fiveHundredOne) require.NoError(r, err) receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than allowed") // Balances shouldn't change after a failed attempt. - balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) - balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, zrc20DistrAmt, checkZRC20Balance(r, staker)) + balanceShouldBe(r, zero, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) - // Raise the allowance to 1000. - approveAllowance(r, distributeContractAddress, oneThousand) + // Raise the allowance to the maximum ZRC20 amount. + approveAllowance(r, distrContractAddress, zrc20DistrAmt) - // Shouldn't be able to distribute more than owned balance. - tx, err = dstrContract.Distribute(r.ZEVMAuth, zrc20Address, oneThousandOne) + // Failed attempt! Shouldn't be able to distribute more than owned balance. + tx, err = distrContract.Distribute(r.ZEVMAuth, zrc20Address, higherThanBalance) require.NoError(r, err) receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than owned balance") // Balances shouldn't change after a failed attempt. - balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, 0, checkZRC20Balance(r, lockerAddress)) - balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, zrc20DistrAmt, checkZRC20Balance(r, staker)) + balanceShouldBe(r, zero, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) - // Should be able to distribute 500, which is within balance and allowance. - tx, err = dstrContract.Distribute(r.ZEVMAuth, zrc20Address, fiveHundred) + // Should be able to distribute an amount which is within balance and allowance. + tx, err = distrContract.Distribute(r.ZEVMAuth, zrc20Address, zrc20DistrAmt) require.NoError(r, err) receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequireTxSuccessful(r, receipt, "distribute should succeed when distributing within balance and allowance") - balanceShouldBe(r, 500, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) - balanceShouldBe(r, 500, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, zero, checkZRC20Balance(r, staker)) + balanceShouldBe(r, zrc20DistrAmt, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, zrc20DistrAmt, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) - eventDitributed, err := dstrContract.ParseDistributed(*receipt.Logs[0]) + eventDitributed, err := distrContract.ParseDistributed(*receipt.Logs[0]) require.NoError(r, err) require.Equal(r, zrc20Address, eventDitributed.Zrc20Token) - require.Equal(r, spenderAddress, eventDitributed.Zrc20Distributor) - require.Equal(r, fiveHundred.Uint64(), eventDitributed.Amount.Uint64()) + require.Equal(r, staker, eventDitributed.Zrc20Distributor) + require.Equal(r, zrc20DistrAmt.Uint64(), eventDitributed.Amount.Uint64()) // After one block the rewards should have been distributed and fee collector should have 0 ZRC20 balance. r.WaitForBlocks(1) - balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) - rewards, _ := dstrContract.GetRewards(&bind.CallOpts{}, spenderAddress, validator.OperatorAddress) - fmt.Println("rewards: ", rewards) + // DelegatorValidators returns the list of validator this delegator has delegated to. + // The result should include the validator address. + dv, err = distrContract.GetDelegatorValidators(&bind.CallOpts{}, staker) + require.NoError(r, err) + require.Contains(r, dv, validator.OperatorAddress, "DelegatorValidators response should include validator address") - fmt.Println("before: ", checkZRC20Balance(r, spenderAddress)) + // Get rewards and check it contains zrc20 tokens. + rewards, err = distrContract.GetRewards(&bind.CallOpts{}, staker, validator.OperatorAddress) + require.NoError(r, err) + require.GreaterOrEqual(r, len(rewards), 2) + found := false + for _, coin := range rewards { + if strings.Contains(coin.Denom, config.ZRC20DenomPrefix) { + found = true + break + } + } + require.True(r, found, "rewards should include the ZRC20 token") - tx, err = dstrContract.ClaimRewards(r.ZEVMAuth, spenderAddress, validator.OperatorAddress) + // Claim the rewards, they'll be unlocked as ZRC20 tokens. + tx, err = distrContract.ClaimRewards(r.ZEVMAuth, staker, validator.OperatorAddress) require.NoError(r, err) receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequireTxSuccessful(r, receipt, "claim rewards should succeed") - fmt.Println("after: ", checkZRC20Balance(r, spenderAddress)) + // Before claiming rewards the ZRC20 balance is 0. After claiming rewards the ZRC20 balance should be 14239697290875601808. + // Which is the amount of ZRC20 distributed, divided by two validators, and subtracted the commissions. + zrc20RewardsAmt, ok := big.NewInt(0).SetString("14239697290875601808", 10) + require.True(r, ok) + balanceShouldBe(r, zrc20RewardsAmt, checkZRC20Balance(r, staker)) + + // Locker final balance should be zrc20Disitributed - zrc20RewardsAmt. + lockerFinalBalance := big.NewInt(0).Sub(zrc20DistrAmt, zrc20RewardsAmt) + balanceShouldBe(r, lockerFinalBalance, checkZRC20Balance(r, lockerAddress)) + + // Set the test to reset the state after it finishes. + sharesAfter, err := distrContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validator.OperatorAddress) + require.NoError(r, err) + resetDistributionTest(r, lockerAddress, previousGasLimit, staker, validatorAddr, sharesAfter) } func TestPrecompilesDistributeNonZRC20(r *runner.E2ERunner, args []string) { @@ -213,14 +248,13 @@ func stakeThroughCosmosAPI( r *runner.E2ERunner, validator sdk.ValAddress, staker common.Address, - denom string, amount *big.Int, ) error { msg := stakingtypes.NewMsgDelegate( sdk.AccAddress(staker.Bytes()), validator, sdk.Coin{ - Denom: denom, + Denom: "azeta", Amount: math.NewIntFromBigInt(amount), }, ) @@ -237,7 +271,11 @@ func resetDistributionTest( r *runner.E2ERunner, lockerAddress common.Address, previousGasLimit uint64, + staker common.Address, + validator sdk.ValAddress, + amount *big.Int, ) { + // Restore the gas limit. r.ZEVMAuth.GasLimit = previousGasLimit // Reset the allowance to 0; this is needed when running upgrade tests where this test runs twice. @@ -250,6 +288,7 @@ func resetDistributionTest( balance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) require.NoError(r, err) + // Burn all ERC20 balance. tx, err = r.ERC20ZRC20.Transfer( r.ZEVMAuth, common.HexToAddress("0x000000000000000000000000000000000000dEaD"), @@ -258,4 +297,18 @@ func resetDistributionTest( require.NoError(r, err) receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequireTxSuccessful(r, receipt, "Resetting balance failed") + + // Clean the delegation. + // Delegator will always delegate on the first validator. + msg := stakingtypes.NewMsgUndelegate( + sdk.AccAddress(staker.Bytes()), + validator, + sdk.Coin{ + Denom: "azeta", + Amount: math.NewIntFromBigInt(amount), + }, + ) + + _, err = r.ZetaTxServer.BroadcastTx(sdk.AccAddress(staker.Bytes()).String(), msg) + require.NoError(r, err) } diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_through_contract.go index c9f6a8bb8c..bc4e756bb9 100644 --- a/e2e/e2etests/test_precompiles_distribute_through_contract.go +++ b/e2e/e2etests/test_precompiles_distribute_through_contract.go @@ -30,8 +30,9 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string oneThousandOne = big.NewInt(1001) fiveHundred = big.NewInt(500) fiveHundredOne = big.NewInt(501) + zero = big.NewInt(0) - previousGasLimit = r.ZEVMAuth.GasLimit + //previousGasLimit = r.ZEVMAuth.GasLimit ) // Get ERC20ZRC20. @@ -50,20 +51,20 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string r.ZEVMAuth.GasLimit = 10_000_000 // Set the test to reset the state after it finishes. - defer resetDistributionTest(r, lockerAddress, previousGasLimit) + //defer resetDistributionTest(r, lockerAddress, previousGasLimit) // Check initial balances. - balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. - balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) receipt = distributeThroughContract(r, testDstrContract, zrc20Address, oneThousand) utils.RequiredTxFailed(r, receipt, "distribute should fail when there's no allowance") // Balances shouldn't change after a failed attempt. - balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. - balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Allow 500. approveAllowance(r, distributeContractAddress, fiveHundred) @@ -72,9 +73,9 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than allowed") // Balances shouldn't change after a failed attempt. - balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. - balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Raise the allowance to 1000. approveAllowance(r, distributeContractAddress, oneThousand) @@ -84,17 +85,17 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than owned balance") // Balances shouldn't change after a failed attempt. - balanceShouldBe(r, 1000, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, 500, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. - balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Should be able to distribute 500, which is within balance and allowance. receipt = distributeThroughContract(r, testDstrContract, zrc20Address, fiveHundred) utils.RequireTxSuccessful(r, receipt, "distribute should succeed when distributing within balance and allowance") - balanceShouldBe(r, 500, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, 1000, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. - balanceShouldBe(r, 500, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, spenderAddress)) + balanceShouldBe(r, oneThousand, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, fiveHundred, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) eventDitributed, err := dstrContract.ParseDistributed(*receipt.Logs[0]) require.NoError(r, err) @@ -104,7 +105,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string // After one block the rewards should have been distributed and fee collector should have 0 ZRC20 balance. r.WaitForBlocks(1) - balanceShouldBe(r, 0, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) } func distributeThroughContract( diff --git a/precompiles/staking/method_get_rewards.go b/precompiles/staking/method_get_rewards.go index 92337d41ea..8cbb39865b 100644 --- a/precompiles/staking/method_get_rewards.go +++ b/precompiles/staking/method_get_rewards.go @@ -1,6 +1,8 @@ package staking import ( + "errors" + sdk "github.com/cosmos/cosmos-sdk/types" distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" @@ -41,10 +43,19 @@ func (c *Contract) getRewards( DelegatorAddress: delegatorCosmosAddr.String(), ValidatorAddress: validatorAddr, }) + + // DelegationRewards returns an error if the delegation does not exist. + // In this case, simply return an empty list of rewards, so external contracts + // can process this case without failing. if err != nil { - return nil, precompiletypes.ErrUnexpected{ - When: "DelegationRewards", - Got: err.Error(), + if errors.Is(err, distrtypes.ErrNoDelegationExists) { + rewards := make([]DecCoin, 0) + return method.Outputs.Pack(rewards) + } else { + return nil, &precompiletypes.ErrUnexpected{ + When: "DelegationRewards", + Got: err.Error(), + } } } From 3502cea0900765d5e518bc9d1fe41431138bbad2 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Sat, 16 Nov 2024 00:46:13 +0100 Subject: [PATCH 08/20] e2e: distribute through contract --- cmd/zetae2e/local/local.go | 26 +++++++++---------- cmd/zetae2e/local/precompiles.go | 1 - .../testdistribute/TestDistribute.sol | 19 ++++++++++++++ e2e/e2etests/e2etests.go | 4 +-- e2e/e2etests/test_precompiles_distribute.go | 8 +++++- ...precompiles_distribute_through_contract.go | 25 ++++++++++-------- 6 files changed, 55 insertions(+), 28 deletions(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 87034a34c5..273225fe2c 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -108,14 +108,14 @@ func localE2ETest(cmd *cobra.Command, _ []string) { light = must(cmd.Flags().GetBool(flagLight)) setupOnly = must(cmd.Flags().GetBool(flagSetupOnly)) skipSetup = must(cmd.Flags().GetBool(flagSkipSetup)) - skipBitcoinSetup = must(cmd.Flags().GetBool(flagSkipBitcoinSetup)) - skipHeaderProof = must(cmd.Flags().GetBool(flagSkipHeaderProof)) - skipTrackerCheck = must(cmd.Flags().GetBool(flagSkipTrackerCheck)) - testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration)) - testV2 = must(cmd.Flags().GetBool(flagTestV2)) - testV2Migration = must(cmd.Flags().GetBool(flagTestV2Migration)) - skipPrecompiles = must(cmd.Flags().GetBool(flagSkipPrecompiles)) - upgradeContracts = must(cmd.Flags().GetBool(flagUpgradeContracts)) + //skipBitcoinSetup = must(cmd.Flags().GetBool(flagSkipBitcoinSetup)) + skipHeaderProof = must(cmd.Flags().GetBool(flagSkipHeaderProof)) + skipTrackerCheck = must(cmd.Flags().GetBool(flagSkipTrackerCheck)) + testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration)) + testV2 = must(cmd.Flags().GetBool(flagTestV2)) + testV2Migration = must(cmd.Flags().GetBool(flagTestV2Migration)) + skipPrecompiles = must(cmd.Flags().GetBool(flagSkipPrecompiles)) + upgradeContracts = must(cmd.Flags().GetBool(flagUpgradeContracts)) ) logger := runner.NewLogger(verbose, color.FgWhite, "setup") @@ -359,11 +359,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } eg.Go(statefulPrecompilesTestRoutine(conf, deployerRunner, verbose, precompiledContractTests...)) - eg.Go(erc20TestRoutine(conf, deployerRunner, verbose, erc20Tests...)) - eg.Go(zetaTestRoutine(conf, deployerRunner, verbose, zetaTests...)) - eg.Go(zevmMPTestRoutine(conf, deployerRunner, verbose, zevmMPTests...)) - eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, bitcoinTests...)) - eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, ethereumTests...)) + // eg.Go(erc20TestRoutine(conf, deployerRunner, verbose, erc20Tests...)) + // eg.Go(zetaTestRoutine(conf, deployerRunner, verbose, zetaTests...)) + // eg.Go(zevmMPTestRoutine(conf, deployerRunner, verbose, zevmMPTests...)) + // eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, bitcoinTests...)) + // eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, ethereumTests...)) } if testAdmin { diff --git a/cmd/zetae2e/local/precompiles.go b/cmd/zetae2e/local/precompiles.go index 090a51da4d..3a095115c0 100644 --- a/cmd/zetae2e/local/precompiles.go +++ b/cmd/zetae2e/local/precompiles.go @@ -29,7 +29,6 @@ func statefulPrecompilesTestRoutine( deployerRunner, account, runner.NewLogger(verbose, color.FgRed, "precompiles"), - //runner.WithZetaTxServer(deployerRunner.ZetaTxServer), ) if err != nil { return err diff --git a/e2e/contracts/testdistribute/TestDistribute.sol b/e2e/contracts/testdistribute/TestDistribute.sol index 5cf2277b88..1ca328cfbe 100644 --- a/e2e/contracts/testdistribute/TestDistribute.sol +++ b/e2e/contracts/testdistribute/TestDistribute.sol @@ -1,12 +1,31 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.10; +struct DecCoin { + string denom; + uint256 amount; +} + // @dev Interface to interact with distribute. interface IDistribute { function distribute( address zrc20, uint256 amount ) external returns (bool success); + + function claimRewards( + address delegator, + string memory validator + ) external returns (bool success); + + function getDelegatorValidators( + address delegator + ) external view returns (string[] calldata validators); + + function getRewards( + address delegator, + string memory validator + ) external view returns (DecCoin[] calldata rewards); } // @dev Call IBank contract functions diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 30c25b33a3..915845d1e6 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -1057,7 +1057,7 @@ var AllE2ETests = []runner.E2ETest{ TestPrecompilesDistributeName, "test stateful precompiled contracts distribute", []runner.ArgDefinition{}, - TestPrecompilesDistribute, + TestPrecompilesDistributeAndClaim, ), runner.NewE2ETest( TestPrecompilesDistributeNonZRC20Name, @@ -1069,6 +1069,6 @@ var AllE2ETests = []runner.E2ETest{ TestPrecompilesDistributeThroughContractName, "test stateful precompiled contracts distribute through contract", []runner.ArgDefinition{}, - TestPrecompilesDistributeThroughContract, + TestPrecompilesDistributeAndClaimThroughContract, ), } diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index ed9eb3ecb8..8626ed6062 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -20,7 +20,7 @@ import ( precompiletypes "github.com/zeta-chain/node/precompiles/types" ) -func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { +func TestPrecompilesDistributeAndClaim(r *runner.E2ERunner, args []string) { require.Len(r, args, 0, "No arguments expected") var ( @@ -185,6 +185,12 @@ func TestPrecompilesDistribute(r *runner.E2ERunner, args []string) { require.True(r, ok) balanceShouldBe(r, zrc20RewardsAmt, checkZRC20Balance(r, staker)) + eventClaimed, err := distrContract.ParseClaimedRewards(*receipt.Logs[0]) + require.NoError(r, err) + require.Equal(r, zrc20Address, eventClaimed.Zrc20Token) + require.Equal(r, staker, eventClaimed.ClaimAddress) + require.Equal(r, zrc20RewardsAmt.Uint64(), eventClaimed.Amount.Uint64()) + // Locker final balance should be zrc20Disitributed - zrc20RewardsAmt. lockerFinalBalance := big.NewInt(0).Sub(zrc20DistrAmt, zrc20RewardsAmt) balanceShouldBe(r, lockerFinalBalance, checkZRC20Balance(r, lockerAddress)) diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_through_contract.go index bc4e756bb9..a74f24e961 100644 --- a/e2e/e2etests/test_precompiles_distribute_through_contract.go +++ b/e2e/e2etests/test_precompiles_distribute_through_contract.go @@ -15,7 +15,7 @@ import ( precompiletypes "github.com/zeta-chain/node/precompiles/types" ) -func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string) { +func TestPrecompilesDistributeAndClaimThroughContract(r *runner.E2ERunner, args []string) { require.Len(r, args, 0, "No arguments expected") var ( @@ -26,11 +26,14 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string zrc20Address = r.ERC20ZRC20Addr zrc20Denom = precompiletypes.ZRC20ToCosmosDenom(zrc20Address) - oneThousand = big.NewInt(1e3) - oneThousandOne = big.NewInt(1001) - fiveHundred = big.NewInt(500) - fiveHundredOne = big.NewInt(501) - zero = big.NewInt(0) + // carry is carried from the TestPrecompilesDistributeName test. It's applicable only to locker address. + carry = big.NewInt(6210810988040846448) + oneThousand = big.NewInt(1e3) + oneThousandOne = big.NewInt(1001) + fiveHundred = big.NewInt(500) + fiveHundredCarry = new(big.Int).Add(big.NewInt(500), carry) + fiveHundredOne = big.NewInt(501) + zero = big.NewInt(0) //previousGasLimit = r.ZEVMAuth.GasLimit ) @@ -55,7 +58,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string // Check initial balances. balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) receipt = distributeThroughContract(r, testDstrContract, zrc20Address, oneThousand) @@ -63,7 +66,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string // Balances shouldn't change after a failed attempt. balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Allow 500. @@ -74,7 +77,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string // Balances shouldn't change after a failed attempt. balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Raise the allowance to 1000. @@ -86,7 +89,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string // Balances shouldn't change after a failed attempt. balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Should be able to distribute 500, which is within balance and allowance. @@ -94,7 +97,7 @@ func TestPrecompilesDistributeThroughContract(r *runner.E2ERunner, args []string utils.RequireTxSuccessful(r, receipt, "distribute should succeed when distributing within balance and allowance") balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, oneThousand, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, fiveHundredCarry, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. balanceShouldBe(r, fiveHundred, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) eventDitributed, err := dstrContract.ParseDistributed(*receipt.Logs[0]) From 12f319ac5b587d6e934b8d2c77ff3a633c370746 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Sat, 16 Nov 2024 00:59:21 +0100 Subject: [PATCH 09/20] modify staking type --- precompiles/precompiles.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/precompiles.go b/precompiles/precompiles.go index f18c9838a1..3c9a066812 100644 --- a/precompiles/precompiles.go +++ b/precompiles/precompiles.go @@ -51,7 +51,7 @@ func StatefulContracts( // Define the staking contract function. if EnabledStatefulContracts[staking.ContractAddress] { - stakingContract := func(ctx sdktypes.Context, _ ethparams.Rules) vm.PrecompiledContract { + stakingContract := func(ctx sdktypes.Context, _ ethparams.Rules) vm.StatefulPrecompiledContract { return staking.NewIStakingContract( ctx, stakingKeeper, From d95dd4b13991ef1d9df4e250d309e9ffdfb45a76 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Sat, 16 Nov 2024 10:40:19 +0100 Subject: [PATCH 10/20] restore local.go --- cmd/zetae2e/local/local.go | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 1f3b1ba85c..6199f7e33a 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -108,14 +108,14 @@ func localE2ETest(cmd *cobra.Command, _ []string) { light = must(cmd.Flags().GetBool(flagLight)) setupOnly = must(cmd.Flags().GetBool(flagSetupOnly)) skipSetup = must(cmd.Flags().GetBool(flagSkipSetup)) - //skipBitcoinSetup = must(cmd.Flags().GetBool(flagSkipBitcoinSetup)) - skipHeaderProof = must(cmd.Flags().GetBool(flagSkipHeaderProof)) - skipTrackerCheck = must(cmd.Flags().GetBool(flagSkipTrackerCheck)) - testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration)) - testV2 = must(cmd.Flags().GetBool(flagTestV2)) - testV2Migration = must(cmd.Flags().GetBool(flagTestV2Migration)) - skipPrecompiles = must(cmd.Flags().GetBool(flagSkipPrecompiles)) - upgradeContracts = must(cmd.Flags().GetBool(flagUpgradeContracts)) + skipBitcoinSetup = must(cmd.Flags().GetBool(flagSkipBitcoinSetup)) + skipHeaderProof = must(cmd.Flags().GetBool(flagSkipHeaderProof)) + skipTrackerCheck = must(cmd.Flags().GetBool(flagSkipTrackerCheck)) + testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration)) + testV2 = must(cmd.Flags().GetBool(flagTestV2)) + testV2Migration = must(cmd.Flags().GetBool(flagTestV2Migration)) + skipPrecompiles = must(cmd.Flags().GetBool(flagSkipPrecompiles)) + upgradeContracts = must(cmd.Flags().GetBool(flagUpgradeContracts)) ) logger := runner.NewLogger(verbose, color.FgWhite, "setup") @@ -255,11 +255,6 @@ func localE2ETest(cmd *cobra.Command, _ []string) { deployerRunner.UpdateChainParamsV2Contracts() deployerRunner.ERC20CustodyAddr = deployerRunner.ERC20CustodyV2Addr - if testSolana { - deployerRunner.SetupSolana(conf.AdditionalAccounts.UserSolana.SolanaPrivateKey.String()) - } - noError(deployerRunner.FundEmissionsPool()) - deployerRunner.MintERC20OnEvm(1e10) logger.Print("✅ setup completed in %s", time.Since(startTime)) @@ -392,11 +387,6 @@ func localE2ETest(cmd *cobra.Command, _ []string) { eg.Go(erc20TestRoutine(conf, deployerRunner, verbose, erc20Tests...)) eg.Go(zetaTestRoutine(conf, deployerRunner, verbose, zetaTests...)) eg.Go(zevmMPTestRoutine(conf, deployerRunner, verbose, zevmMPTests...)) - eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, bitcoinTests...)) - eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, ethereumTests...)) - eg.Go(erc20TestRoutine(conf, deployerRunner, verbose, erc20Tests...)) - eg.Go(zetaTestRoutine(conf, deployerRunner, verbose, zetaTests...)) - eg.Go(zevmMPTestRoutine(conf, deployerRunner, verbose, zevmMPTests...)) runnerDeposit, runnerWithdraw := initBitcoinTestRunners( conf, deployerRunner, From b7c1dc3c36516bde683942e39ca8bd8a43ec3272 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Sat, 16 Nov 2024 22:05:55 +0100 Subject: [PATCH 11/20] drop the else --- precompiles/staking/method_get_rewards.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/precompiles/staking/method_get_rewards.go b/precompiles/staking/method_get_rewards.go index 8cbb39865b..2b88d2315d 100644 --- a/precompiles/staking/method_get_rewards.go +++ b/precompiles/staking/method_get_rewards.go @@ -51,11 +51,11 @@ func (c *Contract) getRewards( if errors.Is(err, distrtypes.ErrNoDelegationExists) { rewards := make([]DecCoin, 0) return method.Outputs.Pack(rewards) - } else { - return nil, &precompiletypes.ErrUnexpected{ - When: "DelegationRewards", - Got: err.Error(), - } + } + + return nil, &precompiletypes.ErrUnexpected{ + When: "DelegationRewards", + Got: err.Error(), } } From ef1de9c499a37575636c09f1a368b6078d859737 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 18 Nov 2024 18:09:54 +0100 Subject: [PATCH 12/20] test: finish e2e through contract --- .../testdistribute/TestDistribute.abi | 91 +++++-- .../testdistribute/TestDistribute.bin | 2 +- .../testdistribute/TestDistribute.go | 247 +++++++----------- .../testdistribute/TestDistribute.json | 93 +++++-- .../testdistribute/TestDistribute.sol | 39 +-- e2e/e2etests/test_precompiles_distribute.go | 54 ++-- ...precompiles_distribute_through_contract.go | 187 +++++++++---- .../staking/method_get_rewards_test.go | 7 +- 8 files changed, 427 insertions(+), 293 deletions(-) diff --git a/e2e/contracts/testdistribute/TestDistribute.abi b/e2e/contracts/testdistribute/TestDistribute.abi index 26aa07af4e..e8f554bd92 100644 --- a/e2e/contracts/testdistribute/TestDistribute.abi +++ b/e2e/contracts/testdistribute/TestDistribute.abi @@ -1,37 +1,31 @@ [ { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" + "stateMutability": "payable", + "type": "fallback" }, { - "anonymous": false, "inputs": [ { - "indexed": true, "internalType": "address", - "name": "zrc20_distributor", + "name": "delegator", "type": "address" }, { - "indexed": true, - "internalType": "address", - "name": "zrc20_token", - "type": "address" - }, + "internalType": "string", + "name": "validator", + "type": "string" + } + ], + "name": "claimRewardsThroughContract", + "outputs": [ { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" + "internalType": "bool", + "name": "", + "type": "bool" } ], - "name": "Distributed", - "type": "event" - }, - { - "stateMutability": "payable", - "type": "fallback" + "stateMutability": "nonpayable", + "type": "function" }, { "inputs": [ @@ -57,6 +51,61 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "getDelegatorValidatorsThroughContract", + "outputs": [ + { + "internalType": "string[]", + "name": "", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "string", + "name": "validator", + "type": "string" + } + ], + "name": "getRewardsThroughContract", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct DecCoin[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "stateMutability": "payable", "type": "receive" diff --git a/e2e/contracts/testdistribute/TestDistribute.bin b/e2e/contracts/testdistribute/TestDistribute.bin index 5840bf96e5..c59ac56b96 100644 --- a/e2e/contracts/testdistribute/TestDistribute.bin +++ b/e2e/contracts/testdistribute/TestDistribute.bin @@ -1 +1 @@ -60a060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b503373ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff168152505060805161034d6100a06000396000606c015261034d6000f3fe6080604052600436106100225760003560e01c806350b54e841461002b57610029565b3661002957005b005b34801561003757600080fd5b50610052600480360381019061004d9190610201565b610068565b60405161005f919061025c565b60405180910390f35b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146100c257600080fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b815260040161011d929190610295565b6020604051808303816000875af115801561013c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016091906102ea565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101988261016d565b9050919050565b6101a88161018d565b81146101b357600080fd5b50565b6000813590506101c58161019f565b92915050565b6000819050919050565b6101de816101cb565b81146101e957600080fd5b50565b6000813590506101fb816101d5565b92915050565b6000806040838503121561021857610217610168565b5b6000610226858286016101b6565b9250506020610237858286016101ec565b9150509250929050565b60008115159050919050565b61025681610241565b82525050565b6000602082019050610271600083018461024d565b92915050565b6102808161018d565b82525050565b61028f816101cb565b82525050565b60006040820190506102aa6000830185610277565b6102b76020830184610286565b9392505050565b6102c781610241565b81146102d257600080fd5b50565b6000815190506102e4816102be565b92915050565b600060208284031215610300576102ff610168565b5b600061030e848285016102d5565b9150509291505056fea26469706673582212205443ec313ecb8c2e08ca8a30687daed4c3b666f9318ae72ccbe9033479c8b8be64736f6c634300080a0033 +608060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b50610e2d806100616000396000f3fe6080604052600436106100435760003560e01c80630f4865ea1461004c57806350b54e8414610089578063834b902f146100c6578063cdc5ec4a146101035761004a565b3661004a57005b005b34801561005857600080fd5b50610073600480360381019061006e919061059d565b610140565b6040516100809190610614565b60405180910390f35b34801561009557600080fd5b506100b060048036038101906100ab9190610665565b6101e9565b6040516100bd9190610614565b60405180910390f35b3480156100d257600080fd5b506100ed60048036038101906100e8919061059d565b610292565b6040516100fa919061083b565b60405180910390f35b34801561010f57600080fd5b5061012a6004803603810190610125919061085d565b61033d565b604051610137919061094c565b60405180910390f35b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166354dbdc3884846040518363ffffffff1660e01b815260040161019e9291906109c7565b6020604051808303816000875af11580156101bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101e19190610a23565b905092915050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b8152600401610247929190610a5f565b6020604051808303816000875af1158015610266573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061028a9190610a23565b905092915050565b606060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639342879284846040518363ffffffff1660e01b81526004016102ef9291906109c7565b600060405180830381865afa15801561030c573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906103359190610c69565b905092915050565b606060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663b6a216ae836040518263ffffffff1660e01b81526004016103989190610cb2565b600060405180830381865afa1580156103b5573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906103de9190610dae565b9050919050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610424826103f9565b9050919050565b61043481610419565b811461043f57600080fd5b50565b6000813590506104518161042b565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6104aa82610461565b810181811067ffffffffffffffff821117156104c9576104c8610472565b5b80604052505050565b60006104dc6103e5565b90506104e882826104a1565b919050565b600067ffffffffffffffff82111561050857610507610472565b5b61051182610461565b9050602081019050919050565b82818337600083830152505050565b600061054061053b846104ed565b6104d2565b90508281526020810184848401111561055c5761055b61045c565b5b61056784828561051e565b509392505050565b600082601f83011261058457610583610457565b5b813561059484826020860161052d565b91505092915050565b600080604083850312156105b4576105b36103ef565b5b60006105c285828601610442565b925050602083013567ffffffffffffffff8111156105e3576105e26103f4565b5b6105ef8582860161056f565b9150509250929050565b60008115159050919050565b61060e816105f9565b82525050565b60006020820190506106296000830184610605565b92915050565b6000819050919050565b6106428161062f565b811461064d57600080fd5b50565b60008135905061065f81610639565b92915050565b6000806040838503121561067c5761067b6103ef565b5b600061068a85828601610442565b925050602061069b85828601610650565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561070b5780820151818401526020810190506106f0565b8381111561071a576000848401525b50505050565b600061072b826106d1565b61073581856106dc565b93506107458185602086016106ed565b61074e81610461565b840191505092915050565b6107628161062f565b82525050565b600060408301600083015184820360008601526107858282610720565b915050602083015161079a6020860182610759565b508091505092915050565b60006107b18383610768565b905092915050565b6000602082019050919050565b60006107d1826106a5565b6107db81856106b0565b9350836020820285016107ed856106c1565b8060005b85811015610829578484038952815161080a85826107a5565b9450610815836107b9565b925060208a019950506001810190506107f1565b50829750879550505050505092915050565b6000602082019050818103600083015261085581846107c6565b905092915050565b600060208284031215610873576108726103ef565b5b600061088184828501610442565b91505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60006108c28383610720565b905092915050565b6000602082019050919050565b60006108e28261088a565b6108ec8185610895565b9350836020820285016108fe856108a6565b8060005b8581101561093a578484038952815161091b85826108b6565b9450610926836108ca565b925060208a01995050600181019050610902565b50829750879550505050505092915050565b6000602082019050818103600083015261096681846108d7565b905092915050565b61097781610419565b82525050565b600082825260208201905092915050565b6000610999826106d1565b6109a3818561097d565b93506109b38185602086016106ed565b6109bc81610461565b840191505092915050565b60006040820190506109dc600083018561096e565b81810360208301526109ee818461098e565b90509392505050565b610a00816105f9565b8114610a0b57600080fd5b50565b600081519050610a1d816109f7565b92915050565b600060208284031215610a3957610a386103ef565b5b6000610a4784828501610a0e565b91505092915050565b610a598161062f565b82525050565b6000604082019050610a74600083018561096e565b610a816020830184610a50565b9392505050565b600067ffffffffffffffff821115610aa357610aa2610472565b5b602082029050602081019050919050565b600080fd5b600080fd5b600080fd5b6000610ad6610ad1846104ed565b6104d2565b905082815260208101848484011115610af257610af161045c565b5b610afd8482856106ed565b509392505050565b600082601f830112610b1a57610b19610457565b5b8151610b2a848260208601610ac3565b91505092915050565b600081519050610b4281610639565b92915050565b600060408284031215610b5e57610b5d610ab9565b5b610b6860406104d2565b9050600082015167ffffffffffffffff811115610b8857610b87610abe565b5b610b9484828501610b05565b6000830152506020610ba884828501610b33565b60208301525092915050565b6000610bc7610bc284610a88565b6104d2565b90508083825260208201905060208402830185811115610bea57610be9610ab4565b5b835b81811015610c3157805167ffffffffffffffff811115610c0f57610c0e610457565b5b808601610c1c8982610b48565b85526020850194505050602081019050610bec565b5050509392505050565b600082601f830112610c5057610c4f610457565b5b8151610c60848260208601610bb4565b91505092915050565b600060208284031215610c7f57610c7e6103ef565b5b600082015167ffffffffffffffff811115610c9d57610c9c6103f4565b5b610ca984828501610c3b565b91505092915050565b6000602082019050610cc7600083018461096e565b92915050565b600067ffffffffffffffff821115610ce857610ce7610472565b5b602082029050602081019050919050565b6000610d0c610d0784610ccd565b6104d2565b90508083825260208201905060208402830185811115610d2f57610d2e610ab4565b5b835b81811015610d7657805167ffffffffffffffff811115610d5457610d53610457565b5b808601610d618982610b05565b85526020850194505050602081019050610d31565b5050509392505050565b600082601f830112610d9557610d94610457565b5b8151610da5848260208601610cf9565b91505092915050565b600060208284031215610dc457610dc36103ef565b5b600082015167ffffffffffffffff811115610de257610de16103f4565b5b610dee84828501610d80565b9150509291505056fea2646970667358221220d29e8c0ffd7f95c3ae2950ad56c9ec844a4f83f78ebf290ed1f2076d3fa1537864736f6c634300080a0033 diff --git a/e2e/contracts/testdistribute/TestDistribute.go b/e2e/contracts/testdistribute/TestDistribute.go index 18b4201b1a..bdbbfce8e6 100644 --- a/e2e/contracts/testdistribute/TestDistribute.go +++ b/e2e/contracts/testdistribute/TestDistribute.go @@ -29,10 +29,16 @@ var ( _ = abi.ConvertType ) +// DecCoin is an auto generated low-level Go binding around an user-defined struct. +type DecCoin struct { + Denom string + Amount *big.Int +} + // TestDistributeMetaData contains all meta data concerning the TestDistribute contract. var TestDistributeMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_distributor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Distributed\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"distributeThroughContract\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", - Bin: "0x60a060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b503373ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff168152505060805161034d6100a06000396000606c015261034d6000f3fe6080604052600436106100225760003560e01c806350b54e841461002b57610029565b3661002957005b005b34801561003757600080fd5b50610052600480360381019061004d9190610201565b610068565b60405161005f919061025c565b60405180910390f35b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146100c257600080fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b815260040161011d929190610295565b6020604051808303816000875af115801561013c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016091906102ea565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101988261016d565b9050919050565b6101a88161018d565b81146101b357600080fd5b50565b6000813590506101c58161019f565b92915050565b6000819050919050565b6101de816101cb565b81146101e957600080fd5b50565b6000813590506101fb816101d5565b92915050565b6000806040838503121561021857610217610168565b5b6000610226858286016101b6565b9250506020610237858286016101ec565b9150509250929050565b60008115159050919050565b61025681610241565b82525050565b6000602082019050610271600083018461024d565b92915050565b6102808161018d565b82525050565b61028f816101cb565b82525050565b60006040820190506102aa6000830185610277565b6102b76020830184610286565b9392505050565b6102c781610241565b81146102d257600080fd5b50565b6000815190506102e4816102be565b92915050565b600060208284031215610300576102ff610168565b5b600061030e848285016102d5565b9150509291505056fea26469706673582212205443ec313ecb8c2e08ca8a30687daed4c3b666f9318ae72ccbe9033479c8b8be64736f6c634300080a0033", + ABI: "[{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"claimRewardsThroughContract\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"distributeThroughContract\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"}],\"name\":\"getDelegatorValidatorsThroughContract\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"\",\"type\":\"string[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getRewardsThroughContract\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"denom\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structDecCoin[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", + Bin: "0x608060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b50610e2d806100616000396000f3fe6080604052600436106100435760003560e01c80630f4865ea1461004c57806350b54e8414610089578063834b902f146100c6578063cdc5ec4a146101035761004a565b3661004a57005b005b34801561005857600080fd5b50610073600480360381019061006e919061059d565b610140565b6040516100809190610614565b60405180910390f35b34801561009557600080fd5b506100b060048036038101906100ab9190610665565b6101e9565b6040516100bd9190610614565b60405180910390f35b3480156100d257600080fd5b506100ed60048036038101906100e8919061059d565b610292565b6040516100fa919061083b565b60405180910390f35b34801561010f57600080fd5b5061012a6004803603810190610125919061085d565b61033d565b604051610137919061094c565b60405180910390f35b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166354dbdc3884846040518363ffffffff1660e01b815260040161019e9291906109c7565b6020604051808303816000875af11580156101bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101e19190610a23565b905092915050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b8152600401610247929190610a5f565b6020604051808303816000875af1158015610266573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061028a9190610a23565b905092915050565b606060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639342879284846040518363ffffffff1660e01b81526004016102ef9291906109c7565b600060405180830381865afa15801561030c573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906103359190610c69565b905092915050565b606060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663b6a216ae836040518263ffffffff1660e01b81526004016103989190610cb2565b600060405180830381865afa1580156103b5573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906103de9190610dae565b9050919050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610424826103f9565b9050919050565b61043481610419565b811461043f57600080fd5b50565b6000813590506104518161042b565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6104aa82610461565b810181811067ffffffffffffffff821117156104c9576104c8610472565b5b80604052505050565b60006104dc6103e5565b90506104e882826104a1565b919050565b600067ffffffffffffffff82111561050857610507610472565b5b61051182610461565b9050602081019050919050565b82818337600083830152505050565b600061054061053b846104ed565b6104d2565b90508281526020810184848401111561055c5761055b61045c565b5b61056784828561051e565b509392505050565b600082601f83011261058457610583610457565b5b813561059484826020860161052d565b91505092915050565b600080604083850312156105b4576105b36103ef565b5b60006105c285828601610442565b925050602083013567ffffffffffffffff8111156105e3576105e26103f4565b5b6105ef8582860161056f565b9150509250929050565b60008115159050919050565b61060e816105f9565b82525050565b60006020820190506106296000830184610605565b92915050565b6000819050919050565b6106428161062f565b811461064d57600080fd5b50565b60008135905061065f81610639565b92915050565b6000806040838503121561067c5761067b6103ef565b5b600061068a85828601610442565b925050602061069b85828601610650565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561070b5780820151818401526020810190506106f0565b8381111561071a576000848401525b50505050565b600061072b826106d1565b61073581856106dc565b93506107458185602086016106ed565b61074e81610461565b840191505092915050565b6107628161062f565b82525050565b600060408301600083015184820360008601526107858282610720565b915050602083015161079a6020860182610759565b508091505092915050565b60006107b18383610768565b905092915050565b6000602082019050919050565b60006107d1826106a5565b6107db81856106b0565b9350836020820285016107ed856106c1565b8060005b85811015610829578484038952815161080a85826107a5565b9450610815836107b9565b925060208a019950506001810190506107f1565b50829750879550505050505092915050565b6000602082019050818103600083015261085581846107c6565b905092915050565b600060208284031215610873576108726103ef565b5b600061088184828501610442565b91505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60006108c28383610720565b905092915050565b6000602082019050919050565b60006108e28261088a565b6108ec8185610895565b9350836020820285016108fe856108a6565b8060005b8581101561093a578484038952815161091b85826108b6565b9450610926836108ca565b925060208a01995050600181019050610902565b50829750879550505050505092915050565b6000602082019050818103600083015261096681846108d7565b905092915050565b61097781610419565b82525050565b600082825260208201905092915050565b6000610999826106d1565b6109a3818561097d565b93506109b38185602086016106ed565b6109bc81610461565b840191505092915050565b60006040820190506109dc600083018561096e565b81810360208301526109ee818461098e565b90509392505050565b610a00816105f9565b8114610a0b57600080fd5b50565b600081519050610a1d816109f7565b92915050565b600060208284031215610a3957610a386103ef565b5b6000610a4784828501610a0e565b91505092915050565b610a598161062f565b82525050565b6000604082019050610a74600083018561096e565b610a816020830184610a50565b9392505050565b600067ffffffffffffffff821115610aa357610aa2610472565b5b602082029050602081019050919050565b600080fd5b600080fd5b600080fd5b6000610ad6610ad1846104ed565b6104d2565b905082815260208101848484011115610af257610af161045c565b5b610afd8482856106ed565b509392505050565b600082601f830112610b1a57610b19610457565b5b8151610b2a848260208601610ac3565b91505092915050565b600081519050610b4281610639565b92915050565b600060408284031215610b5e57610b5d610ab9565b5b610b6860406104d2565b9050600082015167ffffffffffffffff811115610b8857610b87610abe565b5b610b9484828501610b05565b6000830152506020610ba884828501610b33565b60208301525092915050565b6000610bc7610bc284610a88565b6104d2565b90508083825260208201905060208402830185811115610bea57610be9610ab4565b5b835b81811015610c3157805167ffffffffffffffff811115610c0f57610c0e610457565b5b808601610c1c8982610b48565b85526020850194505050602081019050610bec565b5050509392505050565b600082601f830112610c5057610c4f610457565b5b8151610c60848260208601610bb4565b91505092915050565b600060208284031215610c7f57610c7e6103ef565b5b600082015167ffffffffffffffff811115610c9d57610c9c6103f4565b5b610ca984828501610c3b565b91505092915050565b6000602082019050610cc7600083018461096e565b92915050565b600067ffffffffffffffff821115610ce857610ce7610472565b5b602082029050602081019050919050565b6000610d0c610d0784610ccd565b6104d2565b90508083825260208201905060208402830185811115610d2f57610d2e610ab4565b5b835b81811015610d7657805167ffffffffffffffff811115610d5457610d53610457565b5b808601610d618982610b05565b85526020850194505050602081019050610d31565b5050509392505050565b600082601f830112610d9557610d94610457565b5b8151610da5848260208601610cf9565b91505092915050565b600060208284031215610dc457610dc36103ef565b5b600082015167ffffffffffffffff811115610de257610de16103f4565b5b610dee84828501610d80565b9150509291505056fea2646970667358221220d29e8c0ffd7f95c3ae2950ad56c9ec844a4f83f78ebf290ed1f2076d3fa1537864736f6c634300080a0033", } // TestDistributeABI is the input ABI used to generate the binding from. @@ -202,6 +208,89 @@ func (_TestDistribute *TestDistributeTransactorRaw) Transact(opts *bind.Transact return _TestDistribute.Contract.contract.Transact(opts, method, params...) } +// GetDelegatorValidatorsThroughContract is a free data retrieval call binding the contract method 0xcdc5ec4a. +// +// Solidity: function getDelegatorValidatorsThroughContract(address delegator) view returns(string[]) +func (_TestDistribute *TestDistributeCaller) GetDelegatorValidatorsThroughContract(opts *bind.CallOpts, delegator common.Address) ([]string, error) { + var out []interface{} + err := _TestDistribute.contract.Call(opts, &out, "getDelegatorValidatorsThroughContract", delegator) + + if err != nil { + return *new([]string), err + } + + out0 := *abi.ConvertType(out[0], new([]string)).(*[]string) + + return out0, err + +} + +// GetDelegatorValidatorsThroughContract is a free data retrieval call binding the contract method 0xcdc5ec4a. +// +// Solidity: function getDelegatorValidatorsThroughContract(address delegator) view returns(string[]) +func (_TestDistribute *TestDistributeSession) GetDelegatorValidatorsThroughContract(delegator common.Address) ([]string, error) { + return _TestDistribute.Contract.GetDelegatorValidatorsThroughContract(&_TestDistribute.CallOpts, delegator) +} + +// GetDelegatorValidatorsThroughContract is a free data retrieval call binding the contract method 0xcdc5ec4a. +// +// Solidity: function getDelegatorValidatorsThroughContract(address delegator) view returns(string[]) +func (_TestDistribute *TestDistributeCallerSession) GetDelegatorValidatorsThroughContract(delegator common.Address) ([]string, error) { + return _TestDistribute.Contract.GetDelegatorValidatorsThroughContract(&_TestDistribute.CallOpts, delegator) +} + +// GetRewardsThroughContract is a free data retrieval call binding the contract method 0x834b902f. +// +// Solidity: function getRewardsThroughContract(address delegator, string validator) view returns((string,uint256)[]) +func (_TestDistribute *TestDistributeCaller) GetRewardsThroughContract(opts *bind.CallOpts, delegator common.Address, validator string) ([]DecCoin, error) { + var out []interface{} + err := _TestDistribute.contract.Call(opts, &out, "getRewardsThroughContract", delegator, validator) + + if err != nil { + return *new([]DecCoin), err + } + + out0 := *abi.ConvertType(out[0], new([]DecCoin)).(*[]DecCoin) + + return out0, err + +} + +// GetRewardsThroughContract is a free data retrieval call binding the contract method 0x834b902f. +// +// Solidity: function getRewardsThroughContract(address delegator, string validator) view returns((string,uint256)[]) +func (_TestDistribute *TestDistributeSession) GetRewardsThroughContract(delegator common.Address, validator string) ([]DecCoin, error) { + return _TestDistribute.Contract.GetRewardsThroughContract(&_TestDistribute.CallOpts, delegator, validator) +} + +// GetRewardsThroughContract is a free data retrieval call binding the contract method 0x834b902f. +// +// Solidity: function getRewardsThroughContract(address delegator, string validator) view returns((string,uint256)[]) +func (_TestDistribute *TestDistributeCallerSession) GetRewardsThroughContract(delegator common.Address, validator string) ([]DecCoin, error) { + return _TestDistribute.Contract.GetRewardsThroughContract(&_TestDistribute.CallOpts, delegator, validator) +} + +// ClaimRewardsThroughContract is a paid mutator transaction binding the contract method 0x0f4865ea. +// +// Solidity: function claimRewardsThroughContract(address delegator, string validator) returns(bool) +func (_TestDistribute *TestDistributeTransactor) ClaimRewardsThroughContract(opts *bind.TransactOpts, delegator common.Address, validator string) (*types.Transaction, error) { + return _TestDistribute.contract.Transact(opts, "claimRewardsThroughContract", delegator, validator) +} + +// ClaimRewardsThroughContract is a paid mutator transaction binding the contract method 0x0f4865ea. +// +// Solidity: function claimRewardsThroughContract(address delegator, string validator) returns(bool) +func (_TestDistribute *TestDistributeSession) ClaimRewardsThroughContract(delegator common.Address, validator string) (*types.Transaction, error) { + return _TestDistribute.Contract.ClaimRewardsThroughContract(&_TestDistribute.TransactOpts, delegator, validator) +} + +// ClaimRewardsThroughContract is a paid mutator transaction binding the contract method 0x0f4865ea. +// +// Solidity: function claimRewardsThroughContract(address delegator, string validator) returns(bool) +func (_TestDistribute *TestDistributeTransactorSession) ClaimRewardsThroughContract(delegator common.Address, validator string) (*types.Transaction, error) { + return _TestDistribute.Contract.ClaimRewardsThroughContract(&_TestDistribute.TransactOpts, delegator, validator) +} + // DistributeThroughContract is a paid mutator transaction binding the contract method 0x50b54e84. // // Solidity: function distributeThroughContract(address zrc20, uint256 amount) returns(bool) @@ -264,157 +353,3 @@ func (_TestDistribute *TestDistributeSession) Receive() (*types.Transaction, err func (_TestDistribute *TestDistributeTransactorSession) Receive() (*types.Transaction, error) { return _TestDistribute.Contract.Receive(&_TestDistribute.TransactOpts) } - -// TestDistributeDistributedIterator is returned from FilterDistributed and is used to iterate over the raw logs and unpacked data for Distributed events raised by the TestDistribute contract. -type TestDistributeDistributedIterator struct { - Event *TestDistributeDistributed // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *TestDistributeDistributedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(TestDistributeDistributed) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(TestDistributeDistributed) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *TestDistributeDistributedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *TestDistributeDistributedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// TestDistributeDistributed represents a Distributed event raised by the TestDistribute contract. -type TestDistributeDistributed struct { - Zrc20Distributor common.Address - Zrc20Token common.Address - Amount *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterDistributed is a free log retrieval operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. -// -// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) -func (_TestDistribute *TestDistributeFilterer) FilterDistributed(opts *bind.FilterOpts, zrc20_distributor []common.Address, zrc20_token []common.Address) (*TestDistributeDistributedIterator, error) { - - var zrc20_distributorRule []interface{} - for _, zrc20_distributorItem := range zrc20_distributor { - zrc20_distributorRule = append(zrc20_distributorRule, zrc20_distributorItem) - } - var zrc20_tokenRule []interface{} - for _, zrc20_tokenItem := range zrc20_token { - zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) - } - - logs, sub, err := _TestDistribute.contract.FilterLogs(opts, "Distributed", zrc20_distributorRule, zrc20_tokenRule) - if err != nil { - return nil, err - } - return &TestDistributeDistributedIterator{contract: _TestDistribute.contract, event: "Distributed", logs: logs, sub: sub}, nil -} - -// WatchDistributed is a free log subscription operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. -// -// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) -func (_TestDistribute *TestDistributeFilterer) WatchDistributed(opts *bind.WatchOpts, sink chan<- *TestDistributeDistributed, zrc20_distributor []common.Address, zrc20_token []common.Address) (event.Subscription, error) { - - var zrc20_distributorRule []interface{} - for _, zrc20_distributorItem := range zrc20_distributor { - zrc20_distributorRule = append(zrc20_distributorRule, zrc20_distributorItem) - } - var zrc20_tokenRule []interface{} - for _, zrc20_tokenItem := range zrc20_token { - zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) - } - - logs, sub, err := _TestDistribute.contract.WatchLogs(opts, "Distributed", zrc20_distributorRule, zrc20_tokenRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(TestDistributeDistributed) - if err := _TestDistribute.contract.UnpackLog(event, "Distributed", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseDistributed is a log parse operation binding the contract event 0xad4a9acf26d8bba7a8cf1a41160d59be042ee554578e256c98d2ab74cdd43542. -// -// Solidity: event Distributed(address indexed zrc20_distributor, address indexed zrc20_token, uint256 amount) -func (_TestDistribute *TestDistributeFilterer) ParseDistributed(log types.Log) (*TestDistributeDistributed, error) { - event := new(TestDistributeDistributed) - if err := _TestDistribute.contract.UnpackLog(event, "Distributed", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} diff --git a/e2e/contracts/testdistribute/TestDistribute.json b/e2e/contracts/testdistribute/TestDistribute.json index 05ee369e1c..44201da094 100644 --- a/e2e/contracts/testdistribute/TestDistribute.json +++ b/e2e/contracts/testdistribute/TestDistribute.json @@ -1,38 +1,32 @@ { "abi": [ { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" + "stateMutability": "payable", + "type": "fallback" }, { - "anonymous": false, "inputs": [ { - "indexed": true, "internalType": "address", - "name": "zrc20_distributor", + "name": "delegator", "type": "address" }, { - "indexed": true, - "internalType": "address", - "name": "zrc20_token", - "type": "address" - }, + "internalType": "string", + "name": "validator", + "type": "string" + } + ], + "name": "claimRewardsThroughContract", + "outputs": [ { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" + "internalType": "bool", + "name": "", + "type": "bool" } ], - "name": "Distributed", - "type": "event" - }, - { - "stateMutability": "payable", - "type": "fallback" + "stateMutability": "nonpayable", + "type": "function" }, { "inputs": [ @@ -58,10 +52,65 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "getDelegatorValidatorsThroughContract", + "outputs": [ + { + "internalType": "string[]", + "name": "", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "string", + "name": "validator", + "type": "string" + } + ], + "name": "getRewardsThroughContract", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct DecCoin[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "stateMutability": "payable", "type": "receive" } ], - "bin": "60a060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b503373ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff168152505060805161034d6100a06000396000606c015261034d6000f3fe6080604052600436106100225760003560e01c806350b54e841461002b57610029565b3661002957005b005b34801561003757600080fd5b50610052600480360381019061004d9190610201565b610068565b60405161005f919061025c565b60405180910390f35b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146100c257600080fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b815260040161011d929190610295565b6020604051808303816000875af115801561013c573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061016091906102ea565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101988261016d565b9050919050565b6101a88161018d565b81146101b357600080fd5b50565b6000813590506101c58161019f565b92915050565b6000819050919050565b6101de816101cb565b81146101e957600080fd5b50565b6000813590506101fb816101d5565b92915050565b6000806040838503121561021857610217610168565b5b6000610226858286016101b6565b9250506020610237858286016101ec565b9150509250929050565b60008115159050919050565b61025681610241565b82525050565b6000602082019050610271600083018461024d565b92915050565b6102808161018d565b82525050565b61028f816101cb565b82525050565b60006040820190506102aa6000830185610277565b6102b76020830184610286565b9392505050565b6102c781610241565b81146102d257600080fd5b50565b6000815190506102e4816102be565b92915050565b600060208284031215610300576102ff610168565b5b600061030e848285016102d5565b9150509291505056fea26469706673582212205443ec313ecb8c2e08ca8a30687daed4c3b666f9318ae72ccbe9033479c8b8be64736f6c634300080a0033" + "bin": "608060405260666000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005157600080fd5b50610e2d806100616000396000f3fe6080604052600436106100435760003560e01c80630f4865ea1461004c57806350b54e8414610089578063834b902f146100c6578063cdc5ec4a146101035761004a565b3661004a57005b005b34801561005857600080fd5b50610073600480360381019061006e919061059d565b610140565b6040516100809190610614565b60405180910390f35b34801561009557600080fd5b506100b060048036038101906100ab9190610665565b6101e9565b6040516100bd9190610614565b60405180910390f35b3480156100d257600080fd5b506100ed60048036038101906100e8919061059d565b610292565b6040516100fa919061083b565b60405180910390f35b34801561010f57600080fd5b5061012a6004803603810190610125919061085d565b61033d565b604051610137919061094c565b60405180910390f35b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166354dbdc3884846040518363ffffffff1660e01b815260040161019e9291906109c7565b6020604051808303816000875af11580156101bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101e19190610a23565b905092915050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fb93210884846040518363ffffffff1660e01b8152600401610247929190610a5f565b6020604051808303816000875af1158015610266573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061028a9190610a23565b905092915050565b606060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639342879284846040518363ffffffff1660e01b81526004016102ef9291906109c7565b600060405180830381865afa15801561030c573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906103359190610c69565b905092915050565b606060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663b6a216ae836040518263ffffffff1660e01b81526004016103989190610cb2565b600060405180830381865afa1580156103b5573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906103de9190610dae565b9050919050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610424826103f9565b9050919050565b61043481610419565b811461043f57600080fd5b50565b6000813590506104518161042b565b92915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6104aa82610461565b810181811067ffffffffffffffff821117156104c9576104c8610472565b5b80604052505050565b60006104dc6103e5565b90506104e882826104a1565b919050565b600067ffffffffffffffff82111561050857610507610472565b5b61051182610461565b9050602081019050919050565b82818337600083830152505050565b600061054061053b846104ed565b6104d2565b90508281526020810184848401111561055c5761055b61045c565b5b61056784828561051e565b509392505050565b600082601f83011261058457610583610457565b5b813561059484826020860161052d565b91505092915050565b600080604083850312156105b4576105b36103ef565b5b60006105c285828601610442565b925050602083013567ffffffffffffffff8111156105e3576105e26103f4565b5b6105ef8582860161056f565b9150509250929050565b60008115159050919050565b61060e816105f9565b82525050565b60006020820190506106296000830184610605565b92915050565b6000819050919050565b6106428161062f565b811461064d57600080fd5b50565b60008135905061065f81610639565b92915050565b6000806040838503121561067c5761067b6103ef565b5b600061068a85828601610442565b925050602061069b85828601610650565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561070b5780820151818401526020810190506106f0565b8381111561071a576000848401525b50505050565b600061072b826106d1565b61073581856106dc565b93506107458185602086016106ed565b61074e81610461565b840191505092915050565b6107628161062f565b82525050565b600060408301600083015184820360008601526107858282610720565b915050602083015161079a6020860182610759565b508091505092915050565b60006107b18383610768565b905092915050565b6000602082019050919050565b60006107d1826106a5565b6107db81856106b0565b9350836020820285016107ed856106c1565b8060005b85811015610829578484038952815161080a85826107a5565b9450610815836107b9565b925060208a019950506001810190506107f1565b50829750879550505050505092915050565b6000602082019050818103600083015261085581846107c6565b905092915050565b600060208284031215610873576108726103ef565b5b600061088184828501610442565b91505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60006108c28383610720565b905092915050565b6000602082019050919050565b60006108e28261088a565b6108ec8185610895565b9350836020820285016108fe856108a6565b8060005b8581101561093a578484038952815161091b85826108b6565b9450610926836108ca565b925060208a01995050600181019050610902565b50829750879550505050505092915050565b6000602082019050818103600083015261096681846108d7565b905092915050565b61097781610419565b82525050565b600082825260208201905092915050565b6000610999826106d1565b6109a3818561097d565b93506109b38185602086016106ed565b6109bc81610461565b840191505092915050565b60006040820190506109dc600083018561096e565b81810360208301526109ee818461098e565b90509392505050565b610a00816105f9565b8114610a0b57600080fd5b50565b600081519050610a1d816109f7565b92915050565b600060208284031215610a3957610a386103ef565b5b6000610a4784828501610a0e565b91505092915050565b610a598161062f565b82525050565b6000604082019050610a74600083018561096e565b610a816020830184610a50565b9392505050565b600067ffffffffffffffff821115610aa357610aa2610472565b5b602082029050602081019050919050565b600080fd5b600080fd5b600080fd5b6000610ad6610ad1846104ed565b6104d2565b905082815260208101848484011115610af257610af161045c565b5b610afd8482856106ed565b509392505050565b600082601f830112610b1a57610b19610457565b5b8151610b2a848260208601610ac3565b91505092915050565b600081519050610b4281610639565b92915050565b600060408284031215610b5e57610b5d610ab9565b5b610b6860406104d2565b9050600082015167ffffffffffffffff811115610b8857610b87610abe565b5b610b9484828501610b05565b6000830152506020610ba884828501610b33565b60208301525092915050565b6000610bc7610bc284610a88565b6104d2565b90508083825260208201905060208402830185811115610bea57610be9610ab4565b5b835b81811015610c3157805167ffffffffffffffff811115610c0f57610c0e610457565b5b808601610c1c8982610b48565b85526020850194505050602081019050610bec565b5050509392505050565b600082601f830112610c5057610c4f610457565b5b8151610c60848260208601610bb4565b91505092915050565b600060208284031215610c7f57610c7e6103ef565b5b600082015167ffffffffffffffff811115610c9d57610c9c6103f4565b5b610ca984828501610c3b565b91505092915050565b6000602082019050610cc7600083018461096e565b92915050565b600067ffffffffffffffff821115610ce857610ce7610472565b5b602082029050602081019050919050565b6000610d0c610d0784610ccd565b6104d2565b90508083825260208201905060208402830185811115610d2f57610d2e610ab4565b5b835b81811015610d7657805167ffffffffffffffff811115610d5457610d53610457565b5b808601610d618982610b05565b85526020850194505050602081019050610d31565b5050509392505050565b600082601f830112610d9557610d94610457565b5b8151610da5848260208601610cf9565b91505092915050565b600060208284031215610dc457610dc36103ef565b5b600082015167ffffffffffffffff811115610de257610de16103f4565b5b610dee84828501610d80565b9150509291505056fea2646970667358221220d29e8c0ffd7f95c3ae2950ad56c9ec844a4f83f78ebf290ed1f2076d3fa1537864736f6c634300080a0033" } diff --git a/e2e/contracts/testdistribute/TestDistribute.sol b/e2e/contracts/testdistribute/TestDistribute.sol index 1ca328cfbe..00b384b871 100644 --- a/e2e/contracts/testdistribute/TestDistribute.sol +++ b/e2e/contracts/testdistribute/TestDistribute.sol @@ -30,33 +30,36 @@ interface IDistribute { // @dev Call IBank contract functions contract TestDistribute { - event Distributed( - address indexed zrc20_distributor, - address indexed zrc20_token, - uint256 amount - ); - IDistribute distr = IDistribute(0x0000000000000000000000000000000000000066); - address immutable owner; - - constructor() { - owner = msg.sender; - } + fallback() external payable {} - modifier onlyOwner() { - require(msg.sender == owner); - _; - } + receive() external payable {} function distributeThroughContract( address zrc20, uint256 amount - ) external onlyOwner returns (bool) { + ) external returns (bool) { return distr.distribute(zrc20, amount); } - fallback() external payable {} + function claimRewardsThroughContract( + address delegator, + string memory validator + ) external returns (bool) { + return distr.claimRewards(delegator, validator); + } - receive() external payable {} + function getDelegatorValidatorsThroughContract( + address delegator + ) external view returns (string[] memory) { + return distr.getDelegatorValidators(delegator); + } + + function getRewardsThroughContract( + address delegator, + string memory validator + ) external view returns (DecCoin[] memory) { + return distr.getRewards(delegator, validator); + } } diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index 8626ed6062..a6450826b5 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -62,10 +62,11 @@ func TestPrecompilesDistributeAndClaim(r *runner.E2ERunner, args []string) { require.NoError(r, err) require.GreaterOrEqual(r, len(validators), 2) - // Save first validators as it will be used through the test. - validator := validators[0] - validatorAddr, err := sdk.ValAddressFromBech32(validators[0].OperatorAddress) - require.NoError(r, err) + // Save first validator bech32 address and as it will be used through the test. + validatorAddr, validatorValAddr := getValidatorAddresses(r, distrContract) + + // Reset the test after it finishes. + defer resetDistributionTest(r, distrContract, lockerAddress, previousGasLimit, staker, validatorValAddr) // Get ERC20ZRC20. txHash := r.DepositERC20WithAmountAndMessage(staker, zrc20DistrAmt, []byte{}) @@ -77,17 +78,17 @@ func TestPrecompilesDistributeAndClaim(r *runner.E2ERunner, args []string) { require.Empty(r, dv, "DelegatorValidators response should be empty") // Shares at this point should be 0. - sharesBefore, err := distrContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validator.OperatorAddress) + sharesBefore, err := distrContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validatorAddr) require.NoError(r, err) require.Equal(r, int64(0), sharesBefore.Int64(), "shares should be 0 when there are no delegations") // There should be no rewards. - rewards, err := distrContract.GetRewards(&bind.CallOpts{}, staker, validator.OperatorAddress) + rewards, err := distrContract.GetRewards(&bind.CallOpts{}, staker, validatorAddr) require.NoError(r, err) require.Empty(r, rewards, "rewards should be empty when there are no delegations") // Stake with spender so it's registered as a delegator. - err = stakeThroughCosmosAPI(r, validatorAddr, staker, stakeAmt) + err = stakeThroughCosmosAPI(r, validatorValAddr, staker, stakeAmt) require.NoError(r, err) // Check initial balances. @@ -158,10 +159,10 @@ func TestPrecompilesDistributeAndClaim(r *runner.E2ERunner, args []string) { // The result should include the validator address. dv, err = distrContract.GetDelegatorValidators(&bind.CallOpts{}, staker) require.NoError(r, err) - require.Contains(r, dv, validator.OperatorAddress, "DelegatorValidators response should include validator address") + require.Contains(r, dv, validatorAddr, "DelegatorValidators response should include validator address") // Get rewards and check it contains zrc20 tokens. - rewards, err = distrContract.GetRewards(&bind.CallOpts{}, staker, validator.OperatorAddress) + rewards, err = distrContract.GetRewards(&bind.CallOpts{}, staker, validatorAddr) require.NoError(r, err) require.GreaterOrEqual(r, len(rewards), 2) found := false @@ -174,7 +175,7 @@ func TestPrecompilesDistributeAndClaim(r *runner.E2ERunner, args []string) { require.True(r, found, "rewards should include the ZRC20 token") // Claim the rewards, they'll be unlocked as ZRC20 tokens. - tx, err = distrContract.ClaimRewards(r.ZEVMAuth, staker, validator.OperatorAddress) + tx, err = distrContract.ClaimRewards(r.ZEVMAuth, staker, validatorAddr) require.NoError(r, err) receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequireTxSuccessful(r, receipt, "claim rewards should succeed") @@ -195,10 +196,8 @@ func TestPrecompilesDistributeAndClaim(r *runner.E2ERunner, args []string) { lockerFinalBalance := big.NewInt(0).Sub(zrc20DistrAmt, zrc20RewardsAmt) balanceShouldBe(r, lockerFinalBalance, checkZRC20Balance(r, lockerAddress)) - // Set the test to reset the state after it finishes. - sharesAfter, err := distrContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validator.OperatorAddress) - require.NoError(r, err) - resetDistributionTest(r, lockerAddress, previousGasLimit, staker, validatorAddr, sharesAfter) + // Staker final cosmos balance should be 0. + balanceShouldBe(r, zero, checkCosmosBalance(r, sdk.AccAddress(staker.Bytes()), zrc20Denom)) } func TestPrecompilesDistributeNonZRC20(r *runner.E2ERunner, args []string) { @@ -275,12 +274,17 @@ func stakeThroughCosmosAPI( func resetDistributionTest( r *runner.E2ERunner, + distrContract *staking.IStaking, lockerAddress common.Address, previousGasLimit uint64, staker common.Address, validator sdk.ValAddress, - amount *big.Int, ) { + validatorAddr, _ := getValidatorAddresses(r, distrContract) + + amount, err := distrContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validatorAddr) + require.NoError(r, err) + // Restore the gas limit. r.ZEVMAuth.GasLimit = previousGasLimit @@ -288,7 +292,7 @@ func resetDistributionTest( tx, err := r.ERC20ZRC20.Approve(r.ZEVMAuth, lockerAddress, big.NewInt(0)) require.NoError(r, err) receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - utils.RequireTxSuccessful(r, receipt, "Resetting allowance failed") + utils.RequireTxSuccessful(r, receipt, "resetting allowance failed") // Reset balance to 0 for spender; this is needed when running upgrade tests where this test runs twice. balance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) @@ -311,10 +315,26 @@ func resetDistributionTest( validator, sdk.Coin{ Denom: "azeta", - Amount: math.NewIntFromBigInt(amount), + Amount: math.NewIntFromBigInt(amount.Div(amount, big.NewInt(1e18))), }, ) _, err = r.ZetaTxServer.BroadcastTx(sdk.AccAddress(staker.Bytes()).String(), msg) require.NoError(r, err) } + +func getValidatorAddresses(r *runner.E2ERunner, distrContract *staking.IStaking) (string, sdk.ValAddress) { + // distrContract, err := staking.NewIStaking(staking.ContractAddress, r.ZEVMClient) + // require.NoError(r, err, "failed to create distribute contract caller") + + // Retrieve the list of validators. + validators, err := distrContract.GetAllValidators(&bind.CallOpts{}) + require.NoError(r, err) + require.GreaterOrEqual(r, len(validators), 2) + + // Save first validators as it will be used through the test. + validatorAddr, err := sdk.ValAddressFromBech32(validators[0].OperatorAddress) + require.NoError(r, err) + + return validators[0].OperatorAddress, validatorAddr +} diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_through_contract.go index a74f24e961..aeabca1ceb 100644 --- a/e2e/e2etests/test_precompiles_distribute_through_contract.go +++ b/e2e/e2etests/test_precompiles_distribute_through_contract.go @@ -2,11 +2,13 @@ package e2etests import ( "math/big" + "strings" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/cmd/zetacored/config" "github.com/zeta-chain/node/e2e/contracts/testdistribute" "github.com/zeta-chain/node/e2e/runner" "github.com/zeta-chain/node/e2e/utils" @@ -19,106 +21,181 @@ func TestPrecompilesDistributeAndClaimThroughContract(r *runner.E2ERunner, args require.Len(r, args, 0, "No arguments expected") var ( - spenderAddress = r.EVMAddress() - distributeContractAddress = staking.ContractAddress - lockerAddress = bank.ContractAddress + // Addresses. + staker = r.EVMAddress() + distrContractAddress = staking.ContractAddress + lockerAddress = bank.ContractAddress - zrc20Address = r.ERC20ZRC20Addr - zrc20Denom = precompiletypes.ZRC20ToCosmosDenom(zrc20Address) + // Stake amount. + stakeAmt = new(big.Int) + + // ZRC20 distribution. + zrc20Address = r.ERC20ZRC20Addr + zrc20Denom = precompiletypes.ZRC20ToCosmosDenom(zrc20Address) + zrc20DistrAmt = big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1e6)) // carry is carried from the TestPrecompilesDistributeName test. It's applicable only to locker address. - carry = big.NewInt(6210810988040846448) - oneThousand = big.NewInt(1e3) - oneThousandOne = big.NewInt(1001) - fiveHundred = big.NewInt(500) - fiveHundredCarry = new(big.Int).Add(big.NewInt(500), carry) - fiveHundredOne = big.NewInt(501) - zero = big.NewInt(0) - - //previousGasLimit = r.ZEVMAuth.GasLimit + // This is needed because there's no easy way to retrieve that balance from the locker. + carry = big.NewInt(6210810988040846448) + zrc20DistrAmtCarry = new(big.Int).Add(zrc20DistrAmt, carry) + oneThousand = big.NewInt(1e3) + oneThousandOne = big.NewInt(1001) + fiveHundred = big.NewInt(500) + fiveHundredOne = big.NewInt(501) + zero = big.NewInt(0) + + previousGasLimit = r.ZEVMAuth.GasLimit ) - // Get ERC20ZRC20. - txHash := r.DepositERC20WithAmountAndMessage(spenderAddress, oneThousand, []byte{}) - utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + // stakeAmt has to be as big as the validator self delegation. + // This way the rewards will be distributed 50%. + _, ok := stakeAmt.SetString("1000000000000000000000", 10) + require.True(r, ok) - dstrContract, err := staking.NewIStaking(distributeContractAddress, r.ZEVMClient) + // Set new gas limit to avoid out of gas errors. + r.ZEVMAuth.GasLimit = 10_000_000 + + distrContract, err := staking.NewIStaking(distrContractAddress, r.ZEVMClient) require.NoError(r, err, "failed to create distribute contract caller") + // testDstrContract is the dApp contract that uses the staking precompile under the hood. _, tx, testDstrContract, err := testdistribute.DeployTestDistribute(r.ZEVMAuth, r.ZEVMClient) require.NoError(r, err) receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequireTxSuccessful(r, receipt, "deployment of disitributor caller contract failed") - // Set new gas limit to avoid out of gas errors. - r.ZEVMAuth.GasLimit = 10_000_000 + // Save first validator bech32 address and ValAddress as it will be used through the test. + validatorAddr, validatorValAddr := getValidatorAddresses(r, distrContract) - // Set the test to reset the state after it finishes. - //defer resetDistributionTest(r, lockerAddress, previousGasLimit) + // Reset the test after it finishes. + defer resetDistributionTest(r, distrContract, lockerAddress, previousGasLimit, staker, validatorValAddr) + + // Get ERC20ZRC20. + txHash := r.DepositERC20WithAmountAndMessage(staker, zrc20DistrAmt, []byte{}) + utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + + // There is no delegation, so the response should be empty. + dv, err := testDstrContract.GetDelegatorValidatorsThroughContract( + &bind.CallOpts{}, + staker, + ) + require.NoError(r, err) + require.Empty(r, dv, "DelegatorValidators response should be empty") + + // There should be no rewards. + rewards, err := testDstrContract.GetRewardsThroughContract(&bind.CallOpts{}, staker, validatorAddr) + require.NoError(r, err) + require.Empty(r, rewards, "rewards should be empty when there are no delegations") + + // Stake with spender so it's registered as a delegator. + err = stakeThroughCosmosAPI(r, validatorValAddr, staker, stakeAmt) + require.NoError(r, err) // Check initial balances. - balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, zrc20DistrAmt, checkZRC20Balance(r, staker)) + balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) - receipt = distributeThroughContract(r, testDstrContract, zrc20Address, oneThousand) + tx, err = testDstrContract.DistributeThroughContract(r.ZEVMAuth, zrc20Address, oneThousand) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequiredTxFailed(r, receipt, "distribute should fail when there's no allowance") // Balances shouldn't change after a failed attempt. - balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, zrc20DistrAmt, checkZRC20Balance(r, staker)) + balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Allow 500. - approveAllowance(r, distributeContractAddress, fiveHundred) + approveAllowance(r, distrContractAddress, fiveHundred) - receipt = distributeThroughContract(r, testDstrContract, zrc20Address, fiveHundredOne) + tx, err = testDstrContract.DistributeThroughContract(r.ZEVMAuth, zrc20Address, fiveHundredOne) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than allowed") // Balances shouldn't change after a failed attempt. - balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, zrc20DistrAmt, checkZRC20Balance(r, staker)) + balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) // Raise the allowance to 1000. - approveAllowance(r, distributeContractAddress, oneThousand) + approveAllowance(r, distrContractAddress, oneThousand) // Shouldn't be able to distribute more than owned balance. - receipt = distributeThroughContract(r, testDstrContract, zrc20Address, oneThousandOne) + tx, err = testDstrContract.DistributeThroughContract(r.ZEVMAuth, zrc20Address, oneThousandOne) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequiredTxFailed(r, receipt, "distribute should fail trying to distribute more than owned balance") // Balances shouldn't change after a failed attempt. - balanceShouldBe(r, oneThousand, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. + balanceShouldBe(r, zrc20DistrAmt, checkZRC20Balance(r, staker)) + balanceShouldBe(r, carry, checkZRC20Balance(r, lockerAddress)) balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) - // Should be able to distribute 500, which is within balance and allowance. - receipt = distributeThroughContract(r, testDstrContract, zrc20Address, fiveHundred) + // Raise the allowance to max tokens. + approveAllowance(r, distrContractAddress, zrc20DistrAmt) + + // Should be able to distribute an amount which is within balance and allowance. + tx, err = testDstrContract.DistributeThroughContract(r.ZEVMAuth, zrc20Address, zrc20DistrAmt) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) utils.RequireTxSuccessful(r, receipt, "distribute should succeed when distributing within balance and allowance") - balanceShouldBe(r, fiveHundred, checkZRC20Balance(r, spenderAddress)) - balanceShouldBe(r, fiveHundredCarry, checkZRC20Balance(r, lockerAddress)) // Carries 500 from distribute e2e. - balanceShouldBe(r, fiveHundred, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) + balanceShouldBe(r, zero, checkZRC20Balance(r, staker)) + balanceShouldBe(r, zrc20DistrAmtCarry, checkZRC20Balance(r, lockerAddress)) + balanceShouldBe(r, zrc20DistrAmt, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) - eventDitributed, err := dstrContract.ParseDistributed(*receipt.Logs[0]) + eventDitributed, err := distrContract.ParseDistributed(*receipt.Logs[0]) require.NoError(r, err) require.Equal(r, zrc20Address, eventDitributed.Zrc20Token) - require.Equal(r, spenderAddress, eventDitributed.Zrc20Distributor) - require.Equal(r, fiveHundred.Uint64(), eventDitributed.Amount.Uint64()) + require.Equal(r, staker, eventDitributed.Zrc20Distributor) + require.Equal(r, zrc20DistrAmt.Uint64(), eventDitributed.Amount.Uint64()) // After one block the rewards should have been distributed and fee collector should have 0 ZRC20 balance. r.WaitForBlocks(1) balanceShouldBe(r, zero, checkCosmosBalance(r, r.FeeCollectorAddress, zrc20Denom)) -} -func distributeThroughContract( - r *runner.E2ERunner, - dstr *testdistribute.TestDistribute, - zrc20Address common.Address, - amount *big.Int, -) *types.Receipt { - tx, err := dstr.DistributeThroughContract(r.ZEVMAuth, zrc20Address, amount) + // DelegatorValidators returns the list of validator this delegator has delegated to. + // The result should include the validator address. + dv, err = testDstrContract.GetDelegatorValidatorsThroughContract(&bind.CallOpts{}, staker) require.NoError(r, err) - receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - return receipt + require.Contains(r, dv, validatorAddr, "DelegatorValidators response should include validator address") + + // Get rewards and check it contains zrc20 tokens. + rewards, err = testDstrContract.GetRewardsThroughContract(&bind.CallOpts{}, staker, validatorAddr) + require.NoError(r, err) + require.GreaterOrEqual(r, len(rewards), 2) + found := false + for _, coin := range rewards { + if strings.Contains(coin.Denom, config.ZRC20DenomPrefix) { + found = true + break + } + } + require.True(r, found, "rewards should include the ZRC20 token") + + tx, err = testDstrContract.ClaimRewardsThroughContract(r.ZEVMAuth, staker, validatorAddr) + require.NoError(r, err) + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + utils.RequireTxSuccessful(r, receipt, "claim rewards should succeed") + + // Before claiming rewards the ZRC20 balance is 0. After claiming rewards the ZRC20 balance should be 14239697290875601808. + // Which is the amount of ZRC20 distributed, divided by two validators, and subtracted the commissions. + zrc20RewardsAmt, ok := big.NewInt(0).SetString("14239697290875601808", 10) + require.True(r, ok) + balanceShouldBe(r, zrc20RewardsAmt, checkZRC20Balance(r, staker)) + + eventClaimed, err := distrContract.ParseClaimedRewards(*receipt.Logs[0]) + require.NoError(r, err) + require.Equal(r, zrc20Address, eventClaimed.Zrc20Token) + require.Equal(r, staker, eventClaimed.ClaimAddress) + require.Equal(r, zrc20RewardsAmt.Uint64(), eventClaimed.Amount.Uint64()) + + // Locker final balance should be zrc20Distributed with carry - zrc20RewardsAmt. + lockerFinalBalance := big.NewInt(0).Sub(zrc20DistrAmtCarry, zrc20RewardsAmt) + balanceShouldBe(r, lockerFinalBalance, checkZRC20Balance(r, lockerAddress)) + + // Staker final cosmos balance should be 0. + balanceShouldBe(r, zero, checkCosmosBalance(r, sdk.AccAddress(staker.Bytes()), zrc20Denom)) } diff --git a/precompiles/staking/method_get_rewards_test.go b/precompiles/staking/method_get_rewards_test.go index e40c5a4453..961158c446 100644 --- a/precompiles/staking/method_get_rewards_test.go +++ b/precompiles/staking/method_get_rewards_test.go @@ -36,9 +36,10 @@ func Test_GetRewards(t *testing.T) { /* ASSERT */ bytes, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) - require.Error(t, err) - require.Contains(t, err.Error(), "delegation does not exist") - require.Empty(t, bytes) + require.NoError(t, err) + res, err := getRewardsMethod.Outputs.Unpack(bytes) + require.NoError(t, err) + require.Empty(t, res[0]) }) t.Run("should return the zrc20 rewards list for a staker", func(t *testing.T) { From a19085e81e0e1b8f7e9df25a7a3a9147e532eaac Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Tue, 19 Nov 2024 18:24:23 +0100 Subject: [PATCH 13/20] add more checks to zrc20 is valid --- precompiles/bank/method_deposit.go | 2 +- precompiles/bank/method_withdraw.go | 2 +- precompiles/staking/method_distribute.go | 2 +- .../staking/method_get_validators_test.go | 31 +++++++++++++++++++ precompiles/types/coin.go | 20 ++++++++++-- precompiles/types/coin_test.go | 17 +++++----- 6 files changed, 61 insertions(+), 13 deletions(-) diff --git a/precompiles/bank/method_deposit.go b/precompiles/bank/method_deposit.go index 43ba2a492f..48edc76ac5 100644 --- a/precompiles/bank/method_deposit.go +++ b/precompiles/bank/method_deposit.go @@ -80,7 +80,7 @@ func (c *Contract) deposit( // this way we map ZRC20 addresses to cosmos denoms "zevm/0x12345". // - Mint coins to the fungible module. // - Send coins from fungible to the caller. - coinSet, err := precompiletypes.CreateCoinSet(zrc20Addr, amount) + coinSet, err := precompiletypes.CreateZRC20CoinSet(zrc20Addr, amount) if err != nil { return nil, err } diff --git a/precompiles/bank/method_withdraw.go b/precompiles/bank/method_withdraw.go index 24f83bf5e4..642e113047 100644 --- a/precompiles/bank/method_withdraw.go +++ b/precompiles/bank/method_withdraw.go @@ -79,7 +79,7 @@ func (c *Contract) withdraw( } } - coinSet, err := precompiletypes.CreateCoinSet(zrc20Addr, amount) + coinSet, err := precompiletypes.CreateZRC20CoinSet(zrc20Addr, amount) if err != nil { return nil, err } diff --git a/precompiles/staking/method_distribute.go b/precompiles/staking/method_distribute.go index c173517aeb..7a285d20af 100644 --- a/precompiles/staking/method_distribute.go +++ b/precompiles/staking/method_distribute.go @@ -42,7 +42,7 @@ func (c *Contract) distribute( } // Create the coinSet in advance, if this step fails do not lock ZRC20. - coinSet, err := precompiletypes.CreateCoinSet(zrc20Addr, amount) + coinSet, err := precompiletypes.CreateZRC20CoinSet(zrc20Addr, amount) if err != nil { return nil, err } diff --git a/precompiles/staking/method_get_validators_test.go b/precompiles/staking/method_get_validators_test.go index 6fd3957c96..068973ca6a 100644 --- a/precompiles/staking/method_get_validators_test.go +++ b/precompiles/staking/method_get_validators_test.go @@ -5,6 +5,7 @@ import ( "testing" "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" precompiletypes "github.com/zeta-chain/node/precompiles/types" "github.com/zeta-chain/node/testutil/sample" @@ -44,6 +45,36 @@ func Test_GetValidators(t *testing.T) { require.Len(t, list, 0) }) + t.Run("should return an empty list for an invalid staker", func(t *testing.T) { + /* ARRANGE */ + s := newTestSuite(t) + + // Create validator. + validator := sample.Validator(t, rand.New(rand.NewSource(42))) + s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) + + /* ACT */ + // Call getRewards. + getValidatorsMethod := s.stkContractABI.Methods[GetValidatorsMethodName] + + s.mockVMContract.Input = packInputArgs( + t, + getValidatorsMethod, + []interface{}{common.Address{}}..., + ) + + bytes, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.NoError(t, err) + + res, err := getValidatorsMethod.Outputs.Unpack(bytes) + require.NoError(t, err) + require.NotEmpty(t, res) + + list, ok := res[0].([]string) + require.True(t, ok) + require.Len(t, list, 0) + }) + t.Run("should return staker's validator list", func(t *testing.T) { /* ARRANGE */ s := newTestSuite(t) diff --git a/precompiles/types/coin.go b/precompiles/types/coin.go index f8f41eb766..b2f04c2eed 100644 --- a/precompiles/types/coin.go +++ b/precompiles/types/coin.go @@ -17,13 +17,20 @@ func ZRC20ToCosmosDenom(ZRC20Address common.Address) string { return config.ZRC20DenomPrefix + ZRC20Address.String() } -func CreateCoinSet(zrc20address common.Address, amount *big.Int) (sdk.Coins, error) { +func CreateZRC20CoinSet(zrc20address common.Address, amount *big.Int) (sdk.Coins, error) { defer func() { if r := recover(); r != nil { return } }() + if (zrc20address == common.Address{}) { + return nil, &ErrInvalidAddr{ + Got: zrc20address.String(), + Reason: "empty address", + } + } + denom := ZRC20ToCosmosDenom(zrc20address) coin := sdk.NewCoin(denom, math.NewIntFromBigInt(amount)) @@ -53,5 +60,14 @@ func CreateCoinSet(zrc20address common.Address, amount *big.Int) (sdk.Coins, err // CoinIsZRC20 checks if a given coin is a ZRC20 coin based on its denomination. func CoinIsZRC20(denom string) bool { - return strings.HasPrefix(denom, config.ZRC20DenomPrefix) + // Fail fast if the prefix is not set. + if !strings.HasPrefix(denom, config.ZRC20DenomPrefix) { + return false + } + + // Prefix is correctly set, extract the zrc20 address. + zrc20Addr := strings.TrimPrefix(denom, config.ZRC20DenomPrefix) + + // Return true only if address is not empty and is a valid hex address. + return common.HexToAddress(zrc20Addr) != common.Address{} && common.IsHexAddress(zrc20Addr) } diff --git a/precompiles/types/coin_test.go b/precompiles/types/coin_test.go index 9566662dfe..c8e0e4c841 100644 --- a/precompiles/types/coin_test.go +++ b/precompiles/types/coin_test.go @@ -20,7 +20,7 @@ func Test_createCoinSet(t *testing.T) { tokenDenom := ZRC20ToCosmosDenom(tokenAddr) amount := big.NewInt(100) - coinSet, err := CreateCoinSet(tokenAddr, amount) + coinSet, err := CreateZRC20CoinSet(tokenAddr, amount) require.NoError(t, err, "createCoinSet should not return an error") require.NotNil(t, coinSet, "coinSet should not be nil") @@ -34,18 +34,19 @@ func Test_CoinIsZRC20(t *testing.T) { denom string expected bool }{ - {"zrc20/0x0123456789abcdef", true}, - {"zrc20/0xabcdef0123456789", true}, - {"zrc200xabcdef", false}, - {"foo/0x0123456789", false}, + {"", false}, // Empty string. + {"zrc20/", false}, // Missing address. + {"zrc20/0x514910771af9ca656af840dff83e8264ecf986ca", true}, // Valid ZRC20 address. + {"zrc20/0xCa14007Eff0dB1f8135f4C25B34De49AB0d42766", true}, // Valid ZRC20 address. + {"zrc200xabcdef", false}, // Malformed prefix. + {"foo/0x0123456789", false}, // Invalid prefix. + {"ZRC20/0x0123456789abcdef", false}, // Invalid prefix. } for _, tt := range test { t.Run(tt.denom, func(t *testing.T) { result := CoinIsZRC20(tt.denom) - if result != tt.expected { - t.Errorf("got %v, want %v", result, tt.expected) - } + require.Equal(t, tt.expected, result, "got %v, want %v", result, tt.expected) }) } } From 2b130fdc0f29a4d958d599d633c42f3987a6e7b5 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Thu, 21 Nov 2024 17:37:17 +0100 Subject: [PATCH 14/20] add reviewed changes --- changelog.md | 2 +- e2e/e2etests/test_precompiles_distribute.go | 5 +-- ...precompiles_distribute_through_contract.go | 2 ++ precompiles/staking/IStaking.abi | 6 ++++ precompiles/staking/IStaking.gen.go | 31 ++++++++++++------- precompiles/staking/IStaking.json | 6 ++++ precompiles/staking/IStaking.sol | 2 ++ precompiles/staking/logs.go | 2 ++ precompiles/staking/method_claim_rewards.go | 26 ++++++++++++++-- .../staking/method_get_rewards_test.go | 1 - precompiles/staking/method_get_validators.go | 2 +- .../staking/method_get_validators_test.go | 6 ++-- precompiles/staking/staking.go | 2 +- precompiles/types/coin_test.go | 14 +++++---- 14 files changed, 78 insertions(+), 29 deletions(-) diff --git a/changelog.md b/changelog.md index 65f4fc4a12..5bc978301d 100644 --- a/changelog.md +++ b/changelog.md @@ -55,7 +55,7 @@ * [3028](https://github.com/zeta-chain/node/pull/3028) - whitelist connection gater * [3019](https://github.com/zeta-chain/node/pull/3019) - add ditribute functions to staking precompile * [3020](https://github.com/zeta-chain/node/pull/3020) - add support for TON withdrawals -* [3088](https://github.com/zeta-chain/node/pull/3088) - add functions to check and withdraw delegator rewards +* [3088](https://github.com/zeta-chain/node/pull/3088) - add functions to check and withdraw zrc20 as delegation rewards ### Refactor diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute.go index a6450826b5..f5658a27a2 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute.go @@ -190,6 +190,7 @@ func TestPrecompilesDistributeAndClaim(r *runner.E2ERunner, args []string) { require.NoError(r, err) require.Equal(r, zrc20Address, eventClaimed.Zrc20Token) require.Equal(r, staker, eventClaimed.ClaimAddress) + require.Equal(r, common.BytesToAddress(validatorValAddr.Bytes()), eventClaimed.Validator) require.Equal(r, zrc20RewardsAmt.Uint64(), eventClaimed.Amount.Uint64()) // Locker final balance should be zrc20Disitributed - zrc20RewardsAmt. @@ -259,7 +260,7 @@ func stakeThroughCosmosAPI( sdk.AccAddress(staker.Bytes()), validator, sdk.Coin{ - Denom: "azeta", + Denom: config.BaseDenom, Amount: math.NewIntFromBigInt(amount), }, ) @@ -314,7 +315,7 @@ func resetDistributionTest( sdk.AccAddress(staker.Bytes()), validator, sdk.Coin{ - Denom: "azeta", + Denom: config.BaseDenom, Amount: math.NewIntFromBigInt(amount.Div(amount, big.NewInt(1e18))), }, ) diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_through_contract.go index aeabca1ceb..ff2d2ad51a 100644 --- a/e2e/e2etests/test_precompiles_distribute_through_contract.go +++ b/e2e/e2etests/test_precompiles_distribute_through_contract.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/zeta-chain/node/cmd/zetacored/config" @@ -190,6 +191,7 @@ func TestPrecompilesDistributeAndClaimThroughContract(r *runner.E2ERunner, args require.NoError(r, err) require.Equal(r, zrc20Address, eventClaimed.Zrc20Token) require.Equal(r, staker, eventClaimed.ClaimAddress) + require.Equal(r, common.BytesToAddress(validatorValAddr.Bytes()), eventClaimed.Validator) require.Equal(r, zrc20RewardsAmt.Uint64(), eventClaimed.Amount.Uint64()) // Locker final balance should be zrc20Distributed with carry - zrc20RewardsAmt. diff --git a/precompiles/staking/IStaking.abi b/precompiles/staking/IStaking.abi index 1573fcc82a..76028d79f3 100644 --- a/precompiles/staking/IStaking.abi +++ b/precompiles/staking/IStaking.abi @@ -14,6 +14,12 @@ "name": "zrc20_token", "type": "address" }, + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, { "indexed": false, "internalType": "uint256", diff --git a/precompiles/staking/IStaking.gen.go b/precompiles/staking/IStaking.gen.go index 8c40b7e634..3a6e1bc759 100644 --- a/precompiles/staking/IStaking.gen.go +++ b/precompiles/staking/IStaking.gen.go @@ -45,7 +45,7 @@ type Validator struct { // IStakingMetaData contains all meta data concerning the IStaking contract. var IStakingMetaData = &bind.MetaData{ - ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"claim_address\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"ClaimedRewards\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_distributor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Distributed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorSrc\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorDst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"MoveStake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Stake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Unstake\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"claimRewards\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"distribute\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllValidators\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"operatorAddress\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"consensusPubKey\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"jailed\",\"type\":\"bool\"},{\"internalType\":\"enumBondStatus\",\"name\":\"bondStatus\",\"type\":\"uint8\"}],\"internalType\":\"structValidator[]\",\"name\":\"validators\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"}],\"name\":\"getDelegatorValidators\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"validators\",\"type\":\"string[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getRewards\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"denom\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structDecCoin[]\",\"name\":\"rewards\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getShares\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validatorSrc\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"validatorDst\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"moveStake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"stake\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unstake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"claim_address\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"ClaimedRewards\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_distributor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"zrc20_token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Distributed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorSrc\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validatorDst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"MoveStake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Stake\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Unstake\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"claimRewards\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"distribute\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllValidators\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"operatorAddress\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"consensusPubKey\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"jailed\",\"type\":\"bool\"},{\"internalType\":\"enumBondStatus\",\"name\":\"bondStatus\",\"type\":\"uint8\"}],\"internalType\":\"structValidator[]\",\"name\":\"validators\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"}],\"name\":\"getDelegatorValidators\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"validators\",\"type\":\"string[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getRewards\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"denom\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structDecCoin[]\",\"name\":\"rewards\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"}],\"name\":\"getShares\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validatorSrc\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"validatorDst\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"moveStake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"stake\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"staker\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"validator\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unstake\",\"outputs\":[{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", } // IStakingABI is the input ABI used to generate the binding from. @@ -494,14 +494,15 @@ func (it *IStakingClaimedRewardsIterator) Close() error { type IStakingClaimedRewards struct { ClaimAddress common.Address Zrc20Token common.Address + Validator common.Address Amount *big.Int Raw types.Log // Blockchain specific contextual infos } -// FilterClaimedRewards is a free log retrieval operation binding the contract event 0x2ef606d064225d24c1514dc94907c134faee1237445c2f63f410cce0852b2054. +// FilterClaimedRewards is a free log retrieval operation binding the contract event 0xfad55f843dbd67b821d107dd22535d77fb9384daa21dc35a976588f81997b7b3. // -// Solidity: event ClaimedRewards(address indexed claim_address, address indexed zrc20_token, uint256 amount) -func (_IStaking *IStakingFilterer) FilterClaimedRewards(opts *bind.FilterOpts, claim_address []common.Address, zrc20_token []common.Address) (*IStakingClaimedRewardsIterator, error) { +// Solidity: event ClaimedRewards(address indexed claim_address, address indexed zrc20_token, address indexed validator, uint256 amount) +func (_IStaking *IStakingFilterer) FilterClaimedRewards(opts *bind.FilterOpts, claim_address []common.Address, zrc20_token []common.Address, validator []common.Address) (*IStakingClaimedRewardsIterator, error) { var claim_addressRule []interface{} for _, claim_addressItem := range claim_address { @@ -511,18 +512,22 @@ func (_IStaking *IStakingFilterer) FilterClaimedRewards(opts *bind.FilterOpts, c for _, zrc20_tokenItem := range zrc20_token { zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) } + var validatorRule []interface{} + for _, validatorItem := range validator { + validatorRule = append(validatorRule, validatorItem) + } - logs, sub, err := _IStaking.contract.FilterLogs(opts, "ClaimedRewards", claim_addressRule, zrc20_tokenRule) + logs, sub, err := _IStaking.contract.FilterLogs(opts, "ClaimedRewards", claim_addressRule, zrc20_tokenRule, validatorRule) if err != nil { return nil, err } return &IStakingClaimedRewardsIterator{contract: _IStaking.contract, event: "ClaimedRewards", logs: logs, sub: sub}, nil } -// WatchClaimedRewards is a free log subscription operation binding the contract event 0x2ef606d064225d24c1514dc94907c134faee1237445c2f63f410cce0852b2054. +// WatchClaimedRewards is a free log subscription operation binding the contract event 0xfad55f843dbd67b821d107dd22535d77fb9384daa21dc35a976588f81997b7b3. // -// Solidity: event ClaimedRewards(address indexed claim_address, address indexed zrc20_token, uint256 amount) -func (_IStaking *IStakingFilterer) WatchClaimedRewards(opts *bind.WatchOpts, sink chan<- *IStakingClaimedRewards, claim_address []common.Address, zrc20_token []common.Address) (event.Subscription, error) { +// Solidity: event ClaimedRewards(address indexed claim_address, address indexed zrc20_token, address indexed validator, uint256 amount) +func (_IStaking *IStakingFilterer) WatchClaimedRewards(opts *bind.WatchOpts, sink chan<- *IStakingClaimedRewards, claim_address []common.Address, zrc20_token []common.Address, validator []common.Address) (event.Subscription, error) { var claim_addressRule []interface{} for _, claim_addressItem := range claim_address { @@ -532,8 +537,12 @@ func (_IStaking *IStakingFilterer) WatchClaimedRewards(opts *bind.WatchOpts, sin for _, zrc20_tokenItem := range zrc20_token { zrc20_tokenRule = append(zrc20_tokenRule, zrc20_tokenItem) } + var validatorRule []interface{} + for _, validatorItem := range validator { + validatorRule = append(validatorRule, validatorItem) + } - logs, sub, err := _IStaking.contract.WatchLogs(opts, "ClaimedRewards", claim_addressRule, zrc20_tokenRule) + logs, sub, err := _IStaking.contract.WatchLogs(opts, "ClaimedRewards", claim_addressRule, zrc20_tokenRule, validatorRule) if err != nil { return nil, err } @@ -565,9 +574,9 @@ func (_IStaking *IStakingFilterer) WatchClaimedRewards(opts *bind.WatchOpts, sin }), nil } -// ParseClaimedRewards is a log parse operation binding the contract event 0x2ef606d064225d24c1514dc94907c134faee1237445c2f63f410cce0852b2054. +// ParseClaimedRewards is a log parse operation binding the contract event 0xfad55f843dbd67b821d107dd22535d77fb9384daa21dc35a976588f81997b7b3. // -// Solidity: event ClaimedRewards(address indexed claim_address, address indexed zrc20_token, uint256 amount) +// Solidity: event ClaimedRewards(address indexed claim_address, address indexed zrc20_token, address indexed validator, uint256 amount) func (_IStaking *IStakingFilterer) ParseClaimedRewards(log types.Log) (*IStakingClaimedRewards, error) { event := new(IStakingClaimedRewards) if err := _IStaking.contract.UnpackLog(event, "ClaimedRewards", log); err != nil { diff --git a/precompiles/staking/IStaking.json b/precompiles/staking/IStaking.json index e4e3111f7e..c781eee11d 100644 --- a/precompiles/staking/IStaking.json +++ b/precompiles/staking/IStaking.json @@ -15,6 +15,12 @@ "name": "zrc20_token", "type": "address" }, + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, { "indexed": false, "internalType": "uint256", diff --git a/precompiles/staking/IStaking.sol b/precompiles/staking/IStaking.sol index 11c9ebdfa8..c673531b0e 100644 --- a/precompiles/staking/IStaking.sol +++ b/precompiles/staking/IStaking.sol @@ -76,10 +76,12 @@ interface IStaking { /// @notice ClaimedRewards is emitted when a delegator claims ZRC20. /// @param claim_address Delegator address where the funds were withdrawed. /// @param zrc20_token ZRC20 token address. + /// @param validator Validator address. /// @param amount Claimed amount. event ClaimedRewards( address indexed claim_address, address indexed zrc20_token, + address indexed validator, uint256 amount ); diff --git a/precompiles/staking/logs.go b/precompiles/staking/logs.go index db6d628968..cb07c5cf08 100644 --- a/precompiles/staking/logs.go +++ b/precompiles/staking/logs.go @@ -153,6 +153,7 @@ func (c *Contract) addClaimRewardsLog( stateDB vm.StateDB, delegator common.Address, zrc20Token common.Address, + validator sdk.ValAddress, amount *big.Int, ) error { event := c.Abi().Events[ClaimRewardsEventName] @@ -161,6 +162,7 @@ func (c *Contract) addClaimRewardsLog( event, []interface{}{delegator}, []interface{}{zrc20Token}, + []interface{}{common.BytesToAddress(validator.Bytes())}, ) if err != nil { return err diff --git a/precompiles/staking/method_claim_rewards.go b/precompiles/staking/method_claim_rewards.go index ac9dce8116..2619b814fc 100644 --- a/precompiles/staking/method_claim_rewards.go +++ b/precompiles/staking/method_claim_rewards.go @@ -96,6 +96,12 @@ func (c *Contract) claimRewards( // Check if bank address has enough ZRC20 balance. // This check is also made inside UnlockZRC20, but repeat it here to avoid burning the coins. if err := c.fungibleKeeper.CheckZRC20Balance(ctx, zrc20Addr, bank.ContractAddress, zrc20Amount); err != nil { + ctx.Logger().Info( + "Claimed invalid amount of ZRC20 Validator Rewards", + "Total", zrc20Amount, + "Denom", precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), + ) + continue } @@ -106,18 +112,32 @@ func (c *Contract) claimRewards( continue } + ctx.Logger().Info( + "Sentf ZRC20 coins from delegator to Module", + "Delegator", delegatorCosmosAddr, + "Module", fungibletypes.ModuleName, + "Denom", precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), + "Amount", coin.Amount, + ) + if err := c.bankKeeper.BurnCoins(ctx, fungibletypes.ModuleName, coinSet); err != nil { - continue + return nil, &precompiletypes.ErrUnexpected{ + When: "BurnCoins", + Got: err.Error(), + } } // Finally, unlock the ZRC20 coins. if err := c.fungibleKeeper.UnlockZRC20(ctx, zrc20Addr, delegatorAddr, bank.ContractAddress, zrc20Amount); err != nil { - continue + return nil, &precompiletypes.ErrUnexpected{ + When: "UnlockZRC20", + Got: err.Error(), + } } // Emit an event per ZRC20 coin unlocked. // This keeps events as granular and deterministic as possible. - if err := c.addClaimRewardsLog(ctx, evm.StateDB, delegatorAddr, zrc20Addr, zrc20Amount); err != nil { + if err := c.addClaimRewardsLog(ctx, evm.StateDB, delegatorAddr, zrc20Addr, validatorCosmosAddr, zrc20Amount); err != nil { return nil, &precompiletypes.ErrUnexpected{ When: "AddClaimRewardLog", Got: err.Error(), diff --git a/precompiles/staking/method_get_rewards_test.go b/precompiles/staking/method_get_rewards_test.go index 961158c446..8ddda98b90 100644 --- a/precompiles/staking/method_get_rewards_test.go +++ b/precompiles/staking/method_get_rewards_test.go @@ -96,6 +96,5 @@ func Test_GetRewards(t *testing.T) { /* ASSERT */ _, err = getRewardsMethod.Outputs.Unpack(bytes) require.NoError(t, err) - //fmt.Println("getRewards response: ", res) }) } diff --git a/precompiles/staking/method_get_validators.go b/precompiles/staking/method_get_validators.go index b4633872e4..bdc4e80132 100644 --- a/precompiles/staking/method_get_validators.go +++ b/precompiles/staking/method_get_validators.go @@ -11,7 +11,7 @@ import ( ) // getValidators queries the list of validators for a given delegator. -func (c *Contract) getDelegatorValidators( +func (c *Contract) getValidatorListForDelegator( ctx sdk.Context, method *abi.Method, args []interface{}, diff --git a/precompiles/staking/method_get_validators_test.go b/precompiles/staking/method_get_validators_test.go index 068973ca6a..539feb32ed 100644 --- a/precompiles/staking/method_get_validators_test.go +++ b/precompiles/staking/method_get_validators_test.go @@ -24,7 +24,7 @@ func Test_GetValidators(t *testing.T) { stakerEVMAddr := sample.EthAddress() /* ACT */ - // Call getRewards. + // Call getValidatorListForDelegator. getValidatorsMethod := s.stkContractABI.Methods[GetValidatorsMethodName] s.mockVMContract.Input = packInputArgs( @@ -54,7 +54,7 @@ func Test_GetValidators(t *testing.T) { s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) /* ACT */ - // Call getRewards. + // Call getValidatorListForDelegator. getValidatorsMethod := s.stkContractABI.Methods[GetValidatorsMethodName] s.mockVMContract.Input = packInputArgs( @@ -100,7 +100,7 @@ func Test_GetValidators(t *testing.T) { ) /* ACT */ - // Call getRewards. + // Call getValidatorListForDelegator. getValidatorsMethod := s.stkContractABI.Methods[GetValidatorsMethodName] s.mockVMContract.Input = packInputArgs( diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 1dd2f1f654..9347978e3c 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -253,7 +253,7 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) ([]byt case GetValidatorsMethodName: var res []byte execErr := stateDB.ExecuteNativeAction(contract.Address(), nil, func(ctx sdk.Context) error { - res, err = c.getDelegatorValidators(ctx, method, args) + res, err = c.getValidatorListForDelegator(ctx, method, args) return err }) if execErr != nil { diff --git a/precompiles/types/coin_test.go b/precompiles/types/coin_test.go index c8e0e4c841..25bc983c01 100644 --- a/precompiles/types/coin_test.go +++ b/precompiles/types/coin_test.go @@ -34,13 +34,15 @@ func Test_CoinIsZRC20(t *testing.T) { denom string expected bool }{ - {"", false}, // Empty string. + {"", false}, // Empty string. {"zrc20/", false}, // Missing address. - {"zrc20/0x514910771af9ca656af840dff83e8264ecf986ca", true}, // Valid ZRC20 address. - {"zrc20/0xCa14007Eff0dB1f8135f4C25B34De49AB0d42766", true}, // Valid ZRC20 address. - {"zrc200xabcdef", false}, // Malformed prefix. - {"foo/0x0123456789", false}, // Invalid prefix. - {"ZRC20/0x0123456789abcdef", false}, // Invalid prefix. + {"zrc20/0x0000000000000000000000000000000000000000", false}, // Zero address. + {"zrc20/0x514910771af9ca656af840dff83e8264ecf986ca", true}, // Valid ZRC20 address. + {"zrc20/0xCa14007Eff0dB1f8135f4C25B34De49AB0d42766", true}, // Valid ZRC20 address. + {"zrc20/0x12345", false}, // Valid prefix, invalid ZRC20 address. + {"zrc200xabcdef", false}, // Malformed prefix. + {"foo/0x0123456789", false}, // Invalid prefix. + {"ZRC20/0x0123456789abcdef", false}, // Invalid prefix. } for _, tt := range test { From 98d684eff6469b93b4f805f93668010f010cfa5e Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 25 Nov 2024 12:47:59 +0100 Subject: [PATCH 15/20] do not check for delegator in tx.origin or contract.CallerAddress --- .../staking/methiod_claim_rewards_test.go | 80 +++++++++++++++++++ precompiles/staking/method_claim_rewards.go | 18 +---- precompiles/types/address.go | 7 ++ 3 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 precompiles/staking/methiod_claim_rewards_test.go diff --git a/precompiles/staking/methiod_claim_rewards_test.go b/precompiles/staking/methiod_claim_rewards_test.go new file mode 100644 index 0000000000..07a4fb2920 --- /dev/null +++ b/precompiles/staking/methiod_claim_rewards_test.go @@ -0,0 +1,80 @@ +package staking + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/testutil/sample" +) + +func Test_ClaimRewards(t *testing.T) { + t.Run("should return an error when passing empty delegator", func(t *testing.T) { + /* ARRANGE */ + s := newTestSuite(t) + validator := sample.Validator(t, rand.New(rand.NewSource(42))) + + // Create staker. + //stakerEVMAddr := sample.EthAddress() + + /* ACT */ + // Call claimRewardsMethod. + claimRewardsMethod := s.stkContractABI.Methods[ClaimRewardsMethodName] + + s.mockVMContract.Input = packInputArgs( + t, + claimRewardsMethod, + []interface{}{common.Address{}, validator.OperatorAddress}..., + ) + + _, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid address 0x0000000000000000000000000000000000000000, reason: empty address") + }) + + t.Run("should return an error when passing incorrect validator", func(t *testing.T) { + /* ARRANGE */ + s := newTestSuite(t) + + // Create staker. + stakerEVMAddr := sample.EthAddress() + + /* ACT */ + // Call claimRewardsMethod. + claimRewardsMethod := s.stkContractABI.Methods[ClaimRewardsMethodName] + + s.mockVMContract.Input = packInputArgs( + t, + claimRewardsMethod, + []interface{}{stakerEVMAddr, "cosmosvaloper100000000000000000000000000000000000000"}..., + ) + + _, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.Error(t, err) + require.Contains(t, err.Error(), "decoding bech32 failed") + }) + + t.Run("should return an error when there's no delegation", func(t *testing.T) { + /* ARRANGE */ + s := newTestSuite(t) + validator := sample.Validator(t, rand.New(rand.NewSource(42))) + + // Create staker. + stakerEVMAddr := sample.EthAddress() + + /* ACT */ + // Call claimRewardsMethod. + claimRewardsMethod := s.stkContractABI.Methods[ClaimRewardsMethodName] + + s.mockVMContract.Input = packInputArgs( + t, + claimRewardsMethod, + []interface{}{stakerEVMAddr, validator.OperatorAddress}..., + ) + + _, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.Error(t, err) + require.Contains(t, err.Error(), "unexpected error in WithdrawDelegationRewards: no delegation distribution info") + }) +} diff --git a/precompiles/staking/method_claim_rewards.go b/precompiles/staking/method_claim_rewards.go index 2619b814fc..c0ee56bc43 100644 --- a/precompiles/staking/method_claim_rewards.go +++ b/precompiles/staking/method_claim_rewards.go @@ -20,7 +20,7 @@ import ( func (c *Contract) claimRewards( ctx sdk.Context, evm *vm.EVM, - contract *vm.Contract, + _ *vm.Contract, method *abi.Method, args []interface{}, ) ([]byte, error) { @@ -36,22 +36,6 @@ func (c *Contract) claimRewards( return nil, err } - var ( - // This represents the delegator calling directly the precompile. - callerIsDelegator = contract.CallerAddress == delegatorAddr - - // This represents the delegator calling the precompile through a contract. - originIsDelegator = evm.Origin == delegatorAddr - ) - - // If the delegator is not the origin nor the caller, it's an unauthorized operation. - if !callerIsDelegator && !originIsDelegator { - return nil, precompiletypes.ErrInvalidAddr{ - Got: delegatorAddr.String(), - Reason: "unauthorized to withdraw the delegation rewards for delegator", - } - } - // Get delegator Cosmos address. delegatorCosmosAddr, err := precompiletypes.GetCosmosAddress(c.bankKeeper, delegatorAddr) if err != nil { diff --git a/precompiles/types/address.go b/precompiles/types/address.go index 5e6654cc5b..b8226dfb01 100644 --- a/precompiles/types/address.go +++ b/precompiles/types/address.go @@ -29,6 +29,13 @@ func GetEVMCallerAddress(evm *vm.EVM, contract *vm.Contract) (common.Address, er // 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) { + if (addr == common.Address{}) { + return nil, &ErrInvalidAddr{ + Got: addr.String(), + Reason: "empty address", + } + } + toAddr := sdk.AccAddress(addr.Bytes()) if toAddr.Empty() { return nil, &ErrInvalidAddr{ From 507ce08ff1107f4730a6412c9414b91fa364f7cb Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 25 Nov 2024 12:56:39 +0100 Subject: [PATCH 16/20] update changelog --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 5bc978301d..af4dbbfc25 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ * [3091](https://github.com/zeta-chain/node/pull/3091) - improve build reproducability. `make release{,-build-only}` checksums should now be stable. * [3124](https://github.com/zeta-chain/node/pull/3124) - integrate SPL deposits * [3134](https://github.com/zeta-chain/node/pull/3134) - integrate SPL tokens withdraw to Solana +* [3088](https://github.com/zeta-chain/node/pull/3088) - add functions to check and withdraw zrc20 as delegation rewards ### Tests @@ -55,7 +56,6 @@ * [3028](https://github.com/zeta-chain/node/pull/3028) - whitelist connection gater * [3019](https://github.com/zeta-chain/node/pull/3019) - add ditribute functions to staking precompile * [3020](https://github.com/zeta-chain/node/pull/3020) - add support for TON withdrawals -* [3088](https://github.com/zeta-chain/node/pull/3088) - add functions to check and withdraw zrc20 as delegation rewards ### Refactor From 4794a729e75dd587b32939b8ecd8963c56e2d00d Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 25 Nov 2024 16:49:06 +0100 Subject: [PATCH 17/20] fixes based on PR review --- ...=> test_precompiles_distribute_and_claim.go} | 3 ++- ...es_distribute_and_claim_through_contract.go} | 3 ++- precompiles/staking/method_claim_rewards.go | 17 ++++++++--------- ...rds_test.go => method_claim_rewards_test.go} | 15 ++++++++++----- 4 files changed, 22 insertions(+), 16 deletions(-) rename e2e/e2etests/{test_precompiles_distribute.go => test_precompiles_distribute_and_claim.go} (99%) rename e2e/e2etests/{test_precompiles_distribute_through_contract.go => test_precompiles_distribute_and_claim_through_contract.go} (99%) rename precompiles/staking/{methiod_claim_rewards_test.go => method_claim_rewards_test.go} (86%) diff --git a/e2e/e2etests/test_precompiles_distribute.go b/e2e/e2etests/test_precompiles_distribute_and_claim.go similarity index 99% rename from e2e/e2etests/test_precompiles_distribute.go rename to e2e/e2etests/test_precompiles_distribute_and_claim.go index f5658a27a2..91aa81fc44 100644 --- a/e2e/e2etests/test_precompiles_distribute.go +++ b/e2e/e2etests/test_precompiles_distribute_and_claim.go @@ -42,13 +42,14 @@ func TestPrecompilesDistributeAndClaim(r *runner.E2ERunner, args []string) { fiveHundred = big.NewInt(500) fiveHundredOne = big.NewInt(501) zero = big.NewInt(0) + stake = "1000000000000000000000" previousGasLimit = r.ZEVMAuth.GasLimit ) // stakeAmt has to be as big as the validator self delegation. // This way the rewards will be distributed 50%. - _, ok := stakeAmt.SetString("1000000000000000000000", 10) + _, ok := stakeAmt.SetString(stake, 10) require.True(r, ok) // Set new gas limit to avoid out of gas errors. diff --git a/e2e/e2etests/test_precompiles_distribute_through_contract.go b/e2e/e2etests/test_precompiles_distribute_and_claim_through_contract.go similarity index 99% rename from e2e/e2etests/test_precompiles_distribute_through_contract.go rename to e2e/e2etests/test_precompiles_distribute_and_claim_through_contract.go index ff2d2ad51a..284185de77 100644 --- a/e2e/e2etests/test_precompiles_distribute_through_contract.go +++ b/e2e/e2etests/test_precompiles_distribute_and_claim_through_contract.go @@ -44,13 +44,14 @@ func TestPrecompilesDistributeAndClaimThroughContract(r *runner.E2ERunner, args fiveHundred = big.NewInt(500) fiveHundredOne = big.NewInt(501) zero = big.NewInt(0) + stake = "1000000000000000000000" previousGasLimit = r.ZEVMAuth.GasLimit ) // stakeAmt has to be as big as the validator self delegation. // This way the rewards will be distributed 50%. - _, ok := stakeAmt.SetString("1000000000000000000000", 10) + _, ok := stakeAmt.SetString(stake, 10) require.True(r, ok) // Set new gas limit to avoid out of gas errors. diff --git a/precompiles/staking/method_claim_rewards.go b/precompiles/staking/method_claim_rewards.go index c0ee56bc43..963e36b884 100644 --- a/precompiles/staking/method_claim_rewards.go +++ b/precompiles/staking/method_claim_rewards.go @@ -80,7 +80,7 @@ func (c *Contract) claimRewards( // Check if bank address has enough ZRC20 balance. // This check is also made inside UnlockZRC20, but repeat it here to avoid burning the coins. if err := c.fungibleKeeper.CheckZRC20Balance(ctx, zrc20Addr, bank.ContractAddress, zrc20Amount); err != nil { - ctx.Logger().Info( + ctx.Logger().Error( "Claimed invalid amount of ZRC20 Validator Rewards", "Total", zrc20Amount, "Denom", precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), @@ -96,14 +96,6 @@ func (c *Contract) claimRewards( continue } - ctx.Logger().Info( - "Sentf ZRC20 coins from delegator to Module", - "Delegator", delegatorCosmosAddr, - "Module", fungibletypes.ModuleName, - "Denom", precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), - "Amount", coin.Amount, - ) - if err := c.bankKeeper.BurnCoins(ctx, fungibletypes.ModuleName, coinSet); err != nil { return nil, &precompiletypes.ErrUnexpected{ When: "BurnCoins", @@ -127,6 +119,13 @@ func (c *Contract) claimRewards( Got: err.Error(), } } + + ctx.Logger().Debug( + "Claimed ZRC20 rewards", + "Delegator", delegatorCosmosAddr, + "Denom", precompiletypes.ZRC20ToCosmosDenom(zrc20Addr), + "Amount", coin.Amount, + ) } return method.Outputs.Pack(true) diff --git a/precompiles/staking/methiod_claim_rewards_test.go b/precompiles/staking/method_claim_rewards_test.go similarity index 86% rename from precompiles/staking/methiod_claim_rewards_test.go rename to precompiles/staking/method_claim_rewards_test.go index 07a4fb2920..3ff8222757 100644 --- a/precompiles/staking/methiod_claim_rewards_test.go +++ b/precompiles/staking/method_claim_rewards_test.go @@ -15,9 +15,6 @@ func Test_ClaimRewards(t *testing.T) { s := newTestSuite(t) validator := sample.Validator(t, rand.New(rand.NewSource(42))) - // Create staker. - //stakerEVMAddr := sample.EthAddress() - /* ACT */ // Call claimRewardsMethod. claimRewardsMethod := s.stkContractABI.Methods[ClaimRewardsMethodName] @@ -30,7 +27,11 @@ func Test_ClaimRewards(t *testing.T) { _, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) require.Error(t, err) - require.Contains(t, err.Error(), "invalid address 0x0000000000000000000000000000000000000000, reason: empty address") + require.Contains( + t, + err.Error(), + "invalid address 0x0000000000000000000000000000000000000000, reason: empty address", + ) }) t.Run("should return an error when passing incorrect validator", func(t *testing.T) { @@ -75,6 +76,10 @@ func Test_ClaimRewards(t *testing.T) { _, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) require.Error(t, err) - require.Contains(t, err.Error(), "unexpected error in WithdrawDelegationRewards: no delegation distribution info") + require.Contains( + t, + err.Error(), + "unexpected error in WithdrawDelegationRewards: no delegation distribution info", + ) }) } From c243f1796b581d929baad4e3407c43ce24c0b455 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 25 Nov 2024 17:06:03 +0100 Subject: [PATCH 18/20] fix test --- precompiles/staking/method_get_validators_test.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/precompiles/staking/method_get_validators_test.go b/precompiles/staking/method_get_validators_test.go index 539feb32ed..92d2497735 100644 --- a/precompiles/staking/method_get_validators_test.go +++ b/precompiles/staking/method_get_validators_test.go @@ -45,7 +45,7 @@ func Test_GetValidators(t *testing.T) { require.Len(t, list, 0) }) - t.Run("should return an empty list for an invalid staker", func(t *testing.T) { + t.Run("should return an error for zero address", func(t *testing.T) { /* ARRANGE */ s := newTestSuite(t) @@ -63,16 +63,9 @@ func Test_GetValidators(t *testing.T) { []interface{}{common.Address{}}..., ) - bytes, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) - require.NoError(t, err) - - res, err := getValidatorsMethod.Outputs.Unpack(bytes) - require.NoError(t, err) - require.NotEmpty(t, res) - - list, ok := res[0].([]string) - require.True(t, ok) - require.Len(t, list, 0) + _, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid address 0x0000000000000000000000000000000000000000, reason: empty address") }) t.Run("should return staker's validator list", func(t *testing.T) { From 8a4953b0ccda985cee75b677397ea0d5d82ce39b Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 25 Nov 2024 21:42:47 +0100 Subject: [PATCH 19/20] add big validator set test --- .../staking/method_get_validators_test.go | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/precompiles/staking/method_get_validators_test.go b/precompiles/staking/method_get_validators_test.go index 92d2497735..ee82ada4ab 100644 --- a/precompiles/staking/method_get_validators_test.go +++ b/precompiles/staking/method_get_validators_test.go @@ -114,4 +114,53 @@ func Test_GetValidators(t *testing.T) { require.Len(t, list, 1) require.Equal(t, validator.GetOperator().String(), list[0]) }) + + t.Run(" should return staker's validator list - heavy test with 100 validators", func(t *testing.T) { + /* ARRANGE */ + s := newTestSuite(t) + + // Create staker. + stakerEVMAddr := sample.EthAddress() + stakerCosmosAddr, err := precompiletypes.GetCosmosAddress(s.sdkKeepers.BankKeeper, stakerEVMAddr) + require.NoError(t, err) + + // Create 100 validators, and stake on each of them. + for n := range 100 { + validator := sample.Validator(t, rand.New(rand.NewSource(int64(n)))) + s.sdkKeepers.StakingKeeper.SetValidator(s.ctx, validator) + + stakeThroughCosmosAPI( + t, + s.ctx, + s.sdkKeepers.BankKeeper, + s.sdkKeepers.StakingKeeper, + validator, + stakerCosmosAddr, + math.NewInt(100), + ) + } + + /* ACT */ + // Call getValidatorListForDelegator. + getValidatorsMethod := s.stkContractABI.Methods[GetValidatorsMethodName] + + s.mockVMContract.Input = packInputArgs( + t, + getValidatorsMethod, + []interface{}{stakerEVMAddr}..., + ) + + bytes, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) + require.NoError(t, err) + + res, err := getValidatorsMethod.Outputs.Unpack(bytes) + require.NoError(t, err) + require.NotEmpty(t, res) + + list, ok := res[0].([]string) + require.True(t, ok) + + // The returned list should contain 100 entries. + require.Len(t, list, 100) + }) } From 8f570f2075c7f2076e8017d38e3b41910ab9b89e Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Tue, 26 Nov 2024 12:49:11 +0100 Subject: [PATCH 20/20] fix formatting --- precompiles/staking/method_get_validators_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/precompiles/staking/method_get_validators_test.go b/precompiles/staking/method_get_validators_test.go index ee82ada4ab..8dd06eff58 100644 --- a/precompiles/staking/method_get_validators_test.go +++ b/precompiles/staking/method_get_validators_test.go @@ -65,7 +65,11 @@ func Test_GetValidators(t *testing.T) { _, err := s.stkContract.Run(s.mockEVM, s.mockVMContract, false) require.Error(t, err) - require.Contains(t, err.Error(), "invalid address 0x0000000000000000000000000000000000000000, reason: empty address") + require.Contains( + t, + err.Error(), + "invalid address 0x0000000000000000000000000000000000000000, reason: empty address", + ) }) t.Run("should return staker's validator list", func(t *testing.T) {