diff --git a/precompiles/distribution/Distribution.sol b/precompiles/distribution/Distribution.sol index d47c389951..7c7ee8ff85 100644 --- a/precompiles/distribution/Distribution.sol +++ b/precompiles/distribution/Distribution.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -/// @title Distribution Precompile Interface -/// @notice This interface provides access to Cosmos SDK distribution module functionality /// @dev The distribution precompile is deployed at a fixed address and allows EVM contracts to interact with staking rewards address constant DISTR_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001007; -/// @notice Global instance of the distribution precompile contract IDistr constant DISTR_CONTRACT = IDistr( DISTR_PRECOMPILE_ADDRESS ); @@ -15,6 +12,11 @@ IDistr constant DISTR_CONTRACT = IDistr( /// @notice Interface for interacting with the Cosmos SDK distribution module /// @dev This interface allows managing staking rewards, commission, and withdrawal addresses interface IDistr { + event WithdrawAddressSet(address indexed delegator, address withdrawAddr); + event DelegationRewardsWithdrawn(address indexed delegator, string validator, uint256 amount); + event MultipleDelegationRewardsWithdrawn(address indexed delegator, string[] validators, uint256[] amounts); + event ValidatorCommissionWithdrawn(string indexed validator, uint256 amount); + // Transactions /// @notice Sets the withdrawal address for the caller's staking rewards @@ -46,7 +48,7 @@ interface IDistr { /// @dev Returns rewards from all validators the address has delegated to /// @param delegatorAddress The EVM address of the delegator /// @return rewards Structured data containing all pending rewards - function rewards(address delegatorAddress) external view returns (Rewards rewards); + function rewards(address delegatorAddress) external view returns (Rewards memory rewards); /// @notice Represents a coin/token with amount, decimals, and denomination /// @dev Used to represent various tokens in the Cosmos ecosystem diff --git a/precompiles/distribution/abi.json b/precompiles/distribution/abi.json index 14a803b219..cc5781977c 100755 --- a/precompiles/distribution/abi.json +++ b/precompiles/distribution/abi.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"setWithdrawAddress","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validator","type":"string"}],"name":"withdrawDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string[]","name":"validators","type":"string[]"}],"name":"withdrawMultipleDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawValidatorCommission","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegatorAddress","type":"address"}],"name":"rewards","outputs":[{"components":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct Coin[]","name":"coins","type":"tuple[]"},{"internalType":"string","name":"validator_address","type":"string"}],"internalType":"struct Reward[]","name":"rewards","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct Coin[]","name":"total","type":"tuple[]"}],"internalType":"struct Rewards","name":"rewards","type":"tuple"}],"stateMutability":"view","type":"function"}] +[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"string","name":"validator","type":"string"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DelegationRewardsWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"string[]","name":"validators","type":"string[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"MultipleDelegationRewardsWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"string","name":"validator","type":"string"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ValidatorCommissionWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"WithdrawAddressSet","type":"event"},{"inputs":[{"internalType":"address","name":"delegatorAddress","type":"address"}],"name":"rewards","outputs":[{"components":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IDistr.Coin[]","name":"coins","type":"tuple[]"},{"internalType":"string","name":"validator_address","type":"string"}],"internalType":"struct IDistr.Reward[]","name":"rewards","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IDistr.Coin[]","name":"total","type":"tuple[]"}],"internalType":"struct IDistr.Rewards","name":"rewards","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"setWithdrawAddress","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validator","type":"string"}],"name":"withdrawDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string[]","name":"validators","type":"string[]"}],"name":"withdrawMultipleDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawValidatorCommission","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index b908bc7e14..63cfff3181 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" pcommon "github.com/sei-protocol/sei-chain/precompiles/common" "github.com/sei-protocol/sei-chain/precompiles/utils" @@ -25,12 +26,24 @@ const ( WithdrawMultipleDelegationRewardsMethod = "withdrawMultipleDelegationRewards" WithdrawValidatorCommissionMethod = "withdrawValidatorCommission" RewardsMethod = "rewards" + + SetWithdrawAddressEvent = "WithdrawAddressSet" + DelegationRewardsEvent = "DelegationRewardsWithdrawn" + MultipleDelegationRewardsEvent = "MultipleDelegationRewardsWithdrawn" + ValidatorCommissionEvent = "ValidatorCommissionWithdrawn" ) const ( DistrAddress = "0x0000000000000000000000000000000000001007" ) +var ( + SetWithdrawAddressEventSig = crypto.Keccak256Hash([]byte("WithdrawAddressSet(address,address)")) + DelegationRewardsEventSig = crypto.Keccak256Hash([]byte("DelegationRewardsWithdrawn(address,string,uint256)")) + MultipleDelegationRewardsEventSig = crypto.Keccak256Hash([]byte("MultipleDelegationRewardsWithdrawn(address,string[],uint256[])")) + ValidatorCommissionEventSig = crypto.Keccak256Hash([]byte("ValidatorCommissionWithdrawn(string,uint256)")) +) + // Embed abi json file to the executable binary. Needed when importing as dependency. // //go:embed abi.json @@ -46,6 +59,8 @@ type PrecompileExecutor struct { WithdrawMultipleDelegationRewardsID []byte WithdrawValidatorCommissionID []byte RewardsID []byte + + abi abi.ABI } func NewPrecompile(keepers utils.Keepers) (*pcommon.DynamicGasPrecompile, error) { @@ -55,6 +70,7 @@ func NewPrecompile(keepers utils.Keepers) (*pcommon.DynamicGasPrecompile, error) distrKeeper: keepers.DistributionK(), evmKeeper: keepers.EVMK(), address: common.HexToAddress(DistrAddress), + abi: newAbi, } for name, m := range newAbi.Methods { @@ -84,17 +100,17 @@ func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller if readOnly { return nil, 0, errors.New("cannot call distr precompile from staticcall") } - return p.setWithdrawAddress(ctx, method, caller, args, value) + return p.setWithdrawAddress(ctx, method, caller, args, value, evm) case WithdrawDelegationRewardsMethod: if readOnly { return nil, 0, errors.New("cannot call distr precompile from staticcall") } - return p.withdrawDelegationRewards(ctx, method, caller, args, value) + return p.withdrawDelegationRewards(ctx, method, caller, args, value, evm) case WithdrawMultipleDelegationRewardsMethod: if readOnly { return nil, 0, errors.New("cannot call distr precompile from staticcall") } - return p.withdrawMultipleDelegationRewards(ctx, method, caller, args, value) + return p.withdrawMultipleDelegationRewards(ctx, method, caller, args, value, evm) case WithdrawValidatorCommissionMethod: if err = pcommon.ValidateNonPayable(value); err != nil { return nil, 0, err @@ -102,7 +118,7 @@ func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller if readOnly { return nil, 0, errors.New("cannot call distr precompile from staticcall") } - return p.withdrawValidatorCommission(ctx, method, caller) + return p.withdrawValidatorCommission(ctx, method, caller, evm) case RewardsMethod: return p.rewards(ctx, method, args) } @@ -113,7 +129,7 @@ func (p PrecompileExecutor) EVMKeeper() utils.EVMKeeper { return p.evmKeeper } -func (p PrecompileExecutor) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { +func (p PrecompileExecutor) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, rerr error) { defer func() { if err := recover(); err != nil { ret = nil @@ -148,10 +164,23 @@ func (p PrecompileExecutor) setWithdrawAddress(ctx sdk.Context, method *abi.Meth } ret, rerr = method.Outputs.Pack(true) remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + + logData, err := p.abi.Events[SetWithdrawAddressEvent].Inputs.NonIndexed().Pack(args[0].(common.Address)) + if err != nil { + rerr = err + return + } + if err := pcommon.EmitEVMLog(evm, p.address, []common.Hash{ + SetWithdrawAddressEventSig, + common.BytesToHash(caller.Bytes()), + }, logData); err != nil { + rerr = err + return + } return } -func (p PrecompileExecutor) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { +func (p PrecompileExecutor) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, rerr error) { defer func() { if err := recover(); err != nil { ret = nil @@ -171,13 +200,26 @@ func (p PrecompileExecutor) withdrawDelegationRewards(ctx sdk.Context, method *a rerr = err return } - _, err = p.withdraw(ctx, delegator, args[0].(string)) + amts, err := p.withdraw(ctx, delegator, args[0].(string)) if err != nil { rerr = err return } ret, rerr = method.Outputs.Pack(true) remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + + logData, err := p.abi.Events[DelegationRewardsEvent].Inputs.NonIndexed().Pack(args[0].(string), amts.AmountOf(sdk.DefaultBondDenom).BigInt()) + if err != nil { + rerr = err + return + } + if err := pcommon.EmitEVMLog(evm, p.address, []common.Hash{ + DelegationRewardsEventSig, + common.BytesToHash(caller.Bytes()), + }, logData); err != nil { + rerr = err + return + } return } @@ -210,7 +252,7 @@ func (p PrecompileExecutor) getDelegator(ctx sdk.Context, caller common.Address) return delegator, nil } -func (p PrecompileExecutor) withdrawMultipleDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { +func (p PrecompileExecutor) withdrawMultipleDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, rerr error) { defer func() { if err := recover(); err != nil { ret = nil @@ -231,16 +273,31 @@ func (p PrecompileExecutor) withdrawMultipleDelegationRewards(ctx sdk.Context, m return } validators := args[0].([]string) + amts := make([]*big.Int, 0, len(validators)) for _, valAddr := range validators { - _, err := p.withdraw(ctx, delegator, valAddr) + amt, err := p.withdraw(ctx, delegator, valAddr) if err != nil { rerr = err return } + amts = append(amts, amt.AmountOf(sdk.DefaultBondDenom).BigInt()) } ret, rerr = method.Outputs.Pack(true) remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + + logData, err := p.abi.Events[MultipleDelegationRewardsEvent].Inputs.NonIndexed().Pack(validators, amts) + if err != nil { + rerr = err + return + } + if err := pcommon.EmitEVMLog(evm, p.address, []common.Hash{ + MultipleDelegationRewardsEventSig, + common.BytesToHash(caller.Bytes()), + }, logData); err != nil { + rerr = err + return + } return } @@ -342,7 +399,7 @@ func getResponseOutput(response *distrtypes.QueryDelegationTotalRewardsResponse) } } -func (p PrecompileExecutor) withdrawValidatorCommission(ctx sdk.Context, method *abi.Method, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { +func (p PrecompileExecutor) withdrawValidatorCommission(ctx sdk.Context, method *abi.Method, caller common.Address, evm *vm.EVM) (ret []byte, remainingGas uint64, rerr error) { defer func() { if err := recover(); err != nil { ret = nil @@ -367,7 +424,7 @@ func (p PrecompileExecutor) withdrawValidatorCommission(ctx sdk.Context, method } // Call the distribution keeper to withdraw validator commission - _, err = p.distrKeeper.WithdrawValidatorCommission(ctx, validator) + amts, err := p.distrKeeper.WithdrawValidatorCommission(ctx, validator) if err != nil { rerr = err return @@ -375,5 +432,18 @@ func (p PrecompileExecutor) withdrawValidatorCommission(ctx sdk.Context, method ret, rerr = method.Outputs.Pack(true) remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + + logData, err := p.abi.Events[ValidatorCommissionEvent].Inputs.NonIndexed().Pack(amts.AmountOf(sdk.DefaultBondDenom).BigInt()) + if err != nil { + rerr = err + return + } + if err := pcommon.EmitEVMLog(evm, p.address, []common.Hash{ + ValidatorCommissionEventSig, + crypto.Keccak256Hash([]byte(validator.String())), + }, logData); err != nil { + rerr = err + return + } return } diff --git a/precompiles/distribution/distribution_test.go b/precompiles/distribution/distribution_test.go index bcd706e6c7..c018931448 100644 --- a/precompiles/distribution/distribution_test.go +++ b/precompiles/distribution/distribution_test.go @@ -126,6 +126,12 @@ func TestWithdraw(t *testing.T) { require.Nil(t, err) require.Empty(t, res.VmError) require.Equal(t, withdrawSeiAddr.String(), testApp.DistrKeeper.GetDelegatorWithdrawAddr(ctx, seiAddr).String()) + receipt, err := k.GetTransientReceipt(ctx, tx.Hash(), 0) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.Equal(t, distribution.SetWithdrawAddressEventSig, common.HexToHash(receipt.Logs[0].Topics[0])) + require.Equal(t, common.BytesToHash(evmAddr.Bytes()), common.HexToHash(receipt.Logs[0].Topics[1])) + require.NotEmpty(t, receipt.Logs[0].Data) // withdraw args, err = abi.Pack("withdrawDelegationRewards", val.String()) @@ -154,6 +160,13 @@ func TestWithdraw(t *testing.T) { // reinitialized d, found = testApp.StakingKeeper.GetDelegation(ctx, seiAddr, val) require.True(t, found) + + receipt, err = k.GetTransientReceipt(ctx, tx.Hash(), 0) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.Equal(t, distribution.DelegationRewardsEventSig, common.HexToHash(receipt.Logs[0].Topics[0])) + require.Equal(t, common.BytesToHash(evmAddr.Bytes()), common.HexToHash(receipt.Logs[0].Topics[1])) + require.NotEmpty(t, receipt.Logs[0].Data) } func TestWithdrawMultipleDelegationRewards(t *testing.T) { @@ -273,7 +286,7 @@ func setWithdrawAddressAndWithdraw( res, err := msgServer.EVMTransaction(sdk.WrapSDKContext(ctx), req) require.Nil(t, err) require.Empty(t, res.VmError) - seiAddr, _ := testkeeper.PrivateKeyToAddresses(privKey) + seiAddr, evmAddr := testkeeper.PrivateKeyToAddresses(privKey) require.Equal(t, withdrawSeiAddr.String(), testApp.DistrKeeper.GetDelegatorWithdrawAddr(ctx, seiAddr).String()) var validators []string @@ -304,6 +317,13 @@ func setWithdrawAddressAndWithdraw( require.Empty(t, res.VmError) require.Equal(t, uint64(148290), res.GasUsed) + receipt, err := k.GetTransientReceipt(ctx, tx.Hash(), 0) + require.Nil(t, err) + require.Equal(t, 1, len(receipt.Logs)) + require.Equal(t, distribution.MultipleDelegationRewardsEventSig, common.HexToHash(receipt.Logs[0].Topics[0])) + require.Equal(t, common.BytesToHash(evmAddr.Bytes()), common.HexToHash(receipt.Logs[0].Topics[1])) + require.NotEmpty(t, receipt.Logs[0].Data) + // reinitialized for _, val := range vals { _, found := testApp.StakingKeeper.GetDelegation(ctx, seiAddr, val) diff --git a/precompiles/staking/Staking.sol b/precompiles/staking/Staking.sol index f698c704e1..447eeeb05d 100644 --- a/precompiles/staking/Staking.sol +++ b/precompiles/staking/Staking.sol @@ -151,6 +151,133 @@ interface IStaking { string memory valAddress ) external view returns (Delegation memory delegation); + function validators( + string memory status, + bytes memory nextKey + ) external view returns (ValidatorsResponse memory response); + + /** + * @notice Get validator information for a given validator address + * @param validatorAddress The validator address + * @return validator Validator details + */ + function validator( + string memory validatorAddress + ) external view returns (Validator memory validator); + + /** + * @notice Get delegations for a validator + * @param validatorAddress The validator address + * @param nextKey Pagination key + * @return response Delegations response with pagination + */ + function validatorDelegations( + string memory validatorAddress, + bytes memory nextKey + ) external view returns (DelegationsResponse memory response); + + /** + * @notice Get unbonding delegations for a validator + * @param validatorAddress The validator address + * @param nextKey Pagination key + * @return response Unbonding delegations response with pagination + */ + function validatorUnbondingDelegations( + string memory validatorAddress, + bytes memory nextKey + ) external view returns (UnbondingDelegationsResponse memory response); + + /** + * @notice Get unbonding delegation information for a delegator and validator pair + * @param delegator The delegator's address + * @param validatorAddress The validator address + * @return unbondingDelegation Unbonding delegation details + */ + function unbondingDelegation( + address delegator, + string memory validatorAddress + ) external view returns (UnbondingDelegation memory unbondingDelegation); + + /** + * @notice Get all delegations for a delegator + * @param delegator The delegator's address + * @param nextKey Pagination key + * @return response Delegations response with pagination + */ + function delegatorDelegations( + address delegator, + bytes memory nextKey + ) external view returns (DelegationsResponse memory response); + + /** + * @notice Get validator information for a delegator and validator pair + * @param delegator The delegator's address + * @param validatorAddress The validator address + * @return validator Validator details + */ + function delegatorValidator( + address delegator, + string memory validatorAddress + ) external view returns (Validator memory validator); + + /** + * @notice Get all unbonding delegations for a delegator + * @param delegator The delegator's address + * @param nextKey Pagination key + * @return response Unbonding delegations response with pagination + */ + function delegatorUnbondingDelegations( + address delegator, + bytes memory nextKey + ) external view returns (UnbondingDelegationsResponse memory response); + + /** + * @notice Get redelegations + * @param delegator The delegator's address (empty string for all) + * @param srcValidator The source validator address (empty string for all) + * @param dstValidator The destination validator address (empty string for all) + * @param nextKey Pagination key + * @return response Redelegations response with pagination + */ + function redelegations( + string memory delegator, + string memory srcValidator, + string memory dstValidator, + bytes memory nextKey + ) external view returns (RedelegationsResponse memory response); + + /** + * @notice Get all validators for a delegator + * @param delegator The delegator's address + * @param nextKey Pagination key + * @return response Validators response with pagination + */ + function delegatorValidators( + address delegator, + bytes memory nextKey + ) external view returns (ValidatorsResponse memory response); + + /** + * @notice Get historical info for a given height + * @param height The block height + * @return historicalInfo Historical info + */ + function historicalInfo( + int64 height + ) external view returns (HistoricalInfo memory historicalInfo); + + /** + * @notice Get pool information + * @return pool Pool details + */ + function pool() external view returns (Pool memory pool); + + /** + * @notice Get staking parameters + * @return params Staking parameters + */ + function params() external view returns (Params memory params); + struct Delegation { Balance balance; DelegationDetails delegation; @@ -167,4 +294,89 @@ interface IStaking { uint256 decimals; string validator_address; } + + struct Validator { + string operatorAddress; + string consensusPubkey; + bool jailed; + int32 status; + string tokens; + string delegatorShares; + string description; + int64 unbondingHeight; + int64 unbondingTime; + string commissionRate; + string commissionMaxRate; + string commissionMaxChangeRate; + int64 commissionUpdateTime; + string minSelfDelegation; + } + + struct ValidatorsResponse { + Validator[] validators; + bytes nextKey; + } + + struct DelegationsResponse { + Delegation[] delegations; + bytes nextKey; + } + + struct UnbondingDelegationEntry { + int64 creationHeight; + int64 completionTime; + string initialBalance; + string balance; + } + + struct UnbondingDelegation { + string delegatorAddress; + string validatorAddress; + UnbondingDelegationEntry[] entries; + } + + struct UnbondingDelegationsResponse { + UnbondingDelegation[] unbondingDelegations; + bytes nextKey; + } + + struct RedelegationEntry { + int64 creationHeight; + int64 completionTime; + string initialBalance; + string sharesDst; + } + + struct Redelegation { + string delegatorAddress; + string validatorSrcAddress; + string validatorDstAddress; + RedelegationEntry[] entries; + } + + struct RedelegationsResponse { + Redelegation[] redelegations; + bytes nextKey; + } + + struct HistoricalInfo { + int64 height; + Validator[] validators; + } + + struct Pool { + string notBondedTokens; + string bondedTokens; + } + + struct Params { + uint64 unbondingTime; + uint32 maxValidators; + uint32 maxEntries; + uint32 historicalEntries; + string bondDenom; + string minCommissionRate; + string maxVotingPowerRatio; + string maxVotingPowerEnforcementThreshold; + } } diff --git a/precompiles/staking/abi.json b/precompiles/staking/abi.json index 8778d7d683..229b0370cf 100755 --- a/precompiles/staking/abi.json +++ b/precompiles/staking/abi.json @@ -1,262 +1 @@ -[ - { - "inputs": [ - { "internalType": "string", "name": "valAddress", "type": "string" } - ], - "name": "delegate", - "outputs": [{ "internalType": "bool", "name": "success", "type": "bool" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "srcAddress", "type": "string" }, - { "internalType": "string", "name": "dstAddress", "type": "string" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "redelegate", - "outputs": [{ "internalType": "bool", "name": "success", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "valAddress", "type": "string" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "undelegate", - "outputs": [{ "internalType": "bool", "name": "success", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "pubKeyHex", "type": "string" }, - { "internalType": "string", "name": "moniker", "type": "string" }, - { "internalType": "string", "name": "commissionRate", "type": "string" }, - { - "internalType": "string", - "name": "commissionMaxRate", - "type": "string" - }, - { - "internalType": "string", - "name": "commissionMaxChangeRate", - "type": "string" - }, - { - "internalType": "uint256", - "name": "minSelfDelegation", - "type": "uint256" - } - ], - "name": "createValidator", - "outputs": [{ "internalType": "bool", "name": "success", "type": "bool" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "string", "name": "moniker", "type": "string" }, - { "internalType": "string", "name": "commissionRate", "type": "string" }, - { - "internalType": "uint256", - "name": "minSelfDelegation", - "type": "uint256" - } - ], - "name": "editValidator", - "outputs": [{ "internalType": "bool", "name": "success", "type": "bool" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "delegator", "type": "address" }, - { "internalType": "string", "name": "valAddress", "type": "string" } - ], - "name": "delegation", - "outputs": [ - { - "components": [ - { - "components": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { "internalType": "string", "name": "denom", "type": "string" } - ], - "internalType": "struct Balance", - "name": "balance", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "string", - "name": "delegator_address", - "type": "string" - }, - { - "internalType": "uint256", - "name": "shares", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "decimals", - "type": "uint256" - }, - { - "internalType": "string", - "name": "validator_address", - "type": "string" - } - ], - "internalType": "struct DelegationDetails", - "name": "delegation", - "type": "tuple" - } - ], - "internalType": "struct Delegation", - "name": "delegation", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "delegator", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "validator", - "type": "string" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Delegate", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "delegator", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "srcValidator", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "dstValidator", - "type": "string" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Redelegate", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "delegator", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "validator", - "type": "string" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Undelegate", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "creator", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "validatorAddress", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "moniker", - "type": "string" - } - ], - "name": "ValidatorCreated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "editor", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "validatorAddress", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "moniker", - "type": "string" - } - ], - "name": "ValidatorEdited", - "type": "event" - } -] +[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"string","name":"validator","type":"string"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Delegate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"string","name":"srcValidator","type":"string"},{"indexed":false,"internalType":"string","name":"dstValidator","type":"string"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Redelegate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":false,"internalType":"string","name":"validator","type":"string"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Undelegate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creator","type":"address"},{"indexed":false,"internalType":"string","name":"validatorAddress","type":"string"},{"indexed":false,"internalType":"string","name":"moniker","type":"string"}],"name":"ValidatorCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"editor","type":"address"},{"indexed":false,"internalType":"string","name":"validatorAddress","type":"string"},{"indexed":false,"internalType":"string","name":"moniker","type":"string"}],"name":"ValidatorEdited","type":"event"},{"inputs":[{"internalType":"string","name":"pubKeyHex","type":"string"},{"internalType":"string","name":"moniker","type":"string"},{"internalType":"string","name":"commissionRate","type":"string"},{"internalType":"string","name":"commissionMaxRate","type":"string"},{"internalType":"string","name":"commissionMaxChangeRate","type":"string"},{"internalType":"uint256","name":"minSelfDelegation","type":"uint256"}],"name":"createValidator","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"valAddress","type":"string"}],"name":"delegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"string","name":"valAddress","type":"string"}],"name":"delegation","outputs":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IStaking.Balance","name":"balance","type":"tuple"},{"components":[{"internalType":"string","name":"delegator_address","type":"string"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"validator_address","type":"string"}],"internalType":"struct IStaking.DelegationDetails","name":"delegation","type":"tuple"}],"internalType":"struct IStaking.Delegation","name":"delegation","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"name":"delegatorDelegations","outputs":[{"components":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IStaking.Balance","name":"balance","type":"tuple"},{"components":[{"internalType":"string","name":"delegator_address","type":"string"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"validator_address","type":"string"}],"internalType":"struct IStaking.DelegationDetails","name":"delegation","type":"tuple"}],"internalType":"struct IStaking.Delegation[]","name":"delegations","type":"tuple[]"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"internalType":"struct IStaking.DelegationsResponse","name":"response","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"name":"delegatorUnbondingDelegations","outputs":[{"components":[{"components":[{"internalType":"string","name":"delegatorAddress","type":"string"},{"internalType":"string","name":"validatorAddress","type":"string"},{"components":[{"internalType":"int64","name":"creationHeight","type":"int64"},{"internalType":"int64","name":"completionTime","type":"int64"},{"internalType":"string","name":"initialBalance","type":"string"},{"internalType":"string","name":"balance","type":"string"}],"internalType":"struct IStaking.UnbondingDelegationEntry[]","name":"entries","type":"tuple[]"}],"internalType":"struct IStaking.UnbondingDelegation[]","name":"unbondingDelegations","type":"tuple[]"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"internalType":"struct IStaking.UnbondingDelegationsResponse","name":"response","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"string","name":"validatorAddress","type":"string"}],"name":"delegatorValidator","outputs":[{"components":[{"internalType":"string","name":"operatorAddress","type":"string"},{"internalType":"string","name":"consensusPubkey","type":"string"},{"internalType":"bool","name":"jailed","type":"bool"},{"internalType":"int32","name":"status","type":"int32"},{"internalType":"string","name":"tokens","type":"string"},{"internalType":"string","name":"delegatorShares","type":"string"},{"internalType":"string","name":"description","type":"string"},{"internalType":"int64","name":"unbondingHeight","type":"int64"},{"internalType":"int64","name":"unbondingTime","type":"int64"},{"internalType":"string","name":"commissionRate","type":"string"},{"internalType":"string","name":"commissionMaxRate","type":"string"},{"internalType":"string","name":"commissionMaxChangeRate","type":"string"},{"internalType":"int64","name":"commissionUpdateTime","type":"int64"},{"internalType":"string","name":"minSelfDelegation","type":"string"}],"internalType":"struct IStaking.Validator","name":"validator","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"name":"delegatorValidators","outputs":[{"components":[{"components":[{"internalType":"string","name":"operatorAddress","type":"string"},{"internalType":"string","name":"consensusPubkey","type":"string"},{"internalType":"bool","name":"jailed","type":"bool"},{"internalType":"int32","name":"status","type":"int32"},{"internalType":"string","name":"tokens","type":"string"},{"internalType":"string","name":"delegatorShares","type":"string"},{"internalType":"string","name":"description","type":"string"},{"internalType":"int64","name":"unbondingHeight","type":"int64"},{"internalType":"int64","name":"unbondingTime","type":"int64"},{"internalType":"string","name":"commissionRate","type":"string"},{"internalType":"string","name":"commissionMaxRate","type":"string"},{"internalType":"string","name":"commissionMaxChangeRate","type":"string"},{"internalType":"int64","name":"commissionUpdateTime","type":"int64"},{"internalType":"string","name":"minSelfDelegation","type":"string"}],"internalType":"struct IStaking.Validator[]","name":"validators","type":"tuple[]"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"internalType":"struct IStaking.ValidatorsResponse","name":"response","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"moniker","type":"string"},{"internalType":"string","name":"commissionRate","type":"string"},{"internalType":"uint256","name":"minSelfDelegation","type":"uint256"}],"name":"editValidator","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int64","name":"height","type":"int64"}],"name":"historicalInfo","outputs":[{"components":[{"internalType":"int64","name":"height","type":"int64"},{"components":[{"internalType":"string","name":"operatorAddress","type":"string"},{"internalType":"string","name":"consensusPubkey","type":"string"},{"internalType":"bool","name":"jailed","type":"bool"},{"internalType":"int32","name":"status","type":"int32"},{"internalType":"string","name":"tokens","type":"string"},{"internalType":"string","name":"delegatorShares","type":"string"},{"internalType":"string","name":"description","type":"string"},{"internalType":"int64","name":"unbondingHeight","type":"int64"},{"internalType":"int64","name":"unbondingTime","type":"int64"},{"internalType":"string","name":"commissionRate","type":"string"},{"internalType":"string","name":"commissionMaxRate","type":"string"},{"internalType":"string","name":"commissionMaxChangeRate","type":"string"},{"internalType":"int64","name":"commissionUpdateTime","type":"int64"},{"internalType":"string","name":"minSelfDelegation","type":"string"}],"internalType":"struct IStaking.Validator[]","name":"validators","type":"tuple[]"}],"internalType":"struct IStaking.HistoricalInfo","name":"historicalInfo","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"params","outputs":[{"components":[{"internalType":"uint64","name":"unbondingTime","type":"uint64"},{"internalType":"uint32","name":"maxValidators","type":"uint32"},{"internalType":"uint32","name":"maxEntries","type":"uint32"},{"internalType":"uint32","name":"historicalEntries","type":"uint32"},{"internalType":"string","name":"bondDenom","type":"string"},{"internalType":"string","name":"minCommissionRate","type":"string"},{"internalType":"string","name":"maxVotingPowerRatio","type":"string"},{"internalType":"string","name":"maxVotingPowerEnforcementThreshold","type":"string"}],"internalType":"struct IStaking.Params","name":"params","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pool","outputs":[{"components":[{"internalType":"string","name":"notBondedTokens","type":"string"},{"internalType":"string","name":"bondedTokens","type":"string"}],"internalType":"struct IStaking.Pool","name":"pool","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"srcAddress","type":"string"},{"internalType":"string","name":"dstAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"redelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"delegator","type":"string"},{"internalType":"string","name":"srcValidator","type":"string"},{"internalType":"string","name":"dstValidator","type":"string"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"name":"redelegations","outputs":[{"components":[{"components":[{"internalType":"string","name":"delegatorAddress","type":"string"},{"internalType":"string","name":"validatorSrcAddress","type":"string"},{"internalType":"string","name":"validatorDstAddress","type":"string"},{"components":[{"internalType":"int64","name":"creationHeight","type":"int64"},{"internalType":"int64","name":"completionTime","type":"int64"},{"internalType":"string","name":"initialBalance","type":"string"},{"internalType":"string","name":"sharesDst","type":"string"}],"internalType":"struct IStaking.RedelegationEntry[]","name":"entries","type":"tuple[]"}],"internalType":"struct IStaking.Redelegation[]","name":"redelegations","type":"tuple[]"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"internalType":"struct IStaking.RedelegationsResponse","name":"response","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"string","name":"validatorAddress","type":"string"}],"name":"unbondingDelegation","outputs":[{"components":[{"internalType":"string","name":"delegatorAddress","type":"string"},{"internalType":"string","name":"validatorAddress","type":"string"},{"components":[{"internalType":"int64","name":"creationHeight","type":"int64"},{"internalType":"int64","name":"completionTime","type":"int64"},{"internalType":"string","name":"initialBalance","type":"string"},{"internalType":"string","name":"balance","type":"string"}],"internalType":"struct IStaking.UnbondingDelegationEntry[]","name":"entries","type":"tuple[]"}],"internalType":"struct IStaking.UnbondingDelegation","name":"unbondingDelegation","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"valAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validatorAddress","type":"string"}],"name":"validator","outputs":[{"components":[{"internalType":"string","name":"operatorAddress","type":"string"},{"internalType":"string","name":"consensusPubkey","type":"string"},{"internalType":"bool","name":"jailed","type":"bool"},{"internalType":"int32","name":"status","type":"int32"},{"internalType":"string","name":"tokens","type":"string"},{"internalType":"string","name":"delegatorShares","type":"string"},{"internalType":"string","name":"description","type":"string"},{"internalType":"int64","name":"unbondingHeight","type":"int64"},{"internalType":"int64","name":"unbondingTime","type":"int64"},{"internalType":"string","name":"commissionRate","type":"string"},{"internalType":"string","name":"commissionMaxRate","type":"string"},{"internalType":"string","name":"commissionMaxChangeRate","type":"string"},{"internalType":"int64","name":"commissionUpdateTime","type":"int64"},{"internalType":"string","name":"minSelfDelegation","type":"string"}],"internalType":"struct IStaking.Validator","name":"validator","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"validatorAddress","type":"string"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"name":"validatorDelegations","outputs":[{"components":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IStaking.Balance","name":"balance","type":"tuple"},{"components":[{"internalType":"string","name":"delegator_address","type":"string"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"validator_address","type":"string"}],"internalType":"struct IStaking.DelegationDetails","name":"delegation","type":"tuple"}],"internalType":"struct IStaking.Delegation[]","name":"delegations","type":"tuple[]"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"internalType":"struct IStaking.DelegationsResponse","name":"response","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"validatorAddress","type":"string"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"name":"validatorUnbondingDelegations","outputs":[{"components":[{"components":[{"internalType":"string","name":"delegatorAddress","type":"string"},{"internalType":"string","name":"validatorAddress","type":"string"},{"components":[{"internalType":"int64","name":"creationHeight","type":"int64"},{"internalType":"int64","name":"completionTime","type":"int64"},{"internalType":"string","name":"initialBalance","type":"string"},{"internalType":"string","name":"balance","type":"string"}],"internalType":"struct IStaking.UnbondingDelegationEntry[]","name":"entries","type":"tuple[]"}],"internalType":"struct IStaking.UnbondingDelegation[]","name":"unbondingDelegations","type":"tuple[]"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"internalType":"struct IStaking.UnbondingDelegationsResponse","name":"response","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"status","type":"string"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"name":"validators","outputs":[{"components":[{"components":[{"internalType":"string","name":"operatorAddress","type":"string"},{"internalType":"string","name":"consensusPubkey","type":"string"},{"internalType":"bool","name":"jailed","type":"bool"},{"internalType":"int32","name":"status","type":"int32"},{"internalType":"string","name":"tokens","type":"string"},{"internalType":"string","name":"delegatorShares","type":"string"},{"internalType":"string","name":"description","type":"string"},{"internalType":"int64","name":"unbondingHeight","type":"int64"},{"internalType":"int64","name":"unbondingTime","type":"int64"},{"internalType":"string","name":"commissionRate","type":"string"},{"internalType":"string","name":"commissionMaxRate","type":"string"},{"internalType":"string","name":"commissionMaxChangeRate","type":"string"},{"internalType":"int64","name":"commissionUpdateTime","type":"int64"},{"internalType":"string","name":"minSelfDelegation","type":"string"}],"internalType":"struct IStaking.Validator[]","name":"validators","type":"tuple[]"},{"internalType":"bytes","name":"nextKey","type":"bytes"}],"internalType":"struct IStaking.ValidatorsResponse","name":"response","type":"tuple"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 5c744c4120..e7cdb7d66e 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -1,7 +1,6 @@ package staking import ( - "bytes" "embed" "encoding/hex" "errors" @@ -9,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -20,12 +20,25 @@ import ( ) const ( - DelegateMethod = "delegate" - RedelegateMethod = "redelegate" - UndelegateMethod = "undelegate" - DelegationMethod = "delegation" - CreateValidatorMethod = "createValidator" - EditValidatorMethod = "editValidator" + DelegateMethod = "delegate" + RedelegateMethod = "redelegate" + UndelegateMethod = "undelegate" + DelegationMethod = "delegation" + CreateValidatorMethod = "createValidator" + EditValidatorMethod = "editValidator" + ValidatorsMethod = "validators" + ValidatorMethod = "validator" + ValidatorDelegationsMethod = "validatorDelegations" + ValidatorUnbondingDelegationsMethod = "validatorUnbondingDelegations" + UnbondingDelegationMethod = "unbondingDelegation" + DelegatorDelegationsMethod = "delegatorDelegations" + DelegatorValidatorMethod = "delegatorValidator" + DelegatorUnbondingDelegationsMethod = "delegatorUnbondingDelegations" + RedelegationsMethod = "redelegations" + DelegatorValidatorsMethod = "delegatorValidators" + HistoricalInfoMethod = "historicalInfo" + PoolMethod = "pool" + ParamsMethod = "params" ) const ( @@ -44,15 +57,28 @@ type PrecompileExecutor struct { bankKeeper utils.BankKeeper address common.Address - DelegateID []byte - RedelegateID []byte - UndelegateID []byte - DelegationID []byte - CreateValidatorID []byte - EditValidatorID []byte + DelegateID []byte + RedelegateID []byte + UndelegateID []byte + DelegationID []byte + CreateValidatorID []byte + EditValidatorID []byte + ValidatorsID []byte + ValidatorID []byte + ValidatorDelegationsID []byte + ValidatorUnbondingDelegationsID []byte + UnbondingDelegationID []byte + DelegatorDelegationsID []byte + DelegatorValidatorID []byte + DelegatorUnbondingDelegationsID []byte + RedelegationsID []byte + DelegatorValidatorsID []byte + HistoricalInfoID []byte + PoolID []byte + ParamsID []byte } -func NewPrecompile(keepers utils.Keepers) (*pcommon.Precompile, error) { +func NewPrecompile(keepers utils.Keepers) (*pcommon.DynamicGasPrecompile, error) { newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{ @@ -77,85 +103,122 @@ func NewPrecompile(keepers utils.Keepers) (*pcommon.Precompile, error) { p.CreateValidatorID = m.ID case EditValidatorMethod: p.EditValidatorID = m.ID + case ValidatorsMethod: + p.ValidatorsID = m.ID + case ValidatorMethod: + p.ValidatorID = m.ID + case ValidatorDelegationsMethod: + p.ValidatorDelegationsID = m.ID + case ValidatorUnbondingDelegationsMethod: + p.ValidatorUnbondingDelegationsID = m.ID + case UnbondingDelegationMethod: + p.UnbondingDelegationID = m.ID + case DelegatorDelegationsMethod: + p.DelegatorDelegationsID = m.ID + case DelegatorValidatorMethod: + p.DelegatorValidatorID = m.ID + case DelegatorUnbondingDelegationsMethod: + p.DelegatorUnbondingDelegationsID = m.ID + case RedelegationsMethod: + p.RedelegationsID = m.ID + case DelegatorValidatorsMethod: + p.DelegatorValidatorsID = m.ID + case HistoricalInfoMethod: + p.HistoricalInfoID = m.ID + case PoolMethod: + p.PoolID = m.ID + case ParamsMethod: + p.ParamsID = m.ID } } - return pcommon.NewPrecompile(newAbi, p, p.address, "staking"), nil + return pcommon.NewDynamicGasPrecompile(newAbi, p, p.address, "staking"), nil } -// RequiredGas returns the required bare minimum gas to execute the precompile. -func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { - if bytes.Equal(method.ID, p.DelegateID) { - return 50000 - } else if bytes.Equal(method.ID, p.RedelegateID) { - return 70000 - } else if bytes.Equal(method.ID, p.UndelegateID) { - return 50000 - } else if bytes.Equal(method.ID, p.CreateValidatorID) { - return 100000 - } else if bytes.Equal(method.ID, p.EditValidatorID) { - return 100000 - } else if bytes.Equal(method.ID, p.DelegationID) { - return pcommon.DefaultGasCost(input, false) - } - // This should never happen since this is going to fail during Run - return pcommon.UnknownMethodCallGas +func (p PrecompileExecutor) EVMKeeper() utils.EVMKeeper { + return p.evmKeeper } -func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, hooks *tracing.Hooks) (bz []byte, err error) { +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64, hooks *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { if ctx.EVMPrecompileCalledFromDelegateCall() { - return nil, errors.New("cannot delegatecall staking") + return nil, 0, errors.New("cannot delegatecall staking") } switch method.Name { case DelegateMethod: if readOnly { - return nil, errors.New("cannot call staking precompile from staticcall") + return nil, 0, errors.New("cannot call staking precompile from staticcall") } return p.delegate(ctx, method, caller, args, value, hooks, evm) case RedelegateMethod: if readOnly { - return nil, errors.New("cannot call staking precompile from staticcall") + return nil, 0, errors.New("cannot call staking precompile from staticcall") } return p.redelegate(ctx, method, caller, args, value, evm) case UndelegateMethod: if readOnly { - return nil, errors.New("cannot call staking precompile from staticcall") + return nil, 0, errors.New("cannot call staking precompile from staticcall") } return p.undelegate(ctx, method, caller, args, value, evm) case CreateValidatorMethod: if readOnly { - return nil, errors.New("cannot call staking precompile from staticcall") + return nil, 0, errors.New("cannot call staking precompile from staticcall") } return p.createValidator(ctx, method, caller, args, value, hooks, evm) case EditValidatorMethod: if readOnly { - return nil, errors.New("cannot call staking precompile from staticcall") + return nil, 0, errors.New("cannot call staking precompile from staticcall") } return p.editValidator(ctx, method, caller, args, value, hooks, evm) case DelegationMethod: return p.delegation(ctx, method, args, value) + case ValidatorsMethod: + return p.validators(ctx, method, args, value) + case ValidatorMethod: + return p.validator(ctx, method, args, value) + case ValidatorDelegationsMethod: + return p.validatorDelegations(ctx, method, args, value) + case ValidatorUnbondingDelegationsMethod: + return p.validatorUnbondingDelegations(ctx, method, args, value) + case UnbondingDelegationMethod: + return p.unbondingDelegation(ctx, method, args, value) + case DelegatorDelegationsMethod: + return p.delegatorDelegations(ctx, method, args, value) + case DelegatorValidatorMethod: + return p.delegatorValidator(ctx, method, args, value) + case DelegatorUnbondingDelegationsMethod: + return p.delegatorUnbondingDelegations(ctx, method, args, value) + case RedelegationsMethod: + return p.redelegations(ctx, method, args, value) + case DelegatorValidatorsMethod: + return p.delegatorValidators(ctx, method, args, value) + case HistoricalInfoMethod: + return p.historicalInfo(ctx, method, args, value) + case PoolMethod: + return p.pool(ctx, method, args, value) + case ParamsMethod: + return p.params(ctx, method, args, value) } return } -func (p PrecompileExecutor) delegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, hooks *tracing.Hooks, evm *vm.EVM) ([]byte, error) { +func (p PrecompileExecutor) delegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, hooks *tracing.Hooks, evm *vm.EVM) ([]byte, uint64, error) { if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, err + return nil, 0, err } // if delegator is associated, then it must have Account set already // if delegator is not associated, then it can't delegate anyway (since // there is no good way to merge delegations if it becomes associated) delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) if !associated { - return nil, types.NewAssociationMissingErr(caller.Hex()) + return nil, 0, types.NewAssociationMissingErr(caller.Hex()) } validatorBech32 := args[0].(string) if value == nil || value.Sign() == 0 { - return nil, errors.New("set `value` field to non-zero to send delegate fund") + return nil, 0, errors.New("set `value` field to non-zero to send delegate fund") } coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), delegator, value, p.bankKeeper, p.evmKeeper, hooks, evm.GetDepth()) if err != nil { - return nil, err + return nil, 0, err } _, err = p.stakingKeeper.Delegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgDelegate{ DelegatorAddress: delegator.String(), @@ -163,7 +226,7 @@ func (p PrecompileExecutor) delegate(ctx sdk.Context, method *abi.Method, caller Amount: coin, }) if err != nil { - return nil, err + return nil, 0, err } // Emit EVM event @@ -172,20 +235,24 @@ func (p PrecompileExecutor) delegate(ctx sdk.Context, method *abi.Method, caller ctx.Logger().Error("Failed to emit EVM delegate event", "error", emitErr) } - return method.Outputs.Pack(true) + bz, err := method.Outputs.Pack(true) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil } -func (p PrecompileExecutor) redelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) ([]byte, error) { +func (p PrecompileExecutor) redelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) ([]byte, uint64, error) { if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err + return nil, 0, err } if err := pcommon.ValidateArgsLength(args, 3); err != nil { - return nil, err + return nil, 0, err } delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) if !associated { - return nil, types.NewAssociationMissingErr(caller.Hex()) + return nil, 0, types.NewAssociationMissingErr(caller.Hex()) } srcValidatorBech32 := args[0].(string) dstValidatorBech32 := args[1].(string) @@ -197,7 +264,7 @@ func (p PrecompileExecutor) redelegate(ctx sdk.Context, method *abi.Method, call Amount: sdk.NewCoin(sdk.MustGetBaseDenom(), sdk.NewIntFromBigInt(amount)), }) if err != nil { - return nil, err + return nil, 0, err } // Emit EVM event @@ -206,20 +273,24 @@ func (p PrecompileExecutor) redelegate(ctx sdk.Context, method *abi.Method, call ctx.Logger().Error("Failed to emit EVM redelegate event", "error", emitErr) } - return method.Outputs.Pack(true) + bz, err := method.Outputs.Pack(true) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil } -func (p PrecompileExecutor) undelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) ([]byte, error) { +func (p PrecompileExecutor) undelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) ([]byte, uint64, error) { if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err + return nil, 0, err } if err := pcommon.ValidateArgsLength(args, 2); err != nil { - return nil, err + return nil, 0, err } delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) if !associated { - return nil, types.NewAssociationMissingErr(caller.Hex()) + return nil, 0, types.NewAssociationMissingErr(caller.Hex()) } validatorBech32 := args[0].(string) amount := args[1].(*big.Int) @@ -229,7 +300,7 @@ func (p PrecompileExecutor) undelegate(ctx sdk.Context, method *abi.Method, call Amount: sdk.NewCoin(p.evmKeeper.GetBaseDenom(ctx), sdk.NewIntFromBigInt(amount)), }) if err != nil { - return nil, err + return nil, 0, err } // Emit EVM event @@ -238,7 +309,11 @@ func (p PrecompileExecutor) undelegate(ctx sdk.Context, method *abi.Method, call ctx.Logger().Error("Failed to emit EVM undelegate event", "error", emitErr) } - return method.Outputs.Pack(true) + bz, err := method.Outputs.Pack(true) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil } type Delegation struct { @@ -258,18 +333,18 @@ type DelegationDetails struct { ValidatorAddress string } -func (p PrecompileExecutor) delegation(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { +func (p PrecompileExecutor) delegation(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err + return nil, 0, err } if err := pcommon.ValidateArgsLength(args, 2); err != nil { - return nil, err + return nil, 0, err } seiDelegatorAddress, err := pcommon.GetSeiAddressFromArg(ctx, args[0], p.evmKeeper) if err != nil { - return nil, err + return nil, 0, err } validatorBech32 := args[1].(string) @@ -280,7 +355,7 @@ func (p PrecompileExecutor) delegation(ctx sdk.Context, method *abi.Method, args delegationResponse, err := p.stakingQuerier.Delegation(sdk.WrapSDKContext(ctx), delegationRequest) if err != nil { - return nil, err + return nil, 0, err } delegation := Delegation{ @@ -296,12 +371,104 @@ func (p PrecompileExecutor) delegation(ctx sdk.Context, method *abi.Method, args }, } - return method.Outputs.Pack(delegation) + bz, err := method.Outputs.Pack(delegation) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +type ValidatorsResponse struct { + Validators []Validator + NextKey []byte } -func (p PrecompileExecutor) createValidator(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, hooks *tracing.Hooks, evm *vm.EVM) ([]byte, error) { +type DelegationsResponse struct { + Delegations []Delegation + NextKey []byte +} + +type UnbondingDelegationsResponse struct { + UnbondingDelegations []UnbondingDelegation + NextKey []byte +} + +type RedelegationsResponse struct { + Redelegations []Redelegation + NextKey []byte +} + +type Validator struct { + OperatorAddress string + ConsensusPubkey string + Jailed bool + Status int32 + Tokens string + DelegatorShares string + Description string + UnbondingHeight int64 + UnbondingTime int64 + CommissionRate string + CommissionMaxRate string + CommissionMaxChangeRate string + CommissionUpdateTime int64 + MinSelfDelegation string +} + +func (p PrecompileExecutor) validators(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, 0, err + } + + validatorsRequest := &stakingtypes.QueryValidatorsRequest{ + Status: args[0].(string), + Pagination: &query.PageRequest{ + Key: args[1].([]byte), + }, + } + + validatorsResponse, err := p.stakingQuerier.Validators(sdk.WrapSDKContext(ctx), validatorsRequest) + if err != nil { + return nil, 0, err + } + + res := ValidatorsResponse{ + Validators: make([]Validator, len(validatorsResponse.Validators)), + NextKey: validatorsResponse.Pagination.NextKey, + } + for i, validator := range validatorsResponse.Validators { + res.Validators[i] = Validator{ + OperatorAddress: validator.OperatorAddress, + ConsensusPubkey: string(validator.ConsensusPubkey.Value), + Jailed: validator.Jailed, + Status: int32(validator.Status), + Tokens: validator.Tokens.String(), + DelegatorShares: validator.DelegatorShares.String(), + Description: validator.Description.String(), + UnbondingHeight: validator.UnbondingHeight, + UnbondingTime: validator.UnbondingTime.Unix(), + CommissionRate: validator.Commission.Rate.String(), + CommissionMaxRate: validator.Commission.MaxRate.String(), + CommissionMaxChangeRate: validator.Commission.MaxChangeRate.String(), + CommissionUpdateTime: validator.Commission.UpdateTime.Unix(), + MinSelfDelegation: validator.MinSelfDelegation.String(), + } + } + + bz, err := method.Outputs.Pack(res) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) createValidator(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, hooks *tracing.Hooks, evm *vm.EVM) ([]byte, uint64, error) { if err := pcommon.ValidateArgsLength(args, 6); err != nil { - return nil, err + return nil, 0, err } // Extract arguments @@ -315,13 +482,13 @@ func (p PrecompileExecutor) createValidator(ctx sdk.Context, method *abi.Method, // Get validator address (caller's associated Sei address) valAddress, associated := p.evmKeeper.GetSeiAddress(ctx, caller) if !associated { - return nil, types.NewAssociationMissingErr(caller.Hex()) + return nil, 0, types.NewAssociationMissingErr(caller.Hex()) } // Parse public key from hex pubKeyBytes, err := hex.DecodeString(pubKeyHex) if err != nil { - return nil, errors.New("invalid public key hex format") + return nil, 0, errors.New("invalid public key hex format") } // Create ed25519 public key @@ -330,28 +497,28 @@ func (p PrecompileExecutor) createValidator(ctx sdk.Context, method *abi.Method, // Parse commission rates commissionRate, err := sdk.NewDecFromStr(commissionRateStr) if err != nil { - return nil, errors.New("invalid commission rate") + return nil, 0, errors.New("invalid commission rate") } commissionMaxRate, err := sdk.NewDecFromStr(commissionMaxRateStr) if err != nil { - return nil, errors.New("invalid commission max rate") + return nil, 0, errors.New("invalid commission max rate") } commissionMaxChangeRate, err := sdk.NewDecFromStr(commissionMaxChangeRateStr) if err != nil { - return nil, errors.New("invalid commission max change rate") + return nil, 0, errors.New("invalid commission max change rate") } commission := stakingtypes.NewCommissionRates(commissionRate, commissionMaxRate, commissionMaxChangeRate) if value == nil || value.Sign() == 0 { - return nil, errors.New("set `value` field to non-zero to send delegate fund") + return nil, 0, errors.New("set `value` field to non-zero to send delegate fund") } // Validate minimum self delegation if minSelfDelegation == nil || minSelfDelegation.Sign() <= 0 { - return nil, errors.New("minimum self delegation must be a positive integer: invalid request") + return nil, 0, errors.New("minimum self delegation must be a positive integer: invalid request") } coin, err := pcommon.HandlePaymentUsei( @@ -364,7 +531,7 @@ func (p PrecompileExecutor) createValidator(ctx sdk.Context, method *abi.Method, hooks, evm.GetDepth()) if err != nil { - return nil, err + return nil, 0, err } description := stakingtypes.NewDescription(moniker, "", "", "", "") @@ -378,16 +545,16 @@ func (p PrecompileExecutor) createValidator(ctx sdk.Context, method *abi.Method, sdk.NewIntFromBigInt(minSelfDelegation), ) if err != nil { - return nil, err + return nil, 0, err } if err := msg.ValidateBasic(); err != nil { - return nil, err + return nil, 0, err } _, err = p.stakingKeeper.CreateValidator(sdk.WrapSDKContext(ctx), msg) if err != nil { - return nil, err + return nil, 0, err } // Emit EVM event @@ -396,16 +563,20 @@ func (p PrecompileExecutor) createValidator(ctx sdk.Context, method *abi.Method, ctx.Logger().Error("Failed to emit EVM validator created event", "error", emitErr) } - return method.Outputs.Pack(true) + bz, err := method.Outputs.Pack(true) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil } -func (p PrecompileExecutor) editValidator(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, hooks *tracing.Hooks, evm *vm.EVM) ([]byte, error) { +func (p PrecompileExecutor) editValidator(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int, hooks *tracing.Hooks, evm *vm.EVM) ([]byte, uint64, error) { if err := pcommon.ValidateArgsLength(args, 3); err != nil { - return nil, err + return nil, 0, err } if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err + return nil, 0, err } // Extract arguments @@ -416,7 +587,7 @@ func (p PrecompileExecutor) editValidator(ctx sdk.Context, method *abi.Method, c // Get validator address (caller's associated Sei address) valAddress, associated := p.evmKeeper.GetSeiAddress(ctx, caller) if !associated { - return nil, types.NewAssociationMissingErr(caller.Hex()) + return nil, 0, types.NewAssociationMissingErr(caller.Hex()) } // Parse commission rate if provided @@ -424,7 +595,7 @@ func (p PrecompileExecutor) editValidator(ctx sdk.Context, method *abi.Method, c if commissionRateStr != "" { rate, err := sdk.NewDecFromStr(commissionRateStr) if err != nil { - return nil, errors.New("invalid commission rate") + return nil, 0, errors.New("invalid commission rate") } commissionRate = &rate } @@ -452,12 +623,12 @@ func (p PrecompileExecutor) editValidator(ctx sdk.Context, method *abi.Method, c ) if err := msg.ValidateBasic(); err != nil { - return nil, err + return nil, 0, err } _, err := p.stakingKeeper.EditValidator(sdk.WrapSDKContext(ctx), msg) if err != nil { - return nil, err + return nil, 0, err } // Emit EVM event @@ -466,5 +637,607 @@ func (p PrecompileExecutor) editValidator(ctx sdk.Context, method *abi.Method, c ctx.Logger().Error("Failed to emit EVM validator edited event", "error", emitErr) } - return method.Outputs.Pack(true) + bz, err := method.Outputs.Pack(true) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) validator(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + validatorBech32 := args[0].(string) + validatorRequest := &stakingtypes.QueryValidatorRequest{ + ValidatorAddr: validatorBech32, + } + + validatorResponse, err := p.stakingQuerier.Validator(sdk.WrapSDKContext(ctx), validatorRequest) + if err != nil { + return nil, 0, err + } + + validator := convertValidatorToPrecompileType(validatorResponse.Validator) + bz, err := method.Outputs.Pack(validator) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) validatorDelegations(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, 0, err + } + + validatorBech32 := args[0].(string) + nextKey := args[1].([]byte) + + request := &stakingtypes.QueryValidatorDelegationsRequest{ + ValidatorAddr: validatorBech32, + Pagination: &query.PageRequest{ + Key: nextKey, + }, + } + + response, err := p.stakingQuerier.ValidatorDelegations(sdk.WrapSDKContext(ctx), request) + if err != nil { + return nil, 0, err + } + + delegations := make([]Delegation, len(response.DelegationResponses)) + for i, dr := range response.DelegationResponses { + delegations[i] = Delegation{ + Balance: Balance{ + Amount: dr.Balance.Amount.BigInt(), + Denom: dr.Balance.Denom, + }, + Delegation: DelegationDetails{ + DelegatorAddress: dr.Delegation.DelegatorAddress, + Shares: dr.Delegation.Shares.BigInt(), + Decimals: big.NewInt(sdk.Precision), + ValidatorAddress: dr.Delegation.ValidatorAddress, + }, + } + } + + result := DelegationsResponse{ + Delegations: delegations, + NextKey: response.Pagination.NextKey, + } + + bz, err := method.Outputs.Pack(result) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) validatorUnbondingDelegations(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, 0, err + } + + validatorBech32 := args[0].(string) + nextKey := args[1].([]byte) + + request := &stakingtypes.QueryValidatorUnbondingDelegationsRequest{ + ValidatorAddr: validatorBech32, + Pagination: &query.PageRequest{ + Key: nextKey, + }, + } + + response, err := p.stakingQuerier.ValidatorUnbondingDelegations(sdk.WrapSDKContext(ctx), request) + if err != nil { + return nil, 0, err + } + + unbondingDelegations := make([]UnbondingDelegation, len(response.UnbondingResponses)) + for i, ubd := range response.UnbondingResponses { + entries := make([]UnbondingDelegationEntry, len(ubd.Entries)) + for j, entry := range ubd.Entries { + entries[j] = UnbondingDelegationEntry{ + CreationHeight: entry.CreationHeight, + CompletionTime: entry.CompletionTime.Unix(), + InitialBalance: entry.InitialBalance.String(), + Balance: entry.Balance.String(), + } + } + unbondingDelegations[i] = UnbondingDelegation{ + DelegatorAddress: ubd.DelegatorAddress, + ValidatorAddress: ubd.ValidatorAddress, + Entries: entries, + } + } + + result := UnbondingDelegationsResponse{ + UnbondingDelegations: unbondingDelegations, + NextKey: response.Pagination.NextKey, + } + + bz, err := method.Outputs.Pack(result) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) unbondingDelegation(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, 0, err + } + + seiDelegatorAddress, err := pcommon.GetSeiAddressFromArg(ctx, args[0], p.evmKeeper) + if err != nil { + return nil, 0, err + } + + validatorBech32 := args[1].(string) + request := &stakingtypes.QueryUnbondingDelegationRequest{ + DelegatorAddr: seiDelegatorAddress.String(), + ValidatorAddr: validatorBech32, + } + + response, err := p.stakingQuerier.UnbondingDelegation(sdk.WrapSDKContext(ctx), request) + if err != nil { + return nil, 0, err + } + + entries := make([]UnbondingDelegationEntry, len(response.Unbond.Entries)) + for i, entry := range response.Unbond.Entries { + entries[i] = UnbondingDelegationEntry{ + CreationHeight: entry.CreationHeight, + CompletionTime: entry.CompletionTime.Unix(), + InitialBalance: entry.InitialBalance.String(), + Balance: entry.Balance.String(), + } + } + + unbondingDelegation := UnbondingDelegation{ + DelegatorAddress: response.Unbond.DelegatorAddress, + ValidatorAddress: response.Unbond.ValidatorAddress, + Entries: entries, + } + + bz, err := method.Outputs.Pack(unbondingDelegation) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) delegatorDelegations(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, 0, err + } + + seiDelegatorAddress, err := pcommon.GetSeiAddressFromArg(ctx, args[0], p.evmKeeper) + if err != nil { + return nil, 0, err + } + + nextKey := args[1].([]byte) + request := &stakingtypes.QueryDelegatorDelegationsRequest{ + DelegatorAddr: seiDelegatorAddress.String(), + Pagination: &query.PageRequest{ + Key: nextKey, + }, + } + + response, err := p.stakingQuerier.DelegatorDelegations(sdk.WrapSDKContext(ctx), request) + if err != nil { + return nil, 0, err + } + + delegations := make([]Delegation, len(response.DelegationResponses)) + for i, dr := range response.DelegationResponses { + delegations[i] = Delegation{ + Balance: Balance{ + Amount: dr.Balance.Amount.BigInt(), + Denom: dr.Balance.Denom, + }, + Delegation: DelegationDetails{ + DelegatorAddress: dr.Delegation.DelegatorAddress, + Shares: dr.Delegation.Shares.BigInt(), + Decimals: big.NewInt(sdk.Precision), + ValidatorAddress: dr.Delegation.ValidatorAddress, + }, + } + } + + result := DelegationsResponse{ + Delegations: delegations, + NextKey: response.Pagination.NextKey, + } + + bz, err := method.Outputs.Pack(result) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) delegatorValidator(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, 0, err + } + + seiDelegatorAddress, err := pcommon.GetSeiAddressFromArg(ctx, args[0], p.evmKeeper) + if err != nil { + return nil, 0, err + } + + validatorBech32 := args[1].(string) + request := &stakingtypes.QueryDelegatorValidatorRequest{ + DelegatorAddr: seiDelegatorAddress.String(), + ValidatorAddr: validatorBech32, + } + + response, err := p.stakingQuerier.DelegatorValidator(sdk.WrapSDKContext(ctx), request) + if err != nil { + return nil, 0, err + } + + validator := convertValidatorToPrecompileType(response.Validator) + bz, err := method.Outputs.Pack(validator) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) delegatorUnbondingDelegations(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, 0, err + } + + seiDelegatorAddress, err := pcommon.GetSeiAddressFromArg(ctx, args[0], p.evmKeeper) + if err != nil { + return nil, 0, err + } + + nextKey := args[1].([]byte) + request := &stakingtypes.QueryDelegatorUnbondingDelegationsRequest{ + DelegatorAddr: seiDelegatorAddress.String(), + Pagination: &query.PageRequest{ + Key: nextKey, + }, + } + + response, err := p.stakingQuerier.DelegatorUnbondingDelegations(sdk.WrapSDKContext(ctx), request) + if err != nil { + return nil, 0, err + } + + unbondingDelegations := make([]UnbondingDelegation, len(response.UnbondingResponses)) + for i, ubd := range response.UnbondingResponses { + entries := make([]UnbondingDelegationEntry, len(ubd.Entries)) + for j, entry := range ubd.Entries { + entries[j] = UnbondingDelegationEntry{ + CreationHeight: entry.CreationHeight, + CompletionTime: entry.CompletionTime.Unix(), + InitialBalance: entry.InitialBalance.String(), + Balance: entry.Balance.String(), + } + } + unbondingDelegations[i] = UnbondingDelegation{ + DelegatorAddress: ubd.DelegatorAddress, + ValidatorAddress: ubd.ValidatorAddress, + Entries: entries, + } + } + + result := UnbondingDelegationsResponse{ + UnbondingDelegations: unbondingDelegations, + NextKey: response.Pagination.NextKey, + } + + bz, err := method.Outputs.Pack(result) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) redelegations(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, 0, err + } + + delegatorStr := args[0].(string) + srcValidatorStr := args[1].(string) + dstValidatorStr := args[2].(string) + nextKey := args[3].([]byte) + + request := &stakingtypes.QueryRedelegationsRequest{ + DelegatorAddr: delegatorStr, + SrcValidatorAddr: srcValidatorStr, + DstValidatorAddr: dstValidatorStr, + Pagination: &query.PageRequest{ + Key: nextKey, + }, + } + + response, err := p.stakingQuerier.Redelegations(sdk.WrapSDKContext(ctx), request) + if err != nil { + return nil, 0, err + } + + redelegations := make([]Redelegation, len(response.RedelegationResponses)) + for i, redel := range response.RedelegationResponses { + entries := make([]RedelegationEntry, len(redel.Entries)) + for j, entry := range redel.Entries { + entries[j] = RedelegationEntry{ + CreationHeight: entry.RedelegationEntry.CreationHeight, + CompletionTime: entry.RedelegationEntry.CompletionTime.Unix(), + InitialBalance: entry.RedelegationEntry.InitialBalance.String(), + SharesDst: entry.Balance.String(), + } + } + redelegations[i] = Redelegation{ + DelegatorAddress: redel.Redelegation.DelegatorAddress, + ValidatorSrcAddress: redel.Redelegation.ValidatorSrcAddress, + ValidatorDstAddress: redel.Redelegation.ValidatorDstAddress, + Entries: entries, + } + } + + result := RedelegationsResponse{ + Redelegations: redelegations, + NextKey: response.Pagination.NextKey, + } + + bz, err := method.Outputs.Pack(result) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) delegatorValidators(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, 0, err + } + + seiDelegatorAddress, err := pcommon.GetSeiAddressFromArg(ctx, args[0], p.evmKeeper) + if err != nil { + return nil, 0, err + } + + nextKey := args[1].([]byte) + request := &stakingtypes.QueryDelegatorValidatorsRequest{ + DelegatorAddr: seiDelegatorAddress.String(), + Pagination: &query.PageRequest{ + Key: nextKey, + }, + } + + response, err := p.stakingQuerier.DelegatorValidators(sdk.WrapSDKContext(ctx), request) + if err != nil { + return nil, 0, err + } + + validators := make([]Validator, len(response.Validators)) + for i, val := range response.Validators { + validators[i] = convertValidatorToPrecompileType(val) + } + + result := ValidatorsResponse{ + Validators: validators, + NextKey: response.Pagination.NextKey, + } + + bz, err := method.Outputs.Pack(result) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) historicalInfo(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + height := args[0].(int64) + request := &stakingtypes.QueryHistoricalInfoRequest{ + Height: height, + } + + response, err := p.stakingQuerier.HistoricalInfo(sdk.WrapSDKContext(ctx), request) + if err != nil { + return nil, 0, err + } + + if response.Hist == nil { + return nil, 0, errors.New("historical info not found") + } + + validators := make([]Validator, len(response.Hist.Valset)) + for i, val := range response.Hist.Valset { + validators[i] = convertValidatorToPrecompileType(val) + } + + historicalInfo := HistoricalInfo{ + Height: height, // Use the requested height + Validators: validators, + } + + bz, err := method.Outputs.Pack(historicalInfo) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) pool(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 0); err != nil { + return nil, 0, err + } + + request := &stakingtypes.QueryPoolRequest{} + response, err := p.stakingQuerier.Pool(sdk.WrapSDKContext(ctx), request) + if err != nil { + return nil, 0, err + } + + pool := Pool{ + NotBondedTokens: response.Pool.NotBondedTokens.String(), + BondedTokens: response.Pool.BondedTokens.String(), + } + + bz, err := method.Outputs.Pack(pool) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +func (p PrecompileExecutor) params(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 0); err != nil { + return nil, 0, err + } + + request := &stakingtypes.QueryParamsRequest{} + response, err := p.stakingQuerier.Params(sdk.WrapSDKContext(ctx), request) + if err != nil { + return nil, 0, err + } + + params := Params{ + UnbondingTime: uint64(response.Params.UnbondingTime.Seconds()), + MaxValidators: response.Params.MaxValidators, + MaxEntries: response.Params.MaxEntries, + HistoricalEntries: response.Params.HistoricalEntries, + BondDenom: response.Params.BondDenom, + MinCommissionRate: response.Params.MinCommissionRate.String(), + MaxVotingPowerRatio: response.Params.MaxVotingPowerRatio.String(), + MaxVotingPowerEnforcementThreshold: response.Params.MaxVotingPowerEnforcementThreshold.String(), + } + + bz, err := method.Outputs.Pack(params) + if err != nil { + return nil, 0, err + } + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), nil +} + +// Helper function to convert stakingtypes.Validator to precompile Validator type +func convertValidatorToPrecompileType(val stakingtypes.Validator) Validator { + return Validator{ + OperatorAddress: val.OperatorAddress, + ConsensusPubkey: string(val.ConsensusPubkey.Value), + Jailed: val.Jailed, + Status: int32(val.Status), + Tokens: val.Tokens.String(), + DelegatorShares: val.DelegatorShares.String(), + Description: val.Description.String(), + UnbondingHeight: val.UnbondingHeight, + UnbondingTime: val.UnbondingTime.Unix(), + CommissionRate: val.Commission.Rate.String(), + CommissionMaxRate: val.Commission.MaxRate.String(), + CommissionMaxChangeRate: val.Commission.MaxChangeRate.String(), + CommissionUpdateTime: val.Commission.UpdateTime.Unix(), + MinSelfDelegation: val.MinSelfDelegation.String(), + } +} + +// Additional types for new query methods +type UnbondingDelegationEntry struct { + CreationHeight int64 + CompletionTime int64 + InitialBalance string + Balance string +} + +type UnbondingDelegation struct { + DelegatorAddress string + ValidatorAddress string + Entries []UnbondingDelegationEntry +} + +type RedelegationEntry struct { + CreationHeight int64 + CompletionTime int64 + InitialBalance string + SharesDst string +} + +type Redelegation struct { + DelegatorAddress string + ValidatorSrcAddress string + ValidatorDstAddress string + Entries []RedelegationEntry +} + +type HistoricalInfo struct { + Height int64 + Validators []Validator +} + +type Pool struct { + NotBondedTokens string + BondedTokens string +} + +type Params struct { + UnbondingTime uint64 + MaxValidators uint32 + MaxEntries uint32 + HistoricalEntries uint32 + BondDenom string + MinCommissionRate string + MaxVotingPowerRatio string + MaxVotingPowerEnforcementThreshold string } diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index a25087b21e..aad2e93658 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -6,14 +6,17 @@ import ( "embed" "encoding/hex" "fmt" + "math" "math/big" "reflect" "testing" "time" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" crptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/cosmos-sdk/x/staking/teststaking" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -261,14 +264,79 @@ func setupValidator(t *testing.T, ctx sdk.Context, a *app.App, bondStatus stakin } type TestStakingQuerier struct { - Response *stakingtypes.QueryDelegationResponse - Err error + Response *stakingtypes.QueryDelegationResponse + ValidatorsResponse *stakingtypes.QueryValidatorsResponse + ValidatorResponse *stakingtypes.QueryValidatorResponse + ValidatorDelegationsResponse *stakingtypes.QueryValidatorDelegationsResponse + ValidatorUnbondingDelegationsResponse *stakingtypes.QueryValidatorUnbondingDelegationsResponse + UnbondingDelegationResponse *stakingtypes.QueryUnbondingDelegationResponse + DelegatorDelegationsResponse *stakingtypes.QueryDelegatorDelegationsResponse + DelegatorValidatorResponse *stakingtypes.QueryDelegatorValidatorResponse + DelegatorUnbondingDelegationsResponse *stakingtypes.QueryDelegatorUnbondingDelegationsResponse + RedelegationsResponse *stakingtypes.QueryRedelegationsResponse + DelegatorValidatorsResponse *stakingtypes.QueryDelegatorValidatorsResponse + HistoricalInfoResponse *stakingtypes.QueryHistoricalInfoResponse + PoolResponse *stakingtypes.QueryPoolResponse + ParamsResponse *stakingtypes.QueryParamsResponse + Err error } func (tq *TestStakingQuerier) Delegation(c context.Context, _ *stakingtypes.QueryDelegationRequest) (*stakingtypes.QueryDelegationResponse, error) { return tq.Response, tq.Err } +func (tq *TestStakingQuerier) Validators(c context.Context, _ *stakingtypes.QueryValidatorsRequest) (*stakingtypes.QueryValidatorsResponse, error) { + return tq.ValidatorsResponse, tq.Err +} + +func (tq *TestStakingQuerier) Validator(c context.Context, _ *stakingtypes.QueryValidatorRequest) (*stakingtypes.QueryValidatorResponse, error) { + return tq.ValidatorResponse, tq.Err +} + +func (tq *TestStakingQuerier) ValidatorDelegations(c context.Context, _ *stakingtypes.QueryValidatorDelegationsRequest) (*stakingtypes.QueryValidatorDelegationsResponse, error) { + return tq.ValidatorDelegationsResponse, tq.Err +} + +func (tq *TestStakingQuerier) ValidatorUnbondingDelegations(c context.Context, _ *stakingtypes.QueryValidatorUnbondingDelegationsRequest) (*stakingtypes.QueryValidatorUnbondingDelegationsResponse, error) { + return tq.ValidatorUnbondingDelegationsResponse, tq.Err +} + +func (tq *TestStakingQuerier) UnbondingDelegation(c context.Context, _ *stakingtypes.QueryUnbondingDelegationRequest) (*stakingtypes.QueryUnbondingDelegationResponse, error) { + return tq.UnbondingDelegationResponse, tq.Err +} + +func (tq *TestStakingQuerier) DelegatorDelegations(c context.Context, _ *stakingtypes.QueryDelegatorDelegationsRequest) (*stakingtypes.QueryDelegatorDelegationsResponse, error) { + return tq.DelegatorDelegationsResponse, tq.Err +} + +func (tq *TestStakingQuerier) DelegatorValidator(c context.Context, _ *stakingtypes.QueryDelegatorValidatorRequest) (*stakingtypes.QueryDelegatorValidatorResponse, error) { + return tq.DelegatorValidatorResponse, tq.Err +} + +func (tq *TestStakingQuerier) DelegatorUnbondingDelegations(c context.Context, _ *stakingtypes.QueryDelegatorUnbondingDelegationsRequest) (*stakingtypes.QueryDelegatorUnbondingDelegationsResponse, error) { + return tq.DelegatorUnbondingDelegationsResponse, tq.Err +} + +func (tq *TestStakingQuerier) Redelegations(c context.Context, _ *stakingtypes.QueryRedelegationsRequest) (*stakingtypes.QueryRedelegationsResponse, error) { + return tq.RedelegationsResponse, tq.Err +} + +func (tq *TestStakingQuerier) DelegatorValidators(c context.Context, _ *stakingtypes.QueryDelegatorValidatorsRequest) (*stakingtypes.QueryDelegatorValidatorsResponse, error) { + return tq.DelegatorValidatorsResponse, tq.Err +} + +func (tq *TestStakingQuerier) HistoricalInfo(c context.Context, _ *stakingtypes.QueryHistoricalInfoRequest) (*stakingtypes.QueryHistoricalInfoResponse, error) { + return tq.HistoricalInfoResponse, tq.Err +} + +func (tq *TestStakingQuerier) Pool(c context.Context, _ *stakingtypes.QueryPoolRequest) (*stakingtypes.QueryPoolResponse, error) { + return tq.PoolResponse, tq.Err +} + +func (tq *TestStakingQuerier) Params(c context.Context, _ *stakingtypes.QueryParamsRequest) (*stakingtypes.QueryParamsResponse, error) { + return tq.ParamsResponse, tq.Err +} + func TestPrecompile_Run_Delegation(t *testing.T) { callerSeiAddress, callerEvmAddress := testkeeper.MockAddressPair() _, unassociatedEvmAddress := testkeeper.MockAddressPair() @@ -304,176 +372,1599 @@ func TestPrecompile_Run_Delegation(t *testing.T) { happyPathPackedOutput, _ := delegationMethod.Outputs.Pack(delegation) - type fields struct { - Precompile pcommon.Precompile - stakingKeeper utils.StakingKeeper - stakingQuerier utils.StakingQuerier - evmKeeper utils.EVMKeeper + type fields struct { + Precompile pcommon.Precompile + stakingKeeper utils.StakingKeeper + stakingQuerier utils.StakingQuerier + evmKeeper utils.EVMKeeper + } + type args struct { + evm *vm.EVM + delegatorAddress common.Address + validatorAddress string + caller common.Address + callingContract common.Address + value *big.Int + readOnly bool + isFromDelegateCall bool + } + + tests := []struct { + name string + fields fields + args args + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + delegatorAddress: callerEvmAddress, + validatorAddress: validatorAddress, + value: big.NewInt(100), + }, + wantRet: happyPathPackedOutput, + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "fails if caller != callingContract", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + caller: callerEvmAddress, + callingContract: contractEvmAddress, + delegatorAddress: callerEvmAddress, + validatorAddress: validatorAddress, + value: big.NewInt(100), + isFromDelegateCall: true, + }, + wantErr: true, + wantErrMsg: "cannot delegatecall staking", + }, + { + name: "fails if delegator address unassociated", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + caller: callerEvmAddress, + callingContract: callerEvmAddress, + delegatorAddress: unassociatedEvmAddress, + validatorAddress: validatorAddress, + }, + wantErr: true, + wantErrMsg: fmt.Sprintf("address %s is not linked", unassociatedEvmAddress.String()), + }, + { + name: "fails if delegator address is invalid", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + delegatorAddress: common.Address{}, + validatorAddress: validatorAddress, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + }, + wantErr: true, + wantErrMsg: "invalid addr", + }, + { + name: "should return error if delegation not found", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Err: fmt.Errorf("delegation with delegator %s not found for validator", callerSeiAddress.String()), + }, + }, + args: args{ + delegatorAddress: callerEvmAddress, + validatorAddress: validatorAddress, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + }, + wantErr: true, + wantErrMsg: fmt.Sprintf("delegation with delegator %s not found for validator", callerSeiAddress.String()), + }, + { + name: "should return delegation details", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + delegatorAddress: callerEvmAddress, + validatorAddress: validatorAddress, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + }, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + { + name: "should allow static call", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + Response: delegationResponse, + }, + }, + args: args{ + delegatorAddress: callerEvmAddress, + validatorAddress: validatorAddress, + caller: callerEvmAddress, + callingContract: callerEvmAddress, + readOnly: true, + }, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + k.SetAddressMapping(ctx, callerSeiAddress, callerEvmAddress) + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + TxContext: vm.TxContext{Origin: callerEvmAddress}, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingKeeper: tt.fields.stakingKeeper, + StakingQuerier: tt.fields.stakingQuerier, + EVMKeeper: k, + }) + delegation, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).DelegationID) + require.Nil(t, err) + inputs, err := delegation.Inputs.Pack(tt.args.delegatorAddress, tt.args.validatorAddress) + require.Nil(t, err) + gotRet, _, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.GetExecutor().(*staking.PrecompileExecutor).DelegationID, inputs...), math.MaxUint64, tt.args.value, nil, tt.args.readOnly, tt.args.isFromDelegateCall) + if (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + } else if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_Validators(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + validatorsMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).ValidatorsID) + + // Build a single validator in the staking module format. + val := stakingtypes.Validator{ + OperatorAddress: "seivaloper1validator", + ConsensusPubkey: &codectypes.Any{Value: []byte("pubkey-bytes")}, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: sdk.NewInt(1000), + DelegatorShares: sdk.NewDec(1000), + Description: stakingtypes.NewDescription( + "moniker", + "identity", + "website", + "security", + "details", + ), + UnbondingHeight: 10, + UnbondingTime: time.Unix(1234, 0), + Commission: stakingtypes.Commission{ + CommissionRates: stakingtypes.CommissionRates{ + Rate: sdk.NewDecWithPrec(10, 2), + MaxRate: sdk.NewDecWithPrec(20, 2), + MaxChangeRate: sdk.NewDecWithPrec(1, 2), + }, + UpdateTime: time.Unix(5678, 0), + }, + MinSelfDelegation: sdk.NewInt(5), + } + + nextKey := []byte("next-key") + validatorsResponse := &stakingtypes.QueryValidatorsResponse{ + Validators: []stakingtypes.Validator{val}, + Pagination: &query.PageResponse{NextKey: nextKey}, + } + + expected := staking.ValidatorsResponse{ + Validators: []staking.Validator{ + { + OperatorAddress: val.OperatorAddress, + ConsensusPubkey: string(val.ConsensusPubkey.Value), + Jailed: val.Jailed, + Status: int32(val.Status), + Tokens: val.Tokens.String(), + DelegatorShares: val.DelegatorShares.String(), + Description: val.Description.String(), + UnbondingHeight: val.UnbondingHeight, + UnbondingTime: val.UnbondingTime.Unix(), + CommissionRate: val.Commission.Rate.String(), + CommissionMaxRate: val.Commission.MaxRate.String(), + CommissionMaxChangeRate: val.Commission.MaxChangeRate.String(), + CommissionUpdateTime: val.Commission.UpdateTime.Unix(), + MinSelfDelegation: val.MinSelfDelegation.String(), + }, + }, + NextKey: nextKey, + } + + happyPathPackedOutput, _ := validatorsMethod.Outputs.Pack(expected) + + type fields struct { + stakingQuerier utils.StakingQuerier + } + type args struct { + status string + key []byte + value *big.Int + readOnly bool + } + + tests := []struct { + name string + fields fields + args args + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + ValidatorsResponse: validatorsResponse, + }, + }, + args: args{ + status: "BOND_STATUS_BONDED", + key: []byte{}, + value: big.NewInt(1), + }, + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "should return validators and next key (static call allowed)", + fields: fields{ + stakingQuerier: &TestStakingQuerier{ + ValidatorsResponse: validatorsResponse, + }, + }, + args: args{ + status: "BOND_STATUS_BONDED", + key: []byte{}, + readOnly: true, + }, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields.stakingQuerier, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).ValidatorsID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args.status, tt.args.key) + require.NoError(t, err) + + // Caller and callingContract are irrelevant for validators (query-only). + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.args.value, nil, tt.args.readOnly, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + if tt.wantErrMsg != "" { + require.Contains(t, tt.wantErrMsg, tt.wantErrMsg) + } + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +// Helper function to create a test validator +func createTestValidator() stakingtypes.Validator { + return stakingtypes.Validator{ + OperatorAddress: "seivaloper1validator", + ConsensusPubkey: &codectypes.Any{Value: []byte("pubkey-bytes")}, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: sdk.NewInt(1000), + DelegatorShares: sdk.NewDec(1000), + Description: stakingtypes.NewDescription( + "moniker", + "identity", + "website", + "security", + "details", + ), + UnbondingHeight: 10, + UnbondingTime: time.Unix(1234, 0), + Commission: stakingtypes.Commission{ + CommissionRates: stakingtypes.CommissionRates{ + Rate: sdk.NewDecWithPrec(10, 2), + MaxRate: sdk.NewDecWithPrec(20, 2), + MaxChangeRate: sdk.NewDecWithPrec(1, 2), + }, + UpdateTime: time.Unix(5678, 0), + }, + MinSelfDelegation: sdk.NewInt(5), + } +} + +func TestPrecompile_Run_Validator(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + validatorMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).ValidatorID) + + val := createTestValidator() + validatorResponse := &stakingtypes.QueryValidatorResponse{ + Validator: val, + } + + expected := staking.Validator{ + OperatorAddress: val.OperatorAddress, + ConsensusPubkey: string(val.ConsensusPubkey.Value), + Jailed: val.Jailed, + Status: int32(val.Status), + Tokens: val.Tokens.String(), + DelegatorShares: val.DelegatorShares.String(), + Description: val.Description.String(), + UnbondingHeight: val.UnbondingHeight, + UnbondingTime: val.UnbondingTime.Unix(), + CommissionRate: val.Commission.Rate.String(), + CommissionMaxRate: val.Commission.MaxRate.String(), + CommissionMaxChangeRate: val.Commission.MaxChangeRate.String(), + CommissionUpdateTime: val.Commission.UpdateTime.Unix(), + MinSelfDelegation: val.MinSelfDelegation.String(), + } + + happyPathPackedOutput, _ := validatorMethod.Outputs.Pack(expected) + + tests := []struct { + name string + fields utils.StakingQuerier + args []interface{} + value *big.Int + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: &TestStakingQuerier{ + ValidatorResponse: validatorResponse, + }, + args: []interface{}{val.OperatorAddress}, + value: big.NewInt(1), + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "should return validator (static call allowed)", + fields: &TestStakingQuerier{ + ValidatorResponse: validatorResponse, + }, + args: []interface{}{val.OperatorAddress}, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).ValidatorID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_ValidatorDelegations(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + validatorDelegationsMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).ValidatorDelegationsID) + + callerSeiAddress, _ := testkeeper.MockAddressPair() + validatorAddress := "seivaloper134ykhqrkyda72uq7f463ne77e4tn99steprmz7" + shares := 100 + delegationResponse := &stakingtypes.DelegationResponse{ + Delegation: stakingtypes.Delegation{ + DelegatorAddress: callerSeiAddress.String(), + ValidatorAddress: validatorAddress, + Shares: sdk.NewDec(int64(shares)), + }, + Balance: sdk.NewCoin("usei", sdk.NewInt(int64(shares))), + } + + nextKey := []byte("next-key") + validatorDelegationsResponse := &stakingtypes.QueryValidatorDelegationsResponse{ + DelegationResponses: []stakingtypes.DelegationResponse{*delegationResponse}, + Pagination: &query.PageResponse{NextKey: nextKey}, + } + + hundredSharesValue := new(big.Int) + hundredSharesValue.SetString("100000000000000000000", 10) + expected := staking.DelegationsResponse{ + Delegations: []staking.Delegation{ + { + Balance: staking.Balance{ + Amount: big.NewInt(int64(shares)), + Denom: "usei", + }, + Delegation: staking.DelegationDetails{ + DelegatorAddress: callerSeiAddress.String(), + Shares: hundredSharesValue, + Decimals: big.NewInt(sdk.Precision), + ValidatorAddress: validatorAddress, + }, + }, + }, + NextKey: nextKey, + } + + happyPathPackedOutput, _ := validatorDelegationsMethod.Outputs.Pack(expected) + + tests := []struct { + name string + fields utils.StakingQuerier + args []interface{} + value *big.Int + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: &TestStakingQuerier{ + ValidatorDelegationsResponse: validatorDelegationsResponse, + }, + args: []interface{}{validatorAddress, []byte{}}, + value: big.NewInt(1), + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "should return validator delegations (static call allowed)", + fields: &TestStakingQuerier{ + ValidatorDelegationsResponse: validatorDelegationsResponse, + }, + args: []interface{}{validatorAddress, []byte{}}, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).ValidatorDelegationsID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_ValidatorUnbondingDelegations(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + validatorUnbondingDelegationsMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).ValidatorUnbondingDelegationsID) + + callerSeiAddress, _ := testkeeper.MockAddressPair() + validatorAddress := "seivaloper134ykhqrkyda72uq7f463ne77e4tn99steprmz7" + unbondingDelegation := stakingtypes.UnbondingDelegation{ + DelegatorAddress: callerSeiAddress.String(), + ValidatorAddress: validatorAddress, + Entries: []stakingtypes.UnbondingDelegationEntry{ + { + CreationHeight: 100, + CompletionTime: time.Unix(2000, 0), + InitialBalance: sdk.NewInt(50), + Balance: sdk.NewInt(40), + }, + }, + } + + nextKey := []byte("next-key") + validatorUnbondingDelegationsResponse := &stakingtypes.QueryValidatorUnbondingDelegationsResponse{ + UnbondingResponses: []stakingtypes.UnbondingDelegation{unbondingDelegation}, + Pagination: &query.PageResponse{NextKey: nextKey}, + } + + expected := staking.UnbondingDelegationsResponse{ + UnbondingDelegations: []staking.UnbondingDelegation{ + { + DelegatorAddress: callerSeiAddress.String(), + ValidatorAddress: validatorAddress, + Entries: []staking.UnbondingDelegationEntry{ + { + CreationHeight: 100, + CompletionTime: 2000, + InitialBalance: "50", + Balance: "40", + }, + }, + }, + }, + NextKey: nextKey, + } + + happyPathPackedOutput, _ := validatorUnbondingDelegationsMethod.Outputs.Pack(expected) + + tests := []struct { + name string + fields utils.StakingQuerier + args []interface{} + value *big.Int + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: &TestStakingQuerier{ + ValidatorUnbondingDelegationsResponse: validatorUnbondingDelegationsResponse, + }, + args: []interface{}{validatorAddress, []byte{}}, + value: big.NewInt(1), + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "should return validator unbonding delegations (static call allowed)", + fields: &TestStakingQuerier{ + ValidatorUnbondingDelegationsResponse: validatorUnbondingDelegationsResponse, + }, + args: []interface{}{validatorAddress, []byte{}}, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).ValidatorUnbondingDelegationsID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_UnbondingDelegation(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + unbondingDelegationMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).UnbondingDelegationID) + + callerSeiAddress, callerEvmAddress := testkeeper.MockAddressPair() + validatorAddress := "seivaloper134ykhqrkyda72uq7f463ne77e4tn99steprmz7" + unbondingDelegation := stakingtypes.UnbondingDelegation{ + DelegatorAddress: callerSeiAddress.String(), + ValidatorAddress: validatorAddress, + Entries: []stakingtypes.UnbondingDelegationEntry{ + { + CreationHeight: 100, + CompletionTime: time.Unix(2000, 0), + InitialBalance: sdk.NewInt(50), + Balance: sdk.NewInt(40), + }, + }, + } + + unbondingDelegationResponse := &stakingtypes.QueryUnbondingDelegationResponse{ + Unbond: unbondingDelegation, + } + + expected := staking.UnbondingDelegation{ + DelegatorAddress: callerSeiAddress.String(), + ValidatorAddress: validatorAddress, + Entries: []staking.UnbondingDelegationEntry{ + { + CreationHeight: 100, + CompletionTime: 2000, + InitialBalance: "50", + Balance: "40", + }, + }, + } + + happyPathPackedOutput, _ := unbondingDelegationMethod.Outputs.Pack(expected) + + tests := []struct { + name string + fields utils.StakingQuerier + args []interface{} + value *big.Int + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: &TestStakingQuerier{ + UnbondingDelegationResponse: unbondingDelegationResponse, + }, + args: []interface{}{callerEvmAddress, validatorAddress}, + value: big.NewInt(1), + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "should return unbonding delegation (static call allowed)", + fields: &TestStakingQuerier{ + UnbondingDelegationResponse: unbondingDelegationResponse, + }, + args: []interface{}{callerEvmAddress, validatorAddress}, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + k.SetAddressMapping(ctx, callerSeiAddress, callerEvmAddress) + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).UnbondingDelegationID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_DelegatorDelegations(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + delegatorDelegationsMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).DelegatorDelegationsID) + + callerSeiAddress, callerEvmAddress := testkeeper.MockAddressPair() + validatorAddress := "seivaloper134ykhqrkyda72uq7f463ne77e4tn99steprmz7" + shares := 100 + delegationResponse := &stakingtypes.DelegationResponse{ + Delegation: stakingtypes.Delegation{ + DelegatorAddress: callerSeiAddress.String(), + ValidatorAddress: validatorAddress, + Shares: sdk.NewDec(int64(shares)), + }, + Balance: sdk.NewCoin("usei", sdk.NewInt(int64(shares))), + } + + nextKey := []byte("next-key") + delegatorDelegationsResponse := &stakingtypes.QueryDelegatorDelegationsResponse{ + DelegationResponses: []stakingtypes.DelegationResponse{*delegationResponse}, + Pagination: &query.PageResponse{NextKey: nextKey}, + } + + hundredSharesValue := new(big.Int) + hundredSharesValue.SetString("100000000000000000000", 10) + expected := staking.DelegationsResponse{ + Delegations: []staking.Delegation{ + { + Balance: staking.Balance{ + Amount: big.NewInt(int64(shares)), + Denom: "usei", + }, + Delegation: staking.DelegationDetails{ + DelegatorAddress: callerSeiAddress.String(), + Shares: hundredSharesValue, + Decimals: big.NewInt(sdk.Precision), + ValidatorAddress: validatorAddress, + }, + }, + }, + NextKey: nextKey, + } + + happyPathPackedOutput, _ := delegatorDelegationsMethod.Outputs.Pack(expected) + + tests := []struct { + name string + fields utils.StakingQuerier + args []interface{} + value *big.Int + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: &TestStakingQuerier{ + DelegatorDelegationsResponse: delegatorDelegationsResponse, + }, + args: []interface{}{callerEvmAddress, []byte{}}, + value: big.NewInt(1), + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "should return delegator delegations (static call allowed)", + fields: &TestStakingQuerier{ + DelegatorDelegationsResponse: delegatorDelegationsResponse, + }, + args: []interface{}{callerEvmAddress, []byte{}}, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + k.SetAddressMapping(ctx, callerSeiAddress, callerEvmAddress) + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).DelegatorDelegationsID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_DelegatorValidator(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + delegatorValidatorMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).DelegatorValidatorID) + + callerSeiAddress, callerEvmAddress := testkeeper.MockAddressPair() + val := createTestValidator() + validatorResponse := &stakingtypes.QueryDelegatorValidatorResponse{ + Validator: val, + } + + expected := staking.Validator{ + OperatorAddress: val.OperatorAddress, + ConsensusPubkey: string(val.ConsensusPubkey.Value), + Jailed: val.Jailed, + Status: int32(val.Status), + Tokens: val.Tokens.String(), + DelegatorShares: val.DelegatorShares.String(), + Description: val.Description.String(), + UnbondingHeight: val.UnbondingHeight, + UnbondingTime: val.UnbondingTime.Unix(), + CommissionRate: val.Commission.Rate.String(), + CommissionMaxRate: val.Commission.MaxRate.String(), + CommissionMaxChangeRate: val.Commission.MaxChangeRate.String(), + CommissionUpdateTime: val.Commission.UpdateTime.Unix(), + MinSelfDelegation: val.MinSelfDelegation.String(), + } + + happyPathPackedOutput, _ := delegatorValidatorMethod.Outputs.Pack(expected) + + tests := []struct { + name string + fields utils.StakingQuerier + args []interface{} + value *big.Int + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: &TestStakingQuerier{ + DelegatorValidatorResponse: validatorResponse, + }, + args: []interface{}{callerEvmAddress, val.OperatorAddress}, + value: big.NewInt(1), + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "should return delegator validator (static call allowed)", + fields: &TestStakingQuerier{ + DelegatorValidatorResponse: validatorResponse, + }, + args: []interface{}{callerEvmAddress, val.OperatorAddress}, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + k.SetAddressMapping(ctx, callerSeiAddress, callerEvmAddress) + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).DelegatorValidatorID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_DelegatorUnbondingDelegations(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + delegatorUnbondingDelegationsMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).DelegatorUnbondingDelegationsID) + + callerSeiAddress, callerEvmAddress := testkeeper.MockAddressPair() + validatorAddress := "seivaloper134ykhqrkyda72uq7f463ne77e4tn99steprmz7" + unbondingDelegation := stakingtypes.UnbondingDelegation{ + DelegatorAddress: callerSeiAddress.String(), + ValidatorAddress: validatorAddress, + Entries: []stakingtypes.UnbondingDelegationEntry{ + { + CreationHeight: 100, + CompletionTime: time.Unix(2000, 0), + InitialBalance: sdk.NewInt(50), + Balance: sdk.NewInt(40), + }, + }, + } + + nextKey := []byte("next-key") + delegatorUnbondingDelegationsResponse := &stakingtypes.QueryDelegatorUnbondingDelegationsResponse{ + UnbondingResponses: []stakingtypes.UnbondingDelegation{unbondingDelegation}, + Pagination: &query.PageResponse{NextKey: nextKey}, + } + + expected := staking.UnbondingDelegationsResponse{ + UnbondingDelegations: []staking.UnbondingDelegation{ + { + DelegatorAddress: callerSeiAddress.String(), + ValidatorAddress: validatorAddress, + Entries: []staking.UnbondingDelegationEntry{ + { + CreationHeight: 100, + CompletionTime: 2000, + InitialBalance: "50", + Balance: "40", + }, + }, + }, + }, + NextKey: nextKey, + } + + happyPathPackedOutput, _ := delegatorUnbondingDelegationsMethod.Outputs.Pack(expected) + + tests := []struct { + name string + fields utils.StakingQuerier + args []interface{} + value *big.Int + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: &TestStakingQuerier{ + DelegatorUnbondingDelegationsResponse: delegatorUnbondingDelegationsResponse, + }, + args: []interface{}{callerEvmAddress, []byte{}}, + value: big.NewInt(1), + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "should return delegator unbonding delegations (static call allowed)", + fields: &TestStakingQuerier{ + DelegatorUnbondingDelegationsResponse: delegatorUnbondingDelegationsResponse, + }, + args: []interface{}{callerEvmAddress, []byte{}}, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + k.SetAddressMapping(ctx, callerSeiAddress, callerEvmAddress) + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).DelegatorUnbondingDelegationsID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_Redelegations(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + redelegationsMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).RedelegationsID) + + callerSeiAddress, _ := testkeeper.MockAddressPair() + srcValidatorAddress := "seivaloper1src" + dstValidatorAddress := "seivaloper1dst" + redelegation := stakingtypes.Redelegation{ + DelegatorAddress: callerSeiAddress.String(), + ValidatorSrcAddress: srcValidatorAddress, + ValidatorDstAddress: dstValidatorAddress, + Entries: []stakingtypes.RedelegationEntry{ + { + CreationHeight: 100, + CompletionTime: time.Unix(2000, 0), + InitialBalance: sdk.NewInt(50), + SharesDst: sdk.NewDec(40), + }, + }, + } + + redelegationEntryResponse := stakingtypes.RedelegationEntryResponse{ + RedelegationEntry: redelegation.Entries[0], + Balance: sdk.NewInt(40), + } + + nextKey := []byte("next-key") + redelegationsResponse := &stakingtypes.QueryRedelegationsResponse{ + RedelegationResponses: []stakingtypes.RedelegationResponse{ + { + Redelegation: redelegation, + Entries: []stakingtypes.RedelegationEntryResponse{redelegationEntryResponse}, + }, + }, + Pagination: &query.PageResponse{NextKey: nextKey}, + } + + expected := staking.RedelegationsResponse{ + Redelegations: []staking.Redelegation{ + { + DelegatorAddress: callerSeiAddress.String(), + ValidatorSrcAddress: srcValidatorAddress, + ValidatorDstAddress: dstValidatorAddress, + Entries: []staking.RedelegationEntry{ + { + CreationHeight: 100, + CompletionTime: 2000, + InitialBalance: "50", + SharesDst: "40", + }, + }, + }, + }, + NextKey: nextKey, + } + + happyPathPackedOutput, _ := redelegationsMethod.Outputs.Pack(expected) + + tests := []struct { + name string + fields utils.StakingQuerier + args []interface{} + value *big.Int + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: &TestStakingQuerier{ + RedelegationsResponse: redelegationsResponse, + }, + args: []interface{}{callerSeiAddress.String(), srcValidatorAddress, dstValidatorAddress, []byte{}}, + value: big.NewInt(1), + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "should return redelegations (static call allowed)", + fields: &TestStakingQuerier{ + RedelegationsResponse: redelegationsResponse, + }, + args: []interface{}{callerSeiAddress.String(), srcValidatorAddress, dstValidatorAddress, []byte{}}, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).RedelegationsID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_DelegatorValidators(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + delegatorValidatorsMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).DelegatorValidatorsID) + + callerSeiAddress, callerEvmAddress := testkeeper.MockAddressPair() + val := createTestValidator() + nextKey := []byte("next-key") + delegatorValidatorsResponse := &stakingtypes.QueryDelegatorValidatorsResponse{ + Validators: []stakingtypes.Validator{val}, + Pagination: &query.PageResponse{NextKey: nextKey}, + } + + expected := staking.ValidatorsResponse{ + Validators: []staking.Validator{ + { + OperatorAddress: val.OperatorAddress, + ConsensusPubkey: string(val.ConsensusPubkey.Value), + Jailed: val.Jailed, + Status: int32(val.Status), + Tokens: val.Tokens.String(), + DelegatorShares: val.DelegatorShares.String(), + Description: val.Description.String(), + UnbondingHeight: val.UnbondingHeight, + UnbondingTime: val.UnbondingTime.Unix(), + CommissionRate: val.Commission.Rate.String(), + CommissionMaxRate: val.Commission.MaxRate.String(), + CommissionMaxChangeRate: val.Commission.MaxChangeRate.String(), + CommissionUpdateTime: val.Commission.UpdateTime.Unix(), + MinSelfDelegation: val.MinSelfDelegation.String(), + }, + }, + NextKey: nextKey, + } + + happyPathPackedOutput, _ := delegatorValidatorsMethod.Outputs.Pack(expected) + + tests := []struct { + name string + fields utils.StakingQuerier + args []interface{} + value *big.Int + wantRet []byte + wantErr bool + wantErrMsg string + }{ + { + name: "fails if value passed", + fields: &TestStakingQuerier{ + DelegatorValidatorsResponse: delegatorValidatorsResponse, + }, + args: []interface{}{callerEvmAddress, []byte{}}, + value: big.NewInt(1), + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + { + name: "should return delegator validators (static call allowed)", + fields: &TestStakingQuerier{ + DelegatorValidatorsResponse: delegatorValidatorsResponse, + }, + args: []interface{}{callerEvmAddress, []byte{}}, + wantRet: happyPathPackedOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + k.SetAddressMapping(ctx, callerSeiAddress, callerEvmAddress) + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).DelegatorValidatorsID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_HistoricalInfo(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + historicalInfoMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).HistoricalInfoID) + + val := createTestValidator() + historicalInfo := stakingtypes.HistoricalInfo{ + Header: tmtypes.Header{Height: 100}, + Valset: []stakingtypes.Validator{val}, + } + + historicalInfoResponse := &stakingtypes.QueryHistoricalInfoResponse{ + Hist: &historicalInfo, } - type args struct { - evm *vm.EVM - delegatorAddress common.Address - validatorAddress string - caller common.Address - callingContract common.Address - value *big.Int - readOnly bool - isFromDelegateCall bool + + expected := staking.HistoricalInfo{ + Height: 100, + Validators: []staking.Validator{ + { + OperatorAddress: val.OperatorAddress, + ConsensusPubkey: string(val.ConsensusPubkey.Value), + Jailed: val.Jailed, + Status: int32(val.Status), + Tokens: val.Tokens.String(), + DelegatorShares: val.DelegatorShares.String(), + Description: val.Description.String(), + UnbondingHeight: val.UnbondingHeight, + UnbondingTime: val.UnbondingTime.Unix(), + CommissionRate: val.Commission.Rate.String(), + CommissionMaxRate: val.Commission.MaxRate.String(), + CommissionMaxChangeRate: val.Commission.MaxChangeRate.String(), + CommissionUpdateTime: val.Commission.UpdateTime.Unix(), + MinSelfDelegation: val.MinSelfDelegation.String(), + }, + }, } + happyPathPackedOutput, _ := historicalInfoMethod.Outputs.Pack(expected) + tests := []struct { name string - fields fields - args args + fields utils.StakingQuerier + args []interface{} + value *big.Int wantRet []byte wantErr bool wantErrMsg string }{ { name: "fails if value passed", - fields: fields{ - stakingQuerier: &TestStakingQuerier{ - Response: delegationResponse, - }, - }, - args: args{ - delegatorAddress: callerEvmAddress, - validatorAddress: validatorAddress, - value: big.NewInt(100), + fields: &TestStakingQuerier{ + HistoricalInfoResponse: historicalInfoResponse, }, - wantRet: happyPathPackedOutput, + args: []interface{}{int64(100)}, + value: big.NewInt(1), wantErr: true, wantErrMsg: "sending funds to a non-payable function", }, { - name: "fails if caller != callingContract", - fields: fields{ - stakingQuerier: &TestStakingQuerier{ - Response: delegationResponse, - }, - }, - args: args{ - caller: callerEvmAddress, - callingContract: contractEvmAddress, - delegatorAddress: callerEvmAddress, - validatorAddress: validatorAddress, - value: big.NewInt(100), - isFromDelegateCall: true, + name: "should return historical info (static call allowed)", + fields: &TestStakingQuerier{ + HistoricalInfoResponse: historicalInfoResponse, }, - wantErr: true, - wantErrMsg: "cannot delegatecall staking", + args: []interface{}{int64(100)}, + wantRet: happyPathPackedOutput, + wantErr: false, }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).HistoricalInfoID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_Pool(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + poolMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).PoolID) + + pool := stakingtypes.Pool{ + NotBondedTokens: sdk.NewInt(1000), + BondedTokens: sdk.NewInt(2000), + } + + poolResponse := &stakingtypes.QueryPoolResponse{ + Pool: pool, + } + + expected := staking.Pool{ + NotBondedTokens: pool.NotBondedTokens.String(), + BondedTokens: pool.BondedTokens.String(), + } + + happyPathPackedOutput, _ := poolMethod.Outputs.Pack(expected) + + tests := []struct { + name string + fields utils.StakingQuerier + args []interface{} + value *big.Int + wantRet []byte + wantErr bool + wantErrMsg string + }{ { - name: "fails if delegator address unassociated", - fields: fields{ - stakingQuerier: &TestStakingQuerier{ - Response: delegationResponse, - }, - }, - args: args{ - caller: callerEvmAddress, - callingContract: callerEvmAddress, - delegatorAddress: unassociatedEvmAddress, - validatorAddress: validatorAddress, + name: "fails if value passed", + fields: &TestStakingQuerier{ + PoolResponse: poolResponse, }, + args: []interface{}{}, + value: big.NewInt(1), wantErr: true, - wantErrMsg: fmt.Sprintf("address %s is not linked", unassociatedEvmAddress.String()), + wantErrMsg: "sending funds to a non-payable function", }, { - name: "fails if delegator address is invalid", - fields: fields{ - stakingQuerier: &TestStakingQuerier{ - Response: delegationResponse, - }, + name: "should return pool (static call allowed)", + fields: &TestStakingQuerier{ + PoolResponse: poolResponse, }, - args: args{ - delegatorAddress: common.Address{}, - validatorAddress: validatorAddress, - caller: callerEvmAddress, - callingContract: callerEvmAddress, - }, - wantErr: true, - wantErrMsg: "invalid addr", + args: []interface{}{}, + wantRet: happyPathPackedOutput, + wantErr: false, }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + stateDb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: stateDb, + } + p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ + StakingQuerier: tt.fields, + EVMKeeper: k, + }) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).PoolID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) + if (err != nil) != tt.wantErr { + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + if err != nil { + require.Equal(t, vm.ErrExecutionReverted, err) + require.Nil(t, gotRet) + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func TestPrecompile_Run_Params(t *testing.T) { + pre, _ := staking.NewPrecompile(&utils.EmptyKeepers{}) + paramsMethod, _ := pre.ABI.MethodById(pre.GetExecutor().(*staking.PrecompileExecutor).ParamsID) + + params := stakingtypes.Params{ + UnbondingTime: time.Duration(1000000000000), // 1000 seconds in nanoseconds + MaxValidators: 100, + MaxEntries: 7, + HistoricalEntries: 10000, + BondDenom: "usei", + MinCommissionRate: sdk.NewDecWithPrec(5, 2), + MaxVotingPowerRatio: sdk.NewDecWithPrec(30, 2), + MaxVotingPowerEnforcementThreshold: sdk.NewInt(1000000), + } + + paramsResponse := &stakingtypes.QueryParamsResponse{ + Params: params, + } + + expected := staking.Params{ + UnbondingTime: uint64(params.UnbondingTime.Seconds()), + MaxValidators: params.MaxValidators, + MaxEntries: params.MaxEntries, + HistoricalEntries: params.HistoricalEntries, + BondDenom: params.BondDenom, + MinCommissionRate: params.MinCommissionRate.String(), + MaxVotingPowerRatio: params.MaxVotingPowerRatio.String(), + MaxVotingPowerEnforcementThreshold: params.MaxVotingPowerEnforcementThreshold.String(), + } + + happyPathPackedOutput, _ := paramsMethod.Outputs.Pack(expected) + + tests := []struct { + name string + fields utils.StakingQuerier + args []interface{} + value *big.Int + wantRet []byte + wantErr bool + wantErrMsg string + }{ { - name: "should return error if delegation not found", - fields: fields{ - stakingQuerier: &TestStakingQuerier{ - Err: fmt.Errorf("delegation with delegator %s not found for validator", callerSeiAddress.String()), - }, - }, - args: args{ - delegatorAddress: callerEvmAddress, - validatorAddress: validatorAddress, - caller: callerEvmAddress, - callingContract: callerEvmAddress, + name: "fails if value passed", + fields: &TestStakingQuerier{ + ParamsResponse: paramsResponse, }, + args: []interface{}{}, + value: big.NewInt(1), wantErr: true, - wantErrMsg: fmt.Sprintf("delegation with delegator %s not found for validator", callerSeiAddress.String()), - }, - { - name: "should return delegation details", - fields: fields{ - stakingQuerier: &TestStakingQuerier{ - Response: delegationResponse, - }, - }, - args: args{ - delegatorAddress: callerEvmAddress, - validatorAddress: validatorAddress, - caller: callerEvmAddress, - callingContract: callerEvmAddress, - }, - wantRet: happyPathPackedOutput, - wantErr: false, + wantErrMsg: "sending funds to a non-payable function", }, { - name: "should allow static call", - fields: fields{ - stakingQuerier: &TestStakingQuerier{ - Response: delegationResponse, - }, - }, - args: args{ - delegatorAddress: callerEvmAddress, - validatorAddress: validatorAddress, - caller: callerEvmAddress, - callingContract: callerEvmAddress, - readOnly: true, + name: "should return params (static call allowed)", + fields: &TestStakingQuerier{ + ParamsResponse: paramsResponse, }, + args: []interface{}{}, wantRet: happyPathPackedOutput, wantErr: false, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testApp := testkeeper.EVMTestApp ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) k := &testApp.EvmKeeper - k.SetAddressMapping(ctx, callerSeiAddress, callerEvmAddress) stateDb := state.NewDBImpl(ctx, k, true) evm := vm.EVM{ - StateDB: stateDb, - TxContext: vm.TxContext{Origin: callerEvmAddress}, + StateDB: stateDb, } p, _ := staking.NewPrecompile(&app.PrecompileKeepers{ - StakingKeeper: tt.fields.stakingKeeper, - StakingQuerier: tt.fields.stakingQuerier, + StakingQuerier: tt.fields, EVMKeeper: k, }) - delegation, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).DelegationID) - require.Nil(t, err) - inputs, err := delegation.Inputs.Pack(tt.args.delegatorAddress, tt.args.validatorAddress) - require.Nil(t, err) - gotRet, err := p.Run(&evm, tt.args.caller, tt.args.callingContract, append(p.GetExecutor().(*staking.PrecompileExecutor).DelegationID, inputs...), tt.args.value, tt.args.readOnly, tt.args.isFromDelegateCall, nil) + method, err := p.ABI.MethodById(p.GetExecutor().(*staking.PrecompileExecutor).ParamsID) + require.NoError(t, err) + + inputs, err := method.Inputs.Pack(tt.args...) + require.NoError(t, err) + + gotRet, _, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(method.ID, inputs...), math.MaxUint64, tt.value, nil, true, false) if (err != nil) != tt.wantErr { - t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) - return + t.Fatalf("Run() error = %v, wantErr %v", err, tt.wantErr) } if err != nil { require.Equal(t, vm.ErrExecutionReverted, err) require.Nil(t, gotRet) - } else if !reflect.DeepEqual(gotRet, tt.wantRet) { + return + } + if !reflect.DeepEqual(gotRet, tt.wantRet) { t.Errorf("Run() gotRet = %v, want %v", gotRet, tt.wantRet) } }) @@ -925,81 +2416,6 @@ func TestEditValidator(t *testing.T) { require.Equal(t, validator.ConsensusPubkey, updatedValidator.ConsensusPubkey, "Consensus pubkey should remain the same") } -func TestStakingPrecompile_RequiredGas(t *testing.T) { - pre, err := staking.NewPrecompile(&utils.EmptyKeepers{}) - require.NoError(t, err) - - executor := pre.GetExecutor().(*staking.PrecompileExecutor) - - // Test gas costs for all methods - testCases := []struct { - name string - methodID []byte - methodName string - inputSize int - expectedGas uint64 - }{ - { - name: "delegate method", - methodID: executor.DelegateID, - methodName: staking.DelegateMethod, - expectedGas: 50000, - }, - { - name: "redelegate method", - methodID: executor.RedelegateID, - methodName: staking.RedelegateMethod, - expectedGas: 70000, - }, - { - name: "undelegate method", - methodID: executor.UndelegateID, - methodName: staking.UndelegateMethod, - expectedGas: 50000, - }, - { - name: "createValidator method", - methodID: executor.CreateValidatorID, - methodName: staking.CreateValidatorMethod, - expectedGas: 100000, - }, - { - name: "editValidator method", - methodID: executor.EditValidatorID, - methodName: "editValidator", - expectedGas: 100000, - }, - { - name: "delegation method (query)", - methodID: executor.DelegationID, - methodName: staking.DelegationMethod, - inputSize: 100, - expectedGas: 1300, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Get the method from ABI - method, err := pre.ABI.MethodById(tc.methodID) - require.NoError(t, err, "Should be able to find method by ID") - require.Equal(t, tc.methodName, method.Name, "Method name should match") - - // Create mock input (method ID + some data) - input := make([]byte, 4+tc.inputSize) // method ID (4 bytes) + input data - copy(input[:4], tc.methodID) - - // Test RequiredGas - gasRequired := executor.RequiredGas(input[4:], method) - - // Verify gas - require.Equal(t, tc.expectedGas, gasRequired, - "Gas should be at least %d, got %d", tc.expectedGas, gasRequired) - - }) - } -} - func TestStakingPrecompileDelegateCallPrevention(t *testing.T) { testApp := testkeeper.EVMTestApp ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) @@ -1075,7 +2491,7 @@ func TestStakingPrecompileDelegateCallPrevention(t *testing.T) { StateDB: state.NewDBImpl(ctx, k, false), } - _, err := precompile.GetExecutor().(*staking.PrecompileExecutor).Execute(ctx, &method, evmAddr, evmAddr, tc.args, tc.value, false, evm, nil) + _, _, err := precompile.GetExecutor().(*staking.PrecompileExecutor).Execute(ctx, &method, evmAddr, evmAddr, tc.args, tc.value, false, evm, math.MaxUint64, nil) require.Error(t, err) require.Contains(t, err.Error(), "cannot delegatecall staking") }) @@ -1155,7 +2571,7 @@ func TestStakingPrecompileStaticCallPrevention(t *testing.T) { } // Test with readOnly = true (staticcall) - _, err := precompile.GetExecutor().(*staking.PrecompileExecutor).Execute(ctx, &method, evmAddr, evmAddr, tc.args, tc.value, true, evm, nil) + _, _, err := precompile.GetExecutor().(*staking.PrecompileExecutor).Execute(ctx, &method, evmAddr, evmAddr, tc.args, tc.value, true, evm, math.MaxUint64, nil) require.Error(t, err) require.Contains(t, err.Error(), "cannot call staking precompile from staticcall") }) @@ -1181,7 +2597,7 @@ func TestStakingPrecompileStaticCallPrevention(t *testing.T) { // Should succeed with readOnly = true for query method (even if no delegation exists) // The important thing is that the static call is allowed - _, err := precompile.GetExecutor().(*staking.PrecompileExecutor).Execute(ctx, &method, evmAddr, evmAddr, args, nil, true, evm, nil) + _, _, err := precompile.GetExecutor().(*staking.PrecompileExecutor).Execute(ctx, &method, evmAddr, evmAddr, args, nil, true, evm, math.MaxUint64, nil) // We don't check the error because the delegation might not exist // We're just testing that static calls are allowed for query methods _ = err diff --git a/precompiles/utils/expected_keepers.go b/precompiles/utils/expected_keepers.go index d1831d0817..178423483c 100644 --- a/precompiles/utils/expected_keepers.go +++ b/precompiles/utils/expected_keepers.go @@ -149,6 +149,19 @@ type StakingKeeper interface { type StakingQuerier interface { Delegation(c context.Context, req *stakingtypes.QueryDelegationRequest) (*stakingtypes.QueryDelegationResponse, error) + Validators(c context.Context, req *stakingtypes.QueryValidatorsRequest) (*stakingtypes.QueryValidatorsResponse, error) + Validator(c context.Context, req *stakingtypes.QueryValidatorRequest) (*stakingtypes.QueryValidatorResponse, error) + ValidatorDelegations(c context.Context, req *stakingtypes.QueryValidatorDelegationsRequest) (*stakingtypes.QueryValidatorDelegationsResponse, error) + ValidatorUnbondingDelegations(c context.Context, req *stakingtypes.QueryValidatorUnbondingDelegationsRequest) (*stakingtypes.QueryValidatorUnbondingDelegationsResponse, error) + UnbondingDelegation(c context.Context, req *stakingtypes.QueryUnbondingDelegationRequest) (*stakingtypes.QueryUnbondingDelegationResponse, error) + DelegatorDelegations(c context.Context, req *stakingtypes.QueryDelegatorDelegationsRequest) (*stakingtypes.QueryDelegatorDelegationsResponse, error) + DelegatorValidator(c context.Context, req *stakingtypes.QueryDelegatorValidatorRequest) (*stakingtypes.QueryDelegatorValidatorResponse, error) + DelegatorUnbondingDelegations(c context.Context, req *stakingtypes.QueryDelegatorUnbondingDelegationsRequest) (*stakingtypes.QueryDelegatorUnbondingDelegationsResponse, error) + Redelegations(c context.Context, req *stakingtypes.QueryRedelegationsRequest) (*stakingtypes.QueryRedelegationsResponse, error) + DelegatorValidators(c context.Context, req *stakingtypes.QueryDelegatorValidatorsRequest) (*stakingtypes.QueryDelegatorValidatorsResponse, error) + HistoricalInfo(c context.Context, req *stakingtypes.QueryHistoricalInfoRequest) (*stakingtypes.QueryHistoricalInfoResponse, error) + Pool(c context.Context, req *stakingtypes.QueryPoolRequest) (*stakingtypes.QueryPoolResponse, error) + Params(c context.Context, req *stakingtypes.QueryParamsRequest) (*stakingtypes.QueryParamsResponse, error) } type GovKeeper interface { diff --git a/sei-cosmos/x/staking/keeper/grpc_query.go b/sei-cosmos/x/staking/keeper/grpc_query.go index 8fa16354d4..4abcf9c919 100644 --- a/sei-cosmos/x/staking/keeper/grpc_query.go +++ b/sei-cosmos/x/staking/keeper/grpc_query.go @@ -390,6 +390,7 @@ func (k Querier) Redelegations(c context.Context, req *types.QueryRedelegationsR switch { case req.DelegatorAddr != "" && req.SrcValidatorAddr != "" && req.DstValidatorAddr != "": redels, err = queryRedelegation(ctx, k, req) + pageRes = &query.PageResponse{} case req.DelegatorAddr == "" && req.SrcValidatorAddr != "" && req.DstValidatorAddr == "": redels, pageRes, err = queryRedelegationsFromSrcValidator(store, k, req) default: