diff --git a/app/evm_utils.go b/app/evm_utils.go index 0af749fe..9cf01554 100644 --- a/app/evm_utils.go +++ b/app/evm_utils.go @@ -9,16 +9,17 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "golang.org/x/exp/maps" + // staking and distribution precompiles + distprecompile "github.com/aura-nw/aura/precompiles/distribution" + stakingprecompile "github.com/aura-nw/aura/precompiles/staking" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" channelkeeper "github.com/cosmos/ibc-go/v7/modules/core/04-channel/keeper" - bankprecompile "github.com/evmos/evmos/v16/precompiles/bank" - distprecompile "github.com/evmos/evmos/v16/precompiles/distribution" ics20precompile "github.com/evmos/evmos/v16/precompiles/ics20" "github.com/evmos/evmos/v16/precompiles/p256" - stakingprecompile "github.com/evmos/evmos/v16/precompiles/staking" erc20Keeper "github.com/evmos/evmos/v16/x/erc20/keeper" transferkeeper "github.com/evmos/evmos/v16/x/ibc/transfer/keeper" ) @@ -59,11 +60,6 @@ func Precompiles( panic(fmt.Errorf("failed to instantiate ICS20 precompile: %w", err)) } - bankPrecompile, err := bankprecompile.NewPrecompile(bankKeeper, erc20Keeper) - if err != nil { - panic(fmt.Errorf("failed to instantiate bank precompile: %w", err)) - } - // Stateless precompiles precompiles[bech32Precompile.Address()] = bech32Precompile precompiles[p256Precompile.Address()] = p256Precompile @@ -72,7 +68,6 @@ func Precompiles( precompiles[stakingPrecompile.Address()] = stakingPrecompile precompiles[distributionPrecompile.Address()] = distributionPrecompile precompiles[ibcTransferPrecompile.Address()] = ibcTransferPrecompile - precompiles[bankPrecompile.Address()] = bankPrecompile return precompiles } diff --git a/go.mod b/go.mod index e8b4ced2..e591e4e3 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,8 @@ require ( github.com/gorilla/mux v1.8.1 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 + github.com/onsi/ginkgo/v2 v2.13.2 + github.com/onsi/gomega v1.30.0 github.com/prometheus/client_golang v1.16.0 github.com/regen-network/cosmos-proto v0.3.1 github.com/spf13/cast v1.6.0 @@ -84,6 +86,7 @@ require ( github.com/creachadair/taskgroup v0.4.2 // indirect github.com/crypto-org-chain/cronos/memiavl v0.0.5-0.20231027074119-c05c9c61c90e // indirect github.com/crypto-org-chain/cronos/store v0.0.5-0.20231027074119-c05c9c61c90e // indirect + github.com/crypto-org-chain/cronos/versiondb v0.0.0-20231027074119-c05c9c61c90e // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set v1.8.0 // indirect @@ -111,6 +114,7 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-stack/stack v1.8.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/golang/glog v1.1.2 // indirect @@ -120,6 +124,7 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/orderedcode v0.0.1 // indirect + github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.4.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect @@ -217,6 +222,7 @@ require ( golang.org/x/sys v0.16.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.14.0 // indirect google.golang.org/api v0.149.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect @@ -239,7 +245,7 @@ replace ( // use Evmos geth fork github.com/ethereum/go-ethereum => github.com/evmos/go-ethereum v1.10.26-evmos-rc2 - github.com/evmos/evmos/v16 => github.com/aura-nw/evmos/v16 v16.0.3-aura-2 + github.com/evmos/evmos/v16 => github.com/aura-nw/evmos/v16 v16.0.3-aura.3 // https://github.com/cosmos/cosmos-sdk/issues/14949 // pin the version of goleveldb to v1.0.1-0.20210819022825-2ae1ddf74ef7 required by SDK v47 upgrade guide. diff --git a/go.sum b/go.sum index 9185eb11..df655ce6 100644 --- a/go.sum +++ b/go.sum @@ -253,8 +253,8 @@ github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJ github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/aura-nw/evmos/v16 v16.0.3-aura-2 h1:O6dRTOMdix5ItW8Y8QgQ0XTxxT8R+/mX6a4dVEZN9+g= -github.com/aura-nw/evmos/v16 v16.0.3-aura-2/go.mod h1:w0vtRYI4I0/O8eihq6ZuDvlca4ZiYCKN6vpakG9zHcc= +github.com/aura-nw/evmos/v16 v16.0.3-aura.3 h1:0+lDKyJqduHESvZ9KTW4zy/CfKWDJffi33ZWB5AS8U4= +github.com/aura-nw/evmos/v16 v16.0.3-aura.3/go.mod h1:w0vtRYI4I0/O8eihq6ZuDvlca4ZiYCKN6vpakG9zHcc= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= @@ -408,6 +408,7 @@ github.com/crypto-org-chain/cronos/memiavl v0.0.5-0.20231027074119-c05c9c61c90e/ github.com/crypto-org-chain/cronos/store v0.0.5-0.20231027074119-c05c9c61c90e h1:3mq9zn5+49covVejnfwPIIx0i6pzRXxKJzz4PuVVn9g= github.com/crypto-org-chain/cronos/store v0.0.5-0.20231027074119-c05c9c61c90e/go.mod h1:N6IfJDLTTo0vxyuY4MSMMX2TOdBd/tozpjnYGJmAfmE= github.com/crypto-org-chain/cronos/versiondb v0.0.0-20231027074119-c05c9c61c90e h1:kN1HNLp2xmy8vIHpGWjsX8dCL6sXID2Tw4qZZk7XYd4= +github.com/crypto-org-chain/cronos/versiondb v0.0.0-20231027074119-c05c9c61c90e/go.mod h1:8W9LCw+FhR2sFI+nkjSi0ItZ0IeSyUjc6CSvuaYTU2s= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -532,6 +533,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= @@ -646,6 +648,7 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso= +github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= @@ -903,11 +906,13 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -1519,6 +1524,7 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/precompiles/distribution/DistributionI.sol b/precompiles/distribution/DistributionI.sol new file mode 100644 index 00000000..d46c989d --- /dev/null +++ b/precompiles/distribution/DistributionI.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.17; + +import "../common/Types.sol"; + +/// @dev The DistributionI contract's address. +address constant DISTRIBUTION_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801; + +/// @dev Define all the available distribution methods. +string constant MSG_SET_WITHDRAWER_ADDRESS = "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress"; +string constant MSG_WITHDRAW_DELEGATOR_REWARD = "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward"; +string constant MSG_WITHDRAW_VALIDATOR_COMMISSION = "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission"; + +/// @dev The DistributionI contract's instance. +DistributionI constant DISTRIBUTION_CONTRACT = DistributionI( + DISTRIBUTION_PRECOMPILE_ADDRESS +); + +struct ValidatorSlashEvent { + uint64 validatorPeriod; + Dec fraction; +} + +struct ValidatorDistributionInfo { + string operatorAddress; + DecCoin[] selfBondRewards; + DecCoin[] commission; +} + +struct DelegationDelegatorReward { + string validatorAddress; + DecCoin[] reward; +} + +/// @author Evmos Team +/// @title Distribution Precompile Contract +/// @dev The interface through which solidity contracts will interact with Distribution +/// @custom:address 0x0000000000000000000000000000000000000801 +interface DistributionI { + /// @dev ClaimRewards defines an Event emitted when rewards are claimed + /// @param delegatorAddress the address of the delegator + /// @param amount the amount being claimed + event ClaimRewards( + address indexed delegatorAddress, + uint256 amount + ); + + /// @dev SetWithdrawerAddress defines an Event emitted when a new withdrawer address is being set + /// @param caller the caller of the transaction + /// @param withdrawerAddress the newly set withdrawer address + event SetWithdrawerAddress( + address indexed caller, + string withdrawerAddress + ); + + /// @dev WithdrawDelegatorRewards defines an Event emitted when rewards from a delegation are withdrawn + /// @param delegatorAddress the address of the delegator + /// @param validatorAddress the address of the validator + /// @param amount the amount being withdrawn from the delegation + event WithdrawDelegatorRewards( + address indexed delegatorAddress, + address indexed validatorAddress, + uint256 amount + ); + + /// @dev WithdrawValidatorCommission defines an Event emitted when validator commissions are being withdrawn + /// @param validatorAddress is the address of the validator + /// @param commission is the total commission earned by the validator + event WithdrawValidatorCommission( + string indexed validatorAddress, + uint256 commission + ); + + /// TRANSACTIONS + + /// @dev Claims all rewards from a select set of validators or all of them for a delegator. + /// @param delegatorAddress The address of the delegator + /// @param maxRetrieve The maximum number of validators to claim rewards from + /// @return success Whether the transaction was successful or not + function claimRewards( + address delegatorAddress, + uint32 maxRetrieve + ) external returns (bool success); + + /// @dev Change the address, that can withdraw the rewards of a delegator. + /// Note that this address cannot be a module account. + /// @param delegatorAddress The address of the delegator + /// @param withdrawerAddress The address that will be capable of withdrawing rewards for + /// the given delegator address + function setWithdrawAddress( + address delegatorAddress, + string memory withdrawerAddress + ) external returns (bool success); + + /// @dev Withdraw the rewards of a delegator from a validator + /// @param delegatorAddress The address of the delegator + /// @param validatorAddress The address of the validator + /// @return amount The amount of Coin withdrawn + function withdrawDelegatorRewards( + address delegatorAddress, + string memory validatorAddress + ) external returns (Coin[] calldata amount); + + /// @dev Withdraws the rewards commission of a validator. + /// @param validatorAddress The address of the validator + /// @return amount The amount of Coin withdrawn + function withdrawValidatorCommission( + string memory validatorAddress + ) external returns (Coin[] calldata amount); + + /// QUERIES + /// @dev Queries validator commission and self-delegation rewards for validator. + /// @param validatorAddress The address of the validator + /// @return distributionInfo The validator's distribution info + function validatorDistributionInfo( + string memory validatorAddress + ) + external + view + returns ( + ValidatorDistributionInfo calldata distributionInfo + ); + + /// @dev Queries the outstanding rewards of a validator address. + /// @param validatorAddress The address of the validator + /// @return rewards The validator's outstanding rewards + function validatorOutstandingRewards( + string memory validatorAddress + ) external view returns (DecCoin[] calldata rewards); + + /// @dev Queries the accumulated commission for a validator. + /// @param validatorAddress The address of the validator + /// @return commission The validator's commission + function validatorCommission( + string memory validatorAddress + ) external view returns (DecCoin[] calldata commission); + + /// @dev Queries the slashing events for a validator in a given height interval + /// defined by the starting and ending height. + /// @param validatorAddress The address of the validator + /// @param startingHeight The starting height + /// @param endingHeight The ending height + /// @param pageRequest Defines a pagination for the request. + /// @return slashes The validator's slash events + /// @return pageResponse The pagination response for the query + function validatorSlashes( + string memory validatorAddress, + uint64 startingHeight, + uint64 endingHeight, + PageRequest calldata pageRequest + ) + external + view + returns ( + ValidatorSlashEvent[] calldata slashes, + PageResponse calldata pageResponse + ); + + /// @dev Queries the total rewards accrued by a delegation from a specific address to a given validator. + /// @param delegatorAddress The address of the delegator + /// @param validatorAddress The address of the validator + /// @return rewards The total rewards accrued by a delegation. + function delegationRewards( + address delegatorAddress, + string memory validatorAddress + ) external view returns (DecCoin[] calldata rewards); + + /// @dev Queries the total rewards accrued by each validator, that a given + /// address has delegated to. + /// @param delegatorAddress The address of the delegator + /// @return rewards The total rewards accrued by each validator for a delegator. + /// @return total The total rewards accrued by a delegator. + function delegationTotalRewards( + address delegatorAddress + ) + external + view + returns ( + DelegationDelegatorReward[] calldata rewards, + DecCoin[] calldata total + ); + + /// @dev Queries all validators, that a given address has delegated to. + /// @param delegatorAddress The address of the delegator + /// @return validators The addresses of all validators, that were delegated to by the given address. + function delegatorValidators( + address delegatorAddress + ) external view returns (string[] calldata validators); + + /// @dev Queries the address capable of withdrawing rewards for a given delegator. + /// @param delegatorAddress The address of the delegator + /// @return withdrawAddress The address capable of withdrawing rewards for the delegator. + function delegatorWithdrawAddress( + address delegatorAddress + ) external view returns (string memory withdrawAddress); + +} diff --git a/precompiles/distribution/abi.json b/precompiles/distribution/abi.json new file mode 100644 index 00000000..cf7388d7 --- /dev/null +++ b/precompiles/distribution/abi.json @@ -0,0 +1,592 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ClaimRewards", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "withdrawerAddress", + "type": "string" + } + ], + "name": "SetWithdrawerAddress", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "validatorAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "WithdrawDelegatorRewards", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "validatorAddress", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "commission", + "type": "uint256" + } + ], + "name": "WithdrawValidatorCommission", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "uint32", + "name": "maxRetrieve", + "type": "uint32" + } + ], + "name": "claimRewards", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + } + ], + "name": "delegationRewards", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "precision", + "type": "uint8" + } + ], + "internalType": "struct DecCoin[]", + "name": "rewards", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + } + ], + "name": "delegationTotalRewards", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + }, + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "precision", + "type": "uint8" + } + ], + "internalType": "struct DecCoin[]", + "name": "reward", + "type": "tuple[]" + } + ], + "internalType": "struct DelegationDelegatorReward[]", + "name": "rewards", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "precision", + "type": "uint8" + } + ], + "internalType": "struct DecCoin[]", + "name": "total", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + } + ], + "name": "delegatorValidators", + "outputs": [ + { + "internalType": "string[]", + "name": "validators", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + } + ], + "name": "delegatorWithdrawAddress", + "outputs": [ + { + "internalType": "string", + "name": "withdrawAddress", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "withdrawerAddress", + "type": "string" + } + ], + "name": "setWithdrawAddress", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + } + ], + "name": "validatorCommission", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "precision", + "type": "uint8" + } + ], + "internalType": "struct DecCoin[]", + "name": "commission", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + } + ], + "name": "validatorDistributionInfo", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "operatorAddress", + "type": "string" + }, + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "precision", + "type": "uint8" + } + ], + "internalType": "struct DecCoin[]", + "name": "selfBondRewards", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "precision", + "type": "uint8" + } + ], + "internalType": "struct DecCoin[]", + "name": "commission", + "type": "tuple[]" + } + ], + "internalType": "struct ValidatorDistributionInfo", + "name": "distributionInfo", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + } + ], + "name": "validatorOutstandingRewards", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "precision", + "type": "uint8" + } + ], + "internalType": "struct DecCoin[]", + "name": "rewards", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + }, + { + "internalType": "uint64", + "name": "startingHeight", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "endingHeight", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "offset", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "limit", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "countTotal", + "type": "bool" + }, + { + "internalType": "bool", + "name": "reverse", + "type": "bool" + } + ], + "internalType": "struct PageRequest", + "name": "pageRequest", + "type": "tuple" + } + ], + "name": "validatorSlashes", + "outputs": [ + { + "components": [ + { + "internalType": "uint64", + "name": "validatorPeriod", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "precision", + "type": "uint8" + } + ], + "internalType": "struct Dec", + "name": "fraction", + "type": "tuple" + } + ], + "internalType": "struct ValidatorSlashEvent[]", + "name": "slashes", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "nextKey", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "total", + "type": "uint64" + } + ], + "internalType": "struct PageResponse", + "name": "pageResponse", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + } + ], + "name": "withdrawDelegatorRewards", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Coin[]", + "name": "amount", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + } + ], + "name": "withdrawValidatorCommission", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Coin[]", + "name": "amount", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go new file mode 100644 index 00000000..41f70f73 --- /dev/null +++ b/precompiles/distribution/distribution.go @@ -0,0 +1,156 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package distribution + +import ( + "bytes" + "embed" + "fmt" + + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + cmn "github.com/evmos/evmos/v16/precompiles/common" +) + +var _ vm.PrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +// Precompile defines the precompiled contract for distribution. +type Precompile struct { + cmn.Precompile + distributionKeeper distributionkeeper.Keeper + stakingKeeper stakingkeeper.Keeper +} + +// NewPrecompile creates a new distribution Precompile instance as a +// PrecompiledContract interface. +func NewPrecompile( + distributionKeeper distributionkeeper.Keeper, + stakingKeeper stakingkeeper.Keeper, + authzKeeper authzkeeper.Keeper, +) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the distribution ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, fmt.Errorf(cmn.ErrInvalidABI, err) + } + + return &Precompile{ + Precompile: cmn.Precompile{ + ABI: newAbi, + AuthzKeeper: authzKeeper, + KvGasConfig: storetypes.KVGasConfig(), + TransientKVGasConfig: storetypes.TransientGasConfig(), + ApprovalExpiration: cmn.DefaultExpirationDuration, // should be configurable in the future. + }, + stakingKeeper: stakingKeeper, + distributionKeeper: distributionKeeper, + }, nil +} + +// Address defines the address of the distribution compile contract. +// address: 0x0000000000000000000000000000000000000801 +func (p Precompile) Address() common.Address { + return common.HexToAddress("0x0000000000000000000000000000000000000801") +} + +// RequiredGas calculates the precompiled contract's base gas rate. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID := input[:4] + + method, err := p.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return 0 + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +// Run executes the precompiled contract distribution methods defined in the ABI. +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. + // It avoids panics and returns the out of gas error so the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + switch method.Name { + // Custom transactions + case ClaimRewardsMethod: + bz, err = p.ClaimRewards(ctx, evm.Origin, contract, stateDB, method, args) + // Distribution transactions + case SetWithdrawAddressMethod: + bz, err = p.SetWithdrawAddress(ctx, evm.Origin, contract, stateDB, method, args) + case WithdrawDelegatorRewardsMethod: + bz, err = p.WithdrawDelegatorRewards(ctx, evm.Origin, contract, stateDB, method, args) + case WithdrawValidatorCommissionMethod: + bz, err = p.WithdrawValidatorCommission(ctx, evm.Origin, contract, stateDB, method, args) + // Distribution queries + case ValidatorDistributionInfoMethod: + bz, err = p.ValidatorDistributionInfo(ctx, contract, method, args) + case ValidatorOutstandingRewardsMethod: + bz, err = p.ValidatorOutstandingRewards(ctx, contract, method, args) + case ValidatorCommissionMethod: + bz, err = p.ValidatorCommission(ctx, contract, method, args) + case ValidatorSlashesMethod: + bz, err = p.ValidatorSlashes(ctx, contract, method, args) + case DelegationRewardsMethod: + bz, err = p.DelegationRewards(ctx, contract, method, args) + case DelegationTotalRewardsMethod: + bz, err = p.DelegationTotalRewards(ctx, contract, method, args) + case DelegatorValidatorsMethod: + bz, err = p.DelegatorValidators(ctx, contract, method, args) + case DelegatorWithdrawAddressMethod: + bz, err = p.DelegatorWithdrawAddress(ctx, contract, method, args) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost) { + return nil, vm.ErrOutOfGas + } + + return bz, nil +} + +// IsTransaction checks if the given method name corresponds to a transaction or query. +// +// Available distribution transactions are: +// - ClaimRewards +// - SetWithdrawAddress +// - WithdrawDelegatorRewards +// - WithdrawValidatorCommission +func (Precompile) IsTransaction(methodName string) bool { + switch methodName { + case ClaimRewardsMethod, + SetWithdrawAddressMethod, + WithdrawDelegatorRewardsMethod, + WithdrawValidatorCommissionMethod: + return true + default: + return false + } +} diff --git a/precompiles/distribution/distribution_test.go b/precompiles/distribution/distribution_test.go new file mode 100644 index 00000000..0f90bf1e --- /dev/null +++ b/precompiles/distribution/distribution_test.go @@ -0,0 +1,222 @@ +package distribution_test + +import ( + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/evmos/evmos/v16/app" + "github.com/evmos/evmos/v16/precompiles/distribution" + "github.com/evmos/evmos/v16/utils" + evmtypes "github.com/evmos/evmos/v16/x/evm/types" +) + +func (s *PrecompileTestSuite) TestIsTransaction() { + testCases := []struct { + name string + method string + isTx bool + }{ + { + distribution.SetWithdrawAddressMethod, + s.precompile.Methods[distribution.SetWithdrawAddressMethod].Name, + true, + }, + { + distribution.WithdrawDelegatorRewardsMethod, + s.precompile.Methods[distribution.WithdrawDelegatorRewardsMethod].Name, + true, + }, + { + distribution.WithdrawValidatorCommissionMethod, + s.precompile.Methods[distribution.WithdrawValidatorCommissionMethod].Name, + true, + }, + { + distribution.ValidatorDistributionInfoMethod, + s.precompile.Methods[distribution.ValidatorDistributionInfoMethod].Name, + false, + }, + { + "invalid", + "invalid", + false, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.Require().Equal(s.precompile.IsTransaction(tc.method), tc.isTx) + }) + } +} + +// TestRun tests the precompile's Run method. +func (s *PrecompileTestSuite) TestRun() { + testcases := []struct { + name string + malleate func() (common.Address, []byte) + readOnly bool + expPass bool + errContains string + }{ + { + name: "pass - set withdraw address transaction", + malleate: func() (common.Address, []byte) { + valAddr, err := sdk.ValAddressFromBech32(s.validators[0].OperatorAddress) + s.Require().NoError(err) + val, _ := s.app.StakingKeeper.GetValidator(s.ctx, valAddr) + coins := sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(1e18))) + s.app.DistrKeeper.AllocateTokensToValidator(s.ctx, val, sdk.NewDecCoinsFromCoins(coins...)) + + input, err := s.precompile.Pack( + distribution.SetWithdrawAddressMethod, + s.address, + s.address.String(), + ) + s.Require().NoError(err, "failed to pack input") + return s.address, input + }, + readOnly: false, + expPass: true, + }, + { + name: "pass - withdraw validator commissions transaction", + malleate: func() (common.Address, []byte) { + hexAddr := common.Bytes2Hex(s.address.Bytes()) + valAddr, err := sdk.ValAddressFromHex(hexAddr) + s.Require().NoError(err) + caller := common.BytesToAddress(valAddr) + + valCommission := sdk.DecCoins{sdk.NewDecCoinFromDec(utils.BaseDenom, math.LegacyNewDecWithPrec(1000000000000000000, 1))} + // set outstanding rewards + s.app.DistrKeeper.SetValidatorOutstandingRewards(s.ctx, valAddr, types.ValidatorOutstandingRewards{Rewards: valCommission}) + // set commission + s.app.DistrKeeper.SetValidatorAccumulatedCommission(s.ctx, valAddr, types.ValidatorAccumulatedCommission{Commission: valCommission}) + + input, err := s.precompile.Pack( + distribution.WithdrawValidatorCommissionMethod, + valAddr.String(), + ) + s.Require().NoError(err, "failed to pack input") + return caller, input + }, + readOnly: false, + expPass: true, + }, + { + name: "pass - withdraw delegator rewards transaction", + malleate: func() (common.Address, []byte) { + valAddr, err := sdk.ValAddressFromBech32(s.validators[0].OperatorAddress) + s.Require().NoError(err) + val, _ := s.app.StakingKeeper.GetValidator(s.ctx, valAddr) + coins := sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(1e18))) + s.app.DistrKeeper.AllocateTokensToValidator(s.ctx, val, sdk.NewDecCoinsFromCoins(coins...)) + + input, err := s.precompile.Pack( + distribution.WithdrawDelegatorRewardsMethod, + s.address, + valAddr.String(), + ) + s.Require().NoError(err, "failed to pack input") + + return s.address, input + }, + readOnly: false, + expPass: true, + }, + { + name: "pass - claim rewards transaction", + malleate: func() (common.Address, []byte) { + valAddr, err := sdk.ValAddressFromBech32(s.validators[0].OperatorAddress) + s.Require().NoError(err) + val, _ := s.app.StakingKeeper.GetValidator(s.ctx, valAddr) + coins := sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(1e18))) + s.app.DistrKeeper.AllocateTokensToValidator(s.ctx, val, sdk.NewDecCoinsFromCoins(coins...)) + + input, err := s.precompile.Pack( + distribution.ClaimRewardsMethod, + s.address, + uint32(2), + ) + s.Require().NoError(err, "failed to pack input") + + return s.address, input + }, + readOnly: false, + expPass: true, + }, + } + + for _, tc := range testcases { + tc := tc + s.Run(tc.name, func() { + // setup basic test suite + s.SetupTest() + + baseFee := s.app.FeeMarketKeeper.GetBaseFee(s.ctx) + + // malleate testcase + caller, input := tc.malleate() + + contract := vm.NewPrecompile(vm.AccountRef(caller), s.precompile, big.NewInt(0), uint64(1e6)) + contract.Input = input + + contractAddr := contract.Address() + // Build and sign Ethereum transaction + txArgs := evmtypes.EvmTxArgs{ + ChainID: s.app.EvmKeeper.ChainID(), + Nonce: 0, + To: &contractAddr, + Amount: nil, + GasLimit: 100000, + GasPrice: app.MainnetMinGasPrices.BigInt(), + GasFeeCap: baseFee, + GasTipCap: big.NewInt(1), + Accesses: ðtypes.AccessList{}, + } + msgEthereumTx := evmtypes.NewTx(&txArgs) + + msgEthereumTx.From = s.address.String() + err := msgEthereumTx.Sign(s.ethSigner, s.signer) + s.Require().NoError(err, "failed to sign Ethereum message") + + // Instantiate config + proposerAddress := s.ctx.BlockHeader().ProposerAddress + cfg, err := s.app.EvmKeeper.EVMConfig(s.ctx, proposerAddress, s.app.EvmKeeper.ChainID()) + s.Require().NoError(err, "failed to instantiate EVM config") + + msg, err := msgEthereumTx.AsMessage(s.ethSigner, baseFee) + s.Require().NoError(err, "failed to instantiate Ethereum message") + + // Instantiate EVM + evm := s.app.EvmKeeper.NewEVM( + s.ctx, msg, cfg, nil, s.stateDB, + ) + + params := s.app.EvmKeeper.GetParams(s.ctx) + activePrecompiles := params.GetActivePrecompilesAddrs() + precompileMap := s.app.EvmKeeper.Precompiles(activePrecompiles...) + err = vm.ValidatePrecompiles(precompileMap, activePrecompiles) + s.Require().NoError(err, "invalid precompiles", activePrecompiles) + evm.WithPrecompiles(precompileMap, activePrecompiles) + + // Run precompiled contract + bz, err := s.precompile.Run(evm, contract, tc.readOnly) + + // Check results + if tc.expPass { + s.Require().NoError(err, "expected no error when running the precompile") + s.Require().NotNil(bz, "expected returned bytes not to be nil") + } else { + s.Require().Error(err, "expected error to be returned when running the precompile") + s.Require().Nil(bz, "expected returned bytes to be nil") + s.Require().ErrorContains(err, tc.errContains) + } + }) + } +} diff --git a/precompiles/distribution/errors.go b/precompiles/distribution/errors.go new file mode 100644 index 00000000..9728a93d --- /dev/null +++ b/precompiles/distribution/errors.go @@ -0,0 +1,14 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +package distribution + +const ( + // ErrSetWithdrawAddrAuth is raised when no authorization to set the withdraw address exists. + ErrSetWithdrawAddrAuth = "set withdrawer address authorization for address %s does not exist" + // ErrWithdrawDelRewardsAuth is raised when no authorization to withdraw delegation rewards exists. + ErrWithdrawDelRewardsAuth = "withdraw delegation rewards authorization for address %s does not exist" + // ErrWithdrawValCommissionAuth is raised when no authorization to withdraw validator commission exists. + ErrWithdrawValCommissionAuth = "withdraw validator commission authorization for address %s does not exist" + // ErrDifferentValidator is raised when the origin address is not the same as the validator address. + ErrDifferentValidator = "origin address %s is not the same as validator address %s" +) diff --git a/precompiles/distribution/events.go b/precompiles/distribution/events.go new file mode 100644 index 00000000..ca6872a2 --- /dev/null +++ b/precompiles/distribution/events.go @@ -0,0 +1,159 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package distribution + +import ( + "bytes" + "reflect" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + cmn "github.com/evmos/evmos/v16/precompiles/common" +) + +const ( + // EventTypeSetWithdrawAddress defines the event type for the distribution SetWithdrawAddressMethod transaction. + EventTypeSetWithdrawAddress = "SetWithdrawerAddress" + // EventTypeWithdrawDelegatorRewards defines the event type for the distribution WithdrawDelegatorRewardsMethod transaction. + EventTypeWithdrawDelegatorRewards = "WithdrawDelegatorRewards" + // EventTypeWithdrawValidatorCommission defines the event type for the distribution WithdrawValidatorCommissionMethod transaction. + EventTypeWithdrawValidatorCommission = "WithdrawValidatorCommission" + // EventTypeClaimRewards defines the event type for the distribution ClaimRewardsMethod transaction. + EventTypeClaimRewards = "ClaimRewards" +) + +// EmitClaimRewardsEvent creates a new event emitted on a ClaimRewards transaction. +func (p Precompile) EmitClaimRewardsEvent(ctx sdk.Context, stateDB vm.StateDB, delegatorAddress common.Address, totalCoins sdk.Coins) error { + // Prepare the event topics + event := p.ABI.Events[EventTypeClaimRewards] + topics := make([]common.Hash, 2) + + // The first topic is always the signature of the event. + topics[0] = event.ID + + var err error + topics[1], err = cmn.MakeTopic(delegatorAddress) + if err != nil { + return err + } + + totalAmount := totalCoins.AmountOf(p.stakingKeeper.BondDenom(ctx)) + // Pack the arguments to be used as the Data field + arguments := abi.Arguments{event.Inputs[1]} + packed, err := arguments.Pack(totalAmount.BigInt()) + if err != nil { + return err + } + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: packed, + BlockNumber: uint64(ctx.BlockHeight()), + }) + + return nil +} + +// EmitSetWithdrawAddressEvent creates a new event emitted on a SetWithdrawAddressMethod transaction. +func (p Precompile) EmitSetWithdrawAddressEvent(ctx sdk.Context, stateDB vm.StateDB, caller common.Address, withdrawerAddress string) error { + // Prepare the event topics + event := p.ABI.Events[EventTypeSetWithdrawAddress] + topics := make([]common.Hash, 2) + + // The first topic is always the signature of the event. + topics[0] = event.ID + + var err error + topics[1], err = cmn.MakeTopic(caller) + if err != nil { + return err + } + + // Pack the arguments to be used as the Data field + arguments := abi.Arguments{event.Inputs[1]} + packed, err := arguments.Pack(withdrawerAddress) + if err != nil { + return err + } + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: packed, + BlockNumber: uint64(ctx.BlockHeight()), + }) + + return nil +} + +// EmitWithdrawDelegatorRewardsEvent creates a new event emitted on a WithdrawDelegatorRewards transaction. +func (p Precompile) EmitWithdrawDelegatorRewardsEvent(ctx sdk.Context, stateDB vm.StateDB, delegatorAddress common.Address, validatorAddress string, coins sdk.Coins) error { + valAddr, err := sdk.ValAddressFromBech32(validatorAddress) + if err != nil { + return err + } + + // Prepare the event topics + event := p.ABI.Events[EventTypeWithdrawDelegatorRewards] + topics := make([]common.Hash, 3) + + // The first topic is always the signature of the event. + topics[0] = event.ID + + topics[1], err = cmn.MakeTopic(delegatorAddress) + if err != nil { + return err + } + + topics[2], err = cmn.MakeTopic(common.BytesToAddress(valAddr.Bytes())) + if err != nil { + return err + } + + // Prepare the event data + var b bytes.Buffer + b.Write(cmn.PackNum(reflect.ValueOf(coins[0].Amount.BigInt()))) + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: b.Bytes(), + BlockNumber: uint64(ctx.BlockHeight()), + }) + + return nil +} + +// EmitWithdrawValidatorCommissionEvent creates a new event emitted on a WithdrawValidatorCommission transaction. +func (p Precompile) EmitWithdrawValidatorCommissionEvent(ctx sdk.Context, stateDB vm.StateDB, validatorAddress string, coins sdk.Coins) error { + // Prepare the event topics + event := p.ABI.Events[EventTypeWithdrawValidatorCommission] + topics := make([]common.Hash, 2) + + // The first topic is always the signature of the event. + topics[0] = event.ID + + var err error + topics[1], err = cmn.MakeTopic(validatorAddress) + if err != nil { + return err + } + + // Prepare the event data + var b bytes.Buffer + b.Write(cmn.PackNum(reflect.ValueOf(coins[0].Amount.BigInt()))) + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: b.Bytes(), + BlockNumber: uint64(ctx.BlockHeight()), + }) + + return nil +} diff --git a/precompiles/distribution/events_test.go b/precompiles/distribution/events_test.go new file mode 100644 index 00000000..a667c3a8 --- /dev/null +++ b/precompiles/distribution/events_test.go @@ -0,0 +1,249 @@ +package distribution_test + +import ( + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + cmn "github.com/evmos/evmos/v16/precompiles/common" + "github.com/evmos/evmos/v16/precompiles/distribution" + "github.com/evmos/evmos/v16/utils" +) + +func (s *PrecompileTestSuite) TestSetWithdrawAddressEvent() { + method := s.precompile.Methods[distribution.SetWithdrawAddressMethod] + testCases := []struct { + name string + malleate func(operatorAddress string) []interface{} + postCheck func() + gas uint64 + expError bool + errContains string + }{ + { + "success - the correct event is emitted", + func(operatorAddress string) []interface{} { + return []interface{}{ + s.address, + s.address.String(), + } + }, + func() { + log := s.stateDB.Logs()[0] + s.Require().Equal(log.Address, s.precompile.Address()) + + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[distribution.EventTypeSetWithdrawAddress] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + // Check the fully unpacked event matches the one emitted + var setWithdrawerAddrEvent distribution.EventSetWithdrawAddress + err := cmn.UnpackLog(s.precompile.ABI, &setWithdrawerAddrEvent, distribution.EventTypeSetWithdrawAddress, *log) + s.Require().NoError(err) + s.Require().Equal(s.address, setWithdrawerAddrEvent.Caller) + s.Require().Equal(sdk.MustBech32ifyAddressBytes("evmos", s.address.Bytes()), setWithdrawerAddrEvent.WithdrawerAddress) + }, + 20000, + false, + "", + }, + } + + for _, tc := range testCases { + s.SetupTest() + + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + s.ctx = s.ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + initialGas := s.ctx.GasMeter().GasConsumed() + s.Require().Zero(initialGas) + + _, err := s.precompile.SetWithdrawAddress(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate(s.validators[0].OperatorAddress)) + + if tc.expError { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck() + } + } +} + +func (s *PrecompileTestSuite) TestWithdrawDelegatorRewardsEvent() { + method := s.precompile.Methods[distribution.WithdrawDelegatorRewardsMethod] + testCases := []struct { + name string + malleate func(operatorAddress string) []interface{} + postCheck func() + gas uint64 + expError bool + errContains string + }{ + { + "success - the correct event is emitted", + func(operatorAddress string) []interface{} { + valAddr, err := sdk.ValAddressFromBech32(operatorAddress) + s.Require().NoError(err) + val, _ := s.app.StakingKeeper.GetValidator(s.ctx, valAddr) + coins := sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(1e18))) + s.app.DistrKeeper.AllocateTokensToValidator(s.ctx, val, sdk.NewDecCoinsFromCoins(coins...)) + return []interface{}{ + s.address, + operatorAddress, + } + }, + func() { + log := s.stateDB.Logs()[0] + s.Require().Equal(log.Address, s.precompile.Address()) + + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[distribution.EventTypeWithdrawDelegatorRewards] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + optAddr, err := sdk.ValAddressFromBech32(s.validators[0].OperatorAddress) + s.Require().NoError(err) + optHexAddr := common.BytesToAddress(optAddr) + + // Check the fully unpacked event matches the one emitted + var delegatorRewards distribution.EventWithdrawDelegatorRewards + err = cmn.UnpackLog(s.precompile.ABI, &delegatorRewards, distribution.EventTypeWithdrawDelegatorRewards, *log) + s.Require().NoError(err) + s.Require().Equal(s.address, delegatorRewards.DelegatorAddress) + s.Require().Equal(optHexAddr, delegatorRewards.ValidatorAddress) + s.Require().Equal(big.NewInt(1000000000000000000), delegatorRewards.Amount) + }, + 20000, + false, + "", + }, + } + + for _, tc := range testCases { + s.SetupTest() + + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + s.ctx = s.ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + initialGas := s.ctx.GasMeter().GasConsumed() + s.Require().Zero(initialGas) + + _, err := s.precompile.WithdrawDelegatorRewards(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate(s.validators[0].OperatorAddress)) + + if tc.expError { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck() + } + } +} + +func (s *PrecompileTestSuite) TestWithdrawValidatorCommissionEvent() { + method := s.precompile.Methods[distribution.WithdrawValidatorCommissionMethod] + testCases := []struct { + name string + malleate func(operatorAddress string) []interface{} + postCheck func() + gas uint64 + expError bool + errContains string + }{ + { + "success - the correct event is emitted", + func(operatorAddress string) []interface{} { + valAddr, err := sdk.ValAddressFromBech32(operatorAddress) + s.Require().NoError(err) + valCommission := sdk.DecCoins{sdk.NewDecCoinFromDec(utils.BaseDenom, math.LegacyNewDecWithPrec(1000000000000000000, 1))} + // set outstanding rewards + s.app.DistrKeeper.SetValidatorOutstandingRewards(s.ctx, valAddr, types.ValidatorOutstandingRewards{Rewards: valCommission}) + // set commission + s.app.DistrKeeper.SetValidatorAccumulatedCommission(s.ctx, valAddr, types.ValidatorAccumulatedCommission{Commission: valCommission}) + return []interface{}{ + operatorAddress, + } + }, + func() { + log := s.stateDB.Logs()[0] + s.Require().Equal(log.Address, s.precompile.Address()) + + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[distribution.EventTypeWithdrawValidatorCommission] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + // Check the fully unpacked event matches the one emitted + var validatorRewards distribution.EventWithdrawValidatorRewards + err := cmn.UnpackLog(s.precompile.ABI, &validatorRewards, distribution.EventTypeWithdrawValidatorCommission, *log) + s.Require().NoError(err) + s.Require().Equal(crypto.Keccak256Hash([]byte(s.validators[0].OperatorAddress)), validatorRewards.ValidatorAddress) + s.Require().Equal(big.NewInt(100000000000000000), validatorRewards.Commission) + }, + 20000, + false, + "", + }, + } + + for _, tc := range testCases { + s.SetupTest() + + validatorAddress := common.BytesToAddress(s.validators[0].GetOperator().Bytes()) + contract := vm.NewContract(vm.AccountRef(validatorAddress), s.precompile, big.NewInt(0), tc.gas) + s.ctx = s.ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + initialGas := s.ctx.GasMeter().GasConsumed() + s.Require().Zero(initialGas) + + _, err := s.precompile.WithdrawValidatorCommission(s.ctx, validatorAddress, contract, s.stateDB, &method, tc.malleate(s.validators[0].OperatorAddress)) + + if tc.expError { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck() + } + } +} + +func (s *PrecompileTestSuite) TestClaimRewardsEvent() { + testCases := []struct { + name string + coins sdk.Coins + postCheck func() + }{ + { + "success", + sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(1e18))), + func() { + log := s.stateDB.Logs()[0] + s.Require().Equal(log.Address, s.precompile.Address()) + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[distribution.EventTypeClaimRewards] + s.Require().Equal(event.ID, common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + var claimRewardsEvent distribution.EventClaimRewards + err := cmn.UnpackLog(s.precompile.ABI, &claimRewardsEvent, distribution.EventTypeClaimRewards, *log) + s.Require().NoError(err) + s.Require().Equal(common.BytesToAddress(s.address.Bytes()), claimRewardsEvent.DelegatorAddress) + s.Require().Equal(big.NewInt(1e18), claimRewardsEvent.Amount) + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + err := s.precompile.EmitClaimRewardsEvent(s.ctx, s.stateDB, s.address, tc.coins) + s.Require().NoError(err) + tc.postCheck() + }) + } +} diff --git a/precompiles/distribution/integration_test.go b/precompiles/distribution/integration_test.go new file mode 100644 index 00000000..3be19242 --- /dev/null +++ b/precompiles/distribution/integration_test.go @@ -0,0 +1,1393 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +package distribution_test + +import ( + "fmt" + "math/big" + + "github.com/evmos/evmos/v16/utils" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + cmn "github.com/evmos/evmos/v16/precompiles/common" + "github.com/evmos/evmos/v16/precompiles/distribution" + "github.com/evmos/evmos/v16/precompiles/testutil" + "github.com/evmos/evmos/v16/precompiles/testutil/contracts" + evmosutil "github.com/evmos/evmos/v16/testutil" + testutiltx "github.com/evmos/evmos/v16/testutil/tx" + + //nolint:revive // dot imports are fine for Ginkgo + . "github.com/onsi/ginkgo/v2" + //nolint:revive // dot imports are fine for Ginkgo + . "github.com/onsi/gomega" +) + +// General variables used for integration tests +var ( + // differentAddr is an address generated for testing purposes that e.g. raises the different origin error + differentAddr = testutiltx.GenerateAddress() + // expRewardAmt is the expected amount of rewards + expRewardAmt = big.NewInt(2000000000000000000) + // gasPrice is the gas price used for the transactions + gasPrice = big.NewInt(1e9) + // defaultCallArgs are the default arguments for calling the smart contract + // + // NOTE: this has to be populated in a BeforeEach block because the contractAddr would otherwise be a nil address. + defaultCallArgs contracts.CallArgs + + // defaultLogCheck instantiates a log check arguments struct with the precompile ABI events populated. + defaultLogCheck testutil.LogCheckArgs + // differentOriginCheck defines the arguments to check if the precompile returns different origin error + differentOriginCheck testutil.LogCheckArgs + // passCheck defines the arguments to check if the precompile returns no error + passCheck testutil.LogCheckArgs + // outOfGasCheck defines the arguments to check if the precompile returns out of gas error + outOfGasCheck testutil.LogCheckArgs +) + +var _ = Describe("Calling distribution precompile from EOA", func() { + BeforeEach(func() { + s.SetupTest() + + // set the default call arguments + defaultCallArgs = contracts.CallArgs{ + ContractAddr: s.precompile.Address(), + ContractABI: s.precompile.ABI, + PrivKey: s.privKey, + } + + defaultLogCheck = testutil.LogCheckArgs{ + ABIEvents: s.precompile.ABI.Events, + } + differentOriginCheck = defaultLogCheck.WithErrContains(cmn.ErrDifferentOrigin, s.address, differentAddr) + passCheck = defaultLogCheck.WithExpPass(true) + outOfGasCheck = defaultLogCheck.WithErrContains(vm.ErrOutOfGas.Error()) + }) + + // ===================================== + // TRANSACTIONS + // ===================================== + Describe("Execute SetWithdrawAddress transaction", func() { + const method = distribution.SetWithdrawAddressMethod + // defaultSetWithdrawArgs are the default arguments to set the withdraw address + // + // NOTE: this has to be populated in the BeforeEach block because the private key otherwise is not yet initialized. + var defaultSetWithdrawArgs contracts.CallArgs + + BeforeEach(func() { + // set the default call arguments + defaultSetWithdrawArgs = defaultCallArgs.WithMethodName(method) + }) + + It("should return error if the provided gasLimit is too low", func() { + setWithdrawArgs := defaultSetWithdrawArgs. + WithGasLimit(30000). + WithArgs(s.address, differentAddr.String()) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, setWithdrawArgs, outOfGasCheck) + Expect(err).To(HaveOccurred(), "error while calling the precompile") + Expect(err.Error()).To(ContainSubstring("out of gas"), "expected out of gas error") + + // withdraw address should remain unchanged + withdrawAddr := s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, s.address.Bytes()) + Expect(withdrawAddr.Bytes()).To(Equal(s.address.Bytes()), "expected withdraw address to remain unchanged") + }) + + It("should return error if the origin is different than the delegator", func() { + setWithdrawArgs := defaultSetWithdrawArgs.WithArgs(differentAddr, s.address.String()) + + withdrawAddrSetCheck := defaultLogCheck.WithErrContains(cmn.ErrDifferentOrigin, s.address.String(), differentAddr.String()) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, setWithdrawArgs, withdrawAddrSetCheck) + Expect(err).To(HaveOccurred(), "error while calling the precompile") + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf(cmn.ErrDifferentOrigin, s.address, differentAddr)), "expected different origin error") + }) + + It("should set withdraw address", func() { + // initially, withdraw address should be same as address + withdrawAddr := s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, s.address.Bytes()) + Expect(withdrawAddr.Bytes()).To(Equal(s.address.Bytes())) + + setWithdrawArgs := defaultSetWithdrawArgs.WithArgs(s.address, differentAddr.String()) + + withdrawAddrSetCheck := passCheck. + WithExpEvents(distribution.EventTypeSetWithdrawAddress) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, setWithdrawArgs, withdrawAddrSetCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + // withdraw should be updated + withdrawAddr = s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, s.address.Bytes()) + Expect(withdrawAddr.Bytes()).To(Equal(differentAddr.Bytes()), "expected different withdraw address") + }) + }) + + Describe("Execute WithdrawDelegatorRewards transaction", func() { + // defaultWithdrawRewardsArgs are the default arguments to withdraw rewards + // + // NOTE: this has to be populated in the BeforeEach block because the private key otherwise is not yet initialized. + var defaultWithdrawRewardsArgs contracts.CallArgs + + BeforeEach(func() { + // set the default call arguments + defaultWithdrawRewardsArgs = defaultCallArgs.WithMethodName(distribution.WithdrawDelegatorRewardsMethod) + s.prepareStakingRewards(stakingRewards{s.address.Bytes(), s.validators[0], rewards}) + }) + + It("should return error if the origin is different than the delegator", func() { + withdrawRewardsArgs := defaultWithdrawRewardsArgs.WithArgs(differentAddr, s.validators[0].OperatorAddress) + + withdrawalCheck := defaultLogCheck.WithErrContains(cmn.ErrDifferentOrigin, s.address.String(), differentAddr.String()) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, withdrawRewardsArgs, withdrawalCheck) + Expect(err).To(HaveOccurred(), "error while calling the precompile") + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf(cmn.ErrDifferentOrigin, s.address, differentAddr)), "expected different origin error") + }) + + It("should withdraw delegation rewards", func() { + // get initial balance + initialBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + Expect(initialBalance.Amount).To(Equal(initialBalance.Amount)) + + withdrawRewardsArgs := defaultWithdrawRewardsArgs. + WithArgs(s.address, s.validators[0].OperatorAddress). + WithGasPrice(gasPrice) + + withdrawalCheck := passCheck. + WithExpEvents(distribution.EventTypeWithdrawDelegatorRewards) + + res, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, withdrawRewardsArgs, withdrawalCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + var rewards []cmn.Coin + err = s.precompile.UnpackIntoInterface(&rewards, distribution.WithdrawDelegatorRewardsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(rewards)).To(Equal(1)) + Expect(rewards[0].Denom).To(Equal(s.bondDenom)) + Expect(rewards[0].Amount).To(Equal(expRewardAmt)) + + // check that the rewards were added to the balance + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + fees := gasPrice.Int64() * res.GasUsed + expFinal := initialBalance.Amount.Int64() + expRewardAmt.Int64() - fees + Expect(finalBalance.Amount.Equal(math.NewInt(expFinal))).To(BeTrue(), "expected final balance to be equal to initial balance + rewards - fees") + }) + }) + + Describe("Validator Commission: Execute WithdrawValidatorCommission tx", func() { + var ( + // defaultWithdrawCommissionArgs are the default arguments to withdraw commission + // + // NOTE: this has to be populated in the BeforeEach block because the private key otherwise is not yet initialized. + defaultWithdrawCommissionArgs contracts.CallArgs + + // expCommAmt is the expected commission amount + expCommAmt = big.NewInt(1) + // commDec is the commission rate + commDec = math.LegacyNewDec(1) + valAddr sdk.ValAddress + stakeAmt math.Int + ) + + BeforeEach(func() { + // set the default call arguments + defaultWithdrawCommissionArgs = defaultCallArgs.WithMethodName( + distribution.WithdrawValidatorCommissionMethod, + ) + + // create a validator with s.address and s.privKey because this account is + // used for signing txs + stakeAmt = math.NewInt(100) + testutil.CreateValidator(s.ctx, s.T(), s.privKey.PubKey(), s.app.StakingKeeper, stakeAmt) + + // set some validator commission + valAddr = s.address.Bytes() + val := s.app.StakingKeeper.Validator(s.ctx, valAddr) + valCommission := sdk.DecCoins{sdk.NewDecCoinFromDec(s.bondDenom, commDec)} + + s.app.DistrKeeper.SetValidatorAccumulatedCommission(s.ctx, valAddr, distrtypes.ValidatorAccumulatedCommission{Commission: valCommission}) + s.app.DistrKeeper.AllocateTokensToValidator(s.ctx, val, sdk.DecCoins{sdk.NewDecCoin(s.bondDenom, stakeAmt)}) + }) + + It("should return error if the provided gasLimit is too low", func() { + withdrawCommissionArgs := defaultWithdrawCommissionArgs. + WithGasLimit(50000). + WithArgs(valAddr.String()) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, withdrawCommissionArgs, outOfGasCheck) + Expect(err).To(HaveOccurred(), "error while calling the precompile") + Expect(err.Error()).To(ContainSubstring("out of gas"), "expected out of gas error") + }) + + It("should return error if the origin is different than the validator", func() { + withdrawCommissionArgs := defaultWithdrawCommissionArgs.WithArgs(s.validators[0].OperatorAddress) + validatorHexAddr := common.BytesToAddress(s.validators[0].GetOperator()) + + withdrawalCheck := defaultLogCheck.WithErrContains(cmn.ErrDifferentOrigin, s.address.String(), validatorHexAddr.String()) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, withdrawCommissionArgs, withdrawalCheck) + Expect(err).To(HaveOccurred(), "error while calling the precompile") + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf(cmn.ErrDifferentOrigin, s.address, validatorHexAddr)), "expected different origin error") + }) + + It("should withdraw validator commission", func() { + // initial balance should be the initial amount minus the staked amount used to create the validator + initialBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + Expect(initialBalance.Amount).To(Equal(math.NewInt(4999999999999999900))) + + withdrawCommissionArgs := defaultWithdrawCommissionArgs. + WithArgs(valAddr.String()). + WithGasPrice(gasPrice) + + withdrawalCheck := passCheck. + WithExpEvents(distribution.EventTypeWithdrawValidatorCommission) + + res, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, withdrawCommissionArgs, withdrawalCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + var comm []cmn.Coin + err = s.precompile.UnpackIntoInterface(&comm, distribution.WithdrawValidatorCommissionMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(comm)).To(Equal(1)) + Expect(comm[0].Denom).To(Equal(s.bondDenom)) + Expect(comm[0].Amount).To(Equal(expCommAmt)) + + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + fees := gasPrice.Int64() * res.GasUsed + expFinal := initialBalance.Amount.Int64() + expCommAmt.Int64() - fees + Expect(finalBalance.Amount.Equal(math.NewInt(expFinal))).To(BeTrue(), "expected final balance to be equal to the final balance after withdrawing commission") + }) + }) + + Describe("Execute ClaimRewards transaction", func() { + // defaultWithdrawRewardsArgs are the default arguments to withdraw rewards + // + // NOTE: this has to be populated in the BeforeEach block because the private key otherwise is not yet initialized. + var defaultClaimRewardsArgs contracts.CallArgs + startingBalance := math.NewInt(5e18) + expectedBalance := math.NewInt(8999665039062500000) + + BeforeEach(func() { + // set the default call arguments + defaultClaimRewardsArgs = defaultCallArgs.WithMethodName(distribution.ClaimRewardsMethod) + s.prepareStakingRewards(stakingRewards{s.address.Bytes(), s.validators[0], rewards}) + s.prepareStakingRewards(stakingRewards{s.address.Bytes(), s.validators[1], rewards}) + }) + + It("should return err if the origin is different than the delegator", func() { + claimRewardsArgs := defaultClaimRewardsArgs.WithArgs(differentAddr, uint32(1)) + + claimRewardsCheck := defaultLogCheck.WithErrContains(cmn.ErrDifferentOrigin, s.address.String(), differentAddr.String()) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, claimRewardsArgs, claimRewardsCheck) + Expect(err).To(HaveOccurred(), "error while calling the precompile") + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf(cmn.ErrDifferentOrigin, s.address, differentAddr)), "expected different origin error") + }) + + It("should claim all rewards from all validators", func() { + initialBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + Expect(initialBalance.Amount).To(Equal(startingBalance)) + + claimRewardsArgs := defaultClaimRewardsArgs.WithArgs(s.address, uint32(2)) + claimRewardsCheck := passCheck.WithExpEvents(distribution.EventTypeClaimRewards) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, claimRewardsArgs, claimRewardsCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + // check that the rewards were added to the balance + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + Expect(finalBalance.Amount.Equal(expectedBalance)).To(BeTrue(), "expected final balance to be equal to initial balance + rewards - fees") + }) + }) + + // ===================================== + // QUERIES + // ===================================== + Describe("Execute queries", func() { + It("should get validator distribution info - validatorDistributionInfo query", func() { + addr := sdk.AccAddress(s.validators[0].GetOperator()) + // fund validator account to make self-delegation + err := evmosutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, addr, 10) + Expect(err).To(BeNil()) + // make a self delegation + _, err = s.app.StakingKeeper.Delegate(s.ctx, addr, math.NewInt(1), stakingtypes.Unspecified, s.validators[0], true) + Expect(err).To(BeNil()) + + valDistArgs := defaultCallArgs. + WithMethodName(distribution.ValidatorDistributionInfoMethod). + WithArgs(s.validators[0].OperatorAddress) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, valDistArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + var out distribution.ValidatorDistributionInfoOutput + err = s.precompile.UnpackIntoInterface(&out, distribution.ValidatorDistributionInfoMethod, ethRes.Ret) + Expect(err).To(BeNil()) + + expAddr := sdk.AccAddress(s.validators[0].GetOperator()) + Expect(expAddr.String()).To(Equal(out.DistributionInfo.OperatorAddress)) + Expect(0).To(Equal(len(out.DistributionInfo.Commission))) + Expect(0).To(Equal(len(out.DistributionInfo.SelfBondRewards))) + }) + + It("should get validator outstanding rewards - validatorOutstandingRewards query", func() { //nolint:dupl + valRewards := sdk.DecCoins{sdk.NewDecCoinFromDec(s.bondDenom, math.LegacyNewDec(1))} + // set outstanding rewards + s.app.DistrKeeper.SetValidatorOutstandingRewards(s.ctx, s.validators[0].GetOperator(), distrtypes.ValidatorOutstandingRewards{Rewards: valRewards}) + + valOutRewardsArgs := defaultCallArgs. + WithMethodName(distribution.ValidatorOutstandingRewardsMethod). + WithArgs(s.validators[0].OperatorAddress) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, valOutRewardsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + var rewards []cmn.DecCoin + err = s.precompile.UnpackIntoInterface(&rewards, distribution.ValidatorOutstandingRewardsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(rewards)).To(Equal(1)) + Expect(uint8(18)).To(Equal(rewards[0].Precision)) + Expect(s.bondDenom).To(Equal(rewards[0].Denom)) + Expect(expValAmount).To(Equal(rewards[0].Amount.Int64())) + }) + + It("should get validator commission - validatorCommission query", func() { //nolint:dupl + // set commission + valCommission := sdk.DecCoins{sdk.NewDecCoinFromDec(s.bondDenom, math.LegacyNewDec(1))} + s.app.DistrKeeper.SetValidatorAccumulatedCommission(s.ctx, s.validators[0].GetOperator(), distrtypes.ValidatorAccumulatedCommission{Commission: valCommission}) + + valCommArgs := defaultCallArgs. + WithMethodName(distribution.ValidatorCommissionMethod). + WithArgs(s.validators[0].OperatorAddress) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, valCommArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + var commission []cmn.DecCoin + err = s.precompile.UnpackIntoInterface(&commission, distribution.ValidatorCommissionMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(commission)).To(Equal(1)) + Expect(uint8(18)).To(Equal(commission[0].Precision)) + Expect(s.bondDenom).To(Equal(commission[0].Denom)) + Expect(expValAmount).To(Equal(commission[0].Amount.Int64())) + }) + + Context("validatorSlashes query query", func() { + It("should get validator slashing events (default pagination)", func() { + // set slash event + slashEvent := distrtypes.ValidatorSlashEvent{ValidatorPeriod: 1, Fraction: math.LegacyNewDec(5)} + s.app.DistrKeeper.SetValidatorSlashEvent(s.ctx, s.validators[0].GetOperator(), 2, 1, slashEvent) + + valSlashArgs := defaultCallArgs. + WithMethodName(distribution.ValidatorSlashesMethod). + WithArgs( + s.validators[0].OperatorAddress, + uint64(1), uint64(5), + query.PageRequest{}, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, valSlashArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + var out distribution.ValidatorSlashesOutput + err = s.precompile.UnpackIntoInterface(&out, distribution.ValidatorSlashesMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(out.Slashes)).To(Equal(1)) + Expect(slashEvent.Fraction.BigInt()).To(Equal(out.Slashes[0].Fraction.Value)) + Expect(slashEvent.ValidatorPeriod).To(Equal(out.Slashes[0].ValidatorPeriod)) + Expect(uint64(1)).To(Equal(out.PageResponse.Total)) + Expect(out.PageResponse.NextKey).To(BeEmpty()) + }) + + It("should get validator slashing events - query w/pagination limit = 1)", func() { + // set 2 slashing events for validator[0] + slashEvent := s.setupValidatorSlashes(s.validators[0].GetOperator(), 2) + + valSlashArgs := defaultCallArgs. + WithMethodName(distribution.ValidatorSlashesMethod). + WithArgs( + s.validators[0].OperatorAddress, + uint64(1), uint64(5), + query.PageRequest{ + Limit: 1, + CountTotal: true, + }, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, valSlashArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + var out distribution.ValidatorSlashesOutput + err = s.precompile.UnpackIntoInterface(&out, distribution.ValidatorSlashesMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(out.Slashes)).To(Equal(1)) + Expect(slashEvent.Fraction.BigInt()).To(Equal(out.Slashes[0].Fraction.Value)) + Expect(slashEvent.ValidatorPeriod).To(Equal(out.Slashes[0].ValidatorPeriod)) + // total slashes count is 2 + Expect(uint64(2)).To(Equal(out.PageResponse.Total)) + Expect(out.PageResponse.NextKey).NotTo(BeEmpty()) + }) + }) + + It("should get delegation rewards - delegationRewards query", func() { + s.prepareStakingRewards(stakingRewards{s.address.Bytes(), s.validators[0], rewards}) + + delRewardsArgs := defaultCallArgs. + WithMethodName(distribution.DelegationRewardsMethod). + WithArgs(s.address, s.validators[0].OperatorAddress) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delRewardsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + var rewards []cmn.DecCoin + err = s.precompile.UnpackIntoInterface(&rewards, distribution.DelegationRewardsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(rewards)).To(Equal(1)) + Expect(rewards[0].Denom).To(Equal(s.bondDenom)) + Expect(rewards[0].Amount.Int64()).To(Equal(expDelegationRewards)) + }) + + It("should get delegators's total rewards - delegationTotalRewards query", func() { + // set rewards + s.prepareStakingRewards(stakingRewards{s.address.Bytes(), s.validators[0], rewards}) + + delTotalRewardsArgs := defaultCallArgs. + WithMethodName(distribution.DelegationTotalRewardsMethod). + WithArgs(s.address) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delTotalRewardsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + var ( + out distribution.DelegationTotalRewardsOutput + i int + ) + err = s.precompile.UnpackIntoInterface(&out, distribution.DelegationTotalRewardsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(2).To(Equal(len(out.Rewards))) + + // the response order may change + if out.Rewards[0].ValidatorAddress == s.validators[0].OperatorAddress { + Expect(s.validators[0].OperatorAddress).To(Equal(out.Rewards[0].ValidatorAddress)) + Expect(s.validators[1].OperatorAddress).To(Equal(out.Rewards[1].ValidatorAddress)) + Expect(0).To(Equal(len(out.Rewards[1].Reward))) + } else { + i = 1 + Expect(s.validators[0].OperatorAddress).To(Equal(out.Rewards[1].ValidatorAddress)) + Expect(s.validators[1].OperatorAddress).To(Equal(out.Rewards[0].ValidatorAddress)) + Expect(0).To(Equal(len(out.Rewards[0].Reward))) + } + + // only validator[i] has rewards + Expect(1).To(Equal(len(out.Rewards[i].Reward))) + Expect(s.bondDenom).To(Equal(out.Rewards[i].Reward[0].Denom)) + Expect(uint8(math.LegacyPrecision)).To(Equal(out.Rewards[i].Reward[0].Precision)) + Expect(expDelegationRewards).To(Equal(out.Rewards[i].Reward[0].Amount.Int64())) + + Expect(1).To(Equal(len(out.Total))) + Expect(expDelegationRewards).To(Equal(out.Total[0].Amount.Int64())) + }) + + It("should get all validators a delegators has delegated to - delegatorValidators query", func() { + delValArgs := defaultCallArgs. + WithMethodName(distribution.DelegatorValidatorsMethod). + WithArgs(s.address) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delValArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + var validators []string + err = s.precompile.UnpackIntoInterface(&validators, distribution.DelegatorValidatorsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(2).To(Equal(len(validators))) + + // the response order may change + if validators[0] == s.validators[0].OperatorAddress { + Expect(s.validators[0].OperatorAddress).To(Equal(validators[0])) + Expect(s.validators[1].OperatorAddress).To(Equal(validators[1])) + } else { + Expect(s.validators[1].OperatorAddress).To(Equal(validators[0])) + Expect(s.validators[0].OperatorAddress).To(Equal(validators[1])) + } + }) + + It("should get withdraw address - delegatorWithdrawAddress query", func() { + // set the withdraw address + err := s.app.DistrKeeper.SetWithdrawAddr(s.ctx, s.address.Bytes(), differentAddr.Bytes()) + Expect(err).To(BeNil()) + + delWithdrawAddrArgs := defaultCallArgs. + WithMethodName(distribution.DelegatorWithdrawAddressMethod). + WithArgs(s.address) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delWithdrawAddrArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the precompile") + + withdrawAddr, err := s.precompile.Unpack(distribution.DelegatorWithdrawAddressMethod, ethRes.Ret) + Expect(err).To(BeNil()) + // get the bech32 encoding + expAddr := sdk.AccAddress(differentAddr.Bytes()) + Expect(withdrawAddr[0]).To(Equal(expAddr.String())) + }) + }) +}) + +var _ = Describe("Calling distribution precompile from another contract", func() { + var ( + // initBalanceAmt is the initial balance for testing + initBalanceAmt = math.NewInt(5000000000000000000) + + // contractAddr is the address of the smart contract that will be deployed + contractAddr common.Address + // err is a basic error type + err error + + // execRevertedCheck defines the default log checking arguments which includes the + // standard revert message. + execRevertedCheck testutil.LogCheckArgs + ) + + BeforeEach(func() { + s.SetupTest() + contractAddr, err = s.DeployContract(contracts.DistributionCallerContract) + Expect(err).To(BeNil(), "error while deploying the smart contract: %v", err) + + // NextBlock the smart contract + s.NextBlock() + + // check contract was correctly deployed + cAcc := s.app.EvmKeeper.GetAccount(s.ctx, contractAddr) + Expect(cAcc).ToNot(BeNil(), "contract account should exist") + Expect(cAcc.IsContract()).To(BeTrue(), "account should be a contract") + + // populate default call args + defaultCallArgs = contracts.CallArgs{ + ContractAddr: contractAddr, + ContractABI: contracts.DistributionCallerContract.ABI, + PrivKey: s.privKey, + } + + // default log check arguments + defaultLogCheck = testutil.LogCheckArgs{ABIEvents: s.precompile.Events} + execRevertedCheck = defaultLogCheck.WithErrContains("execution reverted") + passCheck = defaultLogCheck.WithExpPass(true) + }) + + // ===================================== + // TRANSACTIONS + // ===================================== + Context("setWithdrawAddress", func() { + var ( + // defaultSetWithdrawAddrArgs are the default arguments for the set withdraw address call + // + // NOTE: this has to be populated in a BeforeEach block because the contractAddr would otherwise be a nil address. + defaultSetWithdrawAddrArgs contracts.CallArgs + // newWithdrawer is the address to set the withdraw address to + newWithdrawer = differentAddr + ) + + BeforeEach(func() { + // withdraw address should be same as address + withdrawer := s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, s.address.Bytes()) + Expect(withdrawer.Bytes()).To(Equal(s.address.Bytes())) + + // populate default arguments + defaultSetWithdrawAddrArgs = defaultCallArgs.WithMethodName( + "testSetWithdrawAddress", + ) + }) + + It("should set withdraw address successfully", func() { + setWithdrawAddrArgs := defaultSetWithdrawAddrArgs.WithArgs( + s.address, newWithdrawer.String(), + ) + + setWithdrawCheck := passCheck.WithExpEvents(distribution.EventTypeSetWithdrawAddress) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, setWithdrawAddrArgs, setWithdrawCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + withdrawer := s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, s.address.Bytes()) + Expect(withdrawer.Bytes()).To(Equal(newWithdrawer.Bytes())) + }) + }) + + Context("setWithdrawerAddress with contract as delegator", func() { + var ( + // defaultSetWithdrawAddrArgs are the default arguments for the set withdraw address call + // + // NOTE: this has to be populated in a BeforeEach block because the contractAddr would otherwise be a nil address. + defaultSetWithdrawAddrArgs contracts.CallArgs + // newWithdrawer is the address to set the withdraw address to + newWithdrawer = differentAddr + ) + + BeforeEach(func() { + // withdraw address should be same as address + withdrawer := s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, s.address.Bytes()) + Expect(withdrawer.Bytes()).To(Equal(s.address.Bytes())) + + // populate default arguments + defaultSetWithdrawAddrArgs = defaultCallArgs.WithMethodName( + "testSetWithdrawAddressFromContract", + ) + }) + + It("should set withdraw address successfully without origin check", func() { + setWithdrawAddrArgs := defaultSetWithdrawAddrArgs.WithArgs(newWithdrawer.String()) + + setWithdrawCheck := passCheck.WithExpEvents(distribution.EventTypeSetWithdrawAddress) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, setWithdrawAddrArgs, setWithdrawCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + withdrawer := s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, contractAddr.Bytes()) + Expect(withdrawer.Bytes()).To(Equal(newWithdrawer.Bytes())) + }) + }) + + Context("withdrawDelegatorRewards", func() { + var ( + // defaultWithdrawDelRewardsArgs are the default arguments for the withdraw delegator rewards call + // + // NOTE: this has to be populated in a BeforeEach block because the contractAddr would otherwise be a nil address. + defaultWithdrawDelRewardsArgs contracts.CallArgs + // initialBalance is the initial balance of the delegator + initialBalance sdk.Coin + ) + + BeforeEach(func() { + // set some rewards for s.address & another address + s.prepareStakingRewards([]stakingRewards{ + {s.address.Bytes(), s.validators[0], rewards}, + {differentAddr.Bytes(), s.validators[0], rewards}, + }...) + + initialBalance = s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + + // populate default arguments + defaultWithdrawDelRewardsArgs = defaultCallArgs.WithMethodName( + "testWithdrawDelegatorRewards", + ) + }) + + It("should not withdraw rewards when sending from a different address", func() { + withdrawDelRewardsArgs := defaultWithdrawDelRewardsArgs.WithArgs( + differentAddr, s.validators[0].OperatorAddress, + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, withdrawDelRewardsArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + // balance should be equal as initial balance or less (because of fees) + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + Expect(finalBalance.Amount.Uint64() <= initialBalance.Amount.Uint64()).To(BeTrue()) + + // differentAddr balance should remain unchanged + differentAddrFinalBalance := s.app.BankKeeper.GetBalance(s.ctx, differentAddr.Bytes(), s.bondDenom) + Expect(differentAddrFinalBalance.Amount).To(Equal(math.ZeroInt())) + }) + + It("should withdraw rewards successfully", func() { + withdrawDelRewardsArgs := defaultWithdrawDelRewardsArgs.WithArgs( + s.address, s.validators[0].OperatorAddress, + ) + + logCheckArgs := passCheck. + WithExpEvents(distribution.EventTypeWithdrawDelegatorRewards) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, withdrawDelRewardsArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + // balance should remain unchanged + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + Expect(finalBalance.Amount.GT(initialBalance.Amount)).To(BeTrue(), "expected final balance to be greater than initial balance after withdrawing rewards") + }) + + It("should withdraw rewards successfully to the new withdrawer address", func() { + initialBalance := s.app.BankKeeper.GetBalance(s.ctx, differentAddr.Bytes(), s.bondDenom) + // Set new withdrawer address + err := s.app.DistrKeeper.SetWithdrawAddr(s.ctx, s.address.Bytes(), differentAddr.Bytes()) + Expect(err).To(BeNil()) + + withdrawDelRewardsArgs := defaultWithdrawDelRewardsArgs.WithArgs( + s.address, s.validators[0].OperatorAddress, + ) + + logCheckArgs := passCheck. + WithExpEvents(distribution.EventTypeWithdrawDelegatorRewards) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, withdrawDelRewardsArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + // should increase balance by rewards + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, differentAddr.Bytes(), s.bondDenom) + Expect(finalBalance.Amount.GT(initialBalance.Amount)).To(BeTrue(), "expected final balance to be greater than initial balance after withdrawing rewards") + }) + }) + + Context("withdrawDelegatorRewards with contract as delegator", func() { + var ( + // defaultWithdrawDelRewardsArgs are the default arguments for the withdraw delegator rewards call + // + // NOTE: this has to be populated in a BeforeEach block because the contractAddr would otherwise be a nil address. + defaultWithdrawDelRewardsArgs contracts.CallArgs + // initialBalance is the initial balance of the delegator + initialBalance sdk.Coin + ) + + BeforeEach(func() { + // set some rewards for s.address & another address + s.prepareStakingRewards([]stakingRewards{ + { + Delegator: contractAddr.Bytes(), + Validator: s.validators[0], + RewardAmt: rewards, + }, + }...) + + initialBalance = s.app.BankKeeper.GetBalance(s.ctx, contractAddr.Bytes(), s.bondDenom) + + // populate default arguments + defaultWithdrawDelRewardsArgs = defaultCallArgs.WithMethodName( + "testWithdrawDelegatorRewardsFromContract", + ) + }) + + It("should withdraw rewards successfully without origin check", func() { + withdrawDelRewardsArgs := defaultWithdrawDelRewardsArgs.WithArgs(s.validators[0].OperatorAddress) + + logCheckArgs := passCheck.WithExpEvents(distribution.EventTypeWithdrawDelegatorRewards) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, withdrawDelRewardsArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + // balance should increase + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, contractAddr.Bytes(), s.bondDenom) + Expect(finalBalance.Amount.GT(initialBalance.Amount)).To(BeTrue(), "expected final balance to be greater than initial balance after withdrawing rewards") + }) + }) + + Context("withdrawValidatorCommission", func() { + var ( + // defaultWithdrawValCommArgs are the default arguments for the withdraw validator commission call + // + // NOTE: this has to be populated in a BeforeEach block because the contractAddr would otherwise be a nil address. + defaultWithdrawValCommArgs contracts.CallArgs + // commDec is the commission rate of the validator + commDec = math.LegacyNewDec(1) + // valAddr is the address of the validator + valAddr sdk.ValAddress + // initialBalance is the initial balance of the delegator + initialBalance sdk.Coin + ) + + BeforeEach(func() { + // create a validator with s.address because is the address + // used for signing txs + valAddr = s.address.Bytes() + stakeAmt := math.NewInt(100) + testutil.CreateValidator(s.ctx, s.T(), s.privKey.PubKey(), s.app.StakingKeeper, stakeAmt) + + // set some commissions to validators + var valAddresses []sdk.ValAddress + valAddresses = append( + valAddresses, + valAddr, + s.validators[0].GetOperator(), + s.validators[1].GetOperator(), + ) + + for _, addr := range valAddresses { + val := s.app.StakingKeeper.Validator(s.ctx, addr) + valCommission := sdk.DecCoins{sdk.NewDecCoinFromDec(s.bondDenom, commDec)} + + s.app.DistrKeeper.SetValidatorAccumulatedCommission( + s.ctx, addr, + distrtypes.ValidatorAccumulatedCommission{Commission: valCommission}, + ) + s.app.DistrKeeper.AllocateTokensToValidator(s.ctx, val, sdk.DecCoins{sdk.NewDecCoin(s.bondDenom, stakeAmt)}) + } + + initialBalance = s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + + // populate default arguments + defaultWithdrawValCommArgs = defaultCallArgs.WithMethodName( + "testWithdrawValidatorCommission", + ) + }) + + It("should not withdraw commission from validator when sending from a different address", func() { + withdrawValCommArgs := defaultWithdrawValCommArgs.WithArgs( + s.validators[0].OperatorAddress, + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, withdrawValCommArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + // balance should be equal as initial balance or less (because of fees) + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + Expect(finalBalance.Amount.Uint64() <= initialBalance.Amount.Uint64()).To(BeTrue()) + + // validator's balance should remain unchanged + valFinalBalance := s.app.BankKeeper.GetBalance(s.ctx, sdk.AccAddress(s.validators[0].GetOperator()), s.bondDenom) + Expect(valFinalBalance.Amount).To(Equal(math.ZeroInt())) + }) + + It("should withdraw commission successfully", func() { + withdrawValCommArgs := defaultWithdrawValCommArgs. + WithArgs(valAddr.String()). + WithGasPrice(gasPrice) + logCheckArgs := passCheck. + WithExpEvents(distribution.EventTypeWithdrawValidatorCommission) + + res, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, withdrawValCommArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + fees := gasPrice.Int64() * res.GasUsed + expFinal := initialBalance.Amount.Int64() + expValAmount - fees + Expect(finalBalance.Amount).To(Equal(math.NewInt(expFinal)), "expected final balance to be equal to initial balance + validator commission - fees") + }) + }) + + Context("claimRewards", func() { + var ( + // defaultClaimRewardsArgs are the default arguments for the claim rewards call + // + // NOTE: this has to be populated in a BeforeEach block because the contractAddr would otherwise be a nil address. + defaultClaimRewardsArgs contracts.CallArgs + // initialBalance is the initial balance of the delegator + initialBalance sdk.Coin + ) + + BeforeEach(func() { + // set some rewards for s.address & another address + s.prepareStakingRewards([]stakingRewards{ + {s.address.Bytes(), s.validators[0], rewards}, + {differentAddr.Bytes(), s.validators[0], rewards}, + }...) + + initialBalance = s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + + // populate default arguments + defaultClaimRewardsArgs = defaultCallArgs.WithMethodName( + "testClaimRewards", + ) + }) + + It("should not claim rewards when sending from a different address", func() { + claimRewardsArgs := defaultClaimRewardsArgs.WithArgs( + differentAddr, uint32(1), + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, claimRewardsArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + // balance should be equal as initial balance or less (because of fees) + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + Expect(finalBalance.Amount.Uint64() <= initialBalance.Amount.Uint64()).To(BeTrue()) + + // differentAddr balance should remain unchanged + differentAddrFinalBalance := s.app.BankKeeper.GetBalance(s.ctx, differentAddr.Bytes(), s.bondDenom) + Expect(differentAddrFinalBalance.Amount).To(Equal(math.ZeroInt())) + }) + + It("should claim rewards successfully", func() { + claimRewardsArgs := defaultClaimRewardsArgs.WithArgs( + s.address, uint32(2), + ) + + logCheckArgs := passCheck. + WithExpEvents(distribution.EventTypeClaimRewards) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, claimRewardsArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + // balance should remain unchanged + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + Expect(finalBalance.Amount.GT(initialBalance.Amount)).To(BeTrue(), "expected final balance to be greater than initial balance after claiming rewards") + }) + }) + + Context("claimRewards with contract as delegator", func() { + var ( + // defaultClaimRewardsArgs are the default arguments for the claim rewards call + // + // NOTE: this has to be populated in a BeforeEach block because the contractAddr would otherwise be a nil address. + defaultClaimRewardsArgs contracts.CallArgs + // expectedBalance is the total after claiming from both validators + expectedBalance sdk.Coin + ) + + BeforeEach(func() { + // set some rewards for s.address & another address + s.prepareStakingRewards([]stakingRewards{ + { + Delegator: contractAddr.Bytes(), + Validator: s.validators[0], + RewardAmt: rewards, + }, { + Delegator: contractAddr.Bytes(), + Validator: s.validators[1], + RewardAmt: rewards, + }, + }...) + + expectedBalance = sdk.Coin{Denom: utils.BaseDenom, Amount: math.NewInt(2e18)} + + // populate default arguments + defaultClaimRewardsArgs = defaultCallArgs.WithMethodName( + "testClaimRewards", + ) + }) + + It("should withdraw rewards successfully without origin check", func() { + claimRewardsArgs := defaultClaimRewardsArgs.WithArgs(contractAddr, uint32(2)) + + logCheckArgs := passCheck.WithExpEvents(distribution.EventTypeClaimRewards) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, claimRewardsArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + // balance should increase + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, contractAddr.Bytes(), s.bondDenom) + Expect(finalBalance.Amount.Equal(expectedBalance.Amount)).To(BeTrue(), "expected final balance to be greater than initial balance after withdrawing rewards") + }) + + It("should withdraw rewards successfully to a different address without origin check", func() { + expectedBalance = sdk.Coin{Denom: utils.BaseDenom, Amount: math.NewInt(6997329929187000000)} + err := s.app.DistrKeeper.SetWithdrawAddr(s.ctx, contractAddr.Bytes(), s.address.Bytes()) + Expect(err).To(BeNil()) + + claimRewardsArgs := defaultClaimRewardsArgs.WithArgs(contractAddr, uint32(2)) + + logCheckArgs := passCheck.WithExpEvents(distribution.EventTypeClaimRewards) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, claimRewardsArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + // balance should increase + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + Expect(finalBalance.Amount.Equal(expectedBalance.Amount)).To(BeTrue(), "expected final balance to be greater than initial balance after withdrawing rewards") + }) + }) + + Context("Forbidden operations", func() { + It("should revert state: modify withdraw address & then try to withdraw rewards corresponding to another user", func() { + // set rewards to another user + s.prepareStakingRewards(stakingRewards{differentAddr.Bytes(), s.validators[0], rewards}) + + revertArgs := defaultCallArgs. + WithMethodName("testRevertState"). + WithArgs( + differentAddr.String(), differentAddr, s.validators[0].OperatorAddress, + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, revertArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + // check withdraw address didn't change + withdrawer := s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, s.address.Bytes()) + Expect(withdrawer.Bytes()).To(Equal(s.address.Bytes())) + + // check signer address balance should've decreased (fees paid) + finalBalance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + Expect(finalBalance.Amount.Uint64() <= initBalanceAmt.Uint64()).To(BeTrue()) + + // check other address' balance remained unchanged + finalBalance = s.app.BankKeeper.GetBalance(s.ctx, differentAddr.Bytes(), s.bondDenom) + Expect(finalBalance.Amount).To(Equal(math.ZeroInt())) + }) + + It("should not allow to call SetWithdrawAddress using delegatecall", func() { + setWithdrawAddrArgs := defaultCallArgs. + WithMethodName("delegateCallSetWithdrawAddress"). + WithArgs(s.address, differentAddr.String()) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, setWithdrawAddrArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + // check withdraw address didn't change + withdrawer := s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, s.address.Bytes()) + Expect(withdrawer.Bytes()).To(Equal(s.address.Bytes())) + }) + + It("should not allow to call txs (SetWithdrawAddress) using staticcall", func() { + setWithdrawAddrArgs := defaultCallArgs. + WithMethodName("staticCallSetWithdrawAddress"). + WithArgs(s.address, differentAddr.String()) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, setWithdrawAddrArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + // check withdraw address didn't change + withdrawer := s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, s.address.Bytes()) + Expect(withdrawer.Bytes()).To(Equal(s.address.Bytes())) + }) + }) + + // =================================== + // QUERIES + // =================================== + Context("Distribution precompile queries", func() { + Context("get validator distribution info", func() { + // defaultValDistArgs are the default arguments for the getValidatorDistributionInfo query + // + // NOTE: this has to be populated in BeforeEach because the test suite setup is not available prior to that. + var defaultValDistArgs contracts.CallArgs + + BeforeEach(func() { + addr := sdk.AccAddress(s.validators[0].GetOperator()) + // fund validator account to make self-delegation + err := evmosutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, addr, 10) + Expect(err).To(BeNil()) + // make a self delegation + _, err = s.app.StakingKeeper.Delegate(s.ctx, addr, math.NewInt(1), stakingtypes.Unspecified, s.validators[0], true) + Expect(err).To(BeNil()) + + defaultValDistArgs = defaultCallArgs. + WithMethodName("getValidatorDistributionInfo"). + WithArgs(s.validators[0].OperatorAddress) + }) + + It("should get validator distribution info", func() { + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultValDistArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var out distribution.ValidatorDistributionInfoOutput + err = s.precompile.UnpackIntoInterface(&out, distribution.ValidatorDistributionInfoMethod, ethRes.Ret) + Expect(err).To(BeNil()) + + expAddr := sdk.AccAddress(s.validators[0].GetOperator()) + Expect(expAddr.String()).To(Equal(out.DistributionInfo.OperatorAddress)) + Expect(0).To(Equal(len(out.DistributionInfo.Commission))) + Expect(0).To(Equal(len(out.DistributionInfo.SelfBondRewards))) + }) + }) + + Context("get validator outstanding rewards", func() { //nolint:dupl + // defaultValOutRewardsArgs are the default arguments for the getValidatorOutstandingRewards query + // + // NOTE: this has to be populated in BeforeEach because the test suite setup is not available prior to that. + var defaultValOutRewardsArgs contracts.CallArgs + + BeforeEach(func() { + defaultValOutRewardsArgs = defaultCallArgs. + WithMethodName("getValidatorOutstandingRewards"). + WithArgs(s.validators[0].OperatorAddress) + }) + + It("should not get rewards - validator without outstanding rewards", func() { + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultValOutRewardsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var rewards []cmn.DecCoin + err = s.precompile.UnpackIntoInterface(&rewards, distribution.ValidatorOutstandingRewardsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(rewards)).To(Equal(0)) + }) + + It("should get rewards - validator with outstanding rewards", func() { + valRewards := sdk.DecCoins{sdk.NewDecCoinFromDec(s.bondDenom, math.LegacyNewDec(1))} + // set outstanding rewards + s.app.DistrKeeper.SetValidatorOutstandingRewards(s.ctx, s.validators[0].GetOperator(), distrtypes.ValidatorOutstandingRewards{Rewards: valRewards}) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultValOutRewardsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var rewards []cmn.DecCoin + err = s.precompile.UnpackIntoInterface(&rewards, distribution.ValidatorOutstandingRewardsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(rewards)).To(Equal(1)) + Expect(uint8(18)).To(Equal(rewards[0].Precision)) + Expect(s.bondDenom).To(Equal(rewards[0].Denom)) + Expect(expValAmount).To(Equal(rewards[0].Amount.Int64())) + }) + }) + + Context("get validator commission", func() { //nolint:dupl + // defaultValCommArgs are the default arguments for the getValidatorCommission query + // + // NOTE: this has to be populated in BeforeEach because the test suite setup is not available prior to that. + var defaultValCommArgs contracts.CallArgs + + BeforeEach(func() { + defaultValCommArgs = defaultCallArgs. + WithMethodName("getValidatorCommission"). + WithArgs(s.validators[0].OperatorAddress) + }) + + It("should not get commission - validator without commission", func() { + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultValCommArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var commission []cmn.DecCoin + err = s.precompile.UnpackIntoInterface(&commission, distribution.ValidatorCommissionMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(commission)).To(Equal(0)) + }) + + It("should get commission - validator with commission", func() { + // set commission + valCommission := sdk.DecCoins{sdk.NewDecCoinFromDec(s.bondDenom, math.LegacyNewDec(1))} + s.app.DistrKeeper.SetValidatorAccumulatedCommission(s.ctx, s.validators[0].GetOperator(), distrtypes.ValidatorAccumulatedCommission{Commission: valCommission}) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultValCommArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var commission []cmn.DecCoin + err = s.precompile.UnpackIntoInterface(&commission, distribution.ValidatorCommissionMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(commission)).To(Equal(1)) + Expect(uint8(18)).To(Equal(commission[0].Precision)) + Expect(s.bondDenom).To(Equal(commission[0].Denom)) + Expect(expValAmount).To(Equal(commission[0].Amount.Int64())) + }) + }) + + Context("get validator slashing events", func() { + // defaultValSlashArgs are the default arguments for the getValidatorSlashes query + // + // NOTE: this has to be populated in BeforeEach because the test suite setup is not available prior to that. + var defaultValSlashArgs contracts.CallArgs + + BeforeEach(func() { + defaultValSlashArgs = defaultCallArgs. + WithMethodName("getValidatorSlashes"). + WithArgs( + s.validators[0].OperatorAddress, + uint64(1), uint64(5), + query.PageRequest{}, + ) + }) + + It("should not get slashing events - validator without slashes", func() { + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultValSlashArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var out distribution.ValidatorSlashesOutput + err = s.precompile.UnpackIntoInterface(&out, distribution.ValidatorSlashesMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(out.Slashes)).To(Equal(0)) + }) + + It("should get slashing events - validator with slashes (default pagination)", func() { + // set slash event + slashEvent := s.setupValidatorSlashes(s.validators[0].GetOperator(), 1) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultValSlashArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var out distribution.ValidatorSlashesOutput + err = s.precompile.UnpackIntoInterface(&out, distribution.ValidatorSlashesMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(out.Slashes)).To(Equal(1)) + Expect(slashEvent.Fraction.BigInt()).To(Equal(out.Slashes[0].Fraction.Value)) + Expect(slashEvent.ValidatorPeriod).To(Equal(out.Slashes[0].ValidatorPeriod)) + Expect(uint64(1)).To(Equal(out.PageResponse.Total)) + Expect(out.PageResponse.NextKey).To(BeEmpty()) + }) + + It("should get slashing events - validator with slashes w/pagination", func() { + // set 2 slashing events + slashEvent := s.setupValidatorSlashes(s.validators[0].GetOperator(), 2) + + // set pagination + defaultValSlashArgs.Args = []interface{}{ + s.validators[0].OperatorAddress, + uint64(1), uint64(5), + query.PageRequest{ + Limit: 1, + CountTotal: true, + }, + } + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultValSlashArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var out distribution.ValidatorSlashesOutput + err = s.precompile.UnpackIntoInterface(&out, distribution.ValidatorSlashesMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(out.Slashes)).To(Equal(1)) + Expect(slashEvent.Fraction.BigInt()).To(Equal(out.Slashes[0].Fraction.Value)) + Expect(slashEvent.ValidatorPeriod).To(Equal(out.Slashes[0].ValidatorPeriod)) + Expect(uint64(2)).To(Equal(out.PageResponse.Total)) + Expect(out.PageResponse.NextKey).NotTo(BeEmpty()) + }) + }) + + Context("get delegation rewards", func() { + // defaultDelRewardsArgs are the default arguments for the getDelegationRewards query + // + // NOTE: this has to be populated in BeforeEach because the test suite setup is not available prior to that. + var defaultDelRewardsArgs contracts.CallArgs + + BeforeEach(func() { + defaultDelRewardsArgs = defaultCallArgs. + WithMethodName("getDelegationRewards"). + WithArgs(s.address, s.validators[0].OperatorAddress) + }) + + It("should not get rewards - no rewards available", func() { + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultDelRewardsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var rewards []cmn.DecCoin + err = s.precompile.UnpackIntoInterface(&rewards, distribution.DelegationRewardsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(rewards)).To(Equal(0)) + }) + It("should get rewards", func() { + s.prepareStakingRewards(stakingRewards{s.address.Bytes(), s.validators[0], rewards}) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultDelRewardsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var rewards []cmn.DecCoin + err = s.precompile.UnpackIntoInterface(&rewards, distribution.DelegationRewardsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(rewards)).To(Equal(1)) + Expect(len(rewards)).To(Equal(1)) + Expect(rewards[0].Denom).To(Equal(s.bondDenom)) + Expect(rewards[0].Amount.Int64()).To(Equal(expDelegationRewards)) + }) + }) + + Context("get delegator's total rewards", func() { + // defaultDelTotalRewardsArgs are the default arguments for the getDelegationTotalRewards query + // + // NOTE: this has to be populated in BeforeEach because the test suite setup is not available prior to that. + var defaultDelTotalRewardsArgs contracts.CallArgs + + BeforeEach(func() { + defaultDelTotalRewardsArgs = defaultCallArgs. + WithMethodName("getDelegationTotalRewards"). + WithArgs(s.address) + }) + + It("should not get rewards - no rewards available", func() { + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultDelTotalRewardsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var out distribution.DelegationTotalRewardsOutput + err = s.precompile.UnpackIntoInterface(&out, distribution.DelegationTotalRewardsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(len(out.Rewards)).To(Equal(2)) + Expect(len(out.Rewards[0].Reward)).To(Equal(0)) + Expect(len(out.Rewards[1].Reward)).To(Equal(0)) + }) + It("should get total rewards", func() { + // set rewards + s.prepareStakingRewards(stakingRewards{s.address.Bytes(), s.validators[0], rewards}) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultDelTotalRewardsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var ( + out distribution.DelegationTotalRewardsOutput + i int + ) + err = s.precompile.UnpackIntoInterface(&out, distribution.DelegationTotalRewardsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + + // the response order may change + if out.Rewards[0].ValidatorAddress == s.validators[0].OperatorAddress { + Expect(s.validators[0].OperatorAddress).To(Equal(out.Rewards[0].ValidatorAddress)) + Expect(s.validators[1].OperatorAddress).To(Equal(out.Rewards[1].ValidatorAddress)) + Expect(0).To(Equal(len(out.Rewards[1].Reward))) + } else { + i = 1 + Expect(s.validators[0].OperatorAddress).To(Equal(out.Rewards[1].ValidatorAddress)) + Expect(s.validators[1].OperatorAddress).To(Equal(out.Rewards[0].ValidatorAddress)) + Expect(0).To(Equal(len(out.Rewards[0].Reward))) + } + + // only validator[i] has rewards + Expect(1).To(Equal(len(out.Rewards[i].Reward))) + Expect(s.bondDenom).To(Equal(out.Rewards[i].Reward[0].Denom)) + Expect(uint8(math.LegacyPrecision)).To(Equal(out.Rewards[i].Reward[0].Precision)) + Expect(expDelegationRewards).To(Equal(out.Rewards[i].Reward[0].Amount.Int64())) + + Expect(1).To(Equal(len(out.Total))) + Expect(expDelegationRewards).To(Equal(out.Total[0].Amount.Int64())) + }) + }) + + Context("get all delegator validators", func() { + // defaultDelValArgs are the default arguments for the getDelegatorValidators query + // + // NOTE: this has to be populated in BeforeEach because the test suite setup is not available prior to that. + var defaultDelValArgs contracts.CallArgs + + BeforeEach(func() { + defaultDelValArgs = defaultCallArgs. + WithMethodName("getDelegatorValidators"). + WithArgs(s.address) + }) + + It("should get all validators a delegator has delegated to", func() { + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultDelValArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var validators []string + err = s.precompile.UnpackIntoInterface(&validators, distribution.DelegatorValidatorsMethod, ethRes.Ret) + Expect(err).To(BeNil()) + Expect(2).To(Equal(len(validators))) + + // the response order may change + if validators[0] == s.validators[0].OperatorAddress { + Expect(s.validators[0].OperatorAddress).To(Equal(validators[0])) + Expect(s.validators[1].OperatorAddress).To(Equal(validators[1])) + } else { + Expect(s.validators[1].OperatorAddress).To(Equal(validators[0])) + Expect(s.validators[0].OperatorAddress).To(Equal(validators[1])) + } + }) + }) + + Context("get withdraw address", func() { + // defaultWithdrawAddrArgs are the default arguments for the getDelegatorWithdrawAddress query + // + // NOTE: this has to be populated in BeforeEach because the test suite setup is not available prior to that. + var defaultWithdrawAddrArgs contracts.CallArgs + + BeforeEach(func() { + defaultWithdrawAddrArgs = defaultCallArgs. + WithMethodName("getDelegatorWithdrawAddress"). + WithArgs(s.address) + }) + + It("should get withdraw address", func() { + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, defaultWithdrawAddrArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + withdrawAddr, err := s.precompile.Unpack(distribution.DelegatorWithdrawAddressMethod, ethRes.Ret) + Expect(err).To(BeNil()) + // get the bech32 encoding + expAddr := sdk.AccAddress(s.address.Bytes()) + Expect(withdrawAddr[0]).To(Equal(expAddr.String())) + }) + + It("should call GetWithdrawAddress using staticcall", func() { + staticCallArgs := defaultCallArgs. + WithMethodName("staticCallGetWithdrawAddress"). + WithArgs(s.address) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, staticCallArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + withdrawAddr, err := s.precompile.Unpack(distribution.DelegatorWithdrawAddressMethod, ethRes.Ret) + Expect(err).To(BeNil()) + // get the bech32 encoding + expAddr := sdk.AccAddress(s.address.Bytes()) + Expect(withdrawAddr[0]).To(ContainSubstring(expAddr.String())) + }) + }) + }) +}) diff --git a/precompiles/distribution/query.go b/precompiles/distribution/query.go new file mode 100644 index 00000000..032272bd --- /dev/null +++ b/precompiles/distribution/query.go @@ -0,0 +1,220 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package distribution + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/vm" + cmn "github.com/evmos/evmos/v16/precompiles/common" +) + +const ( + // ValidatorDistributionInfoMethod defines the ABI method name for the + // ValidatorDistributionInfo query. + ValidatorDistributionInfoMethod = "validatorDistributionInfo" + // ValidatorOutstandingRewardsMethod defines the ABI method name for the + // ValidatorOutstandingRewards query. + ValidatorOutstandingRewardsMethod = "validatorOutstandingRewards" + // ValidatorCommissionMethod defines the ABI method name for the + // ValidatorCommission query. + ValidatorCommissionMethod = "validatorCommission" + // ValidatorSlashesMethod defines the ABI method name for the + // ValidatorSlashes query. + ValidatorSlashesMethod = "validatorSlashes" + // DelegationRewardsMethod defines the ABI method name for the + // DelegationRewards query. + DelegationRewardsMethod = "delegationRewards" + // DelegationTotalRewardsMethod defines the ABI method name for the + // DelegationTotalRewards query. + DelegationTotalRewardsMethod = "delegationTotalRewards" + // DelegatorValidatorsMethod defines the ABI method name for the + // DelegatorValidators query. + DelegatorValidatorsMethod = "delegatorValidators" + // DelegatorWithdrawAddressMethod defines the ABI method name for the + // DelegatorWithdrawAddress query. + DelegatorWithdrawAddressMethod = "delegatorWithdrawAddress" +) + +// ValidatorDistributionInfo returns the distribution info for a validator. +func (p Precompile) ValidatorDistributionInfo( + ctx sdk.Context, + _ *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + req, err := NewValidatorDistributionInfoRequest(args) + if err != nil { + return nil, err + } + + querier := distributionkeeper.Querier{Keeper: p.distributionKeeper} + + res, err := querier.ValidatorDistributionInfo(ctx, req) + if err != nil { + return nil, err + } + + out := new(ValidatorDistributionInfoOutput).FromResponse(res) + + return method.Outputs.Pack(out.DistributionInfo) +} + +// ValidatorOutstandingRewards returns the outstanding rewards for a validator. +func (p Precompile) ValidatorOutstandingRewards( + ctx sdk.Context, + _ *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + req, err := NewValidatorOutstandingRewardsRequest(args) + if err != nil { + return nil, err + } + + querier := distributionkeeper.Querier{Keeper: p.distributionKeeper} + + res, err := querier.ValidatorOutstandingRewards(ctx, req) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(cmn.NewDecCoinsResponse(res.Rewards.Rewards)) +} + +// ValidatorCommission returns the commission for a validator. +func (p Precompile) ValidatorCommission( + ctx sdk.Context, + _ *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + req, err := NewValidatorCommissionRequest(args) + if err != nil { + return nil, err + } + + querier := distributionkeeper.Querier{Keeper: p.distributionKeeper} + + res, err := querier.ValidatorCommission(ctx, req) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(cmn.NewDecCoinsResponse(res.Commission.Commission)) +} + +// ValidatorSlashes returns the slashes for a validator. +func (p Precompile) ValidatorSlashes( + ctx sdk.Context, + _ *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + req, err := NewValidatorSlashesRequest(method, args) + if err != nil { + return nil, err + } + + querier := distributionkeeper.Querier{Keeper: p.distributionKeeper} + + res, err := querier.ValidatorSlashes(ctx, req) + if err != nil { + return nil, err + } + + out := new(ValidatorSlashesOutput).FromResponse(res) + + return out.Pack(method.Outputs) +} + +// DelegationRewards returns the total rewards accrued by a delegation. +func (p Precompile) DelegationRewards( + ctx sdk.Context, + _ *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + req, err := NewDelegationRewardsRequest(args) + if err != nil { + return nil, err + } + + querier := distributionkeeper.Querier{Keeper: p.distributionKeeper} + res, err := querier.DelegationRewards(ctx, req) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(cmn.NewDecCoinsResponse(res.Rewards)) +} + +// DelegationTotalRewards returns the total rewards accrued by a delegation. +func (p Precompile) DelegationTotalRewards( + ctx sdk.Context, + _ *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + req, err := NewDelegationTotalRewardsRequest(args) + if err != nil { + return nil, err + } + + querier := distributionkeeper.Querier{Keeper: p.distributionKeeper} + + res, err := querier.DelegationTotalRewards(ctx, req) + if err != nil { + return nil, err + } + + out := new(DelegationTotalRewardsOutput).FromResponse(res) + + return out.Pack(method.Outputs) +} + +// DelegatorValidators returns the validators a delegator is bonded to. +func (p Precompile) DelegatorValidators( + ctx sdk.Context, + _ *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + req, err := NewDelegatorValidatorsRequest(args) + if err != nil { + return nil, err + } + + querier := distributionkeeper.Querier{Keeper: p.distributionKeeper} + + res, err := querier.DelegatorValidators(ctx, req) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(res.Validators) +} + +// DelegatorWithdrawAddress returns the withdraw address for a delegator. +func (p Precompile) DelegatorWithdrawAddress( + ctx sdk.Context, + _ *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + req, err := NewDelegatorWithdrawAddressRequest(args) + if err != nil { + return nil, err + } + + querier := distributionkeeper.Querier{Keeper: p.distributionKeeper} + + res, err := querier.DelegatorWithdrawAddress(ctx, req) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(res.WithdrawAddress) +} diff --git a/precompiles/distribution/query_test.go b/precompiles/distribution/query_test.go new file mode 100644 index 00000000..c5ac9a9e --- /dev/null +++ b/precompiles/distribution/query_test.go @@ -0,0 +1,845 @@ +package distribution_test + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/testutil/mock" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/cosmos/cosmos-sdk/x/distribution/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/core/vm" + + "github.com/evmos/evmos/v16/testutil" + testutiltx "github.com/evmos/evmos/v16/testutil/tx" + + cmn "github.com/evmos/evmos/v16/precompiles/common" + "github.com/evmos/evmos/v16/precompiles/distribution" +) + +var ( + expDelegationRewards int64 = 2000000000000000000 + expValAmount int64 = 1 + rewards, _ = math.NewIntFromString("1000000000000000000") +) + +type distrTestCases struct { + name string + malleate func() []interface{} + postCheck func(bz []byte) + gas uint64 + expErr bool + errContains string +} + +var baseTestCases = []distrTestCases{ + { + "fail - empty input args", + func() []interface{} { + return []interface{}{} + }, + func(bz []byte) {}, + 100000, + true, + "invalid number of arguments", + }, + { + "fail - invalid validator address", + func() []interface{} { + return []interface{}{ + "invalid", + } + }, + func(bz []byte) {}, + 100000, + true, + "invalid bech32 string", + }, +} + +func (s *PrecompileTestSuite) TestValidatorDistributionInfo() { + method := s.precompile.Methods[distribution.ValidatorDistributionInfoMethod] + + testCases := []distrTestCases{ + { + "fail - nonexistent validator address", + func() []interface{} { + pv := mock.NewPV() + pk, err := pv.GetPubKey() + s.Require().NoError(err) + return []interface{}{ + sdk.ValAddress(pk.Address().Bytes()).String(), + } + }, + func(bz []byte) {}, + 100000, + true, + "validator does not exist", + }, + { + "fail - existent validator but without self delegation", + func() []interface{} { + return []interface{}{ + s.validators[0].OperatorAddress, + } + }, + func(bz []byte) {}, + 100000, + true, + "delegation does not exist", + }, + { + "success", + func() []interface{} { + addr := sdk.AccAddress(s.validators[0].GetOperator()) + // fund del account to make self-delegation + err := testutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, addr, 10) + s.Require().NoError(err) + // make a self delegation + _, err = s.app.StakingKeeper.Delegate(s.ctx, addr, math.NewInt(1), stakingtypes.Unspecified, s.validators[0], true) + s.Require().NoError(err) + return []interface{}{ + s.validators[0].OperatorAddress, + } + }, + func(bz []byte) { + var out distribution.ValidatorDistributionInfoOutput + err := s.precompile.UnpackIntoInterface(&out, distribution.ValidatorDistributionInfoMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + expAddr := sdk.AccAddress(s.validators[0].GetOperator()) + s.Require().Equal(expAddr.String(), out.DistributionInfo.OperatorAddress) + s.Require().Equal(0, len(out.DistributionInfo.Commission)) + s.Require().Equal(0, len(out.DistributionInfo.SelfBondRewards)) + }, + 100000, + false, + "", + }, + } + testCases = append(testCases, baseTestCases...) + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + bz, err := s.precompile.ValidatorDistributionInfo(s.ctx, contract, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestValidatorOutstandingRewards() { //nolint:dupl + method := s.precompile.Methods[distribution.ValidatorOutstandingRewardsMethod] + + testCases := []distrTestCases{ + { + "success - nonexistent validator address", + func() []interface{} { + pv := mock.NewPV() + pk, err := pv.GetPubKey() + s.Require().NoError(err) + return []interface{}{ + sdk.ValAddress(pk.Address().Bytes()).String(), + } + }, + func(bz []byte) { + var out []sdk.DecCoin + err := s.precompile.UnpackIntoInterface(&out, distribution.ValidatorOutstandingRewardsMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(0, len(out)) + }, + 100000, + false, + "", + }, + { + "success - existent validator, no outstanding rewards", + func() []interface{} { + return []interface{}{ + s.validators[0].OperatorAddress, + } + }, + func(bz []byte) { + var out []sdk.DecCoin + err := s.precompile.UnpackIntoInterface(&out, distribution.ValidatorOutstandingRewardsMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(0, len(out)) + }, + 100000, + false, + "", + }, + { + "success - with outstanding rewards", + func() []interface{} { + valRewards := sdk.DecCoins{sdk.NewDecCoinFromDec(s.bondDenom, math.LegacyNewDec(1))} + // set outstanding rewards + s.app.DistrKeeper.SetValidatorOutstandingRewards(s.ctx, s.validators[0].GetOperator(), types.ValidatorOutstandingRewards{Rewards: valRewards}) + return []interface{}{ + s.validators[0].OperatorAddress, + } + }, + func(bz []byte) { + var out []cmn.DecCoin + err := s.precompile.UnpackIntoInterface(&out, distribution.ValidatorOutstandingRewardsMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(1, len(out)) + s.Require().Equal(uint8(18), out[0].Precision) + s.Require().Equal(s.bondDenom, out[0].Denom) + s.Require().Equal(expValAmount, out[0].Amount.Int64()) + }, + 100000, + false, + "", + }, + } + testCases = append(testCases, baseTestCases...) + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + bz, err := s.precompile.ValidatorOutstandingRewards(s.ctx, contract, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestValidatorCommission() { //nolint:dupl + method := s.precompile.Methods[distribution.ValidatorCommissionMethod] + + testCases := []distrTestCases{ + { + "success - nonexistent validator address", + func() []interface{} { + pv := mock.NewPV() + pk, err := pv.GetPubKey() + s.Require().NoError(err) + return []interface{}{ + sdk.ValAddress(pk.Address().Bytes()).String(), + } + }, + func(bz []byte) { + var out []sdk.DecCoin + err := s.precompile.UnpackIntoInterface(&out, distribution.ValidatorCommissionMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(0, len(out)) + }, + 100000, + false, + "", + }, + { + "success - existent validator, no accumulated commission", + func() []interface{} { + return []interface{}{ + s.validators[0].OperatorAddress, + } + }, + func(bz []byte) { + var out []sdk.DecCoin + err := s.precompile.UnpackIntoInterface(&out, distribution.ValidatorCommissionMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(0, len(out)) + }, + 100000, + false, + "", + }, + { + "success - with accumulated commission", + func() []interface{} { + valCommission := sdk.DecCoins{sdk.NewDecCoinFromDec(s.bondDenom, math.LegacyNewDec(1))} + s.app.DistrKeeper.SetValidatorAccumulatedCommission(s.ctx, s.validators[0].GetOperator(), types.ValidatorAccumulatedCommission{Commission: valCommission}) + return []interface{}{ + s.validators[0].OperatorAddress, + } + }, + func(bz []byte) { + var out []cmn.DecCoin + err := s.precompile.UnpackIntoInterface(&out, distribution.ValidatorCommissionMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(1, len(out)) + s.Require().Equal(uint8(18), out[0].Precision) + s.Require().Equal(s.bondDenom, out[0].Denom) + s.Require().Equal(expValAmount, out[0].Amount.Int64()) + }, + 100000, + false, + "", + }, + } + testCases = append(testCases, baseTestCases...) + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + bz, err := s.precompile.ValidatorCommission(s.ctx, contract, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestValidatorSlashes() { + method := s.precompile.Methods[distribution.ValidatorSlashesMethod] + + testCases := []distrTestCases{ + { + "fail - invalid validator address", + func() []interface{} { + return []interface{}{ + "invalid", uint64(1), uint64(5), query.PageRequest{}, + } + }, + func(bz []byte) { + }, + 100000, + true, + "invalid validator address", + }, + { + "fail - invalid starting height type", + func() []interface{} { + return []interface{}{ + s.validators[0].OperatorAddress, + int64(1), uint64(5), + query.PageRequest{}, + } + }, + func(bz []byte) { + }, + 100000, + true, + "invalid type for startingHeight: expected uint64, received int64", + }, + { + "fail - starting height greater than ending height", + func() []interface{} { + return []interface{}{ + s.validators[0].OperatorAddress, + uint64(6), uint64(5), + query.PageRequest{}, + } + }, + func(bz []byte) { + }, + 100000, + true, + "starting height greater than ending height", + }, + { + "success - nonexistent validator address", + func() []interface{} { + pv := mock.NewPV() + pk, err := pv.GetPubKey() + s.Require().NoError(err) + return []interface{}{ + sdk.ValAddress(pk.Address().Bytes()).String(), + uint64(1), + uint64(5), + query.PageRequest{}, + } + }, + func(bz []byte) { + var out distribution.ValidatorSlashesOutput + err := s.precompile.UnpackIntoInterface(&out, distribution.ValidatorSlashesMethod, bz) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(0, len(out.Slashes)) + s.Require().Equal(uint64(0), out.PageResponse.Total) + }, + 100000, + false, + "", + }, + { + "success - existent validator, no slashes", + func() []interface{} { + return []interface{}{ + s.validators[0].OperatorAddress, + uint64(1), + uint64(5), + query.PageRequest{}, + } + }, + func(bz []byte) { + var out distribution.ValidatorSlashesOutput + err := s.precompile.UnpackIntoInterface(&out, distribution.ValidatorSlashesMethod, bz) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(0, len(out.Slashes)) + s.Require().Equal(uint64(0), out.PageResponse.Total) + }, + 100000, + false, + "", + }, + { + "success - with slashes", + func() []interface{} { + s.app.DistrKeeper.SetValidatorSlashEvent(s.ctx, s.validators[0].GetOperator(), 2, 1, types.ValidatorSlashEvent{ValidatorPeriod: 1, Fraction: math.LegacyNewDec(5)}) + return []interface{}{ + s.validators[0].OperatorAddress, + uint64(1), uint64(5), + query.PageRequest{}, + } + }, + func(bz []byte) { + var out distribution.ValidatorSlashesOutput + err := s.precompile.UnpackIntoInterface(&out, distribution.ValidatorSlashesMethod, bz) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(1, len(out.Slashes)) + s.Require().Equal(math.LegacyNewDec(5).BigInt(), out.Slashes[0].Fraction.Value) + s.Require().Equal(uint64(1), out.Slashes[0].ValidatorPeriod) + s.Require().Equal(uint64(1), out.PageResponse.Total) + }, + 100000, + false, + "", + }, + { + "success - with slashes w/pagination", + func() []interface{} { + s.app.DistrKeeper.SetValidatorSlashEvent(s.ctx, s.validators[0].GetOperator(), 2, 1, types.ValidatorSlashEvent{ValidatorPeriod: 1, Fraction: math.LegacyNewDec(5)}) + return []interface{}{ + s.validators[0].OperatorAddress, + uint64(1), + uint64(5), + query.PageRequest{Limit: 1, CountTotal: true}, + } + }, + func(bz []byte) { + var out distribution.ValidatorSlashesOutput + err := s.precompile.UnpackIntoInterface(&out, distribution.ValidatorSlashesMethod, bz) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(1, len(out.Slashes)) + s.Require().Equal(math.LegacyNewDec(5).BigInt(), out.Slashes[0].Fraction.Value) + s.Require().Equal(uint64(1), out.Slashes[0].ValidatorPeriod) + s.Require().Equal(uint64(1), out.PageResponse.Total) + }, + 100000, + false, + "", + }, + } + testCases = append(testCases, baseTestCases[0]) + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + bz, err := s.precompile.ValidatorSlashes(s.ctx, contract, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestDelegationRewards() { + method := s.precompile.Methods[distribution.DelegationRewardsMethod] + + testCases := []distrTestCases{ + { + "fail - invalid validator address", + func() []interface{} { + return []interface{}{ + s.address, + "invalid", + } + }, + func(bz []byte) {}, + 100000, + true, + "invalid bech32 string", + }, + { + "fail - nonexistent validator address", + func() []interface{} { + pv := mock.NewPV() + pk, err := pv.GetPubKey() + s.Require().NoError(err) + return []interface{}{ + s.address, + sdk.ValAddress(pk.Address().Bytes()).String(), + } + }, + func(bz []byte) {}, + 100000, + true, + "validator does not exist", + }, + { + "fail - existent validator, no delegation", + func() []interface{} { + newAddr, _ := testutiltx.NewAddrKey() + return []interface{}{ + newAddr, + s.validators[0].OperatorAddress, + } + }, + func(bz []byte) {}, + 100000, + true, + "delegation does not exist", + }, + { + "success - existent validator & delegation, but no rewards", + func() []interface{} { + return []interface{}{ + s.address, + s.validators[0].OperatorAddress, + } + }, + func(bz []byte) { + var out []cmn.DecCoin + err := s.precompile.UnpackIntoInterface(&out, distribution.DelegationRewardsMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(0, len(out)) + }, + 100000, + false, + "", + }, + { + "success - with rewards", + func() []interface{} { + s.prepareStakingRewards(stakingRewards{s.address.Bytes(), s.validators[0], rewards}) + return []interface{}{ + s.address, + s.validators[0].OperatorAddress, + } + }, + func(bz []byte) { + var out []cmn.DecCoin + err := s.precompile.UnpackIntoInterface(&out, distribution.DelegationRewardsMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(1, len(out)) + s.Require().Equal(uint8(18), out[0].Precision) + s.Require().Equal(s.bondDenom, out[0].Denom) + s.Require().Equal(expDelegationRewards, out[0].Amount.Int64()) + }, + 100000, + false, + "", + }, + } + testCases = append(testCases, baseTestCases[0]) + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + bz, err := s.precompile.DelegationRewards(s.ctx, contract, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestDelegationTotalRewards() { + method := s.precompile.Methods[distribution.DelegationTotalRewardsMethod] + + testCases := []distrTestCases{ + { + "fail - invalid delegator address", + func() []interface{} { + return []interface{}{ + "invalid", + } + }, + func(bz []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, "invalid"), + }, + { + "success - no delegations", + func() []interface{} { + newAddr, _ := testutiltx.NewAddrKey() + return []interface{}{ + newAddr, + } + }, + func(bz []byte) { + var out distribution.DelegationTotalRewardsOutput + err := s.precompile.UnpackIntoInterface(&out, distribution.DelegationTotalRewardsMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(0, len(out.Rewards)) + s.Require().Equal(0, len(out.Total)) + }, + 100000, + false, + "", + }, + { + "success - existent validator & delegation, but no rewards", + func() []interface{} { + return []interface{}{ + s.address, + } + }, + func(bz []byte) { + var out distribution.DelegationTotalRewardsOutput + err := s.precompile.UnpackIntoInterface(&out, distribution.DelegationTotalRewardsMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(2, len(out.Rewards)) + // the response order may change + if out.Rewards[0].ValidatorAddress == s.validators[0].OperatorAddress { + s.Require().Equal(s.validators[0].OperatorAddress, out.Rewards[0].ValidatorAddress) + s.Require().Equal(s.validators[1].OperatorAddress, out.Rewards[1].ValidatorAddress) + } else { + s.Require().Equal(s.validators[1].OperatorAddress, out.Rewards[0].ValidatorAddress) + s.Require().Equal(s.validators[0].OperatorAddress, out.Rewards[1].ValidatorAddress) + } + // no rewards + s.Require().Equal(0, len(out.Rewards[0].Reward)) + s.Require().Equal(0, len(out.Rewards[1].Reward)) + s.Require().Equal(0, len(out.Total)) + }, + 100000, + false, + "", + }, + { + "success - with rewards", + func() []interface{} { + s.prepareStakingRewards(stakingRewards{s.address.Bytes(), s.validators[0], rewards}) + return []interface{}{ + s.address, + } + }, + func(bz []byte) { + var ( + out distribution.DelegationTotalRewardsOutput + i int + ) + err := s.precompile.UnpackIntoInterface(&out, distribution.DelegationTotalRewardsMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(2, len(out.Rewards)) + + // the response order may change + if out.Rewards[0].ValidatorAddress == s.validators[0].OperatorAddress { + s.Require().Equal(s.validators[0].OperatorAddress, out.Rewards[0].ValidatorAddress) + s.Require().Equal(s.validators[1].OperatorAddress, out.Rewards[1].ValidatorAddress) + s.Require().Equal(0, len(out.Rewards[1].Reward)) + } else { + i = 1 + s.Require().Equal(s.validators[0].OperatorAddress, out.Rewards[1].ValidatorAddress) + s.Require().Equal(s.validators[1].OperatorAddress, out.Rewards[0].ValidatorAddress) + s.Require().Equal(0, len(out.Rewards[0].Reward)) + } + + // only validator[i] has rewards + s.Require().Equal(1, len(out.Rewards[i].Reward)) + s.Require().Equal(s.bondDenom, out.Rewards[i].Reward[0].Denom) + s.Require().Equal(uint8(math.LegacyPrecision), out.Rewards[i].Reward[0].Precision) + s.Require().Equal(expDelegationRewards, out.Rewards[i].Reward[0].Amount.Int64()) + + s.Require().Equal(1, len(out.Total)) + s.Require().Equal(expDelegationRewards, out.Total[0].Amount.Int64()) + }, + 100000, + false, + "", + }, + } + testCases = append(testCases, baseTestCases[0]) + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + bz, err := s.precompile.DelegationTotalRewards(s.ctx, contract, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestDelegatorValidators() { + method := s.precompile.Methods[distribution.DelegatorValidatorsMethod] + + testCases := []distrTestCases{ + { + "fail - invalid delegator address", + func() []interface{} { + return []interface{}{ + "invalid", + } + }, + func(bz []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, "invalid"), + }, + { + "success - no delegations", + func() []interface{} { + newAddr, _ := testutiltx.NewAddrKey() + return []interface{}{ + newAddr, + } + }, + func(bz []byte) { + var out []string + err := s.precompile.UnpackIntoInterface(&out, distribution.DelegatorValidatorsMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(0, len(out)) + }, + 100000, + false, + "", + }, + { + "success - existent delegations", + func() []interface{} { + return []interface{}{ + s.address, + } + }, + func(bz []byte) { + var out []string + err := s.precompile.UnpackIntoInterface(&out, distribution.DelegatorValidatorsMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(2, len(out)) + // the order may change + if out[0] == s.validators[0].OperatorAddress { + s.Require().Equal(s.validators[0].OperatorAddress, out[0]) + s.Require().Equal(s.validators[1].OperatorAddress, out[1]) + } else { + s.Require().Equal(s.validators[1].OperatorAddress, out[0]) + s.Require().Equal(s.validators[0].OperatorAddress, out[1]) + } + }, + 100000, + false, + "", + }, + } + testCases = append(testCases, baseTestCases[0]) + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + bz, err := s.precompile.DelegatorValidators(s.ctx, contract, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestDelegatorWithdrawAddress() { + method := s.precompile.Methods[distribution.DelegatorWithdrawAddressMethod] + + testCases := []distrTestCases{ + { + "fail - invalid delegator address", + func() []interface{} { + return []interface{}{ + "invalid", + } + }, + func(bz []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, "invalid"), + }, + { + "success - withdraw address same as delegator address", + func() []interface{} { + return []interface{}{ + s.address, + } + }, + func(bz []byte) { + var out string + err := s.precompile.UnpackIntoInterface(&out, distribution.DelegatorWithdrawAddressMethod, bz) + s.Require().NoError(err, "failed to unpack output", err) + s.Require().Equal(sdk.AccAddress(s.address.Bytes()).String(), out) + }, + 100000, + false, + "", + }, + } + testCases = append(testCases, baseTestCases[0]) + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + bz, err := s.precompile.DelegatorWithdrawAddress(s.ctx, contract, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz) + } + }) + } +} diff --git a/precompiles/distribution/setup_test.go b/precompiles/distribution/setup_test.go new file mode 100644 index 00000000..e8dda389 --- /dev/null +++ b/precompiles/distribution/setup_test.go @@ -0,0 +1,58 @@ +package distribution_test + +import ( + "testing" + + "github.com/evmos/evmos/v16/precompiles/distribution" + "github.com/evmos/evmos/v16/x/evm/statedb" + + //nolint:revive // dot imports are fine for Ginkgo + . "github.com/onsi/ginkgo/v2" + //nolint:revive // dot imports are fine for Ginkgo + . "github.com/onsi/gomega" + + tmtypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + evmosapp "github.com/evmos/evmos/v16/app" + evmtypes "github.com/evmos/evmos/v16/x/evm/types" + "github.com/stretchr/testify/suite" +) + +var s *PrecompileTestSuite + +type PrecompileTestSuite struct { + suite.Suite + + ctx sdk.Context + app *evmosapp.Evmos + address common.Address + validators []stakingtypes.Validator + valSet *tmtypes.ValidatorSet + ethSigner ethtypes.Signer + privKey cryptotypes.PrivKey + signer keyring.Signer + bondDenom string + + precompile *distribution.Precompile + stateDB *statedb.StateDB + + queryClientEVM evmtypes.QueryClient +} + +func TestPrecompileTestSuite(t *testing.T) { + s = new(PrecompileTestSuite) + suite.Run(t, s) + + // Run Ginkgo integration tests + RegisterFailHandler(Fail) + RunSpecs(t, "Distribution Precompile Suite") +} + +func (s *PrecompileTestSuite) SetupTest() { + s.DoSetupTest() +} diff --git a/precompiles/distribution/tx.go b/precompiles/distribution/tx.go new file mode 100644 index 00000000..09114bfa --- /dev/null +++ b/precompiles/distribution/tx.go @@ -0,0 +1,191 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package distribution + +import ( + "fmt" + "math/big" + + "github.com/evmos/evmos/v16/x/evm/statedb" + + cmn "github.com/evmos/evmos/v16/precompiles/common" + + "github.com/ethereum/go-ethereum/common" + + sdk "github.com/cosmos/cosmos-sdk/types" + distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/vm" +) + +const ( + // SetWithdrawAddressMethod defines the ABI method name for the distribution + // SetWithdrawAddress transaction. + SetWithdrawAddressMethod = "setWithdrawAddress" + // WithdrawDelegatorRewardsMethod defines the ABI method name for the distribution + // WithdrawDelegatorRewards transaction. + WithdrawDelegatorRewardsMethod = "withdrawDelegatorRewards" + // WithdrawValidatorCommissionMethod defines the ABI method name for the distribution + // WithdrawValidatorCommission transaction. + WithdrawValidatorCommissionMethod = "withdrawValidatorCommission" + // ClaimRewardsMethod defines the ABI method name for the custom ClaimRewards transaction + ClaimRewardsMethod = "claimRewards" +) + +// ClaimRewards claims the rewards accumulated by a delegator from multiple or all validators. +func (p Precompile) ClaimRewards( + ctx sdk.Context, + origin common.Address, + contract *vm.Contract, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + delegatorAddr, maxRetrieve, err := parseClaimRewardsArgs(args) + if err != nil { + return nil, err + } + + // If the contract is the delegator, we don't need an origin check + // Otherwise check if the origin matches the delegator address + isContractDelegator := contract.CallerAddress == delegatorAddr + if !isContractDelegator && origin != delegatorAddr { + return nil, fmt.Errorf(cmn.ErrDifferentOrigin, origin.String(), delegatorAddr.String()) + } + + validators := p.stakingKeeper.GetDelegatorValidators(ctx, delegatorAddr.Bytes(), maxRetrieve) + totalCoins := sdk.Coins{} + for _, validator := range validators { + // Convert the validator operator address into an ValAddress + valAddr, err := sdk.ValAddressFromBech32(validator.OperatorAddress) + if err != nil { + return nil, err + } + + // Withdraw the rewards for each validator address + coins, err := p.distributionKeeper.WithdrawDelegationRewards(ctx, delegatorAddr.Bytes(), valAddr) + if err != nil { + return nil, err + } + + totalCoins = totalCoins.Add(coins...) + } + + if err := p.EmitClaimRewardsEvent(ctx, stateDB, delegatorAddr, totalCoins); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +// SetWithdrawAddress sets the withdrawal address for a delegator (or validator self-delegation). +func (p Precompile) SetWithdrawAddress( + ctx sdk.Context, + origin common.Address, + contract *vm.Contract, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + msg, delegatorHexAddr, err := NewMsgSetWithdrawAddress(args) + if err != nil { + return nil, err + } + + // If the contract is the delegator, we don't need an origin check + // Otherwise check if the origin matches the delegator address + isContractDelegator := contract.CallerAddress == delegatorHexAddr + if !isContractDelegator && origin != delegatorHexAddr { + return nil, fmt.Errorf(cmn.ErrDifferentOrigin, origin.String(), delegatorHexAddr.String()) + } + + msgSrv := distributionkeeper.NewMsgServerImpl(p.distributionKeeper) + if _, err = msgSrv.SetWithdrawAddress(sdk.WrapSDKContext(ctx), msg); err != nil { + return nil, err + } + + if err = p.EmitSetWithdrawAddressEvent(ctx, stateDB, delegatorHexAddr, msg.WithdrawAddress); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +// WithdrawDelegatorRewards withdraws the rewards of a delegator from a single validator. +func (p Precompile) WithdrawDelegatorRewards( + ctx sdk.Context, + origin common.Address, + contract *vm.Contract, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + msg, delegatorHexAddr, err := NewMsgWithdrawDelegatorReward(args) + if err != nil { + return nil, err + } + + // If the contract is the delegator, we don't need an origin check + // Otherwise check if the origin matches the delegator address + isContractDelegator := contract.CallerAddress == delegatorHexAddr + if !isContractDelegator && origin != delegatorHexAddr { + return nil, fmt.Errorf(cmn.ErrDifferentOrigin, origin.String(), delegatorHexAddr.String()) + } + + msgSrv := distributionkeeper.NewMsgServerImpl(p.distributionKeeper) + res, err := msgSrv.WithdrawDelegatorReward(sdk.WrapSDKContext(ctx), msg) + if err != nil { + return nil, err + } + + if err = p.EmitWithdrawDelegatorRewardsEvent(ctx, stateDB, delegatorHexAddr, msg.ValidatorAddress, res.Amount); err != nil { + return nil, err + } + + // NOTE: This ensures that the changes in the bank keeper are correctly mirrored to the EVM stateDB. + // This prevents the stateDB from overwriting the changed balance in the bank keeper when committing the EVM state. + if isContractDelegator { + // the responsed amount is from cosmos module, which has 6 decimal points + // convert it to the EVM amount which has 18 decimal points + convertedAmount := res.Amount[0].Amount.BigInt() + convertedAmount.Mul(convertedAmount, big.NewInt(1e12)) + stateDB.(*statedb.StateDB).AddBalance(contract.CallerAddress, convertedAmount) + } + + return method.Outputs.Pack(cmn.NewCoinsResponse(res.Amount)) +} + +// WithdrawValidatorCommission withdraws the rewards of a validator. +func (p Precompile) WithdrawValidatorCommission( + ctx sdk.Context, + origin common.Address, + contract *vm.Contract, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + msg, validatorHexAddr, err := NewMsgWithdrawValidatorCommission(args) + if err != nil { + return nil, err + } + + // If the contract is the validator, we don't need an origin check + // Otherwise check if the origin matches the validator address + isContractValidator := contract.CallerAddress == validatorHexAddr + if !isContractValidator && origin != validatorHexAddr { + return nil, fmt.Errorf(cmn.ErrDifferentOrigin, origin.String(), validatorHexAddr.String()) + } + + msgSrv := distributionkeeper.NewMsgServerImpl(p.distributionKeeper) + res, err := msgSrv.WithdrawValidatorCommission(sdk.WrapSDKContext(ctx), msg) + if err != nil { + return nil, err + } + + if err = p.EmitWithdrawValidatorCommissionEvent(ctx, stateDB, msg.ValidatorAddress, res.Amount); err != nil { + return nil, err + } + + return method.Outputs.Pack(cmn.NewCoinsResponse(res.Amount)) +} diff --git a/precompiles/distribution/tx_test.go b/precompiles/distribution/tx_test.go new file mode 100644 index 00000000..f0973344 --- /dev/null +++ b/precompiles/distribution/tx_test.go @@ -0,0 +1,418 @@ +package distribution_test + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/evmos/evmos/v16/precompiles/testutil" + + "github.com/ethereum/go-ethereum/common" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" + cmn "github.com/evmos/evmos/v16/precompiles/common" + "github.com/evmos/evmos/v16/precompiles/distribution" + utiltx "github.com/evmos/evmos/v16/testutil/tx" + "github.com/evmos/evmos/v16/utils" +) + +func (s *PrecompileTestSuite) TestSetWithdrawAddress() { + method := s.precompile.Methods[distribution.SetWithdrawAddressMethod] + newWithdrawerAddr := utiltx.GenerateAddress() + + testCases := []struct { + name string + malleate func() []interface{} + postCheck func() + gas uint64 + expError bool + errContains string + }{ + { + "fail - empty input args", + func() []interface{} { + return []interface{}{} + }, + func() {}, + 200000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 2, 0), + }, + { + "fail - invalid delegator address", + func() []interface{} { + return []interface{}{ + "", + s.address.String(), + } + }, + func() {}, + 200000, + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, ""), + }, + { + "fail - invalid withdrawer address", + func() []interface{} { + return []interface{}{ + s.address, + nil, + } + }, + func() {}, + 200000, + true, + "invalid withdraw address: empty address string is not allowed: invalid address", + }, + { + "success - using the same address withdrawer address", + func() []interface{} { + return []interface{}{ + s.address, + s.address.String(), + } + }, + func() { + withdrawerAddr := s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, s.address.Bytes()) + s.Require().Equal(withdrawerAddr.Bytes(), s.address.Bytes()) + }, + 20000, + false, + "", + }, + { + "success - using a different withdrawer address", + func() []interface{} { + return []interface{}{ + s.address, + newWithdrawerAddr.String(), + } + }, + func() { + withdrawerAddr := s.app.DistrKeeper.GetDelegatorWithdrawAddr(s.ctx, s.address.Bytes()) + s.Require().Equal(withdrawerAddr.Bytes(), newWithdrawerAddr.Bytes()) + }, + 20000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + var contract *vm.Contract + contract, s.ctx = testutil.NewPrecompileContract(s.T(), s.ctx, s.address, s.precompile, tc.gas) + + _, err := s.precompile.SetWithdrawAddress(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate()) + + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck() + } + }) + } +} + +func (s *PrecompileTestSuite) TestWithdrawDelegatorRewards() { + method := s.precompile.Methods[distribution.WithdrawDelegatorRewardsMethod] + + testCases := []struct { + name string + malleate func(operatorAddress string) []interface{} + postCheck func(data []byte) + gas uint64 + expError bool + errContains string + }{ + { + "fail - empty input args", + func(operatorAddress string) []interface{} { + return []interface{}{} + }, + func(data []byte) {}, + 200000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 2, 0), + }, + { + "fail - invalid delegator address", + func(operatorAddress string) []interface{} { + return []interface{}{ + "", + operatorAddress, + } + }, + func(data []byte) {}, + 200000, + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, ""), + }, + { + "fail - invalid validator address", + func(operatorAddress string) []interface{} { + return []interface{}{ + s.address, + nil, + } + }, + func(data []byte) {}, + 200000, + true, + "invalid validator address", + }, + { + "success - withdraw rewards from a single validator without commission", + func(operatorAddress string) []interface{} { + valAddr, err := sdk.ValAddressFromBech32(operatorAddress) + s.Require().NoError(err) + val, _ := s.app.StakingKeeper.GetValidator(s.ctx, valAddr) + coins := sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(1e18))) + s.app.DistrKeeper.AllocateTokensToValidator(s.ctx, val, sdk.NewDecCoinsFromCoins(coins...)) + return []interface{}{ + s.address, + operatorAddress, + } + }, + func(data []byte) { + var coins []cmn.Coin + err := s.precompile.UnpackIntoInterface(&coins, distribution.WithdrawDelegatorRewardsMethod, data) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(coins[0].Denom, utils.BaseDenom) + s.Require().Equal(coins[0].Amount, big.NewInt(1000000000000000000)) + // Check bank balance after the withdrawal of rewards + balance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), utils.BaseDenom) + s.Require().Equal(balance.Amount.BigInt(), big.NewInt(6000000000000000000)) + }, + 20000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + // sanity check to make sure the starting balance is always 5 EVMOS + balance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), utils.BaseDenom) + s.Require().Equal(balance.Amount.BigInt(), big.NewInt(5000000000000000000)) + + var contract *vm.Contract + contract, s.ctx = testutil.NewPrecompileContract(s.T(), s.ctx, s.address, s.precompile, tc.gas) + + bz, err := s.precompile.WithdrawDelegatorRewards(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate(s.validators[0].OperatorAddress)) + + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestWithdrawValidatorCommission() { + method := s.precompile.Methods[distribution.WithdrawDelegatorRewardsMethod] + + testCases := []struct { + name string + malleate func(operatorAddress string) []interface{} + postCheck func(data []byte) + gas uint64 + expError bool + errContains string + }{ + { + "fail - empty input args", + func(operatorAddress string) []interface{} { + return []interface{}{} + }, + func(data []byte) {}, + 200000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 1, 0), + }, + { + "fail - invalid validator address", + func(operatorAddress string) []interface{} { + return []interface{}{ + nil, + } + }, + func(data []byte) {}, + 200000, + true, + "invalid validator address", + }, + { + "success - withdraw all commission from a single validator", + func(operatorAddress string) []interface{} { + valAddr, err := sdk.ValAddressFromBech32(operatorAddress) + s.Require().NoError(err) + valCommission := sdk.DecCoins{sdk.NewDecCoinFromDec(utils.BaseDenom, math.LegacyNewDecWithPrec(1000000000000000000, 1))} + // set outstanding rewards + s.app.DistrKeeper.SetValidatorOutstandingRewards(s.ctx, valAddr, types.ValidatorOutstandingRewards{Rewards: valCommission}) + // set commission + s.app.DistrKeeper.SetValidatorAccumulatedCommission(s.ctx, valAddr, types.ValidatorAccumulatedCommission{Commission: valCommission}) + return []interface{}{ + operatorAddress, + } + }, + func(data []byte) { + var coins []cmn.Coin + err := s.precompile.UnpackIntoInterface(&coins, distribution.WithdrawValidatorCommissionMethod, data) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(coins[0].Denom, utils.BaseDenom) + s.Require().Equal(coins[0].Amount, big.NewInt(100000000000000000)) + // Check bank balance after the withdrawal of commission + balance := s.app.BankKeeper.GetBalance(s.ctx, s.validators[0].GetOperator().Bytes(), utils.BaseDenom) + s.Require().Equal(balance.Amount.BigInt(), big.NewInt(100000000000000000)) + s.Require().Equal(balance.Denom, utils.BaseDenom) + }, + 20000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + // Sanity check to make sure the starting balance is always 0 + balance := s.app.BankKeeper.GetBalance(s.ctx, s.validators[0].GetOperator().Bytes(), utils.BaseDenom) + s.Require().Equal(balance.Amount.BigInt(), big.NewInt(0)) + s.Require().Equal(balance.Denom, utils.BaseDenom) + + validatorAddress := common.BytesToAddress(s.validators[0].GetOperator().Bytes()) + var contract *vm.Contract + contract, s.ctx = testutil.NewPrecompileContract(s.T(), s.ctx, validatorAddress, s.precompile, tc.gas) + + bz, err := s.precompile.WithdrawValidatorCommission(s.ctx, validatorAddress, contract, s.stateDB, &method, tc.malleate(s.validators[0].OperatorAddress)) + + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestClaimRewards() { + method := s.precompile.Methods[distribution.ClaimRewardsMethod] + + testCases := []struct { + name string + malleate func() []interface{} + postCheck func(data []byte) + gas uint64 + expError bool + errContains string + }{ + { + "fail - empty input args", + func() []interface{} { + return []interface{}{} + }, + func(data []byte) {}, + 200000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 2, 0), + }, + { + "fail - invalid delegator address", + func() []interface{} { + return []interface{}{ + nil, + 10, + } + }, + func(data []byte) {}, + 200000, + true, + "invalid delegator address", + }, + { + "fail - invalid type for maxRetrieve: expected uint32", + func() []interface{} { + return []interface{}{ + s.address, + big.NewInt(100000000000000000), + } + }, + func(data []byte) {}, + 200000, + true, + "invalid type for maxRetrieve: expected uint32", + }, + { + "success - withdraw from all validators - 2", + func() []interface{} { + return []interface{}{ + s.address, + uint32(2), + } + }, + func(data []byte) { + balance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), utils.BaseDenom) + s.Require().Equal(balance.Amount.BigInt(), big.NewInt(7e18)) + }, + 20000, + false, + "", + }, + { + "success - withdraw from only 1 validator", + func() []interface{} { + return []interface{}{ + s.address, + uint32(1), + } + }, + func(data []byte) { + balance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), utils.BaseDenom) + s.Require().Equal(balance.Amount.BigInt(), big.NewInt(6e18)) + }, + 20000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + var contract *vm.Contract + contract, s.ctx = testutil.NewPrecompileContract(s.T(), s.ctx, s.address, s.precompile, tc.gas) + + // Sanity check to make sure the starting balance is always 5 EVMOS + balance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), utils.BaseDenom) + s.Require().Equal(balance.Amount.BigInt(), big.NewInt(5e18)) + + // Distribute rewards to the 2 validators, 1 EVMOS each + for _, val := range s.validators { + coins := sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(1e18))) + s.app.DistrKeeper.AllocateTokensToValidator(s.ctx, val, sdk.NewDecCoinsFromCoins(coins...)) + } + + bz, err := s.precompile.ClaimRewards(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate()) + + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck(bz) + } + }) + } +} diff --git a/precompiles/distribution/types.go b/precompiles/distribution/types.go new file mode 100644 index 00000000..c115b578 --- /dev/null +++ b/precompiles/distribution/types.go @@ -0,0 +1,391 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package distribution + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + cmn "github.com/evmos/evmos/v16/precompiles/common" + + "github.com/aura-nw/aura/precompiles/util" +) + +// EventSetWithdrawAddress defines the event data for the SetWithdrawAddress transaction. +type EventSetWithdrawAddress struct { + Caller common.Address + WithdrawerAddress string +} + +// EventWithdrawDelegatorRewards defines the event data for the WithdrawDelegatorRewards transaction. +type EventWithdrawDelegatorRewards struct { + DelegatorAddress common.Address + ValidatorAddress common.Address + Amount *big.Int +} + +// EventWithdrawValidatorRewards defines the event data for the WithdrawValidatorRewards transaction. +type EventWithdrawValidatorRewards struct { + ValidatorAddress common.Hash + Commission *big.Int +} + +// EventClaimRewards defines the event data for the ClaimRewards transaction. +type EventClaimRewards struct { + DelegatorAddress common.Address + Amount *big.Int +} + +// parseClaimRewardsArgs parses the arguments for the ClaimRewards method. +func parseClaimRewardsArgs(args []interface{}) (common.Address, uint32, error) { + if len(args) != 2 { + return common.Address{}, 0, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 2, len(args)) + } + + delegatorAddress, ok := args[0].(common.Address) + if !ok || delegatorAddress == (common.Address{}) { + return common.Address{}, 0, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + maxRetrieve, ok := args[1].(uint32) + if !ok { + return common.Address{}, 0, fmt.Errorf(cmn.ErrInvalidType, "maxRetrieve", uint32(0), args[1]) + } + + return delegatorAddress, maxRetrieve, nil +} + +// NewMsgSetWithdrawAddress creates a new MsgSetWithdrawAddress instance. +func NewMsgSetWithdrawAddress(args []interface{}) (*distributiontypes.MsgSetWithdrawAddress, common.Address, error) { + if len(args) != 2 { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 2, len(args)) + } + + delegatorAddress, ok := args[0].(common.Address) + if !ok || delegatorAddress == (common.Address{}) { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + withdrawerAddress, _ := args[1].(string) + + // If the withdrawer address is a hex address, convert it to a bech32 address. + if common.IsHexAddress(withdrawerAddress) { + var err error + withdrawerAddress, err = sdk.Bech32ifyAddressBytes("evmos", common.HexToAddress(withdrawerAddress).Bytes()) + if err != nil { + return nil, common.Address{}, err + } + } + + msg := &distributiontypes.MsgSetWithdrawAddress{ + DelegatorAddress: sdk.AccAddress(delegatorAddress.Bytes()).String(), + WithdrawAddress: withdrawerAddress, + } + + if err := msg.ValidateBasic(); err != nil { + return nil, common.Address{}, err + } + + return msg, delegatorAddress, nil +} + +// NewMsgWithdrawDelegatorReward creates a new MsgWithdrawDelegatorReward instance. +func NewMsgWithdrawDelegatorReward(args []interface{}) (*distributiontypes.MsgWithdrawDelegatorReward, common.Address, error) { + if len(args) != 2 { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 2, len(args)) + } + + delegatorAddress, ok := args[0].(common.Address) + if !ok || delegatorAddress == (common.Address{}) { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + validatorAddress, _ := args[1].(string) + + msg := &distributiontypes.MsgWithdrawDelegatorReward{ + DelegatorAddress: sdk.AccAddress(delegatorAddress.Bytes()).String(), + ValidatorAddress: validatorAddress, + } + + if err := msg.ValidateBasic(); err != nil { + return nil, common.Address{}, err + } + + return msg, delegatorAddress, nil +} + +// NewMsgWithdrawValidatorCommission creates a new MsgWithdrawValidatorCommission message. +func NewMsgWithdrawValidatorCommission(args []interface{}) (*distributiontypes.MsgWithdrawValidatorCommission, common.Address, error) { + if len(args) != 1 { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 1, len(args)) + } + + validatorAddress, _ := args[0].(string) + + msg := &distributiontypes.MsgWithdrawValidatorCommission{ + ValidatorAddress: validatorAddress, + } + + if err := msg.ValidateBasic(); err != nil { + return nil, common.Address{}, err + } + + validatorHexAddr, err := cmn.HexAddressFromBech32String(msg.ValidatorAddress) + if err != nil { + return nil, common.Address{}, err + } + + return msg, validatorHexAddr, nil +} + +// NewValidatorDistributionInfoRequest creates a new QueryValidatorDistributionInfoRequest instance and does sanity +// checks on the provided arguments. +func NewValidatorDistributionInfoRequest(args []interface{}) (*distributiontypes.QueryValidatorDistributionInfoRequest, error) { + if len(args) != 1 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 1, len(args)) + } + + validatorAddress, _ := args[0].(string) + + return &distributiontypes.QueryValidatorDistributionInfoRequest{ + ValidatorAddress: validatorAddress, + }, nil +} + +// NewValidatorOutstandingRewardsRequest creates a new QueryValidatorOutstandingRewardsRequest instance and does sanity +// checks on the provided arguments. +func NewValidatorOutstandingRewardsRequest(args []interface{}) (*distributiontypes.QueryValidatorOutstandingRewardsRequest, error) { + if len(args) != 1 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 1, len(args)) + } + + validatorAddress, _ := args[0].(string) + + return &distributiontypes.QueryValidatorOutstandingRewardsRequest{ + ValidatorAddress: validatorAddress, + }, nil +} + +// NewValidatorCommissionRequest creates a new QueryValidatorCommissionRequest instance and does sanity +// checks on the provided arguments. +func NewValidatorCommissionRequest(args []interface{}) (*distributiontypes.QueryValidatorCommissionRequest, error) { + if len(args) != 1 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 1, len(args)) + } + + validatorAddress, _ := args[0].(string) + + return &distributiontypes.QueryValidatorCommissionRequest{ + ValidatorAddress: validatorAddress, + }, nil +} + +// NewValidatorSlashesRequest creates a new QueryValidatorSlashesRequest instance and does sanity +// checks on the provided arguments. +func NewValidatorSlashesRequest(method *abi.Method, args []interface{}) (*distributiontypes.QueryValidatorSlashesRequest, error) { + if len(args) != 4 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 4, len(args)) + } + + if _, ok := args[1].(uint64); !ok { + return nil, fmt.Errorf(cmn.ErrInvalidType, "startingHeight", uint64(0), args[1]) + } + if _, ok := args[2].(uint64); !ok { + return nil, fmt.Errorf(cmn.ErrInvalidType, "endingHeight", uint64(0), args[2]) + } + + var input ValidatorSlashesInput + if err := method.Inputs.Copy(&input, args); err != nil { + return nil, fmt.Errorf("error while unpacking args to ValidatorSlashesInput struct: %s", err) + } + + return &distributiontypes.QueryValidatorSlashesRequest{ + ValidatorAddress: input.ValidatorAddress, + StartingHeight: input.StartingHeight, + EndingHeight: input.EndingHeight, + Pagination: &input.PageRequest, + }, nil +} + +// NewDelegationRewardsRequest creates a new QueryDelegationRewardsRequest instance and does sanity +// checks on the provided arguments. +func NewDelegationRewardsRequest(args []interface{}) (*distributiontypes.QueryDelegationRewardsRequest, error) { + if len(args) != 2 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 2, len(args)) + } + + delegatorAddress, ok := args[0].(common.Address) + if !ok || delegatorAddress == (common.Address{}) { + return nil, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + validatorAddress, _ := args[1].(string) + + return &distributiontypes.QueryDelegationRewardsRequest{ + DelegatorAddress: sdk.AccAddress(delegatorAddress.Bytes()).String(), + ValidatorAddress: validatorAddress, + }, nil +} + +// NewDelegationTotalRewardsRequest creates a new QueryDelegationTotalRewardsRequest instance and does sanity +// checks on the provided arguments. +func NewDelegationTotalRewardsRequest(args []interface{}) (*distributiontypes.QueryDelegationTotalRewardsRequest, error) { + if len(args) != 1 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 1, len(args)) + } + + delegatorAddress, ok := args[0].(common.Address) + if !ok || delegatorAddress == (common.Address{}) { + return nil, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + return &distributiontypes.QueryDelegationTotalRewardsRequest{ + DelegatorAddress: sdk.AccAddress(delegatorAddress.Bytes()).String(), + }, nil +} + +// NewDelegatorValidatorsRequest creates a new QueryDelegatorValidatorsRequest instance and does sanity +// checks on the provided arguments. +func NewDelegatorValidatorsRequest(args []interface{}) (*distributiontypes.QueryDelegatorValidatorsRequest, error) { + if len(args) != 1 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 1, len(args)) + } + + delegatorAddress, ok := args[0].(common.Address) + if !ok || delegatorAddress == (common.Address{}) { + return nil, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + return &distributiontypes.QueryDelegatorValidatorsRequest{ + DelegatorAddress: sdk.AccAddress(delegatorAddress.Bytes()).String(), + }, nil +} + +// NewDelegatorWithdrawAddressRequest creates a new QueryDelegatorWithdrawAddressRequest instance and does sanity +// checks on the provided arguments. +func NewDelegatorWithdrawAddressRequest(args []interface{}) (*distributiontypes.QueryDelegatorWithdrawAddressRequest, error) { + if len(args) != 1 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 1, len(args)) + } + + delegatorAddress, ok := args[0].(common.Address) + if !ok || delegatorAddress == (common.Address{}) { + return nil, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + return &distributiontypes.QueryDelegatorWithdrawAddressRequest{ + DelegatorAddress: sdk.AccAddress(delegatorAddress.Bytes()).String(), + }, nil +} + +// ValidatorDistributionInfo is a struct to represent the key information from +// a ValidatorDistributionInfoResponse. +type ValidatorDistributionInfo struct { + OperatorAddress string `abi:"operatorAddress"` + SelfBondRewards []cmn.DecCoin `abi:"selfBondRewards"` + Commission []cmn.DecCoin `abi:"commission"` +} + +// ValidatorDistributionInfoOutput is a wrapper for ValidatorDistributionInfo to return in the response. +type ValidatorDistributionInfoOutput struct { + DistributionInfo ValidatorDistributionInfo `abi:"distributionInfo"` +} + +// FromResponse converts a response to a ValidatorDistributionInfo. +func (o *ValidatorDistributionInfoOutput) FromResponse(res *distributiontypes.QueryValidatorDistributionInfoResponse) ValidatorDistributionInfoOutput { + return ValidatorDistributionInfoOutput{ + DistributionInfo: ValidatorDistributionInfo{ + OperatorAddress: res.OperatorAddress, + SelfBondRewards: util.NewDecCoinsResponseEVM(res.SelfBondRewards), + Commission: util.NewDecCoinsResponseEVM(res.Commission), + }, + } +} + +// ValidatorSlashEvent is a struct to represent the key information from +// a ValidatorSlashEvent response. +type ValidatorSlashEvent struct { + ValidatorPeriod uint64 `abi:"validatorPeriod"` + Fraction cmn.Dec `abi:"fraction"` +} + +// ValidatorSlashesInput is a struct to represent the key information +// to perform a ValidatorSlashes query. +type ValidatorSlashesInput struct { + ValidatorAddress string + StartingHeight uint64 + EndingHeight uint64 + PageRequest query.PageRequest +} + +// ValidatorSlashesOutput is a struct to represent the key information from +// a ValidatorSlashes response. +type ValidatorSlashesOutput struct { + Slashes []ValidatorSlashEvent + PageResponse query.PageResponse +} + +// FromResponse populates the ValidatorSlashesOutput from a QueryValidatorSlashesResponse. +func (vs *ValidatorSlashesOutput) FromResponse(res *distributiontypes.QueryValidatorSlashesResponse) *ValidatorSlashesOutput { + vs.Slashes = make([]ValidatorSlashEvent, len(res.Slashes)) + for i, s := range res.Slashes { + vs.Slashes[i] = ValidatorSlashEvent{ + ValidatorPeriod: s.ValidatorPeriod, + Fraction: cmn.Dec{ + Value: s.Fraction.BigInt(), + Precision: math.LegacyPrecision, + }, + } + } + + if res.Pagination != nil { + vs.PageResponse.Total = res.Pagination.Total + vs.PageResponse.NextKey = res.Pagination.NextKey + } + + return vs +} + +// Pack packs a given slice of abi arguments into a byte array. +func (vs *ValidatorSlashesOutput) Pack(args abi.Arguments) ([]byte, error) { + return args.Pack(vs.Slashes, vs.PageResponse) +} + +// DelegationDelegatorReward is a struct to represent the key information from +// a query for the rewards of a delegation to a given validator. +type DelegationDelegatorReward struct { + ValidatorAddress string + Reward []cmn.DecCoin +} + +// DelegationTotalRewardsOutput is a struct to represent the key information from +// a DelegationTotalRewards response. +type DelegationTotalRewardsOutput struct { + Rewards []DelegationDelegatorReward + Total []cmn.DecCoin +} + +// FromResponse populates the DelegationTotalRewardsOutput from a QueryDelegationTotalRewardsResponse. +func (dtr *DelegationTotalRewardsOutput) FromResponse(res *distributiontypes.QueryDelegationTotalRewardsResponse) *DelegationTotalRewardsOutput { + dtr.Rewards = make([]DelegationDelegatorReward, len(res.Rewards)) + for i, r := range res.Rewards { + dtr.Rewards[i] = DelegationDelegatorReward{ + ValidatorAddress: r.ValidatorAddress, + Reward: util.NewDecCoinsResponseEVM(r.Reward), + } + } + dtr.Total = util.NewDecCoinsResponseEVM(res.Total) + return dtr +} + +// Pack packs a given slice of abi arguments into a byte array. +func (dtr *DelegationTotalRewardsOutput) Pack(args abi.Arguments) ([]byte, error) { + return args.Pack(dtr.Rewards, dtr.Total) +} diff --git a/precompiles/distribution/utils_test.go b/precompiles/distribution/utils_test.go new file mode 100644 index 00000000..581ba9a9 --- /dev/null +++ b/precompiles/distribution/utils_test.go @@ -0,0 +1,276 @@ +package distribution_test + +import ( + "encoding/json" + "time" + + "cosmossdk.io/math" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/crypto/tmhash" + tmtypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/baseapp" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/testutil/mock" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + sdkstaking "github.com/cosmos/cosmos-sdk/x/staking" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + evmosapp "github.com/evmos/evmos/v16/app" + cmn "github.com/evmos/evmos/v16/precompiles/common" + "github.com/evmos/evmos/v16/precompiles/distribution" + evmosutil "github.com/evmos/evmos/v16/testutil" + evmosutiltx "github.com/evmos/evmos/v16/testutil/tx" + evmostypes "github.com/evmos/evmos/v16/types" + "github.com/evmos/evmos/v16/utils" + "github.com/evmos/evmos/v16/x/evm/statedb" + evmtypes "github.com/evmos/evmos/v16/x/evm/types" + inflationtypes "github.com/evmos/evmos/v16/x/inflation/v1/types" +) + +// SetupWithGenesisValSet initializes a new EvmosApp with a validator set and genesis accounts +// that also act as delegators. For simplicity, each validator is bonded with a delegation +// of one consensus engine unit (10^6) in the default token of the simapp from first genesis +// account. A Nop logger is set in SimApp. +func (s *PrecompileTestSuite) SetupWithGenesisValSet(valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) { + appI, genesisState := evmosapp.SetupTestingApp(cmn.DefaultChainID)() + app, ok := appI.(*evmosapp.Evmos) + s.Require().True(ok) + + // set genesis accounts + authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) + genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis) + + validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) + delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) + + bondAmt := sdk.TokensFromConsensusPower(1, evmostypes.PowerReduction) + + for _, val := range valSet.Validators { + pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey) + s.Require().NoError(err) + pkAny, err := codectypes.NewAnyWithValue(pk) + s.Require().NoError(err) + validator := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: bondAmt, + DelegatorShares: math.LegacyOneDec(), + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()), + MinSelfDelegation: math.ZeroInt(), + } + validators = append(validators, validator) + delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), math.LegacyOneDec())) + } + s.validators = validators + + // set validators and delegations + stakingParams := stakingtypes.DefaultParams() + // set bond demon to be aevmos + stakingParams.BondDenom = utils.BaseDenom + stakingGenesis := stakingtypes.NewGenesisState(stakingParams, validators, delegations) + genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis) + + totalBondAmt := bondAmt.Add(bondAmt) + totalSupply := sdk.NewCoins() + for _, b := range balances { + // add genesis acc tokens and delegated tokens to total supply + totalSupply = totalSupply.Add(b.Coins.Add(sdk.NewCoin(utils.BaseDenom, totalBondAmt))...) + } + + // add bonded amount to bonded pool module account + balances = append(balances, banktypes.Balance{ + Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), + Coins: sdk.Coins{sdk.NewCoin(utils.BaseDenom, totalBondAmt)}, + }) + + // update total supply + bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{}, []banktypes.SendEnabled{}) + genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis) + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + s.Require().NoError(err) + + // init chain will set the validator set and initialize the genesis accounts + app.InitChain( + abci.RequestInitChain{ + ChainId: cmn.DefaultChainID, + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: evmosapp.DefaultConsensusParams, + AppStateBytes: stateBytes, + }, + ) + app.Commit() + + // instantiate new header + header := evmosutil.NewHeader( + 2, + time.Now().UTC(), + cmn.DefaultChainID, + sdk.ConsAddress(validators[0].GetOperator()), + tmhash.Sum([]byte("app")), + tmhash.Sum([]byte("validators")), + ) + + app.BeginBlock(abci.RequestBeginBlock{ + Header: header, + }) + + // create Context + s.ctx = app.BaseApp.NewContext(false, header) + s.app = app +} + +func (s *PrecompileTestSuite) DoSetupTest() { + // generate validator private/public key + privVal := mock.NewPV() + pubKey, err := privVal.GetPubKey() + s.Require().NoError(err) + + privVal2 := mock.NewPV() + pubKey2, err := privVal2.GetPubKey() + s.Require().NoError(err) + + // create validator set with two validators + validator := tmtypes.NewValidator(pubKey, 1) + validator2 := tmtypes.NewValidator(pubKey2, 2) + s.valSet = tmtypes.NewValidatorSet([]*tmtypes.Validator{validator, validator2}) + signers := make(map[string]tmtypes.PrivValidator) + signers[pubKey.Address().String()] = privVal + signers[pubKey2.Address().String()] = privVal2 + + // generate genesis account + addr, priv := evmosutiltx.NewAddrKey() + s.privKey = priv + s.address = addr + s.signer = evmosutiltx.NewSigner(priv) + + baseAcc := authtypes.NewBaseAccount(priv.PubKey().Address().Bytes(), priv.PubKey(), 0, 0) + + acc := &evmostypes.EthAccount{ + BaseAccount: baseAcc, + CodeHash: common.BytesToHash(evmtypes.EmptyCodeHash).Hex(), + } + + amount := sdk.TokensFromConsensusPower(5, evmostypes.PowerReduction) + + balance := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, amount)), + } + + s.SetupWithGenesisValSet(s.valSet, []authtypes.GenesisAccount{acc}, balance) + + // Create StateDB + s.stateDB = statedb.New(s.ctx, s.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(s.ctx.HeaderHash().Bytes()))) + + // bond denom + stakingParams := s.app.StakingKeeper.GetParams(s.ctx) + stakingParams.BondDenom = utils.BaseDenom + s.bondDenom = stakingParams.BondDenom + err = s.app.StakingKeeper.SetParams(s.ctx, stakingParams) + s.Require().NoError(err) + + s.ethSigner = ethtypes.LatestSignerForChainID(s.app.EvmKeeper.ChainID()) + + precompile, err := distribution.NewPrecompile(s.app.DistrKeeper, s.app.StakingKeeper, s.app.AuthzKeeper) + s.Require().NoError(err) + s.precompile = precompile + + coins := sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(5000000000000000000))) + inflCoins := sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(2000000000000000000))) + distrCoins := sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(3000000000000000000))) + err = s.app.BankKeeper.MintCoins(s.ctx, inflationtypes.ModuleName, coins) + s.Require().NoError(err) + err = s.app.BankKeeper.SendCoinsFromModuleToModule(s.ctx, inflationtypes.ModuleName, authtypes.FeeCollectorName, inflCoins) + s.Require().NoError(err) + err = s.app.BankKeeper.SendCoinsFromModuleToModule(s.ctx, inflationtypes.ModuleName, distrtypes.ModuleName, distrCoins) + s.Require().NoError(err) + + queryHelperEvm := baseapp.NewQueryServerTestHelper(s.ctx, s.app.InterfaceRegistry()) + evmtypes.RegisterQueryServer(queryHelperEvm, s.app.EvmKeeper) + s.queryClientEVM = evmtypes.NewQueryClient(queryHelperEvm) +} + +// DeployContract deploys a contract that calls the distribution precompile's methods for testing purposes. +func (s *PrecompileTestSuite) DeployContract(contract evmtypes.CompiledContract) (addr common.Address, err error) { + addr, err = evmosutil.DeployContract( + s.ctx, + s.app, + s.privKey, + s.queryClientEVM, + contract, + ) + return +} + +type stakingRewards struct { + Delegator sdk.AccAddress + Validator stakingtypes.Validator + RewardAmt math.Int +} + +// prepareStakingRewards prepares the test suite for testing delegation rewards. +// +// Specified rewards amount are allocated to the specified validator using the distribution keeper, +// such that the given amount of tokens is outstanding as a staking reward for the account. +// +// The setup is done in the following way: +// - Fund the account with the given address with the given rewards amount. +// - Delegate the rewards amount to the validator specified +// - Allocate rewards to the validator. +func (s *PrecompileTestSuite) prepareStakingRewards(stkRs ...stakingRewards) { + for _, r := range stkRs { + // fund account to make delegation + err := evmosutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, r.Delegator, r.RewardAmt.Int64()) + s.Require().NoError(err) + // set distribution module account balance which pays out the rewards + distrAcc := s.app.DistrKeeper.GetDistributionAccount(s.ctx) + err = evmosutil.FundModuleAccount(s.ctx, s.app.BankKeeper, distrAcc.GetName(), sdk.NewCoins(sdk.NewCoin(s.bondDenom, r.RewardAmt))) + s.Require().NoError(err) + + // make a delegation + _, err = s.app.StakingKeeper.Delegate(s.ctx, r.Delegator, r.RewardAmt, stakingtypes.Unspecified, r.Validator, true) + s.Require().NoError(err) + + // end block to bond validator and increase block height + sdkstaking.EndBlocker(s.ctx, &s.app.StakingKeeper) + // allocate rewards to validator (of these 50% will be paid out to the delegator) + allocatedRewards := sdk.NewDecCoins(sdk.NewDecCoin(s.bondDenom, r.RewardAmt.Mul(math.NewInt(2)))) + s.app.DistrKeeper.AllocateTokensToValidator(s.ctx, r.Validator, allocatedRewards) + } + s.NextBlock() +} + +// NextBlock commits the current block and sets up the next block. +func (s *PrecompileTestSuite) NextBlock() { + var err error + s.ctx, err = evmosutil.CommitAndCreateNewCtx(s.ctx, s.app, time.Second, s.valSet) + s.Require().NoError(err) +} + +// setupValidatorSlashes sets slashes events for the provided validator +// returns the slash event set +func (s *PrecompileTestSuite) setupValidatorSlashes(valAddr sdk.ValAddress, slashesCount uint64) distrtypes.ValidatorSlashEvent { + const ( + initialHeight uint64 = 2 + initialPeriod uint64 = 1 + ) + + slashEvent := distrtypes.ValidatorSlashEvent{ValidatorPeriod: 1, Fraction: math.LegacyNewDec(5)} + + for i := uint64(0); i < slashesCount; i++ { + s.app.DistrKeeper.SetValidatorSlashEvent(s.ctx, valAddr, initialHeight+i, initialPeriod+i, slashEvent) + } + + return slashEvent +} diff --git a/precompiles/staking/StakingI.sol b/precompiles/staking/StakingI.sol new file mode 100644 index 00000000..a2c1bff4 --- /dev/null +++ b/precompiles/staking/StakingI.sol @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.17; + +import "../authorization/AuthorizationI.sol" as authorization; +import "../common/Types.sol"; + +/// @dev The StakingI contract's address. +address constant STAKING_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000800; + +/// @dev The StakingI contract's instance. +StakingI constant STAKING_CONTRACT = StakingI(STAKING_PRECOMPILE_ADDRESS); + +/// @dev Define all the available staking methods. +string constant MSG_CREATE_VALIDATOR = "/cosmos.staking.v1beta1.MsgCreateValidator"; +string constant MSG_DELEGATE = "/cosmos.staking.v1beta1.MsgDelegate"; +string constant MSG_UNDELEGATE = "/cosmos.staking.v1beta1.MsgUndelegate"; +string constant MSG_REDELEGATE = "/cosmos.staking.v1beta1.MsgBeginRedelegate"; +string constant MSG_CANCEL_UNDELEGATION = "/cosmos.staking.v1beta1.MsgCancelUnbondingDelegation"; + +/// @dev Defines the initial description to be used for creating +/// a validator. +struct Description { + string moniker; + string identity; + string website; + string securityContact; + string details; +} + +/// @dev Defines the initial commission rates to be used for creating +/// a validator. +struct CommissionRates { + uint256 rate; + uint256 maxRate; + uint256 maxChangeRate; +} + +/// @dev Defines commission parameters for a given validator. +struct Commission { + CommissionRates commissionRates; + uint256 updateTime; +} + + +/// @dev Represents a validator in the staking module. +struct Validator { + string operatorAddress; + string consensusPubkey; + bool jailed; + BondStatus status; + uint256 tokens; + uint256 delegatorShares; // TODO: decimal + string description; + int64 unbondingHeight; + int64 unbondingTime; + uint256 commission; + uint256 minSelfDelegation; +} + +/// @dev Represents the output of a Redelegations query. +struct RedelegationResponse { + Redelegation redelegation; + RedelegationEntryResponse[] entries; +} + +/// @dev Represents a redelegation between a delegator and a validator. +struct Redelegation { + string delegatorAddress; + string validatorSrcAddress; + string validatorDstAddress; + RedelegationEntry[] entries; +} + +/// @dev Represents a RedelegationEntryResponse for the Redelegations query. +struct RedelegationEntryResponse { + RedelegationEntry redelegationEntry; + uint256 balance; +} + +/// @dev Represents a single Redelegation entry. +struct RedelegationEntry { + int64 creationHeight; + int64 completionTime; + uint256 initialBalance; + uint256 sharesDst; // TODO: decimal +} + +/// @dev Represents the output of the Redelegation query. +struct RedelegationOutput { + string delegatorAddress; + string validatorSrcAddress; + string validatorDstAddress; + RedelegationEntry[] entries; +} + +/// @dev Represents a single entry of an unbonding delegation. +struct UnbondingDelegationEntry { + int64 creationHeight; + int64 completionTime; + uint256 initialBalance; + uint256 balance; + uint64 unbondingId; + int64 unbondingOnHoldRefCount; +} + +/// @dev Represents the output of the UnbondingDelegation query. +struct UnbondingDelegationOutput { + string delegatorAddress; + string validatorAddress; + UnbondingDelegationEntry[] entries; +} + +/// @dev The status of the validator. +enum BondStatus { + Unspecified, + Unbonded, + Unbonding, + Bonded +} + +/// @author Evmos Team +/// @title Staking Precompiled Contract +/// @dev The interface through which solidity contracts will interact with staking. +/// We follow this same interface including four-byte function selectors, in the precompile that +/// wraps the pallet. +/// @custom:address 0x0000000000000000000000000000000000000800 +interface StakingI is authorization.AuthorizationI { + /// @dev Defines a method for creating a new validator. + /// @param description The initial description + /// @param commissionRates The initial commissionRates + /// @param minSelfDelegation The validator's self declared minimum self delegation + /// @param delegatorAddress The delegator address + /// @param validatorAddress The validator address + /// @param pubkey The consensus public key of the validator + /// @param value The amount of the coin to be self delegated to the validator + /// @return success Whether or not the create validator was successful + function createValidator( + Description calldata description, + CommissionRates calldata commissionRates, + uint256 minSelfDelegation, + address delegatorAddress, + string memory validatorAddress, + string memory pubkey, + uint256 value + ) external returns (bool success); + + /// @dev Defines a method for performing a delegation of coins from a delegator to a validator. + /// @param delegatorAddress The address of the delegator + /// @param validatorAddress The address of the validator + /// @param amount The amount of the Coin to be delegated to the validator + /// @return success Whether or not the delegate was successful + function delegate( + address delegatorAddress, + string memory validatorAddress, + uint256 amount + ) external returns (bool success); + + /// @dev Defines a method for performing an undelegation from a delegate and a validator. + /// @param delegatorAddress The address of the delegator + /// @param validatorAddress The address of the validator + /// @param amount The amount to be undelegated from the validator + /// @return completionTime The time when the undelegation is completed + function undelegate( + address delegatorAddress, + string memory validatorAddress, + uint256 amount + ) external returns (int64 completionTime); + + /// @dev Defines a method for performing a redelegation + /// of coins from a delegator and source validator to a destination validator. + /// @param delegatorAddress The address of the delegator + /// @param validatorSrcAddress The validator from which the redelegation is initiated + /// @param validatorDstAddress The validator to which the redelegation is destined + /// @param amount The amount to be redelegated to the validator + /// @return completionTime The time when the redelegation is completed + function redelegate( + address delegatorAddress, + string memory validatorSrcAddress, + string memory validatorDstAddress, + uint256 amount + ) external returns (int64 completionTime); + + /// @dev Allows delegators to cancel the unbondingDelegation entry + /// and to delegate back to a previous validator. + /// @param delegatorAddress The address of the delegator + /// @param validatorAddress The address of the validator + /// @param amount The amount of the Coin + /// @param creationHeight The height at which the unbonding took place + /// @return success Whether or not the unbonding delegation was cancelled + function cancelUnbondingDelegation( + address delegatorAddress, + string memory validatorAddress, + uint256 amount, + uint256 creationHeight + ) external returns (bool success); + + /// @dev Queries the given amount of the bond denomination to a validator. + /// @param delegatorAddress The address of the delegator. + /// @param validatorAddress The address of the validator. + /// @return shares The amount of shares, that the delegator has received. + /// @return balance The amount in Coin, that the delegator has delegated to the given validator. + function delegation( + address delegatorAddress, + string memory validatorAddress + ) external view returns (uint256 shares, Coin calldata balance); + + /// @dev Returns the delegation shares and coins, that are currently + /// unbonding for a given delegator and validator pair. + /// @param delegatorAddress The address of the delegator. + /// @param validatorAddress The address of the validator. + /// @return unbondingDelegation The delegations that are currently unbonding. + function unbondingDelegation( + address delegatorAddress, + string memory validatorAddress + ) external view returns (UnbondingDelegationOutput calldata unbondingDelegation); + + /// @dev Queries validator info for a given validator address. + /// @param validatorAddress The address of the validator. + /// @return validator The validator info for the given validator address. + function validator( + string memory validatorAddress + ) external view returns (Validator calldata validator); + + /// @dev Queries all validators that match the given status. + /// @param status Enables to query for validators matching a given status. + /// @param pageRequest Defines an optional pagination for the request. + function validators( + string memory status, + PageRequest calldata pageRequest + ) + external + view + returns ( + Validator[] calldata validators, + PageResponse calldata pageResponse + ); + + /// @dev Queries all redelegations from a source to a destination validator for a given delegator. + /// @param delegatorAddress The address of the delegator. + /// @param srcValidatorAddress Defines the validator address to redelegate from. + /// @param dstValidatorAddress Defines the validator address to redelegate to. + /// @return redelegation The active redelegations for the given delegator, source and destination validator combination. + function redelegation( + address delegatorAddress, + string memory srcValidatorAddress, + string memory dstValidatorAddress + ) external view returns (RedelegationOutput calldata redelegation); + + /// @dev Queries all redelegations based on the specified criteria: + /// for a given delegator and/or origin validator address + /// and/or destination validator address + /// in a specified pagination manner. + /// @param delegatorAddress The address of the delegator as string (can be a zero address). + /// @param srcValidatorAddress Defines the validator address to redelegate from (can be empty string). + /// @param dstValidatorAddress Defines the validator address to redelegate to (can be empty string). + /// @param pageRequest Defines an optional pagination for the request. + /// @return response Holds the redelegations for the given delegator, source and destination validator combination. + function redelegations( + address delegatorAddress, + string memory srcValidatorAddress, + string memory dstValidatorAddress, + PageRequest calldata pageRequest + ) + external + view + returns ( + RedelegationResponse[] calldata response, + PageResponse calldata pageResponse + ); + + /// @dev CreateValidator defines an Event emitted when a create a new validator. + /// @param delegatorAddress The address of the delegator + /// @param validatorAddress The address of the validator + /// @param value The amount of coin being self delegated + event CreateValidator( + address indexed delegatorAddress, + address indexed validatorAddress, + uint256 value + ); + + /// @dev Delegate defines an Event emitted when a given amount of tokens are delegated from the + /// delegator address to the validator address. + /// @param delegatorAddress The address of the delegator + /// @param validatorAddress The address of the validator + /// @param amount The amount of Coin being delegated + /// @param newShares The new delegation shares being held + event Delegate( + address indexed delegatorAddress, + address indexed validatorAddress, + uint256 amount, + uint256 newShares + ); + + /// @dev Unbond defines an Event emitted when a given amount of tokens are unbonded from the + /// validator address to the delegator address. + /// @param delegatorAddress The address of the delegator + /// @param validatorAddress The address of the validator + /// @param amount The amount of Coin being unbonded + /// @param completionTime The time at which the unbonding is completed + event Unbond( + address indexed delegatorAddress, + address indexed validatorAddress, + uint256 amount, + uint256 completionTime + ); + + /// @dev Redelegate defines an Event emitted when a given amount of tokens are redelegated from + /// the source validator address to the destination validator address. + /// @param delegatorAddress The address of the delegator + /// @param validatorSrcAddress The address of the validator from which the delegation is retracted + /// @param validatorDstAddress The address of the validator to which the delegation is directed + /// @param amount The amount of Coin being redelegated + /// @param completionTime The time at which the redelegation is completed + event Redelegate( + address indexed delegatorAddress, + address indexed validatorSrcAddress, + address indexed validatorDstAddress, + uint256 amount, + uint256 completionTime + ); + + /// @dev CancelUnbondingDelegation defines an Event emitted when a given amount of tokens + /// that are in the process of unbonding from the validator address are bonded again. + /// @param delegatorAddress The address of the delegator + /// @param validatorAddress The address of the validator + /// @param amount The amount of Coin that was in the unbonding process which is to be canceled + /// @param creationHeight The block height at which the unbonding of a delegation was initiated + event CancelUnbondingDelegation( + address indexed delegatorAddress, + address indexed validatorAddress, + uint256 amount, + uint256 creationHeight + ); +} diff --git a/precompiles/staking/abi.json b/precompiles/staking/abi.json new file mode 100644 index 00000000..0e67804d --- /dev/null +++ b/precompiles/staking/abi.json @@ -0,0 +1,1159 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "grantee", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "granter", + "type": "address" + }, + { + "indexed": false, + "internalType": "string[]", + "name": "methods", + "type": "string[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "AllowanceChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "grantee", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "granter", + "type": "address" + }, + { + "indexed": false, + "internalType": "string[]", + "name": "methods", + "type": "string[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "validatorAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "creationHeight", + "type": "uint256" + } + ], + "name": "CancelUnbondingDelegation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "validatorAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "CreateValidator", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "validatorAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newShares", + "type": "uint256" + } + ], + "name": "Delegate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "validatorSrcAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "validatorDstAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "completionTime", + "type": "uint256" + } + ], + "name": "Redelegate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "grantee", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "granter", + "type": "address" + }, + { + "indexed": false, + "internalType": "string[]", + "name": "methods", + "type": "string[]" + } + ], + "name": "Revocation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "validatorAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "completionTime", + "type": "uint256" + } + ], + "name": "Unbond", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "grantee", + "type": "address" + }, + { + "internalType": "address", + "name": "granter", + "type": "address" + }, + { + "internalType": "string", + "name": "method", + "type": "string" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "remaining", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "grantee", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "string[]", + "name": "methods", + "type": "string[]" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "creationHeight", + "type": "uint256" + } + ], + "name": "cancelUnbondingDelegation", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "moniker", + "type": "string" + }, + { + "internalType": "string", + "name": "identity", + "type": "string" + }, + { + "internalType": "string", + "name": "website", + "type": "string" + }, + { + "internalType": "string", + "name": "securityContact", + "type": "string" + }, + { + "internalType": "string", + "name": "details", + "type": "string" + } + ], + "internalType": "struct Description", + "name": "description", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "rate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxChangeRate", + "type": "uint256" + } + ], + "internalType": "struct CommissionRates", + "name": "commissionRates", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "minSelfDelegation", + "type": "uint256" + }, + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + }, + { + "internalType": "string", + "name": "pubkey", + "type": "string" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "createValidator", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "grantee", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "string[]", + "name": "methods", + "type": "string[]" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "delegate", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + } + ], + "name": "delegation", + "outputs": [ + { + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct Coin", + "name": "balance", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "grantee", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "string[]", + "name": "methods", + "type": "string[]" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "validatorSrcAddress", + "type": "string" + }, + { + "internalType": "string", + "name": "validatorDstAddress", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "redelegate", + "outputs": [ + { + "internalType": "int64", + "name": "completionTime", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "srcValidatorAddress", + "type": "string" + }, + { + "internalType": "string", + "name": "dstValidatorAddress", + "type": "string" + } + ], + "name": "redelegation", + "outputs": [ + { + "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": "uint256", + "name": "initialBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesDst", + "type": "uint256" + } + ], + "internalType": "struct RedelegationEntry[]", + "name": "entries", + "type": "tuple[]" + } + ], + "internalType": "struct RedelegationOutput", + "name": "redelegation", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "srcValidatorAddress", + "type": "string" + }, + { + "internalType": "string", + "name": "dstValidatorAddress", + "type": "string" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "offset", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "limit", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "countTotal", + "type": "bool" + }, + { + "internalType": "bool", + "name": "reverse", + "type": "bool" + } + ], + "internalType": "struct PageRequest", + "name": "pageRequest", + "type": "tuple" + } + ], + "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": "uint256", + "name": "initialBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesDst", + "type": "uint256" + } + ], + "internalType": "struct RedelegationEntry[]", + "name": "entries", + "type": "tuple[]" + } + ], + "internalType": "struct Redelegation", + "name": "redelegation", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "int64", + "name": "creationHeight", + "type": "int64" + }, + { + "internalType": "int64", + "name": "completionTime", + "type": "int64" + }, + { + "internalType": "uint256", + "name": "initialBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesDst", + "type": "uint256" + } + ], + "internalType": "struct RedelegationEntry", + "name": "redelegationEntry", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "internalType": "struct RedelegationEntryResponse[]", + "name": "entries", + "type": "tuple[]" + } + ], + "internalType": "struct RedelegationResponse[]", + "name": "response", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "nextKey", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "total", + "type": "uint64" + } + ], + "internalType": "struct PageResponse", + "name": "pageResponse", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "grantee", + "type": "address" + }, + { + "internalType": "string[]", + "name": "methods", + "type": "string[]" + } + ], + "name": "revoke", + "outputs": [ + { + "internalType": "bool", + "name": "revoked", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "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": "uint256", + "name": "initialBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "unbondingId", + "type": "uint64" + }, + { + "internalType": "int64", + "name": "unbondingOnHoldRefCount", + "type": "int64" + } + ], + "internalType": "struct UnbondingDelegationEntry[]", + "name": "entries", + "type": "tuple[]" + } + ], + "internalType": "struct UnbondingDelegationOutput", + "name": "unbondingDelegation", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "undelegate", + "outputs": [ + { + "internalType": "int64", + "name": "completionTime", + "type": "int64" + } + ], + "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": "enum BondStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "tokens", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatorShares", + "type": "uint256" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + }, + { + "internalType": "int64", + "name": "unbondingHeight", + "type": "int64" + }, + { + "internalType": "int64", + "name": "unbondingTime", + "type": "int64" + }, + { + "internalType": "uint256", + "name": "commission", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minSelfDelegation", + "type": "uint256" + } + ], + "internalType": "struct Validator", + "name": "validator", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "status", + "type": "string" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "offset", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "limit", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "countTotal", + "type": "bool" + }, + { + "internalType": "bool", + "name": "reverse", + "type": "bool" + } + ], + "internalType": "struct PageRequest", + "name": "pageRequest", + "type": "tuple" + } + ], + "name": "validators", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "operatorAddress", + "type": "string" + }, + { + "internalType": "string", + "name": "consensusPubkey", + "type": "string" + }, + { + "internalType": "bool", + "name": "jailed", + "type": "bool" + }, + { + "internalType": "enum BondStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "tokens", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "delegatorShares", + "type": "uint256" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + }, + { + "internalType": "int64", + "name": "unbondingHeight", + "type": "int64" + }, + { + "internalType": "int64", + "name": "unbondingTime", + "type": "int64" + }, + { + "internalType": "uint256", + "name": "commission", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minSelfDelegation", + "type": "uint256" + } + ], + "internalType": "struct Validator[]", + "name": "validators", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "nextKey", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "total", + "type": "uint64" + } + ], + "internalType": "struct PageResponse", + "name": "pageResponse", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/precompiles/staking/approve.go b/precompiles/staking/approve.go new file mode 100644 index 00000000..42d30dd7 --- /dev/null +++ b/precompiles/staking/approve.go @@ -0,0 +1,355 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package staking + +import ( + "fmt" + "time" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/evmos/evmos/v16/precompiles/authorization" + cmn "github.com/evmos/evmos/v16/precompiles/common" +) + +var ( + // DelegateMsg defines the authorization type for MsgDelegate + DelegateMsg = sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}) + // UndelegateMsg defines the authorization type for MsgUndelegate + UndelegateMsg = sdk.MsgTypeURL(&stakingtypes.MsgUndelegate{}) + // RedelegateMsg defines the authorization type for MsgRedelegate + RedelegateMsg = sdk.MsgTypeURL(&stakingtypes.MsgBeginRedelegate{}) + // CancelUnbondingDelegationMsg defines the authorization type for MsgCancelUnbondingDelegation + CancelUnbondingDelegationMsg = sdk.MsgTypeURL(&stakingtypes.MsgCancelUnbondingDelegation{}) +) + +// Approve sets amount as the allowance of a grantee over the caller’s tokens. +// Returns a boolean value indicating whether the operation succeeded. +func (p Precompile) Approve( + ctx sdk.Context, + origin common.Address, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + grantee, coin, typeURLs, err := authorization.CheckApprovalArgs(args, p.stakingKeeper.BondDenom(ctx)) + if err != nil { + return nil, err + } + + for _, typeURL := range typeURLs { + switch typeURL { + case DelegateMsg, UndelegateMsg, RedelegateMsg, CancelUnbondingDelegationMsg: + authzType, err := convertMsgToAuthz(typeURL) + if err != nil { + return nil, errorsmod.Wrap(err, fmt.Sprintf(cmn.ErrInvalidMsgType, "staking", typeURL)) + } + if err = p.grantOrDeleteStakingAuthz(ctx, grantee, origin, coin, authzType); err != nil { + return nil, err + } + default: + // TODO: do we need to return an error here or just no-op? + // Implications of returning an error could be that we approve some parts of the txs but not all + return nil, fmt.Errorf(cmn.ErrInvalidMsgType, "staking", typeURL) + } + } + + // TODO: do we want to emit one approval for all typeUrls, or one approval for each typeUrl? + // NOTE: This might have gas implications as we are emitting a slice of strings + if err := p.EmitApprovalEvent(ctx, stateDB, grantee, origin, coin, typeURLs); err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +// Revoke removes the authorization grants given in the typeUrls for a given granter to a given grantee. +// It only works if the origin matches the spender to avoid unauthorized revocations. +// Works only for staking messages. +func (p Precompile) Revoke( + ctx sdk.Context, + origin common.Address, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + grantee, typeURLs, err := authorization.CheckRevokeArgs(args) + if err != nil { + return nil, err + } + + for _, typeURL := range typeURLs { + switch typeURL { + case DelegateMsg, UndelegateMsg, RedelegateMsg, CancelUnbondingDelegationMsg: + if err = p.AuthzKeeper.DeleteGrant(ctx, grantee.Bytes(), origin.Bytes(), typeURL); err != nil { + return nil, err + } + default: + // TODO: do we need to return an error here or just no-op? + // Implications of returning an error could be that we approve some parts of the txs but not all + return nil, fmt.Errorf(cmn.ErrInvalidMsgType, "staking", typeURL) + } + } + + // NOTE: Using the new more generic event emitter that was created + if err = authorization.EmitRevocationEvent(cmn.EmitEventArgs{ + Ctx: ctx, + StateDB: stateDB, + ContractAddr: p.Address(), + ContractEvents: p.ABI.Events, + EventData: authorization.EventRevocation{ + Granter: origin, + Grantee: grantee, + TypeUrls: typeURLs, + }, + }); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +// DecreaseAllowance decreases the allowance of grantee over the caller’s tokens by the amount. +func (p Precompile) DecreaseAllowance( + ctx sdk.Context, + origin common.Address, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + grantee, coin, typeUrls, err := authorization.CheckApprovalArgs(args, p.stakingKeeper.BondDenom(ctx)) + if err != nil { + return nil, err + } + + for _, typeURL := range typeUrls { + switch typeURL { + case DelegateMsg, UndelegateMsg, RedelegateMsg, CancelUnbondingDelegationMsg: + authzGrant, expiration, err := authorization.CheckAuthzExists(ctx, p.AuthzKeeper, grantee, origin, typeURL) + if err != nil { + return nil, err + } + + stakeAuthz, ok := authzGrant.(*stakingtypes.StakeAuthorization) + if !ok { + return nil, errorsmod.Wrapf(authz.ErrUnknownAuthorizationType, "expected: *types.StakeAuthorization, received: %T", authzGrant) + } + + if err = p.decreaseAllowance(ctx, grantee, origin, coin, stakeAuthz, expiration); err != nil { + return nil, err + } + default: + // TODO: do we need to return an error here or just no-op? + // Implications of returning an error could be that we decrease allowance for some parts of the typeUrls but not all + return nil, fmt.Errorf(cmn.ErrInvalidMsgType, "staking", typeURL) + } + } + + if err := p.EmitAllowanceChangeEvent(ctx, stateDB, grantee, origin, typeUrls); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +// IncreaseAllowance increases the allowance of grantee over the caller’s tokens by the amount. +func (p Precompile) IncreaseAllowance( + ctx sdk.Context, + origin common.Address, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + grantee, coin, typeUrls, err := authorization.CheckApprovalArgs(args, p.stakingKeeper.BondDenom(ctx)) + if err != nil { + return nil, err + } + + for _, typeURL := range typeUrls { + switch typeURL { + case DelegateMsg, UndelegateMsg, RedelegateMsg: + if err = p.increaseAllowance(ctx, grantee, origin, coin, typeURL); err != nil { + return nil, err + } + default: + // TODO: do we need to return an error here or just no-op? + // Implications of returning an error could be that we decrease allowance for some parts of the typeUrls but not all + return nil, fmt.Errorf(cmn.ErrInvalidMsgType, "staking", typeURL) + } + } + if err := p.EmitAllowanceChangeEvent(ctx, stateDB, grantee, origin, typeUrls); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +// grantOrDeleteStakingAuthz grants staking method authorization to the precompiled contract for a spender. +// If the amount is zero, it deletes the authorization if it exists. +func (p Precompile) grantOrDeleteStakingAuthz( + ctx sdk.Context, + grantee, granter common.Address, + coin *sdk.Coin, + authzType stakingtypes.AuthorizationType, +) error { + // Case 1: coin is nil -> set authorization with no limit + if coin == nil || coin.IsNil() { + p.Logger(ctx).Debug( + "setting authorization without limit", + "grantee", grantee.String(), + "granter", granter.String(), + ) + return p.createStakingAuthz(ctx, grantee, granter, coin, authzType) + } + + // Case 2: coin amount is zero or negative -> delete the authorization + if !coin.Amount.IsPositive() { + p.Logger(ctx).Debug( + "deleting authorization", + "grantee", grantee.String(), + "granter", granter.String(), + ) + stakingAuthz := stakingtypes.StakeAuthorization{AuthorizationType: authzType} + return p.AuthzKeeper.DeleteGrant(ctx, grantee.Bytes(), granter.Bytes(), stakingAuthz.MsgTypeURL()) + } + + // Case 3: coin amount is non zero -> and not coin is not nil set with custom amount + return p.createStakingAuthz(ctx, grantee, granter, coin, authzType) +} + +// createStakingAuthz creates a staking authorization for a spender. +func (p Precompile) createStakingAuthz( + ctx sdk.Context, + grantee, granter common.Address, + coin *sdk.Coin, + authzType stakingtypes.AuthorizationType, +) error { + // Get all available validators and filter out jailed validators + validators := make([]sdk.ValAddress, 0) + p.stakingKeeper.IterateValidators( + ctx, func(_ int64, validator stakingtypes.ValidatorI) (stop bool) { + if validator.IsJailed() { + return + } + validators = append(validators, validator.GetOperator()) + return + }, + ) + stakingAuthz, err := stakingtypes.NewStakeAuthorization(validators, nil, authzType, coin) + if err != nil { + return err + } + + if err := stakingAuthz.ValidateBasic(); err != nil { + return err + } + + expiration := ctx.BlockTime().Add(p.ApprovalExpiration).UTC() + return p.AuthzKeeper.SaveGrant(ctx, grantee.Bytes(), granter.Bytes(), stakingAuthz, &expiration) +} + +// decreaseAllowance decreases the allowance of spender over the caller’s tokens by the amount. +func (p Precompile) decreaseAllowance( + ctx sdk.Context, + grantee, granter common.Address, + coin *sdk.Coin, + stakeAuthz *stakingtypes.StakeAuthorization, + expiration *time.Time, +) error { + // If the authorization has no limit, no operation is performed + if stakeAuthz.MaxTokens == nil { + p.Logger(ctx).Debug("decreaseAllowance called with no limit (stakeAuthz.MaxTokens == nil): no-op") + return nil + } + + // If the authorization limit is less than the substation amount, return error + if stakeAuthz.MaxTokens.Amount.LT(coin.Amount) { + return fmt.Errorf(ErrDecreaseAmountTooBig, coin.Amount, stakeAuthz.MaxTokens.Amount) + } + + // If amount is less than or equal to the Authorization amount, subtract the amount from the limit + if coin.Amount.LTE(stakeAuthz.MaxTokens.Amount) { + stakeAuthz.MaxTokens.Amount = stakeAuthz.MaxTokens.Amount.Sub(coin.Amount) + } + + return p.AuthzKeeper.SaveGrant(ctx, grantee.Bytes(), granter.Bytes(), stakeAuthz, expiration) +} + +// increaseAllowance increases the allowance of spender over the caller’s tokens by the amount. +func (p Precompile) increaseAllowance( + ctx sdk.Context, + grantee, granter common.Address, + coin *sdk.Coin, + msgURL string, +) error { + // Check if the authorization exists for the given spender + existingAuthz, expiration, err := authorization.CheckAuthzExists(ctx, p.AuthzKeeper, grantee, granter, msgURL) + if err != nil { + return err + } + + // Cast the authorization to a staking authorization + stakeAuthz, ok := existingAuthz.(*stakingtypes.StakeAuthorization) + if !ok { + return errorsmod.Wrapf(authz.ErrUnknownAuthorizationType, "expected: *types.StakeAuthorization, received: %T", existingAuthz) + } + + // If the authorization has no limit, no operation is performed + if stakeAuthz.MaxTokens == nil { + p.Logger(ctx).Debug("increaseAllowance called with no limit (stakeAuthz.MaxTokens == nil): no-op") + return nil + } + + // Add the amount to the limit + stakeAuthz.MaxTokens.Amount = stakeAuthz.MaxTokens.Amount.Add(coin.Amount) + + return p.AuthzKeeper.SaveGrant(ctx, grantee.Bytes(), granter.Bytes(), stakeAuthz, expiration) +} + +// UpdateStakingAuthorization updates the staking grant based on the authz AcceptResponse for the given granter and grantee. +func (p Precompile) UpdateStakingAuthorization( + ctx sdk.Context, + grantee, granter common.Address, + stakeAuthz *stakingtypes.StakeAuthorization, + expiration *time.Time, + messageType string, + msg sdk.Msg, +) error { + updatedResponse, err := stakeAuthz.Accept(ctx, msg) + if err != nil { + return err + } + + if updatedResponse.Delete { + err = p.AuthzKeeper.DeleteGrant(ctx, grantee.Bytes(), granter.Bytes(), messageType) + } else { + err = p.AuthzKeeper.SaveGrant(ctx, grantee.Bytes(), granter.Bytes(), updatedResponse.Updated, expiration) + } + + if err != nil { + return err + } + return nil +} + +// convertMsgToAuthz converts a msg to an authorization type. +func convertMsgToAuthz(msg string) (stakingtypes.AuthorizationType, error) { + switch msg { + case DelegateMsg: + return DelegateAuthz, nil + case UndelegateMsg: + return UndelegateAuthz, nil + case RedelegateMsg: + return RedelegateAuthz, nil + case CancelUnbondingDelegationMsg: + return CancelUnbondingDelegationAuthz, nil + default: + return stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNSPECIFIED, authz.ErrUnknownAuthorizationType.Wrapf("cannot convert msg to authorization type with %T", msg) + } +} diff --git a/precompiles/staking/approve_test.go b/precompiles/staking/approve_test.go new file mode 100644 index 00000000..b4720bcd --- /dev/null +++ b/precompiles/staking/approve_test.go @@ -0,0 +1,727 @@ +package staking_test + +import ( + "fmt" + "math/big" + "time" + + "cosmossdk.io/math" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + evmosutiltx "github.com/evmos/evmos/v16/testutil/tx" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkauthz "github.com/cosmos/cosmos-sdk/x/authz" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/evmos/evmos/v16/precompiles/authorization" + cmn "github.com/evmos/evmos/v16/precompiles/common" + "github.com/evmos/evmos/v16/precompiles/staking" + "github.com/evmos/evmos/v16/precompiles/testutil" + evmosutil "github.com/evmos/evmos/v16/testutil" +) + +func (s *PrecompileTestSuite) TestApprove() { + method := s.precompile.Methods[authorization.ApproveMethod] + + testCases := []struct { + name string + malleate func(*vm.Contract) []interface{} + postCheck func(data []byte, inputArgs []interface{}) + gas uint64 + expError bool + errContains string + }{ + { + "fail - empty input args", + func(_ *vm.Contract) []interface{} { + return []interface{}{} + }, + func(data []byte, inputArgs []interface{}) {}, + 200000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 3, 0), + }, + { + "fail - invalid message type", + func(_ *vm.Contract) []interface{} { + return []interface{}{ + s.address, + abi.MaxUint256, + []string{"invalid"}, + } + }, + func(data []byte, inputArgs []interface{}) {}, + 200000, + true, + fmt.Sprintf(cmn.ErrInvalidMsgType, "staking", "invalid"), + }, + // TODO: enable this test once we check if spender and origin are the same + // { + // "fail - origin address is the same the spender address", + // func(_ *vm.Contract) []interface{} { + // return []interface{}{ + // s.address, + // abi.MaxUint256, + // []string{"invalid"}, + // } + // }, + // func(data []byte, inputArgs []interface{}) {}, + // 200000, + // true, + // "is the same as spender", + // }, + { + "success - MsgDelegate with unlimited coins", + func(_ *vm.Contract) []interface{} { + return []interface{}{ + s.address, + abi.MaxUint256, + []string{staking.DelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + s.Require().Equal(data, cmn.TrueValue) + authz, expirationTime := s.CheckAuthorization(staking.DelegateAuthz, s.address, s.address) + + s.Require().NotNil(authz) + s.Require().NotNil(expirationTime) + s.Require().Equal(authz.AuthorizationType, staking.DelegateAuthz) + var coin *sdk.Coin + s.Require().Equal(authz.MaxTokens, coin) + }, + 20000, + false, + "", + }, + { + "success - MsgUndelegate with unlimited coins", + func(_ *vm.Contract) []interface{} { + return []interface{}{ + s.address, + abi.MaxUint256, + []string{staking.UndelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + s.Require().Equal(data, cmn.TrueValue) + + authz, expirationTime := s.CheckAuthorization(staking.UndelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().NotNil(expirationTime) + s.Require().Equal(authz.AuthorizationType, staking.UndelegateAuthz) + var coin *sdk.Coin + s.Require().Equal(authz.MaxTokens, coin) + }, + 20000, + false, + "", + }, + { + "success - MsgRedelegate with unlimited coins", + func(_ *vm.Contract) []interface{} { + return []interface{}{ + s.address, + abi.MaxUint256, + []string{staking.RedelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + s.Require().Equal(data, cmn.TrueValue) + + authz, expirationTime := s.CheckAuthorization(staking.RedelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().NotNil(expirationTime) + s.Require().Equal(authz.AuthorizationType, staking.RedelegateAuthz) + var coin *sdk.Coin + s.Require().Equal(authz.MaxTokens, coin) + }, + 20000, + false, + "", + }, + { + "success - All staking methods with certain amount of coins", + func(_ *vm.Contract) []interface{} { + return []interface{}{ + s.address, + big.NewInt(1e18), + []string{ + staking.DelegateMsg, + staking.UndelegateMsg, + staking.RedelegateMsg, + }, + } + }, + func(data []byte, inputArgs []interface{}) { + s.Require().Equal(data, cmn.TrueValue) + + allAuthz, err := s.app.AuthzKeeper.GetAuthorizations(s.ctx, s.address.Bytes(), s.address.Bytes()) + s.Require().NoError(err) + s.Require().Len(allAuthz, 3) + }, + 20000, + false, + "", + }, + { + "success - remove MsgDelegate authorization", + func(contract *vm.Contract) []interface{} { + res, err := s.precompile.Approve(s.ctx, s.address, s.stateDB, &method, []interface{}{s.address, big.NewInt(1), []string{staking.DelegateMsg}}) + s.Require().NoError(err) + s.Require().Equal(res, cmn.TrueValue) + + authz, expirationTime := s.CheckAuthorization(staking.DelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().NotNil(expirationTime) + + return []interface{}{ + s.address, + big.NewInt(0), + []string{staking.DelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + s.Require().Equal(data, cmn.TrueValue) + + authz, expirationTime := s.CheckAuthorization(staking.DelegateAuthz, s.address, s.address) + s.Require().Nil(authz) + s.Require().Nil(expirationTime) + }, + 200000, + false, + "", + }, + { + "success - MsgDelegate with 1 Evmos as limit amount", + func(_ *vm.Contract) []interface{} { + return []interface{}{ + s.address, + big.NewInt(1e18), + []string{staking.DelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + s.Require().Equal(data, cmn.TrueValue) + + authz, expirationTime := s.CheckAuthorization(staking.DelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().NotNil(expirationTime) + s.Require().Equal(authz.AuthorizationType, staking.DelegateAuthz) + s.Require().Equal(authz.MaxTokens, &sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(1e18)}) + }, + 20000, + false, + "", + }, + { + "success - Authorization should only be created for validators that are not jailed", + func(_ *vm.Contract) []interface{} { + // Commit block (otherwise test logic will not be executed correctly, i.e. somehow unbonding does not take effect) + var err error + s.ctx, err = evmosutil.Commit(s.ctx, s.app, time.Second, nil) + s.Require().NoError(err, "failed to commit block") + + // Jail a validator + s.app.StakingKeeper.Jail(s.ctx, sdk.ConsAddress(s.validators[0].GetOperator())) + + // When a delegator redelegates/undelegates from a validator, the validator + // switches to Unbonding status. + // Thus, validators with this status should be considered for the authorization + + // Unbond another validator + amount, err := s.app.StakingKeeper.Unbond(s.ctx, s.address.Bytes(), s.validators[1].GetOperator(), math.LegacyOneDec()) + s.Require().NoError(err, "expected no error unbonding validator") + s.Require().Equal(math.NewInt(1e18), amount, "expected different amount of tokens to be unbonded") + + // Commit block and update time to one year later + s.ctx, err = evmosutil.Commit(s.ctx, s.app, time.Hour*24*365, nil) + s.Require().NoError(err, "failed to commit block") + + return []interface{}{ + s.address, + big.NewInt(1e18), + []string{staking.DelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + s.Require().Equal(data, cmn.TrueValue) + + authz, expirationTime := s.CheckAuthorization(staking.DelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().NotNil(expirationTime) + s.Require().Equal(authz.AuthorizationType, staking.DelegateAuthz) + s.Require().Equal(authz.MaxTokens, &sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(1e18)}) + // Check that the bonded and unbonding validators are included on the authorization + s.Require().Len(authz.GetAllowList().Address, 2, "should only be two validators in the allow list") + }, + 1e6, + false, + "", + }, + { + "success - MsgUndelegate with 1 Evmos as limit amount", + func(_ *vm.Contract) []interface{} { + return []interface{}{ + s.address, + big.NewInt(1e18), + []string{staking.UndelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + s.Require().Equal(data, cmn.TrueValue) + + authz, expirationTime := s.CheckAuthorization(staking.UndelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().NotNil(expirationTime) + s.Require().Equal(authz.AuthorizationType, staking.UndelegateAuthz) + s.Require().Equal(authz.MaxTokens, &sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(1e18)}) + }, + 20000, + false, + "", + }, + { + "success - MsgRedelegate with 1 Evmos as limit amount", + func(_ *vm.Contract) []interface{} { + return []interface{}{ + s.address, + big.NewInt(1e18), + []string{staking.RedelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + s.Require().Equal(data, cmn.TrueValue) + + authz, expirationTime := s.CheckAuthorization(staking.RedelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().NotNil(expirationTime) + }, + 20000, + false, + "", + }, + { + "success - MsgRedelegate, MsgUndelegate and MsgDelegate with 1 Evmos as limit amount", + func(_ *vm.Contract) []interface{} { + return []interface{}{ + s.address, + big.NewInt(1e18), + []string{ + staking.RedelegateMsg, + staking.UndelegateMsg, + staking.DelegateMsg, + }, + } + }, + func(data []byte, inputArgs []interface{}) { + s.Require().Equal(data, cmn.TrueValue) + + authz, expirationTime := s.CheckAuthorization(staking.DelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().NotNil(expirationTime) + s.Require().Equal(authz.AuthorizationType, staking.DelegateAuthz) + s.Require().Equal(authz.MaxTokens, &sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(1e18)}) + + authz, expirationTime = s.CheckAuthorization(staking.UndelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().NotNil(expirationTime) + + s.Require().Equal(authz.AuthorizationType, staking.UndelegateAuthz) + s.Require().Equal(authz.MaxTokens, &sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(1e18)}) + + authz, expirationTime = s.CheckAuthorization(staking.RedelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().NotNil(expirationTime) + + s.Require().Equal(authz.AuthorizationType, staking.RedelegateAuthz) + s.Require().Equal(authz.MaxTokens, &sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(1e18)}) + + // TODO: Bug here it returns 3 REDELEGATE authorizations + allAuthz, err := s.app.AuthzKeeper.GetAuthorizations(s.ctx, s.address.Bytes(), s.address.Bytes()) + s.Require().NoError(err) + s.Require().Len(allAuthz, 3) + }, + 20000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + var contract *vm.Contract + contract, s.ctx = testutil.NewPrecompileContract(s.T(), s.ctx, s.address, s.precompile, tc.gas) + + args := tc.malleate(contract) + bz, err := s.precompile.Approve(s.ctx, s.address, s.stateDB, &method, args) + + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + s.Require().Empty(bz) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz, args) + } + }) + } +} + +func (s *PrecompileTestSuite) TestDecreaseAllowance() { + method := s.precompile.Methods[authorization.DecreaseAllowanceMethod] + + testCases := []struct { + name string + malleate func(_ *vm.Contract) []interface{} + postCheck func(data []byte, inputArgs []interface{}) + gas uint64 + expError bool + errContains string + }{ + { + "fail - empty input args", + func(_ *vm.Contract) []interface{} { + return []interface{}{} + }, + func(data []byte, inputArgs []interface{}) {}, + 200000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 3, 0), + }, + //// TODO: enable this once we check origin is not the spender + // { + // "fail - origin address is the spender address", + // func(_ *vm.Contract) []interface{} { + // return []interface{}{ + // s.address, + // abi.MaxUint256, + // []string{staking.DelegateMsg}, + // } + // }, + // func(data []byte, inputArgs []interface{}) {}, + // 200000, + // true, + // "is the same as spender", + // }, + { + "fail - delegate authorization does not exists", + func(_ *vm.Contract) []interface{} { + return []interface{}{ + s.address, + big.NewInt(15000), + []string{staking.DelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + }, + 200000, + true, + "authorization to /cosmos.staking.v1beta1.MsgDelegate", + }, + { + "fail - delegate authorization is a generic Authorization", + func(_ *vm.Contract) []interface{} { + authz := sdkauthz.NewGenericAuthorization(staking.DelegateMsg) + exp := time.Now().Add(time.Hour) + err := s.app.AuthzKeeper.SaveGrant(s.ctx, s.address.Bytes(), s.address.Bytes(), authz, &exp) + s.Require().NoError(err) + return []interface{}{ + s.address, + big.NewInt(15000), + []string{staking.DelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + }, + 200000, + true, + sdkauthz.ErrUnknownAuthorizationType.Error(), + }, + { + "fail - decrease allowance amount is greater than the authorization limit", + func(contract *vm.Contract) []interface{} { + approveArgs := []interface{}{ + s.address, + big.NewInt(1e18), + []string{staking.DelegateMsg}, + } + resp, err := s.precompile.Approve(s.ctx, s.address, s.stateDB, &method, approveArgs) + s.Require().NoError(err) + s.Require().Equal(resp, cmn.TrueValue) + + authz, _ := s.CheckAuthorization(staking.DelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().Equal(authz.AuthorizationType, staking.DelegateAuthz) + s.Require().Equal(authz.MaxTokens, &sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(1e18)}) + + return []interface{}{ + s.address, + big.NewInt(2e18), + []string{staking.DelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) {}, + 200000, + true, + "amount by which the allowance should be decreased is greater than the authorization limit", + }, + { + "success - decrease delegate authorization allowance by 1 Evmos", + func(_ *vm.Contract) []interface{} { + s.ApproveAndCheckAuthz(method, staking.DelegateMsg, big.NewInt(2e18)) + return []interface{}{ + s.address, + big.NewInt(1e18), + []string{staking.DelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + authz, _ := s.CheckAuthorization(staking.DelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().Equal(authz.AuthorizationType, staking.DelegateAuthz) + s.Require().Equal(authz.MaxTokens, &sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(1e18)}) + }, + 200000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + + var contract *vm.Contract + contract, s.ctx = testutil.NewPrecompileContract(s.T(), s.ctx, s.address, s.precompile, tc.gas) + + args := tc.malleate(contract) + bz, err := s.precompile.DecreaseAllowance(s.ctx, s.address, s.stateDB, &method, args) + + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + s.Require().Empty(bz) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz, args) + } + }) + } +} + +func (s *PrecompileTestSuite) TestIncreaseAllowance() { + method := s.precompile.Methods[authorization.IncreaseAllowanceMethod] + + testCases := []struct { + name string + malleate func() []interface{} + postCheck func(data []byte, inputArgs []interface{}) + gas uint64 + expError bool + errContains string + }{ + { + "fail - empty input args", + func() []interface{} { + return []interface{}{} + }, + func(data []byte, inputArgs []interface{}) {}, + 200000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 3, 0), + }, + // TODO: enable this once we check origin is not the same as spender + // { + // "fail - origin address is the spender address", + // func(_ *vm.Contract) []interface{} { + // return []interface{}{ + // s.address, + // abi.MaxUint256, + // []string{staking.DelegateMsg}, + // } + // }, + // func(data []byte, inputArgs []interface{}) {}, + // 200000, + // true, + // "is the same as spender", + // }, + { + "fail - delegate authorization does not exists", + func() []interface{} { + return []interface{}{ + s.address, + big.NewInt(15000), + []string{staking.DelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + }, + 200000, + true, + "authorization to /cosmos.staking.v1beta1.MsgDelegate", + }, + { + "success - no-op, allowance amount is already set to the maximum value", + func() []interface{} { + approveArgs := []interface{}{ + s.address, + abi.MaxUint256, + []string{staking.DelegateMsg}, + } + resp, err := s.precompile.Approve(s.ctx, s.address, s.stateDB, &method, approveArgs) + s.Require().NoError(err) + s.Require().Equal(resp, cmn.TrueValue) + + authz, _ := s.CheckAuthorization(staking.DelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().Equal(authz.AuthorizationType, staking.DelegateAuthz) + var coin *sdk.Coin + s.Require().Equal(authz.MaxTokens, coin) + + return []interface{}{ + s.address, + big.NewInt(2e18), + []string{staking.DelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) {}, + 200000, + false, + "", + }, + { + "success - increase delegate authorization allowance by 1 Evmos", + func() []interface{} { + s.ApproveAndCheckAuthz(method, staking.DelegateMsg, big.NewInt(1e18)) + return []interface{}{ + s.address, + big.NewInt(1e18), + []string{staking.DelegateMsg}, + } + }, + func(data []byte, inputArgs []interface{}) { + authz, _ := s.CheckAuthorization(staking.DelegateAuthz, s.address, s.address) + s.Require().NotNil(authz) + s.Require().Equal(authz.AuthorizationType, staking.DelegateAuthz) + s.Require().Equal(authz.MaxTokens, &sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(2e18)}) + }, + 200000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + + args := tc.malleate() + bz, err := s.precompile.IncreaseAllowance(s.ctx, s.address, s.stateDB, &method, args) + + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + s.Require().Empty(bz) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz, args) + } + }) + } +} + +func (s *PrecompileTestSuite) TestRevoke() { + method := s.precompile.Methods[authorization.RevokeMethod] + granteeAddr := evmosutiltx.GenerateAddress() + granterAddr := s.address + createdAuthz := staking.DelegateAuthz + approvedCoin := &sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(1e18)} + + testCases := []struct { + name string + malleate func() []interface{} + postCheck func(data []byte, inputArgs []interface{}) + expError bool + errContains string + }{ + { + name: "fail - empty input args", + malleate: func() []interface{} { + return []interface{}{} + }, + expError: true, + errContains: fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 2, 0), + }, + { + name: "fail - authorization does not exist", + malleate: func() []interface{} { + return []interface{}{ + granteeAddr, + []string{staking.UndelegateMsg}, + } + }, + postCheck: func(data []byte, inputArgs []interface{}) { + // expect authorization to still be there + authz, _ := s.CheckAuthorization(createdAuthz, granteeAddr, granterAddr) + s.Require().NotNil(authz) + }, + expError: true, + errContains: "authorization not found", + }, + { + name: "pass - authorization revoked", + malleate: func() []interface{} { + return []interface{}{ + granteeAddr, + []string{staking.DelegateMsg}, + } + }, + postCheck: func(data []byte, inputArgs []interface{}) { + // expect authorization to be removed + authz, _ := s.CheckAuthorization(createdAuthz, granteeAddr, granterAddr) + s.Require().Nil(authz, "expected authorization to be removed") + }, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + s.SetupTest() // reset + + // Create a delegate authorization + validators := s.app.StakingKeeper.GetLastValidators(s.ctx) + valAddrs := make([]sdk.ValAddress, len(validators)) + for i, val := range validators { + valAddrs[i] = val.GetOperator() + } + delegationAuthz, err := stakingtypes.NewStakeAuthorization( + valAddrs, + nil, + createdAuthz, + approvedCoin, + ) + s.Require().NoError(err) + + expiration := s.ctx.BlockTime().Add(time.Hour * 24 * 365).UTC() + err = s.app.AuthzKeeper.SaveGrant(s.ctx, granteeAddr.Bytes(), granterAddr.Bytes(), delegationAuthz, &expiration) + s.Require().NoError(err, "failed to save authorization") + authz, _ := s.CheckAuthorization(createdAuthz, granteeAddr, granterAddr) + s.Require().NotNil(authz, "expected authorization to be set") + + args := tc.malleate() + bz, err := s.precompile.Revoke(s.ctx, granterAddr, s.stateDB, &method, args) + + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + s.Require().Empty(bz) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz, args) + } + }) + } +} diff --git a/precompiles/staking/errors.go b/precompiles/staking/errors.go new file mode 100644 index 00000000..e427b486 --- /dev/null +++ b/precompiles/staking/errors.go @@ -0,0 +1,13 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +package staking + +const ( + // ErrDecreaseAmountTooBig is raised when the amount by which the allowance should be decreased is greater + // than the authorization limit. + ErrDecreaseAmountTooBig = "amount by which the allowance should be decreased is greater than the authorization limit: %s > %s" + // ErrDifferentOriginFromDelegator is raised when the origin address is not the same as the delegator address. + ErrDifferentOriginFromDelegator = "origin address %s is not the same as delegator address %s" + // ErrNoDelegationFound is raised when no delegation is found for the given delegator and validator addresses. + ErrNoDelegationFound = "delegation with delegator %s not found for validator %s" +) diff --git a/precompiles/staking/events.go b/precompiles/staking/events.go new file mode 100644 index 00000000..ef90c39f --- /dev/null +++ b/precompiles/staking/events.go @@ -0,0 +1,313 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package staking + +import ( + "bytes" + "math/big" + "reflect" + + "github.com/evmos/evmos/v16/precompiles/authorization" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + cmn "github.com/evmos/evmos/v16/precompiles/common" +) + +const ( + // EventTypeCreateValidator defines the event type for the staking CreateValidator transaction. + EventTypeCreateValidator = "CreateValidator" + // EventTypeDelegate defines the event type for the staking Delegate transaction. + EventTypeDelegate = "Delegate" + // EventTypeUnbond defines the event type for the staking Undelegate transaction. + EventTypeUnbond = "Unbond" + // EventTypeRedelegate defines the event type for the staking Redelegate transaction. + EventTypeRedelegate = "Redelegate" + // EventTypeCancelUnbondingDelegation defines the event type for the staking CancelUnbondingDelegation transaction. + EventTypeCancelUnbondingDelegation = "CancelUnbondingDelegation" +) + +// EmitApprovalEvent creates a new approval event emitted on an Approve, IncreaseAllowance and DecreaseAllowance transactions. +func (p Precompile) EmitApprovalEvent(ctx sdk.Context, stateDB vm.StateDB, grantee, granter common.Address, coin *sdk.Coin, typeUrls []string) error { + // Prepare the event topics + event := p.ABI.Events[authorization.EventTypeApproval] + topics := make([]common.Hash, 3) + + // The first topic is always the signature of the event. + topics[0] = event.ID + + var err error + topics[1], err = cmn.MakeTopic(grantee) + if err != nil { + return err + } + + topics[2], err = cmn.MakeTopic(granter) + if err != nil { + return err + } + + // Check if the coin is set to infinite + value := abi.MaxUint256 + if coin != nil { + value = coin.Amount.BigInt() + } + + // Pack the arguments to be used as the Data field + arguments := abi.Arguments{event.Inputs[2], event.Inputs[3]} + packed, err := arguments.Pack(typeUrls, value) + if err != nil { + return err + } + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: packed, + BlockNumber: uint64(ctx.BlockHeight()), + }) + + return nil +} + +// EmitAllowanceChangeEvent creates a new allowance change event emitted on an IncreaseAllowance and DecreaseAllowance transactions. +func (p Precompile) EmitAllowanceChangeEvent(ctx sdk.Context, stateDB vm.StateDB, grantee, granter common.Address, typeUrls []string) error { + // Prepare the event topics + event := p.ABI.Events[authorization.EventTypeAllowanceChange] + topics := make([]common.Hash, 3) + + // The first topic is always the signature of the event. + topics[0] = event.ID + + var err error + topics[1], err = cmn.MakeTopic(grantee) + if err != nil { + return err + } + + topics[2], err = cmn.MakeTopic(granter) + if err != nil { + return err + } + + newValues := make([]*big.Int, len(typeUrls)) + for i, msgURL := range typeUrls { + // Not including expiration and convert check because we have already checked it in the previous call + msgAuthz, _ := p.AuthzKeeper.GetAuthorization(ctx, grantee.Bytes(), granter.Bytes(), msgURL) + stakeAuthz, _ := msgAuthz.(*stakingtypes.StakeAuthorization) + if stakeAuthz.MaxTokens == nil { + newValues[i] = abi.MaxUint256 + } else { + newValues[i] = stakeAuthz.MaxTokens.Amount.BigInt() + } + } + + // Pack the arguments to be used as the Data field + arguments := abi.Arguments{event.Inputs[2], event.Inputs[3]} + packed, err := arguments.Pack(typeUrls, newValues) + if err != nil { + return err + } + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: packed, + BlockNumber: uint64(ctx.BlockHeight()), + }) + + return nil +} + +// EmitCreateValidatorEvent creates a new create validator event emitted on a CreateValidator transaction. +func (p Precompile) EmitCreateValidatorEvent(ctx sdk.Context, stateDB vm.StateDB, msg *stakingtypes.MsgCreateValidator, delegatorAddr common.Address) error { + // Prepare the event topics + event := p.ABI.Events[EventTypeCreateValidator] + + valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) + if err != nil { + return err + } + + topics, err := p.createStakingTxTopics(3, event, delegatorAddr, common.BytesToAddress(valAddr.Bytes())) + if err != nil { + return err + } + + // Prepare the event data + var b bytes.Buffer + b.Write(cmn.PackNum(reflect.ValueOf(msg.Value.Amount.BigInt()))) + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: b.Bytes(), + BlockNumber: uint64(ctx.BlockHeight()), + }) + + return nil +} + +// EmitDelegateEvent creates a new delegate event emitted on a Delegate transaction. +func (p Precompile) EmitDelegateEvent(ctx sdk.Context, stateDB vm.StateDB, msg *stakingtypes.MsgDelegate, delegatorAddr common.Address) error { + valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) + if err != nil { + return err + } + + // Get the validator to estimate the new shares delegated + // NOTE: At this point the validator has already been checked, so no need to check again + validator, _ := p.stakingKeeper.GetValidator(ctx, valAddr) + + // Get only the new shares based on the delegation amount + newShares, err := validator.SharesFromTokens(msg.Amount.Amount) + if err != nil { + return err + } + + // Prepare the event topics + event := p.ABI.Events[EventTypeDelegate] + topics, err := p.createStakingTxTopics(3, event, delegatorAddr, common.BytesToAddress(valAddr.Bytes())) + if err != nil { + return err + } + + // Prepare the event data + var b bytes.Buffer + b.Write(cmn.PackNum(reflect.ValueOf(msg.Amount.Amount.BigInt()))) + b.Write(cmn.PackNum(reflect.ValueOf(newShares.BigInt()))) + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: b.Bytes(), + BlockNumber: uint64(ctx.BlockHeight()), + }) + + return nil +} + +// EmitUnbondEvent creates a new unbond event emitted on an Undelegate transaction. +func (p Precompile) EmitUnbondEvent(ctx sdk.Context, stateDB vm.StateDB, msg *stakingtypes.MsgUndelegate, delegatorAddr common.Address, completionTime int64) error { + valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) + if err != nil { + return err + } + + // Prepare the event topics + event := p.ABI.Events[EventTypeUnbond] + topics, err := p.createStakingTxTopics(3, event, delegatorAddr, common.BytesToAddress(valAddr.Bytes())) + if err != nil { + return err + } + + // Prepare the event data + var b bytes.Buffer + b.Write(cmn.PackNum(reflect.ValueOf(msg.Amount.Amount.BigInt()))) + b.Write(cmn.PackNum(reflect.ValueOf(big.NewInt(completionTime)))) + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: b.Bytes(), + BlockNumber: uint64(ctx.BlockHeight()), + }) + + return nil +} + +// EmitRedelegateEvent creates a new redelegate event emitted on a Redelegate transaction. +func (p Precompile) EmitRedelegateEvent(ctx sdk.Context, stateDB vm.StateDB, msg *stakingtypes.MsgBeginRedelegate, delegatorAddr common.Address, completionTime int64) error { + valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddress) + if err != nil { + return err + } + + valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddress) + if err != nil { + return err + } + + // Prepare the event topics + event := p.ABI.Events[EventTypeRedelegate] + topics, err := p.createStakingTxTopics(4, event, delegatorAddr, common.BytesToAddress(valSrcAddr.Bytes())) + if err != nil { + return err + } + + topics[3], err = cmn.MakeTopic(common.BytesToAddress(valDstAddr.Bytes())) + if err != nil { + return err + } + + // Prepare the event data + var b bytes.Buffer + b.Write(cmn.PackNum(reflect.ValueOf(msg.Amount.Amount.BigInt()))) + b.Write(cmn.PackNum(reflect.ValueOf(big.NewInt(completionTime)))) + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: b.Bytes(), + BlockNumber: uint64(ctx.BlockHeight()), + }) + + return nil +} + +// EmitCancelUnbondingDelegationEvent creates a new cancel unbonding delegation event emitted on a CancelUnbondingDelegation transaction. +func (p Precompile) EmitCancelUnbondingDelegationEvent(ctx sdk.Context, stateDB vm.StateDB, msg *stakingtypes.MsgCancelUnbondingDelegation, delegatorAddr common.Address) error { + valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) + if err != nil { + return err + } + + // Prepare the event topics + event := p.ABI.Events[EventTypeCancelUnbondingDelegation] + topics, err := p.createStakingTxTopics(3, event, delegatorAddr, common.BytesToAddress(valAddr.Bytes())) + if err != nil { + return err + } + + // Prepare the event data + var b bytes.Buffer + b.Write(cmn.PackNum(reflect.ValueOf(msg.Amount.Amount.BigInt()))) + b.Write(cmn.PackNum(reflect.ValueOf(big.NewInt(msg.CreationHeight)))) + + stateDB.AddLog(ðtypes.Log{ + Address: p.Address(), + Topics: topics, + Data: b.Bytes(), + BlockNumber: uint64(ctx.BlockHeight()), + }) + + return nil +} + +// createStakingTxTopics creates the topics for staking transactions CreateValidator, Delegate, Undelegate, Redelegate and CancelUnbondingDelegation. +func (p Precompile) createStakingTxTopics(topicsLen uint64, event abi.Event, delegatorAddr common.Address, validatorAddr common.Address) ([]common.Hash, error) { + topics := make([]common.Hash, topicsLen) + // NOTE: If your solidity event contains indexed event types, then they become a topic rather than part of the data property of the log. + // In solidity you may only have up to 4 topics but only 3 indexed event types. The first topic is always the signature of the event. + + // The first topic is always the signature of the event. + topics[0] = event.ID + + var err error + topics[1], err = cmn.MakeTopic(delegatorAddr) + if err != nil { + return nil, err + } + + topics[2], err = cmn.MakeTopic(validatorAddr) + if err != nil { + return nil, err + } + + return topics, nil +} diff --git a/precompiles/staking/events_test.go b/precompiles/staking/events_test.go new file mode 100644 index 00000000..7ab9c8f8 --- /dev/null +++ b/precompiles/staking/events_test.go @@ -0,0 +1,586 @@ +package staking_test + +import ( + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/evmos/evmos/v16/precompiles/authorization" + cmn "github.com/evmos/evmos/v16/precompiles/common" + "github.com/evmos/evmos/v16/precompiles/staking" +) + +func (s *PrecompileTestSuite) TestApprovalEvent() { + method := s.precompile.Methods[authorization.ApproveMethod] + testCases := []struct { + name string + malleate func() []interface{} + expErr bool + errContains string + postCheck func() + }{ + { + "success - all four methods are present in the emitted event", + func() []interface{} { + return []interface{}{ + s.address, + abi.MaxUint256, + []string{ + staking.DelegateMsg, + staking.UndelegateMsg, + staking.RedelegateMsg, + staking.CancelUnbondingDelegationMsg, + }, + } + }, + false, + "", + func() { + log := s.stateDB.Logs()[0] + s.Require().Equal(log.Address, s.precompile.Address()) + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[authorization.EventTypeApproval] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + var approvalEvent authorization.EventApproval + err := cmn.UnpackLog(s.precompile.ABI, &approvalEvent, authorization.EventTypeApproval, *log) + s.Require().NoError(err) + s.Require().Equal(s.address, approvalEvent.Grantee) + s.Require().Equal(s.address, approvalEvent.Granter) + s.Require().Equal(abi.MaxUint256, approvalEvent.Value) + s.Require().Equal(4, len(approvalEvent.Methods)) + s.Require().Equal(staking.DelegateMsg, approvalEvent.Methods[0]) + s.Require().Equal(staking.UndelegateMsg, approvalEvent.Methods[1]) + s.Require().Equal(staking.RedelegateMsg, approvalEvent.Methods[2]) + s.Require().Equal(staking.CancelUnbondingDelegationMsg, approvalEvent.Methods[3]) + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + + err := s.CreateAuthorization(s.address, staking.DelegateAuthz, nil) + s.Require().NoError(err) + + _, err = s.precompile.Approve(s.ctx, s.address, s.stateDB, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck() + } + }) + } +} + +func (s *PrecompileTestSuite) TestIncreaseAllowanceEvent() { + approvalMethod := s.precompile.Methods[authorization.ApproveMethod] + method := s.precompile.Methods[authorization.IncreaseAllowanceMethod] + testCases := []struct { + name string + malleate func() []interface{} + expErr bool + errContains string + postCheck func() + }{ + { + "success - increased allowance for all 3 methods by 1 evmos", + func() []interface{} { + return []interface{}{ + s.address, + big.NewInt(1000000000000000000), + []string{ + staking.DelegateMsg, + staking.UndelegateMsg, + staking.RedelegateMsg, + }, + } + }, + false, + "", + func() { + log := s.stateDB.Logs()[1] + methods := []string{ + staking.DelegateMsg, + staking.UndelegateMsg, + staking.RedelegateMsg, + } + amounts := []*big.Int{ + big.NewInt(2000000000000000000), + big.NewInt(2000000000000000000), + big.NewInt(2000000000000000000), + } + s.CheckAllowanceChangeEvent(log, methods, amounts) + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + + err := s.CreateAuthorization(s.address, staking.DelegateAuthz, nil) + s.Require().NoError(err) + + // Approve first with 1 evmos + _, err = s.precompile.Approve(s.ctx, s.address, s.stateDB, &approvalMethod, tc.malleate()) + s.Require().NoError(err) + + // Increase allowance after approval + _, err = s.precompile.IncreaseAllowance(s.ctx, s.address, s.stateDB, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck() + } + }) + } +} + +func (s *PrecompileTestSuite) TestDecreaseAllowanceEvent() { + approvalMethod := s.precompile.Methods[authorization.ApproveMethod] + method := s.precompile.Methods[authorization.DecreaseAllowanceMethod] + testCases := []struct { + name string + malleate func() []interface{} + expErr bool + errContains string + postCheck func() + }{ + { + "success - decreased allowance for all 3 methods by 1 evmos", + func() []interface{} { + return []interface{}{ + s.address, + big.NewInt(1000000000000000000), + []string{ + staking.DelegateMsg, + staking.UndelegateMsg, + staking.RedelegateMsg, + }, + } + }, + false, + "", + func() { + log := s.stateDB.Logs()[1] + methods := []string{ + staking.DelegateMsg, + staking.UndelegateMsg, + staking.RedelegateMsg, + } + amounts := []*big.Int{ + big.NewInt(1000000000000000000), + big.NewInt(1000000000000000000), + big.NewInt(1000000000000000000), + } + s.CheckAllowanceChangeEvent(log, methods, amounts) + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + + err := s.CreateAuthorization(s.address, staking.DelegateAuthz, nil) + s.Require().NoError(err) + + // Approve first with 2 evmos + args := []interface{}{ + s.address, + big.NewInt(2000000000000000000), + []string{ + staking.DelegateMsg, + staking.UndelegateMsg, + staking.RedelegateMsg, + }, + } + _, err = s.precompile.Approve(s.ctx, s.address, s.stateDB, &approvalMethod, args) + s.Require().NoError(err) + + // Decrease allowance after approval + _, err = s.precompile.DecreaseAllowance(s.ctx, s.address, s.stateDB, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck() + } + }) + } +} + +func (s *PrecompileTestSuite) TestCreateValidatorEvent() { + var ( + delegationValue = big.NewInt(1205000000000000000) + method = s.precompile.Methods[staking.CreateValidatorMethod] + operatorAddress = sdk.ValAddress(s.address.Bytes()).String() + pubkey = "nfJ0axJC9dhta1MAE1EBFaVdxxkYzxYrBaHuJVjG//M=" + ) + + testCases := []struct { + name string + malleate func() []interface{} + expErr bool + errContains string + postCheck func() + }{ + { + name: "success - the correct event is emitted", + malleate: func() []interface{} { + return []interface{}{ + staking.Description{ + Moniker: "node0", + Identity: "", + Website: "", + SecurityContact: "", + Details: "", + }, + staking.Commission{ + Rate: math.LegacyOneDec().BigInt(), + MaxRate: math.LegacyOneDec().BigInt(), + MaxChangeRate: math.LegacyOneDec().BigInt(), + }, + big.NewInt(1), + s.address, + operatorAddress, + pubkey, + delegationValue, + } + }, + postCheck: func() { + log := s.stateDB.Logs()[0] + s.Require().Equal(log.Address, s.precompile.Address()) + + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[staking.EventTypeCreateValidator] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + // Check the fully unpacked event matches the one emitted + var createValidatorEvent staking.EventCreateValidator + err := cmn.UnpackLog(s.precompile.ABI, &createValidatorEvent, staking.EventTypeCreateValidator, *log) + s.Require().NoError(err) + s.Require().Equal(s.address, createValidatorEvent.DelegatorAddress) + s.Require().Equal(s.address, createValidatorEvent.ValidatorAddress) + s.Require().Equal(delegationValue, createValidatorEvent.Value) + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + operatorAddress = sdk.ValAddress(s.address.Bytes()).String() + + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), 200000) + _, err := s.precompile.CreateValidator(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck() + } + }) + } +} + +func (s *PrecompileTestSuite) TestDelegateEvent() { + var ( + delegationAmt = big.NewInt(1500000000000000000) + newSharesExp = delegationAmt + method = s.precompile.Methods[staking.DelegateMethod] + ) + testCases := []struct { + name string + malleate func() []interface{} + expErr bool + errContains string + postCheck func() + }{ + { + "success - the correct event is emitted", + func() []interface{} { + return []interface{}{ + s.address, + s.validators[0].OperatorAddress, + delegationAmt, + } + }, + false, + "", + func() { + log := s.stateDB.Logs()[0] + s.Require().Equal(log.Address, s.precompile.Address()) + + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[staking.EventTypeDelegate] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + optAddr, err := sdk.ValAddressFromBech32(s.validators[0].OperatorAddress) + s.Require().NoError(err) + optHexAddr := common.BytesToAddress(optAddr) + + // Check the fully unpacked event matches the one emitted + var delegationEvent staking.EventDelegate + err = cmn.UnpackLog(s.precompile.ABI, &delegationEvent, staking.EventTypeDelegate, *log) + s.Require().NoError(err) + s.Require().Equal(s.address, delegationEvent.DelegatorAddress) + s.Require().Equal(optHexAddr, delegationEvent.ValidatorAddress) + s.Require().Equal(delegationAmt, delegationEvent.Amount) + s.Require().Equal(newSharesExp, delegationEvent.NewShares) + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + + err := s.CreateAuthorization(s.address, staking.DelegateAuthz, nil) + s.Require().NoError(err) + + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), 20000) + _, err = s.precompile.Delegate(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck() + } + }) + } +} + +func (s *PrecompileTestSuite) TestUnbondEvent() { + method := s.precompile.Methods[staking.UndelegateMethod] + + testCases := []struct { + name string + malleate func() []interface{} + expErr bool + errContains string + postCheck func() + }{ + { + "success - the correct event is emitted", + func() []interface{} { + return []interface{}{ + s.address, + s.validators[0].OperatorAddress, + big.NewInt(1000000000000000000), + } + }, + false, + "", + func() { + log := s.stateDB.Logs()[0] + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[staking.EventTypeUnbond] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + optAddr, err := sdk.ValAddressFromBech32(s.validators[0].OperatorAddress) + s.Require().NoError(err) + optHexAddr := common.BytesToAddress(optAddr) + + // Check the fully unpacked event matches the one emitted + var unbondEvent staking.EventUnbond + err = cmn.UnpackLog(s.precompile.ABI, &unbondEvent, staking.EventTypeUnbond, *log) + s.Require().NoError(err) + s.Require().Equal(s.address, unbondEvent.DelegatorAddress) + s.Require().Equal(optHexAddr, unbondEvent.ValidatorAddress) + s.Require().Equal(big.NewInt(1000000000000000000), unbondEvent.Amount) + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + + err := s.CreateAuthorization(s.address, staking.UndelegateAuthz, nil) + s.Require().NoError(err) + + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), 20000) + _, err = s.precompile.Undelegate(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + tc.postCheck() + } + }) + } +} + +func (s *PrecompileTestSuite) TestRedelegateEvent() { + method := s.precompile.Methods[staking.RedelegateMethod] + + testCases := []struct { + name string + malleate func() []interface{} + expErr bool + errContains string + postCheck func() + }{ + { + "success - the correct event is emitted", + func() []interface{} { + return []interface{}{ + s.address, + s.validators[0].OperatorAddress, + s.validators[1].OperatorAddress, + big.NewInt(1000000000000000000), + } + }, + false, + "", + func() { + log := s.stateDB.Logs()[0] + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[staking.EventTypeRedelegate] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + optSrcAddr, err := sdk.ValAddressFromBech32(s.validators[0].OperatorAddress) + s.Require().NoError(err) + optSrcHexAddr := common.BytesToAddress(optSrcAddr) + + optDstAddr, err := sdk.ValAddressFromBech32(s.validators[1].OperatorAddress) + s.Require().NoError(err) + optDstHexAddr := common.BytesToAddress(optDstAddr) + + var redelegateEvent staking.EventRedelegate + err = cmn.UnpackLog(s.precompile.ABI, &redelegateEvent, staking.EventTypeRedelegate, *log) + s.Require().NoError(err) + s.Require().Equal(s.address, redelegateEvent.DelegatorAddress) + s.Require().Equal(optSrcHexAddr, redelegateEvent.ValidatorSrcAddress) + s.Require().Equal(optDstHexAddr, redelegateEvent.ValidatorDstAddress) + s.Require().Equal(big.NewInt(1000000000000000000), redelegateEvent.Amount) + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + + err := s.CreateAuthorization(s.address, staking.RedelegateAuthz, nil) + s.Require().NoError(err) + + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), 20000) + _, err = s.precompile.Redelegate(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate()) + s.Require().NoError(err) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + tc.postCheck() + } + }) + } +} + +func (s *PrecompileTestSuite) TestCancelUnbondingDelegationEvent() { + methodCancelUnbonding := s.precompile.Methods[staking.CancelUnbondingDelegationMethod] + methodUndelegate := s.precompile.Methods[staking.UndelegateMethod] + + testCases := []struct { + name string + malleate func(contract *vm.Contract) []interface{} + expErr bool + errContains string + postCheck func() + }{ + { + "success - the correct event is emitted", + func(contract *vm.Contract) []interface{} { + err := s.CreateAuthorization(s.address, staking.UndelegateAuthz, nil) + s.Require().NoError(err) + undelegateArgs := []interface{}{ + s.address, + s.validators[0].OperatorAddress, + big.NewInt(1000000000000000000), + } + _, err = s.precompile.Undelegate(s.ctx, s.address, contract, s.stateDB, &methodUndelegate, undelegateArgs) + s.Require().NoError(err) + + return []interface{}{ + s.address, + s.validators[0].OperatorAddress, + big.NewInt(1000000000000000000), + big.NewInt(2), + } + }, + false, + "", + func() { + log := s.stateDB.Logs()[1] + + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[staking.EventTypeCancelUnbondingDelegation] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + optAddr, err := sdk.ValAddressFromBech32(s.validators[0].OperatorAddress) + s.Require().NoError(err) + optHexAddr := common.BytesToAddress(optAddr) + + // Check event fields match the ones emitted + var cancelUnbondEvent staking.EventCancelUnbonding + err = cmn.UnpackLog(s.precompile.ABI, &cancelUnbondEvent, staking.EventTypeCancelUnbondingDelegation, *log) + s.Require().NoError(err) + s.Require().Equal(s.address, cancelUnbondEvent.DelegatorAddress) + s.Require().Equal(optHexAddr, cancelUnbondEvent.ValidatorAddress) + s.Require().Equal(big.NewInt(1000000000000000000), cancelUnbondEvent.Amount) + s.Require().Equal(big.NewInt(2), cancelUnbondEvent.CreationHeight) + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + + err := s.CreateAuthorization(s.address, staking.CancelUnbondingDelegationAuthz, nil) + s.Require().NoError(err) + + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), 20000) + callArgs := tc.malleate(contract) + _, err = s.precompile.CancelUnbondingDelegation(s.ctx, s.address, contract, s.stateDB, &methodCancelUnbonding, callArgs) + s.Require().NoError(err) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + tc.postCheck() + } + }) + } +} diff --git a/precompiles/staking/integration_test.go b/precompiles/staking/integration_test.go new file mode 100644 index 00000000..c76b90f1 --- /dev/null +++ b/precompiles/staking/integration_test.go @@ -0,0 +1,2901 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +package staking_test + +import ( + "fmt" + "math/big" + "time" + + //nolint:revive // dot imports are fine for Ginkgo + . "github.com/onsi/ginkgo/v2" + //nolint:revive // dot imports are fine for Ginkgo + . "github.com/onsi/gomega" + + "cosmossdk.io/math" + 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" + "github.com/ethereum/go-ethereum/core/vm" + compiledcontracts "github.com/evmos/evmos/v16/contracts" + "github.com/evmos/evmos/v16/precompiles/authorization" + cmn "github.com/evmos/evmos/v16/precompiles/common" + "github.com/evmos/evmos/v16/precompiles/distribution" + "github.com/evmos/evmos/v16/precompiles/staking" + "github.com/evmos/evmos/v16/precompiles/staking/testdata" + "github.com/evmos/evmos/v16/precompiles/testutil" + "github.com/evmos/evmos/v16/precompiles/testutil/contracts" + evmosutil "github.com/evmos/evmos/v16/testutil" + testutiltx "github.com/evmos/evmos/v16/testutil/tx" +) + +// General variables used for integration tests +var ( + // valAddr and valAddr2 are the two validator addresses used for testing + valAddr, valAddr2 sdk.ValAddress + + // defaultCallArgs and defaultApproveArgs are the default arguments for calling the smart contract and to + // call the approve method specifically. + // + // NOTE: this has to be populated in a BeforeEach block because the contractAddr would otherwise be a nil address. + defaultCallArgs, defaultApproveArgs contracts.CallArgs + + // defaultLogCheck instantiates a log check arguments struct with the precompile ABI events populated. + defaultLogCheck testutil.LogCheckArgs + // passCheck defines the arguments to check if the precompile returns no error + passCheck testutil.LogCheckArgs + // outOfGasCheck defines the arguments to check if the precompile returns out of gas error + outOfGasCheck testutil.LogCheckArgs +) + +var _ = Describe("Calling staking precompile directly", func() { + var ( + // oneE18Coin is a sdk.Coin with an amount of 1e18 in the test suite's bonding denomination + oneE18Coin = sdk.NewCoin(s.bondDenom, math.NewInt(1e18)) + // twoE18Coin is a sdk.Coin with an amount of 2e18 in the test suite's bonding denomination + twoE18Coin = sdk.NewCoin(s.bondDenom, math.NewInt(2e18)) + ) + + BeforeEach(func() { + s.SetupTest() + s.NextBlock() + + valAddr = s.validators[0].GetOperator() + valAddr2 = s.validators[1].GetOperator() + + defaultCallArgs = contracts.CallArgs{ + ContractAddr: s.precompile.Address(), + ContractABI: s.precompile.ABI, + PrivKey: s.privKey, + } + defaultApproveArgs = defaultCallArgs.WithMethodName(authorization.ApproveMethod) + + defaultLogCheck = testutil.LogCheckArgs{ABIEvents: s.precompile.ABI.Events} + passCheck = defaultLogCheck.WithExpPass(true) + outOfGasCheck = defaultLogCheck.WithErrContains(vm.ErrOutOfGas.Error()) + }) + + Describe("when the precompile is not enabled in the EVM params", func() { + It("should return an error", func() { + // disable the precompile + params := s.app.EvmKeeper.GetParams(s.ctx) + var activePrecompiles []string + for _, precompile := range params.ActivePrecompiles { + if precompile != s.precompile.Address().String() { + activePrecompiles = append(activePrecompiles, precompile) + } + } + params.ActivePrecompiles = activePrecompiles + err := s.app.EvmKeeper.SetParams(s.ctx, params) + Expect(err).To(BeNil(), "error while setting params") + + // try to call the precompile + delegateArgs := defaultCallArgs. + WithMethodName(staking.DelegateMethod). + WithArgs( + s.address, valAddr.String(), big.NewInt(2e18), + ) + + failCheck := defaultLogCheck. + WithErrContains("precompile not enabled") + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, delegateArgs, failCheck) + Expect(err).To(HaveOccurred(), "expected error while calling the precompile") + Expect(err.Error()).To(ContainSubstring("precompile not enabled")) + }) + }) + + Describe("Revert transaction", func() { + It("should run out of gas if the gas limit is too low", func() { + outOfGasArgs := defaultApproveArgs. + WithGasLimit(30000). + WithArgs( + s.precompile.Address(), + abi.MaxUint256, + []string{staking.DelegateMsg}, + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, outOfGasArgs, outOfGasCheck) + Expect(err).To(HaveOccurred(), "error while calling precompile") + }) + }) + + Describe("Execute approve transaction", func() { + // TODO: enable once we check that the spender is not the origin + // It("should return error if the origin is the spender", func() { + // args := defaultApproveArgs.WithArgs( + // s.address, + // abi.MaxUint256, + // []string{staking.DelegateMsg}, + // ) + // + // differentOriginCheck := defaultLogCheck.WithErrContains(cmn.ErrDifferentOrigin, s.address, addr) + // + // _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, args, differentOriginCheck) + // Expect(err).To(BeNil(), "error while calling precompile") + // }) + + It("should return error if the staking method is not supported on the precompile", func() { + approveArgs := defaultApproveArgs.WithArgs( + s.precompile.Address(), abi.MaxUint256, []string{distribution.DelegationRewardsMethod}, + ) + + logCheckArgs := defaultLogCheck.WithErrContains( + cmn.ErrInvalidMsgType, "staking", distribution.DelegationRewardsMethod, + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, approveArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the contract and checking logs") + }) + + It("should approve the delegate method with the max uint256 value", func() { + s.SetupApproval( + s.privKey, s.precompile.Address(), abi.MaxUint256, []string{staking.DelegateMsg}, + ) + + s.ExpectAuthorization(staking.DelegateAuthz, s.precompile.Address(), s.address, nil) + }) + + It("should approve the undelegate method with 1 evmos", func() { + s.SetupApproval( + s.privKey, s.precompile.Address(), big.NewInt(1e18), []string{staking.UndelegateMsg}, + ) + + s.ExpectAuthorization(staking.UndelegateAuthz, s.precompile.Address(), s.address, &oneE18Coin) + }) + + It("should approve the redelegate method with 2 evmos", func() { + s.SetupApproval( + s.privKey, s.precompile.Address(), big.NewInt(2e18), []string{staking.RedelegateMsg}, + ) + + s.ExpectAuthorization(staking.RedelegateAuthz, s.precompile.Address(), s.address, &twoE18Coin) + }) + + It("should approve the cancel unbonding delegation method with 1 evmos", func() { + s.SetupApproval( + s.privKey, s.precompile.Address(), big.NewInt(1e18), []string{staking.CancelUnbondingDelegationMsg}, + ) + + s.ExpectAuthorization(staking.CancelUnbondingDelegationAuthz, s.precompile.Address(), s.address, &oneE18Coin) + }) + }) + + Describe("Execute increase allowance transaction", func() { + // defaultIncreaseArgs are the default arguments to call the increase allowance method. + // + // NOTE: this has to be populated in BeforeEach, because the private key is not initialized outside of it. + var defaultIncreaseArgs contracts.CallArgs + + BeforeEach(func() { + s.SetupApproval( + s.privKey, s.precompile.Address(), big.NewInt(1e18), []string{staking.DelegateMsg}, + ) + + defaultIncreaseArgs = defaultCallArgs.WithMethodName(authorization.IncreaseAllowanceMethod) + }) + + // TODO: enable once we check that the spender is not the origin + // It("should return error if the origin is the spender", func() { + // increaseArgs := defaultCallArgs. + // WithMethodName(authorization.IncreaseAllowanceMethod). + // WithArgs( + // s.address, big.NewInt(1e18), []string{staking.DelegateMsg}, + // ) + // + // _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, increaseArgs, differentOriginCheck) + // Expect(err).To(BeNil(), "error while calling the contract and checking logs") + // }) + + It("Should increase the allowance of the delegate method with 1 evmos", func() { + increaseArgs := defaultCallArgs. + WithMethodName(authorization.IncreaseAllowanceMethod). + WithArgs( + s.precompile.Address(), big.NewInt(1e18), []string{staking.DelegateMsg}, + ) + + logCheckArgs := passCheck.WithExpEvents(authorization.EventTypeAllowanceChange) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, increaseArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the contract and checking logs") + + s.ExpectAuthorization(staking.DelegateAuthz, s.precompile.Address(), s.address, &twoE18Coin) + }) + + It("should return error if the allowance to increase does not exist", func() { + increaseArgs := defaultIncreaseArgs.WithArgs( + s.precompile.Address(), big.NewInt(1e18), []string{staking.UndelegateMsg}, + ) + + logCheckArgs := defaultLogCheck.WithErrContains( + "does not exist", + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, increaseArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the contract and checking logs") + Expect(err.Error()).To(ContainSubstring("does not exist")) + + authz, _ := s.CheckAuthorization(staking.UndelegateAuthz, s.precompile.Address(), s.address) + Expect(authz).To(BeNil(), "expected authorization to not be set") + }) + }) + + Describe("Execute decrease allowance transaction", func() { + // defaultDecreaseArgs are the default arguments to call the decrease allowance method. + // + // NOTE: this has to be populated in BeforeEach, because the private key is not initialized outside of it. + var defaultDecreaseArgs contracts.CallArgs + + BeforeEach(func() { + s.SetupApproval( + s.privKey, s.precompile.Address(), big.NewInt(2e18), []string{staking.DelegateMsg}, + ) + + defaultDecreaseArgs = defaultCallArgs.WithMethodName(authorization.DecreaseAllowanceMethod) + }) + + // TODO: enable once we check that the spender is not the origin + // It("should return error if the origin is the spender", func() { + // addr, _ := testutiltx.NewAddrKey() + // decreaseArgs := defaultDecreaseArgs.WithArgs( + // s.precompile.Address(), big.NewInt(1e18), []string{staking.DelegateMsg}, + // ) + // + // logCheckArgs := defaultLogCheck.WithErrContains( + // cmn.ErrDifferentOrigin, s.address, addr, + // ) + // + // _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, decreaseArgs, logCheckArgs) + // Expect(err).To(BeNil(), "error while calling the contract and checking logs") + // }) + + It("Should decrease the allowance of the delegate method with 1 evmos", func() { + decreaseArgs := defaultDecreaseArgs.WithArgs( + s.precompile.Address(), big.NewInt(1e18), []string{staking.DelegateMsg}, + ) + + logCheckArgs := passCheck.WithExpEvents(authorization.EventTypeAllowanceChange) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, decreaseArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the contract and checking logs") + + s.ExpectAuthorization(staking.DelegateAuthz, s.precompile.Address(), s.address, &oneE18Coin) + }) + + It("should return error if the allowance to decrease does not exist", func() { + decreaseArgs := defaultDecreaseArgs.WithArgs( + s.precompile.Address(), big.NewInt(1e18), []string{staking.UndelegateMsg}, + ) + + logCheckArgs := defaultLogCheck.WithErrContains( + "does not exist", + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, decreaseArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the contract and checking logs") + Expect(err.Error()).To(ContainSubstring("does not exist")) + + authz, _ := s.CheckAuthorization(staking.UndelegateAuthz, s.precompile.Address(), s.address) + Expect(authz).To(BeNil(), "expected authorization to not be set") + }) + }) + + Describe("to revoke an approval", func() { + var ( + // defaultRevokeArgs are the default arguments to call the revoke method. + // + // NOTE: this has to be populated in BeforeEach, because the default call args are not initialized outside of it. + defaultRevokeArgs contracts.CallArgs + + // granteeAddr is the address of the grantee used in the revocation tests. + granteeAddr = testutiltx.GenerateAddress() + ) + + BeforeEach(func() { + defaultRevokeArgs = defaultCallArgs.WithMethodName(authorization.RevokeMethod) + }) + + It("should revoke the approval when executing as the granter", func() { + typeURLs := []string{staking.DelegateMsg} + + s.SetupApproval( + s.privKey, granteeAddr, abi.MaxUint256, typeURLs, + ) + s.ExpectAuthorization(staking.DelegateAuthz, granteeAddr, s.address, nil) + + revokeArgs := defaultRevokeArgs.WithArgs( + granteeAddr, typeURLs, + ) + + revocationCheck := passCheck.WithExpEvents(authorization.EventTypeRevocation) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, revokeArgs, revocationCheck) + Expect(err).To(BeNil(), "error while calling the contract and checking logs") + + // check that the authorization is revoked + authz, _ := s.CheckAuthorization(staking.DelegateAuthz, granteeAddr, s.address) + Expect(authz).To(BeNil(), "expected authorization to be revoked") + }) + + It("should not revoke the approval when trying to revoke for a different message type", func() { + typeURLs := []string{staking.DelegateMsg} + + s.SetupApproval( + s.privKey, granteeAddr, abi.MaxUint256, typeURLs, + ) + s.ExpectAuthorization(staking.DelegateAuthz, granteeAddr, s.address, nil) + + revokeArgs := defaultRevokeArgs.WithArgs( + granteeAddr, []string{staking.UndelegateMsg}, + ) + + notFoundCheck := defaultLogCheck. + WithErrContains("failed to delete grant") + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, revokeArgs, notFoundCheck) + Expect(err).To(HaveOccurred(), "error while calling the contract and checking logs") + + // the authorization should still be there. + s.ExpectAuthorization(staking.DelegateAuthz, granteeAddr, s.address, nil) + }) + + It("should return error if the approval does not exist", func() { + revokeArgs := defaultRevokeArgs.WithArgs( + s.address, []string{staking.DelegateMsg}, + ) + + notFoundCheck := defaultLogCheck. + WithErrContains("failed to delete grant") + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, revokeArgs, notFoundCheck) + Expect(err).To(HaveOccurred(), "error while calling the contract and checking logs") + }) + + It("should not revoke the approval if sent by someone else than the granter", func() { + typeURLs := []string{staking.DelegateMsg} + + // set up an approval with a different key than the one used to sign the transaction. + differentAddr, differentPriv := testutiltx.NewAddrKey() + err := evmosutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, differentAddr.Bytes(), 1e18) + Expect(err).To(BeNil(), "error while funding account") + + s.NextBlock() + s.SetupApproval( + differentPriv, granteeAddr, abi.MaxUint256, typeURLs, + ) + s.ExpectAuthorization(staking.DelegateAuthz, granteeAddr, differentAddr, nil) + + revokeArgs := defaultRevokeArgs.WithArgs( + differentAddr, typeURLs, + ) + + notFoundCheck := defaultLogCheck. + WithErrContains("failed to delete grant") + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, revokeArgs, notFoundCheck) + Expect(err).To(HaveOccurred(), "error while calling the contract and checking logs") + + // the authorization should still be set + s.ExpectAuthorization(staking.DelegateAuthz, granteeAddr, differentAddr, nil) + }) + }) + + Describe("to delegate", func() { + var ( + // prevDelegation is the delegation that is available prior to the test (an initial delegation is + // added in the test suite setup). + prevDelegation stakingtypes.Delegation + // defaultDelegateArgs are the default arguments for the delegate call + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + defaultDelegateArgs contracts.CallArgs + ) + + BeforeEach(func() { + // get the delegation that is available prior to the test + prevDelegation, _ = s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), valAddr) + + // populate the default delegate args + defaultDelegateArgs = defaultCallArgs.WithMethodName(staking.DelegateMethod) + }) + + Context("as the token owner", func() { + It("should delegate without need for authorization", func() { + delegateArgs := defaultDelegateArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(2e18), + ) + + logCheckArgs := passCheck.WithExpEvents(staking.EventTypeDelegate) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delegateArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + delegation, found := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), valAddr) + Expect(found).To(BeTrue(), "expected delegation to be found") + expShares := prevDelegation.GetShares().Add(math.LegacyNewDec(2)) + Expect(delegation.GetShares()).To(Equal(expShares), "expected different delegation shares") + }) + + It("should not delegate if the account has no sufficient balance", func() { + // send funds away from account to only have target balance remaining + balance := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + targetBalance := math.NewInt(1e17) + sentBalance := balance.Amount.Sub(targetBalance) + newAddr, _ := testutiltx.NewAccAddressAndKey() + err := s.app.BankKeeper.SendCoins(s.ctx, s.address.Bytes(), newAddr, + sdk.Coins{sdk.Coin{Denom: s.bondDenom, Amount: sentBalance}}) + Expect(err).To(BeNil(), "error while sending coins") + + // try to delegate more than left in account + delegateArgs := defaultDelegateArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(1e18), + ) + + logCheckArgs := defaultLogCheck.WithErrContains("insufficient funds") + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, delegateArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + Expect(err.Error()).To(ContainSubstring("insufficient funds")) + }) + + It("should not delegate if the validator does not exist", func() { + nonExistingAddr := testutiltx.GenerateAddress() + nonExistingValAddr := sdk.ValAddress(nonExistingAddr.Bytes()) + + delegateArgs := defaultDelegateArgs.WithArgs( + s.address, nonExistingValAddr.String(), big.NewInt(2e18), + ) + + logCheckArgs := defaultLogCheck.WithErrContains("validator does not exist") + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delegateArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + Expect(err.Error()).To(ContainSubstring("validator does not exist")) + }) + }) + + Context("on behalf of another account", func() { + It("should not delegate if delegator address is not the origin", func() { + differentAddr := testutiltx.GenerateAddress() + + delegateArgs := defaultDelegateArgs.WithArgs( + differentAddr, valAddr.String(), big.NewInt(2e18), + ) + + logCheckArgs := defaultLogCheck.WithErrContains( + fmt.Sprintf(staking.ErrDifferentOriginFromDelegator, s.address, differentAddr), + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delegateArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + }) + }) + }) + + Describe("to undelegate", func() { + // defaultUndelegateArgs are the default arguments for the undelegate call + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + var defaultUndelegateArgs contracts.CallArgs + + BeforeEach(func() { + defaultUndelegateArgs = defaultCallArgs.WithMethodName(staking.UndelegateMethod) + }) + + Context("as the token owner", func() { + It("should undelegate without need for authorization", func() { + undelegations := s.app.StakingKeeper.GetUnbondingDelegationsFromValidator(s.ctx, s.validators[0].GetOperator()) + Expect(undelegations).To(HaveLen(0), "expected no unbonding delegations before test") + + undelegateArgs := defaultUndelegateArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(1e18), + ) + + logCheckArgs := passCheck.WithExpEvents(staking.EventTypeUnbond) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + undelegations = s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(undelegations).To(HaveLen(1), "expected one undelegation") + Expect(undelegations[0].ValidatorAddress).To(Equal(valAddr.String()), "expected validator address to be %s", valAddr) + }) + + It("should not undelegate if the amount exceeds the delegation", func() { + undelegateArgs := defaultUndelegateArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(2e18), + ) + + logCheckArgs := defaultLogCheck.WithErrContains("invalid shares amount") + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + Expect(err.Error()).To(ContainSubstring("invalid shares amount")) + }) + + It("should not undelegate if the validator does not exist", func() { + nonExistingAddr := testutiltx.GenerateAddress() + nonExistingValAddr := sdk.ValAddress(nonExistingAddr.Bytes()) + + undelegateArgs := defaultUndelegateArgs.WithArgs( + s.address, nonExistingValAddr.String(), big.NewInt(1e18), + ) + + logCheckArgs := defaultLogCheck.WithErrContains("validator does not exist") + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + Expect(err.Error()).To(ContainSubstring("validator does not exist")) + }) + }) + + Context("on behalf of another account", func() { + It("should not undelegate if delegator address is not the origin", func() { + differentAddr := testutiltx.GenerateAddress() + + undelegateArgs := defaultUndelegateArgs.WithArgs( + differentAddr, valAddr.String(), big.NewInt(1e18), + ) + + logCheckArgs := defaultLogCheck.WithErrContains( + fmt.Sprintf(staking.ErrDifferentOriginFromDelegator, s.address, differentAddr), + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + }) + }) + }) + + Describe("to redelegate", func() { + // defaultRedelegateArgs are the default arguments for the redelegate call + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + var defaultRedelegateArgs contracts.CallArgs + + BeforeEach(func() { + defaultRedelegateArgs = defaultCallArgs.WithMethodName(staking.RedelegateMethod) + }) + + Context("as the token owner", func() { + It("should redelegate without need for authorization", func() { + redelegateArgs := defaultRedelegateArgs.WithArgs( + s.address, valAddr.String(), valAddr2.String(), big.NewInt(1e18), + ) + + logCheckArgs := passCheck. + WithExpEvents(staking.EventTypeRedelegate) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + redelegations := s.app.StakingKeeper.GetAllRedelegations(s.ctx, s.address.Bytes(), valAddr, valAddr2) + Expect(redelegations).To(HaveLen(1), "expected one redelegation to be found") + bech32Addr := sdk.AccAddress(s.address.Bytes()) + Expect(redelegations[0].DelegatorAddress).To(Equal(bech32Addr.String()), "expected delegator address to be %s", s.address) + Expect(redelegations[0].ValidatorSrcAddress).To(Equal(valAddr.String()), "expected source validator address to be %s", valAddr) + Expect(redelegations[0].ValidatorDstAddress).To(Equal(valAddr2.String()), "expected destination validator address to be %s", valAddr2) + }) + + It("should not redelegate if the amount exceeds the delegation", func() { + redelegateArgs := defaultRedelegateArgs.WithArgs( + s.address, valAddr.String(), valAddr2.String(), big.NewInt(2e18), + ) + + logCheckArgs := defaultLogCheck.WithErrContains("invalid shares amount") + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + Expect(err.Error()).To(ContainSubstring("invalid shares amount")) + }) + + It("should not redelegate if the validator does not exist", func() { + nonExistingAddr := testutiltx.GenerateAddress() + nonExistingValAddr := sdk.ValAddress(nonExistingAddr.Bytes()) + + redelegateArgs := defaultRedelegateArgs.WithArgs( + s.address, valAddr.String(), nonExistingValAddr.String(), big.NewInt(1e18), + ) + + logCheckArgs := defaultLogCheck.WithErrContains("redelegation destination validator not found") + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + Expect(err.Error()).To(ContainSubstring("redelegation destination validator not found")) + }) + }) + + Context("on behalf of another account", func() { + It("should not redelegate if delegator address is not the origin", func() { + differentAddr := testutiltx.GenerateAddress() + + redelegateArgs := defaultRedelegateArgs.WithArgs( + differentAddr, valAddr.String(), valAddr2.String(), big.NewInt(1e18), + ) + + logCheckArgs := defaultLogCheck.WithErrContains( + fmt.Sprintf(staking.ErrDifferentOriginFromDelegator, s.address, differentAddr), + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + }) + }) + }) + + Describe("to cancel an unbonding delegation", func() { + var ( + // defaultCancelUnbondingArgs are the default arguments for the cancelUnbondingDelegation call + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + defaultCancelUnbondingArgs contracts.CallArgs + + // expCreationHeight is the expected creation height of the unbonding delegation + expCreationHeight = int64(3) + ) + + BeforeEach(func() { + defaultCancelUnbondingArgs = defaultCallArgs.WithMethodName(staking.CancelUnbondingDelegationMethod) + + // Set up an unbonding delegation + undelegateArgs := defaultCallArgs. + WithMethodName(staking.UndelegateMethod). + WithArgs(s.address, valAddr.String(), big.NewInt(1e18)) + + logCheckArgs := passCheck. + WithExpEvents(staking.EventTypeUnbond) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while setting up an unbonding delegation: %v", err) + + s.NextBlock() + + // Check that the unbonding delegation was created + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(1), "expected one unbonding delegation to be found") + Expect(unbondingDelegations[0].DelegatorAddress).To(Equal(sdk.AccAddress(s.address.Bytes()).String()), "expected delegator address to be %s", s.address) + Expect(unbondingDelegations[0].ValidatorAddress).To(Equal(valAddr.String()), "expected validator address to be %s", valAddr) + Expect(unbondingDelegations[0].Entries).To(HaveLen(1), "expected one unbonding delegation entry to be found") + Expect(unbondingDelegations[0].Entries[0].CreationHeight).To(Equal(expCreationHeight), "expected different creation height") + Expect(unbondingDelegations[0].Entries[0].Balance).To(Equal(math.NewInt(1e18)), "expected different balance") + }) + + Context("as the token owner", func() { + It("should cancel unbonding delegation", func() { + delegations := s.app.StakingKeeper.GetValidatorDelegations(s.ctx, s.validators[0].GetOperator()) + Expect(delegations).To(HaveLen(0)) + + cArgs := defaultCancelUnbondingArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(1e18), big.NewInt(expCreationHeight), + ) + + logCheckArgs := passCheck. + WithExpEvents(staking.EventTypeCancelUnbondingDelegation) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, cArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(0), "expected unbonding delegation to be canceled") + + delegations = s.app.StakingKeeper.GetValidatorDelegations(s.ctx, s.validators[0].GetOperator()) + Expect(delegations).To(HaveLen(1), "expected one delegation to be found") + }) + + It("should not cancel an unbonding delegation if the amount is not correct", func() { + cArgs := defaultCancelUnbondingArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(2e18), big.NewInt(expCreationHeight), + ) + + logCheckArgs := defaultLogCheck.WithErrContains("amount is greater than the unbonding delegation entry balance") + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, cArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + Expect(err.Error()).To(ContainSubstring("amount is greater than the unbonding delegation entry balance")) + + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(1), "expected unbonding delegation not to have been canceled") + }) + + It("should not cancel an unbonding delegation if the creation height is not correct", func() { + cArgs := defaultCancelUnbondingArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(1e18), big.NewInt(expCreationHeight+1), + ) + + logCheckArgs := defaultLogCheck.WithErrContains("unbonding delegation entry is not found at block height") + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, cArgs, logCheckArgs) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + Expect(err.Error()).To(ContainSubstring("unbonding delegation entry is not found at block height")) + + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(1), "expected unbonding delegation not to have been canceled") + }) + }) + }) + + Describe("to query allowance", func() { + var ( + defaultAllowanceArgs contracts.CallArgs + + differentAddr = testutiltx.GenerateAddress() + ) + + BeforeEach(func() { + defaultAllowanceArgs = defaultCallArgs.WithMethodName(authorization.AllowanceMethod) + }) + + It("should return an empty allowance if none is set", func() { + allowanceArgs := defaultAllowanceArgs.WithArgs( + s.address, differentAddr, staking.CancelUnbondingDelegationMsg, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, allowanceArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var allowanceInt *big.Int + err = s.precompile.UnpackIntoInterface(&allowanceInt, "allowance", ethRes.Ret) + Expect(err).To(BeNil(), "error while unmarshalling the allowance: %v", err) + Expect(allowanceInt.Int64()).To(BeZero(), "expected allowance to be zero") + }) + + It("should return the granted allowance if set", func() { + // setup approval for another address + s.SetupApproval( + s.privKey, differentAddr, big.NewInt(1e18), []string{staking.CancelUnbondingDelegationMsg}, + ) + + // query allowance + allowanceArgs := defaultAllowanceArgs.WithArgs( + differentAddr, s.address, staking.CancelUnbondingDelegationMsg, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, allowanceArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var allowanceInt *big.Int + err = s.precompile.UnpackIntoInterface(&allowanceInt, "allowance", ethRes.Ret) + Expect(err).To(BeNil(), "error while unmarshalling the allowance: %v", err) + Expect(allowanceInt).To(Equal(big.NewInt(1e18)), "expected allowance to be 1e18") + }) + }) + + Describe("Validator queries", func() { + // defaultValidatorArgs are the default arguments for the validator call + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + var defaultValidatorArgs contracts.CallArgs + + BeforeEach(func() { + defaultValidatorArgs = defaultCallArgs.WithMethodName(staking.ValidatorMethod) + }) + + It("should return validator", func() { + validatorArgs := defaultValidatorArgs.WithArgs( + valAddr.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var valOut staking.ValidatorOutput + err = s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + Expect(valOut.Validator.OperatorAddress).To(Equal(valAddr.String()), "expected validator address to match") + Expect(valOut.Validator.DelegatorShares).To(Equal(big.NewInt(1e18)), "expected different delegator shares") + }) + + It("should return an empty validator if the validator is not found", func() { + newValAddr := sdk.ValAddress(testutiltx.GenerateAddress().Bytes()) + validatorArgs := defaultValidatorArgs.WithArgs( + newValAddr.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var valOut staking.ValidatorOutput + err = s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + Expect(valOut.Validator.OperatorAddress).To(Equal(""), "expected validator address to be empty") + Expect(valOut.Validator.Status).To(BeZero(), "expected unspecified bonding status") + }) + }) + + Describe("Validators queries", func() { + var defaultValidatorArgs contracts.CallArgs + + BeforeEach(func() { + defaultValidatorArgs = defaultCallArgs.WithMethodName(staking.ValidatorsMethod) + }) + + It("should return validators (default pagination)", func() { + validatorArgs := defaultValidatorArgs.WithArgs( + stakingtypes.Bonded.String(), + query.PageRequest{}, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var valOut staking.ValidatorsOutput + err = s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorsMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + + Expect(valOut.PageResponse.NextKey).To(BeEmpty()) + Expect(valOut.PageResponse.Total).To(Equal(uint64(len(s.validators)))) + + Expect(valOut.Validators).To(HaveLen(len(s.validators)), "expected two validators to be returned") + // return order can change, that's why each validator is checked individually + for _, val := range valOut.Validators { + s.CheckValidatorOutput(val) + } + }) + + //nolint:dupl // this is a duplicate of the test for smart contract calls to the precompile + It("should return validators w/pagination limit = 1", func() { + const limit uint64 = 1 + validatorArgs := defaultValidatorArgs.WithArgs( + stakingtypes.Bonded.String(), + query.PageRequest{ + Limit: limit, + CountTotal: true, + }, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var valOut staking.ValidatorsOutput + err = s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorsMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + + // no pagination, should return default values + Expect(valOut.PageResponse.NextKey).NotTo(BeEmpty()) + Expect(valOut.PageResponse.Total).To(Equal(uint64(len(s.validators)))) + + Expect(valOut.Validators).To(HaveLen(int(limit)), "expected one validator to be returned") + + // return order can change, that's why each validator is checked individually + for _, val := range valOut.Validators { + s.CheckValidatorOutput(val) + } + }) + + It("should return an error if the bonding type is not known", func() { + validatorArgs := defaultValidatorArgs.WithArgs( + "15", // invalid bonding type + query.PageRequest{}, + ) + + invalidStatusCheck := defaultLogCheck.WithErrContains("invalid validator status 15") + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, invalidStatusCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + Expect(err.Error()).To(ContainSubstring("invalid validator status 15")) + }) + + It("should return an empty array if there are no validators with the given bonding type", func() { + validatorArgs := defaultValidatorArgs.WithArgs( + stakingtypes.Unbonded.String(), + query.PageRequest{}, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var valOut staking.ValidatorsOutput + err = s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorsMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + + Expect(valOut.PageResponse.NextKey).To(BeEmpty()) + Expect(valOut.PageResponse.Total).To(Equal(uint64(0))) + Expect(valOut.Validators).To(HaveLen(0), "expected no validators to be returned") + }) + }) + + Describe("Delegation queries", func() { + var defaultDelegationArgs contracts.CallArgs + + BeforeEach(func() { + defaultDelegationArgs = defaultCallArgs.WithMethodName(staking.DelegationMethod) + }) + + It("should return a delegation if it is found", func() { + delegationArgs := defaultDelegationArgs.WithArgs( + s.address, + valAddr.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delegationArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var delOut staking.DelegationOutput + err = s.precompile.UnpackIntoInterface(&delOut, staking.DelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the delegation output: %v", err) + Expect(delOut.Shares).To(Equal(big.NewInt(1e18)), "expected different shares") + Expect(delOut.Balance).To(Equal(cmn.Coin{Denom: s.bondDenom, Amount: big.NewInt(1e18)}), "expected different shares") + }) + + It("should return an empty delegation if it is not found", func() { + newValAddr := sdk.ValAddress(testutiltx.GenerateAddress().Bytes()) + delegationArgs := defaultDelegationArgs.WithArgs( + s.address, newValAddr.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delegationArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var delOut staking.DelegationOutput + err = s.precompile.UnpackIntoInterface(&delOut, staking.DelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the delegation output: %v", err) + Expect(delOut.Shares.Int64()).To(BeZero(), "expected no shares") + Expect(delOut.Balance.Denom).To(Equal(s.bondDenom), "expected different denomination") + Expect(delOut.Balance.Amount.Int64()).To(BeZero(), "expected a zero amount") + }) + }) + + Describe("UnbondingDelegation queries", func() { + var ( + defaultUnbondingDelegationArgs contracts.CallArgs + + // undelAmount is the amount of tokens to be unbonded + undelAmount = big.NewInt(1e17) + ) + + BeforeEach(func() { + defaultUnbondingDelegationArgs = defaultCallArgs.WithMethodName(staking.UnbondingDelegationMethod) + + // unbond a delegation + s.SetupApproval(s.privKey, s.precompile.Address(), abi.MaxUint256, []string{staking.UndelegateMsg}) + + unbondArgs := defaultCallArgs. + WithMethodName(staking.UndelegateMethod). + WithArgs(s.address, valAddr.String(), undelAmount) + unbondCheck := passCheck.WithExpEvents(staking.EventTypeUnbond) + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, unbondArgs, unbondCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + // check that the unbonding delegation exists + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(1), "expected one unbonding delegation") + }) + + It("should return an unbonding delegation if it is found", func() { + unbondingDelegationsArgs := defaultUnbondingDelegationArgs.WithArgs( + s.address, + valAddr.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, unbondingDelegationsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var unbondingDelegationOutput staking.UnbondingDelegationOutput + err = s.precompile.UnpackIntoInterface(&unbondingDelegationOutput, staking.UnbondingDelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the unbonding delegation output: %v", err) + Expect(unbondingDelegationOutput.UnbondingDelegation.Entries).To(HaveLen(1), "expected one unbonding delegation entry") + // TODO: why are initial balance and balance the same always? + Expect(unbondingDelegationOutput.UnbondingDelegation.Entries[0].InitialBalance).To(Equal(undelAmount), "expected different initial balance") + Expect(unbondingDelegationOutput.UnbondingDelegation.Entries[0].Balance).To(Equal(undelAmount), "expected different balance") + }) + + It("should return an empty slice if the unbonding delegation is not found", func() { + unbondingDelegationsArgs := defaultUnbondingDelegationArgs.WithArgs( + s.address, + valAddr2.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, unbondingDelegationsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var unbondingDelegationOutput staking.UnbondingDelegationOutput + err = s.precompile.UnpackIntoInterface(&unbondingDelegationOutput, staking.UnbondingDelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the unbonding delegation output: %v", err) + Expect(unbondingDelegationOutput.UnbondingDelegation.Entries).To(HaveLen(0), "expected one unbonding delegation entry") + }) + }) + + Describe("to query a redelegation", func() { + var defaultRedelegationArgs contracts.CallArgs + + BeforeEach(func() { + defaultRedelegationArgs = defaultCallArgs.WithMethodName(staking.RedelegationMethod) + }) + + It("should return the redelegation if it exists", func() { + // approve the redelegation + s.SetupApproval(s.privKey, s.precompile.Address(), abi.MaxUint256, []string{staking.RedelegateMsg}) + + // create a redelegation + redelegateArgs := defaultCallArgs. + WithMethodName(staking.RedelegateMethod). + WithArgs(s.address, valAddr.String(), valAddr2.String(), big.NewInt(1e17)) + + redelegateCheck := passCheck.WithExpEvents(staking.EventTypeRedelegate) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, redelegateCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + // query the redelegation + redelegationArgs := defaultRedelegationArgs.WithArgs( + s.address, + valAddr.String(), + valAddr2.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegationArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var redelegationOutput staking.RedelegationOutput + err = s.precompile.UnpackIntoInterface(&redelegationOutput, staking.RedelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the redelegation output: %v", err) + Expect(redelegationOutput.Redelegation.Entries).To(HaveLen(1), "expected one redelegation entry") + Expect(redelegationOutput.Redelegation.Entries[0].InitialBalance).To(Equal(big.NewInt(1e17)), "expected different initial balance") + Expect(redelegationOutput.Redelegation.Entries[0].SharesDst).To(Equal(big.NewInt(1e17)), "expected different balance") + }) + + It("should return an empty output if the redelegation is not found", func() { + redelegationArgs := defaultRedelegationArgs.WithArgs( + s.address, + valAddr.String(), + valAddr2.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegationArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var redelegationOutput staking.RedelegationOutput + err = s.precompile.UnpackIntoInterface(&redelegationOutput, staking.RedelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the redelegation output: %v", err) + Expect(redelegationOutput.Redelegation.Entries).To(HaveLen(0), "expected no redelegation entries") + }) + }) + + Describe("Redelegations queries", func() { + var ( + // defaultRedelegationsArgs are the default arguments for the redelegations query + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + defaultRedelegationsArgs contracts.CallArgs + + // delAmt is the amount of tokens to be delegated + delAmt = big.NewInt(3e17) + // redelTotalCount is the total number of redelegations + redelTotalCount uint64 = 1 + ) + + BeforeEach(func() { + defaultRedelegationsArgs = defaultCallArgs.WithMethodName(staking.RedelegationsMethod) + // create some redelegations + s.SetupApproval( + s.privKey, s.precompile.Address(), abi.MaxUint256, []string{staking.RedelegateMsg}, + ) + + defaultRedelegateArgs := defaultCallArgs.WithMethodName(staking.RedelegateMethod) + redelegationsArgs := []contracts.CallArgs{ + defaultRedelegateArgs.WithArgs( + s.address, valAddr.String(), valAddr2.String(), delAmt, + ), + defaultRedelegateArgs.WithArgs( + s.address, valAddr.String(), valAddr2.String(), delAmt, + ), + } + + logCheckArgs := passCheck. + WithExpEvents(staking.EventTypeRedelegate) + + for _, args := range redelegationsArgs { + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, args, logCheckArgs) + Expect(err).To(BeNil(), "error while creating redelegation: %v", err) + } + }) + + It("should return all redelegations for delegator (default pagination)", func() { + redelegationArgs := defaultRedelegationsArgs.WithArgs( + s.address, + "", + "", + query.PageRequest{}, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegationArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var redelOut staking.RedelegationsOutput + err = s.precompile.UnpackIntoInterface(&redelOut, staking.RedelegationsMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + + Expect(redelOut.PageResponse.NextKey).To(BeEmpty()) + Expect(redelOut.PageResponse.Total).To(Equal(redelTotalCount)) + + Expect(redelOut.Response).To(HaveLen(int(redelTotalCount)), "expected two redelegations to be returned") + // return order can change + redOrder := []int{0, 1} + if len(redelOut.Response[0].Entries) == 2 { + redOrder = []int{1, 0} + } + + for i, r := range redelOut.Response { + Expect(r.Entries).To(HaveLen(redOrder[i] + 1)) + } + }) + + It("should return all redelegations for delegator w/pagination", func() { + // make 2 queries + // 1st one with pagination limit = 1 + // 2nd using the next page key + var nextPageKey []byte + for i := 0; i < 2; i++ { + var pagination query.PageRequest + if nextPageKey == nil { + pagination.Limit = 1 + pagination.CountTotal = true + } else { + pagination.Key = nextPageKey + } + redelegationArgs := defaultRedelegationsArgs.WithArgs( + s.address, + "", + "", + pagination, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegationArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var redelOut staking.RedelegationsOutput + err = s.precompile.UnpackIntoInterface(&redelOut, staking.RedelegationsMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + + if nextPageKey == nil { + nextPageKey = redelOut.PageResponse.NextKey + Expect(redelOut.PageResponse.Total).To(Equal(redelTotalCount)) + } else { + Expect(redelOut.PageResponse.NextKey).To(BeEmpty()) + Expect(redelOut.PageResponse.Total).To(Equal(uint64(1))) + } + + Expect(redelOut.Response).To(HaveLen(1), "expected two redelegations to be returned") + // return order can change + redOrder := []int{0, 1} + if len(redelOut.Response[0].Entries) == 2 { + redOrder = []int{1, 0} + } + + for i, r := range redelOut.Response { + Expect(r.Entries).To(HaveLen(redOrder[i] + 1)) + } + } + }) + + It("should return an empty array if no redelegation is found for the given source validator", func() { + // NOTE: the way that the functionality is implemented in the Cosmos SDK, the following combinations are + // possible (see https://github.com/evmos/cosmos-sdk/blob/e773cf768844c87245d0c737cda1893a2819dd89/x/staking/keeper/querier.go#L361-L373): + // + // - delegator is NOT empty, source validator is empty, destination validator is empty + // --> filtering for all redelegations of the given delegator + // - delegator is empty, source validator is NOT empty, destination validator is empty + // --> filtering for all redelegations with the given source validator + // - delegator is NOT empty, source validator is NOT empty, destination validator is NOT empty + // --> filtering for all redelegations with the given combination of delegator, source and destination validator + redelegationsArgs := defaultRedelegationsArgs.WithArgs( + common.Address{}, // passing in an empty address to filter for all redelegations from valAddr2 + valAddr2.String(), + "", + query.PageRequest{}, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegationsArgs, passCheck) + Expect(err).To(BeNil(), "expected error while calling the smart contract") + + var redelOut staking.RedelegationsOutput + err = s.precompile.UnpackIntoInterface(&redelOut, staking.RedelegationsMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + + Expect(redelOut.PageResponse.NextKey).To(BeEmpty()) + Expect(redelOut.PageResponse.Total).To(BeZero(), "expected no redelegations to be returned") + + Expect(redelOut.Response).To(HaveLen(0), "expected no redelegations to be returned") + }) + }) + + It("Should refund leftover gas", func() { + balancePre := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + gasPrice := big.NewInt(1e9) + + // Call the precompile with a lot of gas + approveArgs := defaultApproveArgs. + WithGasPrice(gasPrice). + WithArgs(s.precompile.Address(), big.NewInt(1e18), []string{staking.DelegateMsg}) + + approvalCheck := passCheck.WithExpEvents(authorization.EventTypeApproval) + + res, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, approveArgs, approvalCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + s.NextBlock() + + balancePost := s.app.BankKeeper.GetBalance(s.ctx, s.address.Bytes(), s.bondDenom) + difference := balancePre.Sub(balancePost) + + // NOTE: the expected difference is the gas price multiplied by the gas used, because the rest should be refunded + expDifference := gasPrice.Int64() * res.GasUsed + Expect(difference.Amount.Int64()).To(Equal(expDifference), "expected different total transaction cost") + }) +}) + +var _ = Describe("Calling staking precompile via Solidity", func() { + var ( + // contractAddr is the address of the smart contract that will be deployed + contractAddr common.Address + + // approvalCheck is a configuration for the log checker to see if an approval event was emitted. + approvalCheck testutil.LogCheckArgs + // execRevertedCheck defines the default log checking arguments which include the + // standard revert message + execRevertedCheck testutil.LogCheckArgs + // err is a basic error type + err error + + // nonExistingAddr is an address that does not exist in the state of the test suite + nonExistingAddr = testutiltx.GenerateAddress() + // nonExistingVal is a validator address that does not exist in the state of the test suite + nonExistingVal = sdk.ValAddress(nonExistingAddr.Bytes()) + ) + + BeforeEach(func() { + s.SetupTest() + contractAddr, err = s.DeployContract(testdata.StakingCallerContract) + Expect(err).To(BeNil(), "error while deploying the smart contract: %v", err) + valAddr = s.validators[0].GetOperator() + valAddr2 = s.validators[1].GetOperator() + + s.NextBlock() + + // check contract was correctly deployed + cAcc := s.app.EvmKeeper.GetAccount(s.ctx, contractAddr) + Expect(cAcc).ToNot(BeNil(), "contract account should exist") + Expect(cAcc.IsContract()).To(BeTrue(), "account should be a contract") + + // populate default call args + defaultCallArgs = contracts.CallArgs{ + ContractAddr: contractAddr, + ContractABI: testdata.StakingCallerContract.ABI, + PrivKey: s.privKey, + } + // populate default approval args + defaultApproveArgs = defaultCallArgs.WithMethodName("testApprove") + + // populate default log check args + defaultLogCheck = testutil.LogCheckArgs{ + ABIEvents: s.precompile.Events, + } + execRevertedCheck = defaultLogCheck.WithErrContains(vm.ErrExecutionReverted.Error()) + passCheck = defaultLogCheck.WithExpPass(true) + approvalCheck = passCheck.WithExpEvents(authorization.EventTypeApproval) + }) + + Describe("when the precompile is not enabled in the EVM params", func() { + It("should return an error", func() { + // disable the precompile + params := s.app.EvmKeeper.GetParams(s.ctx) + var activePrecompiles []string + for _, precompile := range params.ActivePrecompiles { + if precompile != s.precompile.Address().String() { + activePrecompiles = append(activePrecompiles, precompile) + } + } + params.ActivePrecompiles = activePrecompiles + err := s.app.EvmKeeper.SetParams(s.ctx, params) + Expect(err).To(BeNil(), "error while setting params") + + // try to call the precompile + delegateArgs := defaultCallArgs. + WithMethodName("testDelegate"). + WithArgs( + s.address, valAddr.String(), big.NewInt(2e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, delegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "expected error while calling the precompile") + Expect(err.Error()).To(ContainSubstring(vm.ErrExecutionReverted.Error())) + }) + }) + + Context("approving methods", func() { + Context("with valid input", func() { + It("should approve one method", func() { + approvalArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.DelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(approvalArgs) + }) + + It("should approve all methods", func() { + approvalArgs := defaultApproveArgs. + WithGasLimit(1e8). + WithArgs( + contractAddr, + []string{staking.DelegateMsg, staking.RedelegateMsg, staking.UndelegateMsg, staking.CancelUnbondingDelegationMsg}, + big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(approvalArgs) + }) + + It("should update a previous approval", func() { + approvalArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.DelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(approvalArgs) + + s.NextBlock() + + // update approval + approvalArgs = defaultApproveArgs.WithArgs( + contractAddr, []string{staking.DelegateMsg}, big.NewInt(2e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, approvalArgs, approvalCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + // check approvals + authorization, expirationTime := s.CheckAuthorization(staking.DelegateAuthz, contractAddr, s.address) + Expect(authorization).ToNot(BeNil(), "expected authorization to not be nil") + Expect(expirationTime).ToNot(BeNil(), "expected expiration time to not be nil") + Expect(authorization.MsgTypeURL()).To(Equal(staking.DelegateMsg), "expected authorization msg type url to be %s", staking.DelegateMsg) + Expect(authorization.MaxTokens.Amount).To(Equal(math.NewInt(2e18)), "expected different max tokens after updated approval") + }) + + It("should remove approval when setting amount to zero", func() { + s.SetupApprovalWithContractCalls( + defaultApproveArgs.WithArgs(contractAddr, []string{staking.DelegateMsg}, big.NewInt(1e18)), + ) + + s.NextBlock() + + // check approvals pre-removal + allAuthz, err := s.app.AuthzKeeper.GetAuthorizations(s.ctx, contractAddr.Bytes(), s.address.Bytes()) + Expect(err).To(BeNil(), "error while reading authorizations") + Expect(allAuthz).To(HaveLen(1), "expected no authorizations") + + approveArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.DelegateMsg}, big.NewInt(0), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, approveArgs, approvalCheck) + Expect(err).To(BeNil(), "error while calling the smart contract") + + // check approvals after approving with amount 0 + allAuthz, err = s.app.AuthzKeeper.GetAuthorizations(s.ctx, contractAddr.Bytes(), s.address.Bytes()) + Expect(err).To(BeNil(), "error while reading authorizations") + Expect(allAuthz).To(HaveLen(0), "expected no authorizations") + }) + + It("should not approve if the gas is not enough", func() { + approveArgs := defaultApproveArgs. + WithGasLimit(1e5). + WithArgs( + contractAddr, + []string{ + staking.DelegateMsg, + staking.UndelegateMsg, + staking.RedelegateMsg, + staking.CancelUnbondingDelegationMsg, + }, + big.NewInt(1e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, approveArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract") + }) + }) + + Context("with invalid input", func() { + // TODO: enable once we check that origin is not the sender + // It("shouldn't approve any methods for if the sender is the origin", func() { + // approveArgs := defaultApproveArgs.WithArgs( + // nonExistingAddr, []string{staking.DelegateMsg}, big.NewInt(1e18), + // ) + // + // _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, approveArgs, execRevertedCheck) + // Expect(err).To(BeNil(), "error while calling the smart contract") + // + // // check approvals + // allAuthz, err := s.app.AuthzKeeper.GetAuthorizations(s.ctx, contractAddr.Bytes(), s.address.Bytes()) + // Expect(err).To(BeNil(), "error while reading authorizations") + // Expect(allAuthz).To(HaveLen(0), "expected no authorizations") + // }) + + It("shouldn't approve for invalid methods", func() { + approveArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{"invalid method"}, big.NewInt(1e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, approveArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract") + + // check approvals + allAuthz, err := s.app.AuthzKeeper.GetAuthorizations(s.ctx, contractAddr.Bytes(), s.address.Bytes()) + Expect(err).To(BeNil(), "error while reading authorizations") + Expect(allAuthz).To(HaveLen(0), "expected no authorizations") + }) + }) + }) + + Context("to revoke an approval", func() { + var defaultRevokeArgs contracts.CallArgs + + BeforeEach(func() { + defaultRevokeArgs = defaultCallArgs.WithMethodName("testRevoke") + }) + + It("should revoke when sending as the granter", func() { + // set up an approval to be revoked + cArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.DelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(cArgs) + + s.NextBlock() + + revokeArgs := defaultRevokeArgs.WithArgs(contractAddr, []string{staking.DelegateMsg}) + + revocationCheck := passCheck.WithExpEvents(authorization.EventTypeRevocation) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, revokeArgs, revocationCheck) + Expect(err).To(BeNil(), "error while calling the smart contract") + + // check approvals + authz, _ := s.CheckAuthorization(staking.DelegateAuthz, contractAddr, s.address) + Expect(authz).To(BeNil(), "expected authorization to be revoked") + }) + + It("should not revoke when approval is issued by a different granter", func() { + // Create a delegate authorization where the granter is a different account from the default test suite one + createdAuthz := staking.DelegateAuthz + granteeAddr := testutiltx.GenerateAddress() + granterAddr := testutiltx.GenerateAddress() + validators := s.app.StakingKeeper.GetLastValidators(s.ctx) + valAddrs := make([]sdk.ValAddress, len(validators)) + for i, val := range validators { + valAddrs[i] = val.GetOperator() + } + delegationAuthz, err := stakingtypes.NewStakeAuthorization( + valAddrs, + nil, + createdAuthz, + &sdk.Coin{Denom: sdk.DefaultBondDenom, Amount: math.NewInt(1e18)}, + ) + Expect(err).To(BeNil(), "failed to create authorization") + + expiration := s.ctx.BlockTime().Add(time.Hour * 24 * 365).UTC() + err = s.app.AuthzKeeper.SaveGrant(s.ctx, granteeAddr.Bytes(), granterAddr.Bytes(), delegationAuthz, &expiration) + Expect(err).ToNot(HaveOccurred(), "failed to save authorization") + authz, _ := s.CheckAuthorization(createdAuthz, granteeAddr, granterAddr) + Expect(authz).ToNot(BeNil(), "expected authorization to be created") + + revokeArgs := defaultRevokeArgs.WithArgs(granteeAddr, []string{staking.DelegateMsg}) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, revokeArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract") + + // check approvals + authz, _ = s.CheckAuthorization(createdAuthz, granteeAddr, granterAddr) + Expect(authz).ToNot(BeNil(), "expected authorization not to be revoked") + }) + + It("should revert the execution when no approval is found", func() { + revokeArgs := defaultRevokeArgs.WithArgs(contractAddr, []string{staking.DelegateMsg}) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, revokeArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract") + + // check approvals + authz, _ := s.CheckAuthorization(staking.DelegateAuthz, contractAddr, s.address) + Expect(authz).To(BeNil(), "expected no authorization to be found") + }) + + It("should not revoke if the approval is for a different message type", func() { + // set up an approval + cArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.DelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(cArgs) + + s.NextBlock() + + revokeArgs := defaultRevokeArgs.WithArgs(contractAddr, []string{staking.UndelegateMsg}) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, revokeArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract") + + // check approval is still there + s.ExpectAuthorization( + staking.DelegateAuthz, + contractAddr, + s.address, + &sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(1e18)}, + ) + }) + }) + + Context("delegating", func() { + var ( + // prevDelegation is the delegation that is available prior to the test (an initial delegation is + // added in the test suite setup). + prevDelegation stakingtypes.Delegation + // defaultDelegateArgs are the default arguments for the delegate call + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + defaultDelegateArgs contracts.CallArgs + ) + + BeforeEach(func() { + // get the delegation that is available prior to the test + prevDelegation, _ = s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), valAddr) + + defaultDelegateArgs = defaultCallArgs.WithMethodName("testDelegate") + }) + + Context("without approval set", func() { + BeforeEach(func() { + authz, _ := s.CheckAuthorization(staking.DelegateAuthz, contractAddr, s.address) + Expect(authz).To(BeNil(), "expected authorization to be nil") + }) + + It("should not delegate", func() { + Expect(s.app.EvmKeeper.GetAccount(s.ctx, contractAddr)).ToNot(BeNil(), "expected contract to exist") + + cArgs := defaultDelegateArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(1e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, cArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + del, _ := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), valAddr) + Expect(del).To(Equal(prevDelegation), "no new delegation to be found") + }) + }) + + Context("with approval set", func() { + BeforeEach(func() { + cArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.DelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(cArgs) + }) + + It("should delegate when not exceeding the allowance", func() { + cArgs := defaultDelegateArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(1e18), + ) + + logCheckArgs := passCheck. + WithExpEvents(staking.EventTypeDelegate) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, cArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + delegation, found := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), valAddr) + Expect(found).To(BeTrue(), "expected delegation to be found") + expShares := prevDelegation.GetShares().Add(math.LegacyNewDec(1)) + Expect(delegation.GetShares()).To(Equal(expShares), "expected delegation shares to be 2") + }) + + It("should not delegate when exceeding the allowance", func() { + cArgs := defaultDelegateArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(2e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, cArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + del, _ := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), valAddr) + Expect(del).To(Equal(prevDelegation), "no new delegation to be found") + }) + + It("should not delegate when sending from a different address", func() { + newAddr, newPriv := testutiltx.NewAccAddressAndKey() + err := evmosutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, newAddr, 1e18) + Expect(err).To(BeNil(), "error while funding account: %v", err) + + s.NextBlock() + + delegateArgs := defaultDelegateArgs. + WithPrivKey(newPriv). + WithArgs(s.address, valAddr.String(), big.NewInt(1e18)) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, delegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + del, _ := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), valAddr) + Expect(del).To(Equal(prevDelegation), "no new delegation to be found") + }) + + It("should not delegate when validator does not exist", func() { + delegateArgs := defaultDelegateArgs.WithArgs( + s.address, nonExistingVal.String(), big.NewInt(1e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, delegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + del, _ := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), nonExistingVal) + Expect(del).To(BeZero(), "expected no delegation to be found") + }) + + It("shouldn't delegate to a validator that is not in the allow list of the approval", func() { + // create a new validator, which is not included in the active set of the last block + testutil.CreateValidator(s.ctx, s.T(), s.privKey.PubKey(), s.app.StakingKeeper, math.NewInt(100)) + newValAddr := sdk.ValAddress(s.address.Bytes()) + + delegateArgs := defaultDelegateArgs.WithArgs( + s.address, newValAddr.String(), big.NewInt(2e18), + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + delegation, _ := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), newValAddr) + Expect(delegation.GetShares()).To(Equal(math.LegacyNewDecFromInt(math.NewInt(100))), "expected only the delegation from creating the validator, no more") + }) + }) + }) + + Context("unbonding", func() { + // NOTE: there's no additional setup necessary because the test suite is already set up with + // delegations to the validator + + // defaultUndelegateArgs are the default arguments for the undelegate call + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + var defaultUndelegateArgs contracts.CallArgs + + BeforeEach(func() { + defaultUndelegateArgs = defaultCallArgs.WithMethodName("testUndelegate") + }) + + Context("without approval set", func() { + BeforeEach(func() { + authz, _ := s.CheckAuthorization(staking.UndelegateAuthz, contractAddr, s.address) + Expect(authz).To(BeNil(), "expected authorization to be nil before test execution") + }) + It("should not undelegate", func() { + undelegateArgs := defaultUndelegateArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(1e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + undelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(undelegations).To(HaveLen(0), "expected no undelegations to be found") + }) + }) + + Context("with approval set", func() { + BeforeEach(func() { + approveArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.UndelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(approveArgs) + }) + + It("should undelegate when not exceeding the allowance", func() { + undelegateArgs := defaultUndelegateArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(1e18), + ) + + logCheckArgs := defaultLogCheck. + WithExpEvents(staking.EventTypeUnbond). + WithExpPass(true) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + undelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(undelegations).To(HaveLen(1), "expected one undelegation") + Expect(undelegations[0].ValidatorAddress).To(Equal(valAddr.String()), "expected validator address to be %s", valAddr) + }) + + It("should not undelegate when exceeding the allowance", func() { + undelegateArgs := defaultUndelegateArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(2e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + undelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(undelegations).To(HaveLen(0), "expected no undelegations to be found") + }) + + It("should not undelegate if the delegation does not exist", func() { + undelegateArgs := defaultUndelegateArgs.WithArgs( + s.address, nonExistingVal.String(), big.NewInt(1e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + undelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(undelegations).To(HaveLen(0), "expected no undelegations to be found") + }) + + It("should not undelegate when called from a different address", func() { + newAddr, newPriv := testutiltx.NewAccAddressAndKey() + err := evmosutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, newAddr, 1e18) + Expect(err).To(BeNil(), "error while funding account: %v", err) + + s.NextBlock() + + undelegateArgs := defaultUndelegateArgs. + WithPrivKey(newPriv). + WithArgs(s.address, valAddr.String(), big.NewInt(1e18)) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + undelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(undelegations).To(HaveLen(0), "expected no undelegations to be found") + }) + }) + }) + + Context("redelegating", func() { + // NOTE: there's no additional setup necessary because the test suite is already set up with + // delegations to the validator + + // defaultRedelegateArgs are the default arguments for the redelegate call + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + var defaultRedelegateArgs contracts.CallArgs + + BeforeEach(func() { + defaultRedelegateArgs = defaultCallArgs.WithMethodName("testRedelegate") + }) + + Context("without approval set", func() { + BeforeEach(func() { + authz, _ := s.CheckAuthorization(staking.UndelegateAuthz, contractAddr, s.address) + Expect(authz).To(BeNil(), "expected authorization to be nil before test execution") + }) + + It("should not redelegate", func() { + redelegateArgs := defaultRedelegateArgs.WithArgs( + s.address, valAddr.String(), valAddr2.String(), big.NewInt(1e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + redelegations := s.app.StakingKeeper.GetAllRedelegations(s.ctx, s.address.Bytes(), valAddr, valAddr2) + Expect(redelegations).To(HaveLen(0), "expected no redelegations to be found") + }) + }) + + Context("with approval set", func() { + BeforeEach(func() { + approveArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.RedelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(approveArgs) + }) + + It("should redelegate when not exceeding the allowance", func() { + redelegateArgs := defaultRedelegateArgs.WithArgs( + s.address, valAddr.String(), valAddr2.String(), big.NewInt(1e18), + ) + + logCheckArgs := defaultLogCheck. + WithExpEvents(staking.EventTypeRedelegate). + WithExpPass(true) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + redelegations := s.app.StakingKeeper.GetAllRedelegations(s.ctx, s.address.Bytes(), valAddr, valAddr2) + Expect(redelegations).To(HaveLen(1), "expected one redelegation to be found") + bech32Addr := sdk.AccAddress(s.address.Bytes()) + Expect(redelegations[0].DelegatorAddress).To(Equal(bech32Addr.String()), "expected delegator address to be %s", s.address) + Expect(redelegations[0].ValidatorSrcAddress).To(Equal(valAddr.String()), "expected source validator address to be %s", valAddr) + Expect(redelegations[0].ValidatorDstAddress).To(Equal(valAddr2.String()), "expected destination validator address to be %s", valAddr2) + }) + + It("should not redelegate when exceeding the allowance", func() { + redelegateArgs := defaultRedelegateArgs.WithArgs( + s.address, valAddr.String(), valAddr2.String(), big.NewInt(2e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + redelegations := s.app.StakingKeeper.GetAllRedelegations(s.ctx, s.address.Bytes(), valAddr, valAddr2) + Expect(redelegations).To(HaveLen(0), "expected no redelegations to be found") + }) + + It("should not redelegate if the delegation does not exist", func() { + redelegateArgs := defaultRedelegateArgs.WithArgs( + s.address, nonExistingVal.String(), valAddr2.String(), big.NewInt(1e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + redelegations := s.app.StakingKeeper.GetAllRedelegations(s.ctx, s.address.Bytes(), nonExistingVal, valAddr2) + Expect(redelegations).To(HaveLen(0), "expected no redelegations to be found") + }) + + It("should not redelegate when calling from a different address", func() { + newAddr, newPriv := testutiltx.NewAccAddressAndKey() + err := evmosutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, newAddr, 1e18) + Expect(err).To(BeNil(), "error while funding account: %v", err) + + s.NextBlock() + + redelegateArgs := defaultRedelegateArgs. + WithPrivKey(newPriv). + WithArgs(s.address, valAddr.String(), valAddr2.String(), big.NewInt(1e18)) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + redelegations := s.app.StakingKeeper.GetAllRedelegations(s.ctx, s.address.Bytes(), valAddr, valAddr2) + Expect(redelegations).To(HaveLen(0), "expected no redelegations to be found") + }) + + It("should not redelegate when the validator does not exist", func() { + redelegateArgs := defaultRedelegateArgs.WithArgs( + s.address, valAddr.String(), nonExistingVal.String(), big.NewInt(1e18), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + redelegations := s.app.StakingKeeper.GetAllRedelegations(s.ctx, s.address.Bytes(), valAddr, nonExistingVal) + Expect(redelegations).To(HaveLen(0), "expected no redelegations to be found") + }) + }) + }) + + Context("canceling unbonding delegations", func() { + var ( + // defaultCancelUnbondingArgs are the default arguments for the cancelUnbondingDelegation call + // + // NOTE: this has to be set up in the BeforeEach block because the private key is only available then + defaultCancelUnbondingArgs contracts.CallArgs + + // expCreationHeight is the expected creation height of the unbonding delegation + expCreationHeight = int64(4) + ) + + BeforeEach(func() { + defaultCancelUnbondingArgs = defaultCallArgs.WithMethodName("testCancelUnbonding") + + // Set up an unbonding delegation + approvalArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.UndelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(approvalArgs) + + s.NextBlock() + + undelegateArgs := defaultCallArgs. + WithMethodName("testUndelegate"). + WithArgs(s.address, valAddr.String(), big.NewInt(1e18)) + + logCheckArgs := defaultLogCheck. + WithExpEvents(staking.EventTypeUnbond). + WithExpPass(true) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while setting up an unbonding delegation: %v", err) + + s.NextBlock() + + // Check that the unbonding delegation was created + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(1), "expected one unbonding delegation to be found") + Expect(unbondingDelegations[0].DelegatorAddress).To(Equal(sdk.AccAddress(s.address.Bytes()).String()), "expected delegator address to be %s", s.address) + Expect(unbondingDelegations[0].ValidatorAddress).To(Equal(valAddr.String()), "expected validator address to be %s", valAddr) + Expect(unbondingDelegations[0].Entries).To(HaveLen(1), "expected one unbonding delegation entry to be found") + Expect(unbondingDelegations[0].Entries[0].CreationHeight).To(Equal(expCreationHeight), "expected different creation height") + Expect(unbondingDelegations[0].Entries[0].Balance).To(Equal(math.NewInt(1e18)), "expected different balance") + }) + + Context("without approval set", func() { + It("should not cancel unbonding delegations", func() { + cArgs := defaultCancelUnbondingArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(1e18), big.NewInt(expCreationHeight), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, cArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(1), "expected unbonding delegation not to be canceled") + }) + }) + + Context("with approval set", func() { + BeforeEach(func() { + // Set up an unbonding delegation + approvalArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.CancelUnbondingDelegationMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(approvalArgs) + + s.NextBlock() + }) + + It("should cancel unbonding delegations when not exceeding allowance", func() { + cArgs := defaultCancelUnbondingArgs.WithGasLimit(1e9).WithArgs( + s.address, valAddr.String(), big.NewInt(1e18), big.NewInt(expCreationHeight), + ) + + logCheckArgs := passCheck. + WithExpEvents(staking.EventTypeCancelUnbondingDelegation) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, cArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(0), "expected unbonding delegation to be canceled") + }) + + It("should not cancel unbonding delegations when exceeding allowance", func() { + approvalArgs := defaultApproveArgs. + WithArgs(contractAddr, []string{staking.CancelUnbondingDelegationMsg}, big.NewInt(1)) + s.SetupApprovalWithContractCalls(approvalArgs) + + cArgs := defaultCancelUnbondingArgs.WithArgs( + s.address, valAddr.String(), big.NewInt(1e18), big.NewInt(expCreationHeight), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, cArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(1), "expected unbonding delegation to not be canceled") + }) + + It("should not cancel unbonding any delegations when unbonding delegation does not exist", func() { + cancelArgs := defaultCancelUnbondingArgs.WithArgs( + s.address, nonExistingVal.String(), big.NewInt(1e18), big.NewInt(expCreationHeight), + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, cancelArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(1), "expected unbonding delegation to not be canceled") + }) + + It("should not cancel unbonding delegations when calling from a different address", func() { + newAddr, newPriv := testutiltx.NewAccAddressAndKey() + err := evmosutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, newAddr, 1e18) + Expect(err).To(BeNil(), "error while funding account: %v", err) + + s.NextBlock() + + cancelUnbondArgs := defaultCancelUnbondingArgs. + WithPrivKey(newPriv). + WithArgs(s.address, valAddr.String(), big.NewInt(1e18), big.NewInt(expCreationHeight)) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, cancelUnbondArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(1), "expected unbonding delegation to not be canceled") + }) + }) + }) + + Context("querying allowance", func() { + // defaultAllowanceArgs are the default arguments for querying the allowance + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + var defaultAllowanceArgs contracts.CallArgs + + BeforeEach(func() { + defaultAllowanceArgs = defaultCallArgs.WithMethodName("getAllowance") + }) + + It("without approval set it should show no allowance", func() { + allowanceArgs := defaultAllowanceArgs.WithArgs( + contractAddr, staking.CancelUnbondingDelegationMsg, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, allowanceArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var allowanceInt *big.Int + err = s.precompile.UnpackIntoInterface(&allowanceInt, "allowance", ethRes.Ret) + Expect(err).To(BeNil(), "error while unmarshalling the allowance: %v", err) + Expect(allowanceInt.Int64()).To(Equal(int64(0)), "expected empty allowance") + }) + + It("with approval set it should show the granted allowance", func() { + // setup approval + approvalArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.CancelUnbondingDelegationMsg}, big.NewInt(1e18), + ) + + s.SetupApprovalWithContractCalls(approvalArgs) + + // query allowance + allowanceArgs := defaultAllowanceArgs.WithArgs( + contractAddr, staking.CancelUnbondingDelegationMsg, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, allowanceArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var allowanceInt *big.Int + err = s.precompile.UnpackIntoInterface(&allowanceInt, "allowance", ethRes.Ret) + Expect(err).To(BeNil(), "error while unmarshalling the allowance: %v", err) + Expect(allowanceInt).To(Equal(big.NewInt(1e18)), "expected allowance to be 1e18") + }) + }) + + Context("querying validator", func() { + // defaultValidatorArgs are the default arguments for querying the validator + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + var defaultValidatorArgs contracts.CallArgs + + BeforeEach(func() { + defaultValidatorArgs = defaultCallArgs.WithMethodName("getValidator") + }) + + It("with non-existing address should return an empty validator", func() { + validatorArgs := defaultValidatorArgs.WithArgs( + nonExistingVal.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var valOut staking.ValidatorOutput + err = s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + Expect(valOut.Validator.OperatorAddress).To(Equal(""), "expected empty validator address") + Expect(valOut.Validator.Status).To(Equal(uint8(0)), "expected validator status to be 0 (unspecified)") + }) + + It("with existing address should return the validator", func() { + validatorArgs := defaultValidatorArgs.WithArgs( + valAddr.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var valOut staking.ValidatorOutput + err = s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + Expect(valOut.Validator.OperatorAddress).To(Equal(valAddr.String()), "expected validator address to match") + Expect(valOut.Validator.DelegatorShares).To(Equal(big.NewInt(1e18)), "expected different delegator shares") + }) + + It("with status bonded and pagination", func() { + validatorArgs := defaultCallArgs. + WithMethodName("getValidators"). + WithArgs( + stakingtypes.Bonded.String(), + query.PageRequest{ + Limit: 1, + CountTotal: true, + }, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var valOut staking.ValidatorsOutput + err = s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorsMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + Expect(valOut.PageResponse.Total).To(Equal(uint64(len(s.validators)))) + Expect(valOut.PageResponse.NextKey).NotTo(BeEmpty()) + Expect(valOut.Validators[0].DelegatorShares).To(Equal(big.NewInt(1e18)), "expected different delegator shares") + }) + }) + + Context("querying validators", func() { + var defaultValidatorsArgs contracts.CallArgs + + BeforeEach(func() { + defaultValidatorsArgs = defaultCallArgs.WithMethodName("getValidators") + }) + + It("should return validators (default pagination)", func() { + validatorsArgs := defaultValidatorsArgs.WithArgs( + stakingtypes.Bonded.String(), + query.PageRequest{}, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorsArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var valOut staking.ValidatorsOutput + err = s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorsMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + Expect(valOut.PageResponse.Total).To(Equal(uint64(len(s.validators)))) + Expect(valOut.PageResponse.NextKey).To(BeEmpty()) + Expect(valOut.Validators).To(HaveLen(len(s.validators)), "expected all validators to be returned") + // return order can change, that's why each validator is checked individually + for _, val := range valOut.Validators { + s.CheckValidatorOutput(val) + } + }) + + //nolint:dupl // this is a duplicate of the test for EOA calls to the precompile + It("should return validators with pagination limit = 1", func() { + const limit uint64 = 1 + validatorArgs := defaultValidatorsArgs.WithArgs( + stakingtypes.Bonded.String(), + query.PageRequest{ + Limit: limit, + CountTotal: true, + }, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var valOut staking.ValidatorsOutput + err = s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorsMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + + // no pagination, should return default values + Expect(valOut.PageResponse.NextKey).NotTo(BeEmpty()) + Expect(valOut.PageResponse.Total).To(Equal(uint64(len(s.validators)))) + + Expect(valOut.Validators).To(HaveLen(int(limit)), "expected one validator to be returned") + + // return order can change, that's why each validator is checked individually + for _, val := range valOut.Validators { + s.CheckValidatorOutput(val) + } + }) + + It("should revert the execution if the bonding type is not known", func() { + validatorArgs := defaultValidatorsArgs.WithArgs( + "15", // invalid bonding type + query.PageRequest{}, + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + }) + + It("should return an empty array if there are no validators with the given bonding type", func() { + validatorArgs := defaultValidatorsArgs.WithArgs( + stakingtypes.Unbonded.String(), + query.PageRequest{}, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, validatorArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var valOut staking.ValidatorsOutput + err = s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorsMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the validator output: %v", err) + + Expect(valOut.PageResponse.NextKey).To(BeEmpty()) + Expect(valOut.PageResponse.Total).To(Equal(uint64(0))) + Expect(valOut.Validators).To(HaveLen(0), "expected no validators to be returned") + }) + }) + + Context("querying delegation", func() { + // defaultDelegationArgs are the default arguments for querying the delegation + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + var defaultDelegationArgs contracts.CallArgs + + BeforeEach(func() { + defaultDelegationArgs = defaultCallArgs.WithMethodName("getDelegation") + }) + + It("which does not exist should return an empty delegation", func() { + delegationArgs := defaultDelegationArgs.WithArgs( + nonExistingAddr, valAddr.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delegationArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var delOut staking.DelegationOutput + err = s.precompile.UnpackIntoInterface(&delOut, staking.DelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the delegation output: %v", err) + Expect(delOut.Balance.Amount.Int64()).To(Equal(int64(0)), "expected a different delegation balance") + Expect(delOut.Balance.Denom).To(Equal("aevmos"), "expected a different delegation balance") + }) + + It("which exists should return the delegation", func() { + delegationArgs := defaultDelegationArgs.WithArgs( + s.address, valAddr.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, delegationArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var delOut staking.DelegationOutput + err = s.precompile.UnpackIntoInterface(&delOut, staking.DelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the delegation output: %v", err) + Expect(delOut.Balance).To(Equal( + cmn.Coin{Denom: "aevmos", Amount: big.NewInt(1e18)}), + "expected a different delegation balance", + ) + }) + }) + + Context("querying redelegation", func() { + // defaultRedelegationArgs are the default arguments for querying the redelegation + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + var defaultRedelegationArgs contracts.CallArgs + + BeforeEach(func() { + defaultRedelegationArgs = defaultCallArgs.WithMethodName("getRedelegation") + }) + + It("which does not exist should return an empty redelegation", func() { + redelegationArgs := defaultRedelegationArgs.WithArgs( + s.address, valAddr.String(), nonExistingVal.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegationArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var redOut staking.RedelegationOutput + err = s.precompile.UnpackIntoInterface(&redOut, staking.RedelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the redelegation output: %v", err) + Expect(redOut.Redelegation.Entries).To(HaveLen(0), "expected no redelegation entries") + }) + + It("which exists should return the redelegation", func() { + // set up approval + approvalArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.RedelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(approvalArgs) + + s.NextBlock() + + // set up redelegation + redelegateArgs := defaultCallArgs. + WithMethodName("testRedelegate"). + WithArgs(s.address, valAddr.String(), valAddr2.String(), big.NewInt(1)) + + redelegateCheck := passCheck. + WithExpEvents(staking.EventTypeRedelegate) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, redelegateCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + // check that the redelegation was created + redelegations := s.app.StakingKeeper.GetAllRedelegations(s.ctx, s.address.Bytes(), valAddr, valAddr2) + Expect(redelegations).To(HaveLen(1), "expected one redelegation to be found") + bech32Addr := sdk.AccAddress(s.address.Bytes()) + Expect(redelegations[0].DelegatorAddress).To(Equal(bech32Addr.String()), "expected delegator address to be %s", s.address) + Expect(redelegations[0].ValidatorSrcAddress).To(Equal(valAddr.String()), "expected source validator address to be %s", valAddr) + Expect(redelegations[0].ValidatorDstAddress).To(Equal(valAddr2.String()), "expected destination validator address to be %s", valAddr2) + + // query redelegation + redelegationArgs := defaultRedelegationArgs.WithArgs( + s.address, valAddr.String(), valAddr2.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegationArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var redOut staking.RedelegationOutput + err = s.precompile.UnpackIntoInterface(&redOut, staking.RedelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the redelegation output: %v", err) + Expect(redOut.Redelegation.Entries).To(HaveLen(1), "expected one redelegation entry to be returned") + }) + }) + + Describe("query redelegations", func() { + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + var defaultRedelegationsArgs contracts.CallArgs + + BeforeEach(func() { + defaultRedelegationsArgs = defaultCallArgs.WithMethodName("getRedelegations") + }) + + It("which exists should return all the existing redelegations w/pagination", func() { + // set up approval + approvalArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.RedelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(approvalArgs) + s.NextBlock() + + // set up redelegation + redelegateArgs := defaultCallArgs. + WithMethodName("testRedelegate"). + WithArgs(s.address, valAddr.String(), valAddr2.String(), big.NewInt(1)) + + redelegateCheck := passCheck. + WithExpEvents(staking.EventTypeRedelegate) + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegateArgs, redelegateCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + // check that the redelegation was created + redelegations := s.app.StakingKeeper.GetAllRedelegations(s.ctx, s.address.Bytes(), valAddr, valAddr2) + Expect(redelegations).To(HaveLen(1), "expected one redelegation to be found") + bech32Addr := sdk.AccAddress(s.address.Bytes()) + Expect(redelegations[0].DelegatorAddress).To(Equal(bech32Addr.String()), "expected delegator address to be %s", s.address) + Expect(redelegations[0].ValidatorSrcAddress).To(Equal(valAddr.String()), "expected source validator address to be %s", valAddr) + Expect(redelegations[0].ValidatorDstAddress).To(Equal(valAddr2.String()), "expected destination validator address to be %s", valAddr2) + + // query redelegations by delegator address + redelegationArgs := defaultRedelegationsArgs. + WithArgs( + s.address, "", "", query.PageRequest{Limit: 1, CountTotal: true}, + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, redelegationArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var redOut staking.RedelegationsOutput + err = s.precompile.UnpackIntoInterface(&redOut, staking.RedelegationsMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the redelegation output: %v", err) + Expect(redOut.Response).To(HaveLen(1), "expected one redelegation entry to be returned") + Expect(redOut.Response[0].Entries).To(HaveLen(1), "expected one redelegation entry to be returned") + Expect(redOut.PageResponse.Total).To(Equal(uint64(1))) + Expect(redOut.PageResponse.NextKey).To(BeEmpty()) + }) + }) + + Context("querying unbonding delegation", func() { + // defaultQueryUnbondingArgs are the default arguments for querying the unbonding delegation + // + // NOTE: this has to be populated in the BeforeEach block because the private key is not initialized before + var defaultQueryUnbondingArgs contracts.CallArgs + + BeforeEach(func() { + defaultQueryUnbondingArgs = defaultCallArgs.WithMethodName("getUnbondingDelegation") + + // Set up an unbonding delegation + approvalArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.UndelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(approvalArgs) + + s.NextBlock() + + undelegateArgs := defaultCallArgs. + WithMethodName("testUndelegate"). + WithArgs(s.address, valAddr.String(), big.NewInt(1e18)) + + logCheckArgs := passCheck. + WithExpEvents(staking.EventTypeUnbond) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, undelegateArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while setting up an unbonding delegation: %v", err) + + // Check that the unbonding delegation was created + unbondingDelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(unbondingDelegations).To(HaveLen(1), "expected one unbonding delegation to be found") + Expect(unbondingDelegations[0].DelegatorAddress).To(Equal(sdk.AccAddress(s.address.Bytes()).String()), "expected delegator address to be %s", s.address) + Expect(unbondingDelegations[0].ValidatorAddress).To(Equal(valAddr.String()), "expected validator address to be %s", valAddr) + Expect(unbondingDelegations[0].Entries).To(HaveLen(1), "expected one unbonding delegation entry to be found") + Expect(unbondingDelegations[0].Entries[0].CreationHeight).To(Equal(int64(4)), "expected different creation height") + Expect(unbondingDelegations[0].Entries[0].Balance).To(Equal(math.NewInt(1e18)), "expected different balance") + }) + + It("which does not exist should return an empty unbonding delegation", func() { + queryUnbondingArgs := defaultQueryUnbondingArgs.WithArgs( + s.address, valAddr2.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, queryUnbondingArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var unbondingDelegationOutput staking.UnbondingDelegationOutput + err = s.precompile.UnpackIntoInterface(&unbondingDelegationOutput, staking.UnbondingDelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the unbonding delegation output: %v", err) + Expect(unbondingDelegationOutput.UnbondingDelegation.Entries).To(HaveLen(0), "expected one unbonding delegation entry") + }) + + It("which exists should return the unbonding delegation", func() { + queryUnbondingArgs := defaultQueryUnbondingArgs.WithArgs( + s.address, valAddr.String(), + ) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, queryUnbondingArgs, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var unbondOut staking.UnbondingDelegationOutput + err = s.precompile.UnpackIntoInterface(&unbondOut, staking.UnbondingDelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the unbonding delegation output: %v", err) + Expect(unbondOut.UnbondingDelegation.Entries).To(HaveLen(1), "expected one unbonding delegation entry to be returned") + Expect(unbondOut.UnbondingDelegation.Entries[0].Balance).To(Equal(big.NewInt(1e18)), "expected different balance") + }) + }) + + Context("testing sequential function calls to the precompile", func() { + // NOTE: there's no additional setup necessary because the test suite is already set up with + // delegations to the validator + It("should revert everything if any operation fails", func() { + cArgs := defaultCallArgs. + WithMethodName("testApproveAndThenUndelegate"). + WithGasLimit(1e8). + WithArgs(contractAddr, big.NewInt(250), big.NewInt(500), valAddr.String()) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, cArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + // There should be no authorizations because everything should have been reverted + authz, _ := s.CheckAuthorization(staking.UndelegateAuthz, contractAddr, s.address) + Expect(authz).To(BeNil(), "expected authorization to be nil") + + undelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(undelegations).To(HaveLen(0), "expected no unbonding delegations") + }) + + It("should write to state if all operations succeed", func() { + cArgs := defaultCallArgs. + WithMethodName("testApproveAndThenUndelegate"). + WithGasLimit(1e8). + WithArgs(contractAddr, big.NewInt(1000), big.NewInt(500), valAddr.String()) + + logCheckArgs := passCheck. + WithExpEvents(authorization.EventTypeApproval, staking.EventTypeUnbond) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, cArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + authz, _ := s.CheckAuthorization(staking.UndelegateAuthz, contractAddr, s.address) + Expect(authz).ToNot(BeNil(), "expected authorization not to be nil") + + undelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + Expect(undelegations).To(HaveLen(1), "expected one unbonding delegation") + Expect(undelegations[0].ValidatorAddress).To(Equal(valAddr.String()), "expected different validator address") + }) + }) + + Context("when using special call opcodes", func() { + testcases := []struct { + // calltype is the opcode to use + calltype string + // expTxPass defines if executing transactions should be possible with the given opcode. + // Queries should work for all options. + expTxPass bool + }{ + {"call", true}, + {"callcode", false}, + {"staticcall", false}, + {"delegatecall", false}, + } + + BeforeEach(func() { + // approve undelegate message + approveArgs := defaultApproveArgs.WithArgs( + contractAddr, []string{staking.UndelegateMsg}, big.NewInt(1e18), + ) + s.SetupApprovalWithContractCalls(approveArgs) + + s.NextBlock() + }) + + for _, tc := range testcases { + // NOTE: this is necessary because of Ginkgo behavior -- if not done, the value of tc + // inside the It block will always be the last entry in the testcases slice + testcase := tc + + It(fmt.Sprintf("should not execute transactions for calltype %q", testcase.calltype), func() { + args := defaultCallArgs. + WithMethodName("testCallUndelegate"). + WithArgs(s.address, valAddr.String(), big.NewInt(1e18), testcase.calltype) + + checkArgs := execRevertedCheck + if testcase.expTxPass { + checkArgs = passCheck.WithExpEvents(staking.EventTypeUnbond) + } + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, args, checkArgs) + if testcase.expTxPass { + Expect(err).To(BeNil(), "error while calling the smart contract for calltype %s: %v", testcase.calltype, err) + } else { + Expect(err).To(HaveOccurred(), "error while calling the smart contract for calltype %s: %v", testcase.calltype, err) + } + // check no delegations are unbonding + undelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + + if testcase.expTxPass { + Expect(undelegations).To(HaveLen(1), "expected an unbonding delegation") + Expect(undelegations[0].ValidatorAddress).To(Equal(valAddr.String()), "expected different validator address") + Expect(undelegations[0].DelegatorAddress).To(Equal(sdk.AccAddress(s.address.Bytes()).String()), "expected different delegator address") + } else { + Expect(undelegations).To(HaveLen(0), "expected no unbonding delegations for calltype %s", testcase.calltype) + } + }) + + It(fmt.Sprintf("should execute queries for calltype %q", testcase.calltype), func() { + args := defaultCallArgs. + WithMethodName("testCallDelegation"). + WithArgs(s.address, valAddr.String(), testcase.calltype) + + _, ethRes, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, args, passCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + var delOut staking.DelegationOutput + err = s.precompile.UnpackIntoInterface(&delOut, staking.DelegationMethod, ethRes.Ret) + Expect(err).To(BeNil(), "error while unpacking the delegation output: %v", err) + Expect(delOut.Shares).To(Equal(math.LegacyNewDec(1).BigInt()), "expected different delegation shares") + Expect(delOut.Balance.Amount).To(Equal(big.NewInt(1e18)), "expected different delegation balance") + if testcase.calltype != "callcode" { // having some trouble with returning the denom from inline assembly but that's a very special edge case which might never be used + Expect(delOut.Balance.Denom).To(Equal(s.bondDenom), "expected different denomination") + } + }) + } + }) + + // NOTE: These tests were added to replicate a problematic behavior, that occurred when a contract + // adjusted the state in multiple subsequent function calls, which adjusted the EVM state as well as + // things from the Cosmos SDK state (e.g. a bank balance). + // The result was, that changes made to the Cosmos SDK state have been overwritten during the next function + // call, because the EVM state was not updated in between. + // + // This behavior was fixed by updating the EVM state after each function call. + Context("when triggering multiple state changes in one function", func() { + // delegationAmount is the amount to be delegated + delegationAmount := big.NewInt(1e18) + + BeforeEach(func() { + // Set up funding for the contract address. + // NOTE: we are first asserting that no balance exists and then check successful + // funding afterwards. + balanceBefore := s.app.BankKeeper.GetBalance(s.ctx, contractAddr.Bytes(), s.bondDenom) + Expect(balanceBefore.Amount.Int64()).To(BeZero(), "expected contract balance to be 0 before funding") + + err = s.app.BankKeeper.SendCoins( + s.ctx, s.address.Bytes(), contractAddr.Bytes(), + sdk.Coins{sdk.Coin{Denom: s.bondDenom, Amount: math.NewIntFromBigInt(delegationAmount)}}, + ) + Expect(err).To(BeNil(), "error while sending coins: %v", err) + + s.NextBlock() + + balanceAfterFunding := s.app.BankKeeper.GetBalance(s.ctx, contractAddr.Bytes(), s.bondDenom) + Expect(balanceAfterFunding.Amount.BigInt()).To(Equal(delegationAmount), "expected different contract balance after funding") + + // Check no delegation exists from the contract to the validator + _, found := s.app.StakingKeeper.GetDelegation(s.ctx, contractAddr.Bytes(), valAddr) + Expect(found).To(BeFalse(), "expected delegation not to be found before testing") + }) + + It("delegating and increasing counter should change the bank balance accordingly", func() { + delegationArgs := defaultCallArgs. + WithGasLimit(1e9). + WithMethodName("testDelegateIncrementCounter"). + WithArgs(valAddr.String(), delegationAmount) + + approvalAndDelegationCheck := passCheck.WithExpEvents( + authorization.EventTypeApproval, staking.EventTypeDelegate, + ) + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, delegationArgs, approvalAndDelegationCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + + del, found := s.app.StakingKeeper.GetDelegation(s.ctx, contractAddr.Bytes(), valAddr) + + Expect(found).To(BeTrue(), "expected delegation to be found") + Expect(del.GetShares().BigInt()).To(Equal(delegationAmount), "expected different delegation shares") + + postBalance := s.app.BankKeeper.GetBalance(s.ctx, contractAddr.Bytes(), s.bondDenom) + Expect(postBalance.Amount.Int64()).To(BeZero(), "expected balance to be 0 after contract call") + }) + }) + + Context("when updating the stateDB prior to calling the precompile", func() { + It("should utilize the same contract balance to delegate", func() { + delegationArgs := defaultCallArgs. + WithGasLimit(1e9). + WithMethodName("approveDepositAndDelegate"). + WithArgs(valAddr.String()). + WithAmount(big.NewInt(2e18)) + + approvalAndDelegationCheck := passCheck.WithExpEvents( + authorization.EventTypeApproval, staking.EventTypeDelegate, + ) + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, delegationArgs, approvalAndDelegationCheck) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + balance := s.app.BankKeeper.GetBalance(s.ctx, contractAddr.Bytes(), s.bondDenom) + Expect(balance.Amount.Int64()).To(BeZero(), "expected different contract balance after funding") + delegation := s.app.StakingKeeper.GetAllDelegatorDelegations(s.ctx, contractAddr.Bytes()) + Expect(delegation).To(HaveLen(1), "expected one delegation") + Expect(delegation[0].GetShares().BigInt()).To(Equal(big.NewInt(2e18)), "expected different delegation shares") + }) + //nolint:dupl + It("should revert the contract balance to the original value when the precompile fails", func() { + delegationArgs := defaultCallArgs. + WithGasLimit(1e9). + WithMethodName("approveDepositAndDelegateExceedingAllowance"). + WithArgs(valAddr.String()). + WithAmount(big.NewInt(2e18)) + + approvalAndDelegationCheck := defaultLogCheck.WithErrContains(vm.ErrExecutionReverted.Error()) + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, delegationArgs, approvalAndDelegationCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + balance := s.app.BankKeeper.GetBalance(s.ctx, contractAddr.Bytes(), s.bondDenom) + Expect(balance.Amount.Int64()).To(BeZero(), "expected different contract balance after funding") + auth, _ := s.app.AuthzKeeper.GetAuthorization(s.ctx, contractAddr.Bytes(), s.address.Bytes(), staking.DelegateMsg) + Expect(auth).To(BeNil(), "expected no authorization") + delegation := s.app.StakingKeeper.GetAllDelegatorDelegations(s.ctx, contractAddr.Bytes()) + Expect(delegation).To(HaveLen(0), "expected no delegations") + }) + + //nolint:dupl + It("should revert the contract balance to the original value when the custom logic after the precompile fails ", func() { + delegationArgs := defaultCallArgs. + WithGasLimit(1e9). + WithMethodName("approveDepositDelegateAndFailCustomLogic"). + WithArgs(valAddr.String()). + WithAmount(big.NewInt(2e18)) + + approvalAndDelegationCheck := defaultLogCheck.WithErrContains(vm.ErrExecutionReverted.Error()) + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, delegationArgs, approvalAndDelegationCheck) + Expect(err).To(HaveOccurred(), "error while calling the smart contract: %v", err) + + balance := s.app.BankKeeper.GetBalance(s.ctx, contractAddr.Bytes(), s.bondDenom) + Expect(balance.Amount.Int64()).To(BeZero(), "expected different contract balance after funding") + auth, _ := s.app.AuthzKeeper.GetAuthorization(s.ctx, contractAddr.Bytes(), s.address.Bytes(), staking.DelegateMsg) + Expect(auth).To(BeNil(), "expected no authorization") + delegation := s.app.StakingKeeper.GetAllDelegatorDelegations(s.ctx, contractAddr.Bytes()) + Expect(delegation).To(HaveLen(0), "expected no delegations") + }) + }) +}) + +// These tests are used to check that when batching multiple state changing transactions +// in one block, both states (Cosmos and EVM) are updated or reverted correctly. +// +// For this purpose, we are deploying an ERC20 contract and updating StakingCaller.sol +// to include a method where an ERC20 balance is sent between accounts as well as +// an interaction with the staking precompile is made. +// +// There are ERC20 tokens minted to the address of the deployed StakingCaller contract, +// which will transfer these to the message sender when successfully executed. +// Using the staking EVM extension, there is an approval made before the ERC20 transfer +// as well as a delegation after the ERC20 transfer. +var _ = Describe("Batching cosmos and eth interactions", func() { + const ( + erc20Name = "Test" + erc20Token = "TTT" + erc20Decimals = uint8(18) + ) + + var ( + // contractAddr is the address of the deployed StakingCaller contract + contractAddr common.Address + // erc20ContractAddr is the address of the deployed ERC20 contract + erc20ContractAddr common.Address + // erc20Contract is the compiled ERC20 contract + erc20Contract = compiledcontracts.ERC20MinterBurnerDecimalsContract + + // err is a standard error + err error + // execRevertedCheck is a standard log check for a reverted transaction + execRevertedCheck = defaultLogCheck.WithErrContains(vm.ErrExecutionReverted.Error()) + + // mintAmount is the amount of ERC20 tokens minted to the StakingCaller contract + mintAmount = big.NewInt(1e18) + // transferredAmount is the amount of ERC20 tokens to transfer during the tests + transferredAmount = big.NewInt(1234e9) + ) + + BeforeEach(func() { + s.SetupTest() + s.NextBlock() + + // Deploy StakingCaller contract + contractAddr, err = evmosutil.DeployContract(s.ctx, s.app, s.privKey, s.queryClientEVM, testdata.StakingCallerContract) + Expect(err).To(BeNil(), "error while deploying the StakingCaller contract") + + // Deploy ERC20 contract + erc20ContractAddr, err = evmosutil.DeployContract(s.ctx, s.app, s.privKey, s.queryClientEVM, erc20Contract, + erc20Name, erc20Token, erc20Decimals, + ) + Expect(err).To(BeNil(), "error while deploying the ERC20 contract") + + // Mint tokens to the StakingCaller contract + mintArgs := contracts.CallArgs{ + ContractAddr: erc20ContractAddr, + ContractABI: erc20Contract.ABI, + MethodName: "mint", + PrivKey: s.privKey, + Args: []interface{}{contractAddr, mintAmount}, + } + + mintCheck := testutil.LogCheckArgs{ + ABIEvents: erc20Contract.ABI.Events, + ExpEvents: []string{"Transfer"}, // minting produces a Transfer event + ExpPass: true, + } + + _, _, err = contracts.CallContractAndCheckLogs(s.ctx, s.app, mintArgs, mintCheck) + Expect(err).To(BeNil(), "error while minting tokens to the StakingCaller contract") + + // Check that the StakingCaller contract has the correct balance + erc20Balance := s.app.Erc20Keeper.BalanceOf(s.ctx, erc20Contract.ABI, erc20ContractAddr, contractAddr) + Expect(erc20Balance).To(Equal(mintAmount), "expected different ERC20 balance for the StakingCaller contract") + + // populate default call args + defaultCallArgs = contracts.CallArgs{ + ContractABI: testdata.StakingCallerContract.ABI, + ContractAddr: contractAddr, + MethodName: "callERC20AndDelegate", + PrivKey: s.privKey, + } + + // populate default log check args + defaultLogCheck = testutil.LogCheckArgs{ + ABIEvents: s.precompile.Events, + } + execRevertedCheck = defaultLogCheck.WithErrContains(vm.ErrExecutionReverted.Error()) + passCheck = defaultLogCheck.WithExpPass(true) + }) + + Describe("when batching multiple transactions", func() { + // validator is the validator address used for testing + var validator sdk.ValAddress + + BeforeEach(func() { + delegations := s.app.StakingKeeper.GetAllDelegatorDelegations(s.ctx, s.address.Bytes()) + Expect(delegations).ToNot(HaveLen(0), "expected address to have delegations") + + validator = delegations[0].GetValidatorAddr() + + _ = erc20ContractAddr + }) + + It("should revert both states if a staking transaction fails", func() { + delegationPre, found := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), validator) + Expect(found).To(BeTrue(), + "expected delegation from %s to validator %s to be found", + sdk.AccAddress(s.address.Bytes()).String(), validator.String(), + ) + + sharesPre := delegationPre.GetShares() + + // NOTE: passing an invalid validator address here should fail AFTER the erc20 transfer was made in the smart contract. + // Therefore this can be used to check that both EVM and Cosmos states are reverted correctly. + failArgs := defaultCallArgs. + WithArgs(erc20ContractAddr, "invalid validator", transferredAmount) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, failArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "expected error while calling the smart contract") + Expect(err.Error()).To(ContainSubstring("execution reverted"), "expected different error message") + + delegationPost, found := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), validator) + Expect(found).To(BeTrue(), + "expected delegation from %s to validator %s to be found after calling the smart contract", + sdk.AccAddress(s.address.Bytes()).String(), validator.String(), + ) + + auths, err := s.app.AuthzKeeper.GetAuthorizations(s.ctx, contractAddr.Bytes(), s.address.Bytes()) + Expect(err).To(BeNil(), "error while getting authorizations: %v", err) + sharesPost := delegationPost.GetShares() + erc20BalancePost := s.app.Erc20Keeper.BalanceOf(s.ctx, erc20Contract.ABI, erc20ContractAddr, s.address) + + Expect(auths).To(BeEmpty(), "expected no authorizations when reverting state") + Expect(sharesPost).To(Equal(sharesPre), "expected shares to be equal when reverting state") + Expect(erc20BalancePost.Int64()).To(BeZero(), "expected erc20 balance of target address to be zero when reverting state") + }) + + It("should revert both states if an ERC20 transaction fails", func() { + delegationPre, found := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), validator) + Expect(found).To(BeTrue(), + "expected delegation from %s to validator %s to be found", + sdk.AccAddress(s.address.Bytes()).String(), validator.String(), + ) + + sharesPre := delegationPre.GetShares() + + // NOTE: trying to transfer more than the balance of the contract should fail AFTER the approval + // for delegating was made in the smart contract. + // Therefore this can be used to check that both EVM and Cosmos states are reverted correctly. + moreThanMintedAmount := new(big.Int).Add(mintAmount, big.NewInt(1)) + failArgs := defaultCallArgs. + WithArgs(erc20ContractAddr, s.validators[0].OperatorAddress, moreThanMintedAmount) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, failArgs, execRevertedCheck) + Expect(err).To(HaveOccurred(), "expected error while calling the smart contract") + Expect(err.Error()).To(ContainSubstring("execution reverted"), "expected different error message") + + delegationPost, found := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), validator) + Expect(found).To(BeTrue(), + "expected delegation from %s to validator %s to be found after calling the smart contract", + sdk.AccAddress(s.address.Bytes()).String(), validator.String(), + ) + + auths, err := s.app.AuthzKeeper.GetAuthorizations(s.ctx, contractAddr.Bytes(), s.address.Bytes()) + Expect(err).To(BeNil(), "error while getting authorizations: %v", err) + sharesPost := delegationPost.GetShares() + erc20BalancePost := s.app.Erc20Keeper.BalanceOf(s.ctx, erc20Contract.ABI, erc20ContractAddr, s.address) + + Expect(auths).To(BeEmpty(), "expected no authorizations when reverting state") + Expect(sharesPost).To(Equal(sharesPre), "expected shares to be equal when reverting state") + Expect(erc20BalancePost.Int64()).To(BeZero(), "expected erc20 balance of target address to be zero when reverting state") + }) + + It("should persist changes in both the cosmos and eth states", func() { + delegationPre, found := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), validator) + Expect(found).To(BeTrue(), + "expected delegation from %s to validator %s to be found", + sdk.AccAddress(s.address.Bytes()).String(), validator.String(), + ) + + sharesPre := delegationPre.GetShares() + + // NOTE: trying to transfer more than the balance of the contract should fail AFTER the approval + // for delegating was made in the smart contract. + // Therefore this can be used to check that both EVM and Cosmos states are reverted correctly. + successArgs := defaultCallArgs. + WithArgs(erc20ContractAddr, s.validators[0].OperatorAddress, transferredAmount) + + // Build combined map of ABI events to check for both ERC20 events as well as precompile events + // + // NOTE: only add the transfer event - when adding all contract events to the combined map, + // the ERC20 Approval event will overwrite the precompile Approval event, which will cause + // the check to fail because of unexpected events in the logs. + combinedABIEvents := s.precompile.Events + combinedABIEvents["Transfer"] = erc20Contract.ABI.Events["Transfer"] + + successCheck := passCheck. + WithABIEvents(combinedABIEvents). + WithExpEvents( + authorization.EventTypeApproval, "Transfer", staking.EventTypeDelegate, + ) + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, successArgs, successCheck) + Expect(err).ToNot(HaveOccurred(), "error while calling the smart contract") + + delegationPost, found := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), validator) + Expect(found).To(BeTrue(), + "expected delegation from %s to validator %s to be found after calling the smart contract", + sdk.AccAddress(s.address.Bytes()).String(), validator.String(), + ) + + auths, err := s.app.AuthzKeeper.GetAuthorizations(s.ctx, contractAddr.Bytes(), s.address.Bytes()) + Expect(err).To(BeNil(), "error while getting authorizations: %v", err) + sharesPost := delegationPost.GetShares() + erc20BalancePost := s.app.Erc20Keeper.BalanceOf(s.ctx, erc20Contract.ABI, erc20ContractAddr, s.address) + + Expect(sharesPost.GT(sharesPre)).To(BeTrue(), "expected shares to be more than before") + Expect(erc20BalancePost).To(Equal(transferredAmount), "expected different erc20 balance of target address") + // NOTE: there should be no authorizations because the full approved amount is delegated + Expect(auths).To(HaveLen(0), "expected no authorization to be found") + }) + }) +}) diff --git a/precompiles/staking/query.go b/precompiles/staking/query.go new file mode 100644 index 00000000..cb8b482b --- /dev/null +++ b/precompiles/staking/query.go @@ -0,0 +1,227 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package staking + +import ( + "fmt" + "math/big" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/evmos/evmos/v16/precompiles/authorization" + cmn "github.com/evmos/evmos/v16/precompiles/common" +) + +const ( + // DelegationMethod defines the ABI method name for the staking Delegation + // query. + DelegationMethod = "delegation" + // UnbondingDelegationMethod defines the ABI method name for the staking + // UnbondingDelegationMethod query. + UnbondingDelegationMethod = "unbondingDelegation" + // ValidatorMethod defines the ABI method name for the staking + // Validator query. + ValidatorMethod = "validator" + // ValidatorsMethod defines the ABI method name for the staking + // Validators query. + ValidatorsMethod = "validators" + // RedelegationMethod defines the ABI method name for the staking + // Redelegation query. + RedelegationMethod = "redelegation" + // RedelegationsMethod defines the ABI method name for the staking + // Redelegations query. + RedelegationsMethod = "redelegations" +) + +// Delegation returns the delegation that a delegator has with a specific validator. +func (p Precompile) Delegation( + ctx sdk.Context, + _ *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + req, err := NewDelegationRequest(args) + if err != nil { + return nil, err + } + + queryServer := stakingkeeper.Querier{Keeper: &p.stakingKeeper} + + res, err := queryServer.Delegation(sdk.WrapSDKContext(ctx), req) + if err != nil { + // If there is no delegation found, return the response with zero values. + if strings.Contains(err.Error(), fmt.Sprintf(ErrNoDelegationFound, req.DelegatorAddr, req.ValidatorAddr)) { + return method.Outputs.Pack(big.NewInt(0), cmn.Coin{Denom: p.stakingKeeper.BondDenom(ctx), Amount: big.NewInt(0)}) + } + + return nil, err + } + + out := new(DelegationOutput).FromResponse(res) + + return out.Pack(method.Outputs) +} + +// UnbondingDelegation returns the delegation currently being unbonded for a delegator from +// a specific validator. +func (p Precompile) UnbondingDelegation( + ctx sdk.Context, + _ *vm.Contract, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + req, err := NewUnbondingDelegationRequest(args) + if err != nil { + return nil, err + } + + queryServer := stakingkeeper.Querier{Keeper: &p.stakingKeeper} + + res, err := queryServer.UnbondingDelegation(sdk.WrapSDKContext(ctx), req) + if err != nil { + // return empty unbonding delegation output if the unbonding delegation is not found + expError := fmt.Sprintf("unbonding delegation with delegator %s not found for validator %s", req.DelegatorAddr, req.ValidatorAddr) + if strings.Contains(err.Error(), expError) { + return method.Outputs.Pack(UnbondingDelegationResponse{}) + } + return nil, err + } + + out := new(UnbondingDelegationOutput).FromResponse(res) + + return method.Outputs.Pack(out.UnbondingDelegation) +} + +// Validator returns the validator information for a given validator address. +func (p Precompile) Validator( + ctx sdk.Context, + method *abi.Method, + _ *vm.Contract, + args []interface{}, +) ([]byte, error) { + req, err := NewValidatorRequest(args) + if err != nil { + return nil, err + } + + queryServer := stakingkeeper.Querier{Keeper: &p.stakingKeeper} + + res, err := queryServer.Validator(sdk.WrapSDKContext(ctx), req) + if err != nil { + // return empty validator info if the validator is not found + expError := fmt.Sprintf("validator %s not found", req.ValidatorAddr) + if strings.Contains(err.Error(), expError) { + return method.Outputs.Pack(DefaultValidatorOutput().Validator) + } + return nil, err + } + + out := new(ValidatorOutput).FromResponse(res) + + return method.Outputs.Pack(out.Validator) +} + +// Validators returns the validators information with a provided status & pagination (optional). +func (p Precompile) Validators( + ctx sdk.Context, + method *abi.Method, + _ *vm.Contract, + args []interface{}, +) ([]byte, error) { + req, err := NewValidatorsRequest(method, args) + if err != nil { + return nil, err + } + + queryServer := stakingkeeper.Querier{Keeper: &p.stakingKeeper} + + res, err := queryServer.Validators(sdk.WrapSDKContext(ctx), req) + if err != nil { + return nil, err + } + + out := new(ValidatorsOutput).FromResponse(res) + + return out.Pack(method.Outputs) +} + +// Redelegation returns the redelegation between two validators for a delegator. +func (p Precompile) Redelegation( + ctx sdk.Context, + method *abi.Method, + _ *vm.Contract, + args []interface{}, +) ([]byte, error) { + req, err := NewRedelegationRequest(args) + if err != nil { + return nil, err + } + + res, _ := p.stakingKeeper.GetRedelegation(ctx, req.DelegatorAddress, req.ValidatorSrcAddress, req.ValidatorDstAddress) + + out := new(RedelegationOutput).FromResponse(res) + + return method.Outputs.Pack(out.Redelegation) +} + +// Redelegations returns the redelegations according to +// the specified criteria (delegator address and/or validator source address +// and/or validator destination address or all existing redelegations) with pagination. +// Pagination is only supported for querying redelegations from a source validator or to query all redelegations. +func (p Precompile) Redelegations( + ctx sdk.Context, + method *abi.Method, + _ *vm.Contract, + args []interface{}, +) ([]byte, error) { + req, err := NewRedelegationsRequest(method, args) + if err != nil { + return nil, err + } + + queryServer := stakingkeeper.Querier{Keeper: &p.stakingKeeper} + + res, err := queryServer.Redelegations(ctx, req) + if err != nil { + return nil, err + } + + out := new(RedelegationsOutput).FromResponse(res) + + return out.Pack(method.Outputs) +} + +// Allowance returns the remaining allowance of a grantee to the contract. +func (p Precompile) Allowance( + ctx sdk.Context, + method *abi.Method, + _ *vm.Contract, + args []interface{}, +) ([]byte, error) { + grantee, granter, msg, err := authorization.CheckAllowanceArgs(args) + if err != nil { + return nil, err + } + + msgAuthz, _ := p.AuthzKeeper.GetAuthorization(ctx, grantee.Bytes(), granter.Bytes(), msg) + + if msgAuthz == nil { + return method.Outputs.Pack(big.NewInt(0)) + } + + stakeAuthz, ok := msgAuthz.(*stakingtypes.StakeAuthorization) + if !ok { + return nil, fmt.Errorf(cmn.ErrInvalidType, "staking authorization", &stakingtypes.StakeAuthorization{}, stakeAuthz) + } + + if stakeAuthz.MaxTokens == nil { + return method.Outputs.Pack(abi.MaxUint256) + } + + return method.Outputs.Pack(stakeAuthz.MaxTokens.Amount.BigInt()) +} diff --git a/precompiles/staking/query_test.go b/precompiles/staking/query_test.go new file mode 100644 index 00000000..0291ba34 --- /dev/null +++ b/precompiles/staking/query_test.go @@ -0,0 +1,752 @@ +package staking_test + +import ( + "fmt" + "math/big" + + "cosmossdk.io/math" + 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/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/evmos/evmos/v16/precompiles/authorization" + cmn "github.com/evmos/evmos/v16/precompiles/common" + "github.com/evmos/evmos/v16/precompiles/staking" + testutiltx "github.com/evmos/evmos/v16/testutil/tx" +) + +func (s *PrecompileTestSuite) TestDelegation() { + method := s.precompile.Methods[staking.DelegationMethod] + + testCases := []struct { + name string + malleate func(operatorAddress string) []interface{} + postCheck func(bz []byte) + gas uint64 + expErr bool + errContains string + }{ + { + "fail - empty input args", + func(operatorAddress string) []interface{} { + return []interface{}{} + }, + func(bz []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 2, 0), + }, + { + "fail - invalid delegator address", + func(operatorAddress string) []interface{} { + return []interface{}{ + "invalid", + operatorAddress, + } + }, + func(bz []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, "invalid"), + }, + { + "fail - invalid operator address", + func(operatorAddress string) []interface{} { + return []interface{}{ + s.address, + "invalid", + } + }, + func(bz []byte) {}, + 100000, + true, + "decoding bech32 failed: invalid bech32 string", + }, + { + "success - empty delegation", + func(operatorAddress string) []interface{} { + addr, _ := testutiltx.NewAddrKey() + return []interface{}{ + addr, + operatorAddress, + } + }, + func(bz []byte) { + var delOut staking.DelegationOutput + err := s.precompile.UnpackIntoInterface(&delOut, staking.DelegationMethod, bz) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(delOut.Shares.Int64(), big.NewInt(0).Int64()) + }, + 100000, + false, + "", + }, + { + "success", + func(operatorAddress string) []interface{} { + return []interface{}{ + s.address, + operatorAddress, + } + }, + func(bz []byte) { + var delOut staking.DelegationOutput + err := s.precompile.UnpackIntoInterface(&delOut, staking.DelegationMethod, bz) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(delOut.Shares, big.NewInt(1e18)) + }, + 100000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + bz, err := s.precompile.Delegation(s.ctx, contract, &method, tc.malleate(s.validators[0].OperatorAddress)) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestUnbondingDelegation() { + method := s.precompile.Methods[staking.UnbondingDelegationMethod] + + testCases := []struct { + name string + malleate func(operatorAddress string) []interface{} + postCheck func(bz []byte) + gas uint64 + expErr bool + errContains string + }{ + { + "fail - empty input args", + func(operatorAddress string) []interface{} { + return []interface{}{} + }, + func(bz []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 2, 0), + }, + { + "fail - invalid delegator address", + func(operatorAddress string) []interface{} { + return []interface{}{ + "invalid", + operatorAddress, + } + }, + func(bz []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, "invalid"), + }, + { + "success - no unbonding delegation found", + func(operatorAddress string) []interface{} { + addr, _ := testutiltx.NewAddrKey() + return []interface{}{ + addr, + operatorAddress, + } + }, + func(data []byte) { + var ubdOut staking.UnbondingDelegationOutput + err := s.precompile.UnpackIntoInterface(&ubdOut, staking.UnbondingDelegationMethod, data) + s.Require().NoError(err, "failed to unpack output") + s.Require().Len(ubdOut.UnbondingDelegation.Entries, 0) + }, + 100000, + false, + "", + }, + { + "success", + func(operatorAddress string) []interface{} { + return []interface{}{ + s.address, + operatorAddress, + } + }, + func(data []byte) { + var ubdOut staking.UnbondingDelegationOutput + err := s.precompile.UnpackIntoInterface(&ubdOut, staking.UnbondingDelegationMethod, data) + s.Require().NoError(err, "failed to unpack output") + s.Require().Len(ubdOut.UnbondingDelegation.Entries, 1) + s.Require().Equal(ubdOut.UnbondingDelegation.Entries[0].CreationHeight, s.ctx.BlockHeight()) + s.Require().Equal(ubdOut.UnbondingDelegation.Entries[0].Balance, big.NewInt(1e18)) + }, + 100000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + _, err := s.app.StakingKeeper.Undelegate(s.ctx, s.address.Bytes(), s.validators[0].GetOperator(), math.LegacyNewDec(1)) + s.Require().NoError(err) + + bz, err := s.precompile.UnbondingDelegation(s.ctx, contract, &method, tc.malleate(s.validators[0].OperatorAddress)) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotNil(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestValidator() { + method := s.precompile.Methods[staking.ValidatorMethod] + + testCases := []struct { + name string + malleate func(operatorAddress string) []interface{} + postCheck func(bz []byte) + gas uint64 + expErr bool + errContains string + }{ + { + "fail - empty input args", + func(operatorAddress string) []interface{} { + return []interface{}{} + }, + func(_ []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 1, 0), + }, + { + "success", + func(operatorAddress string) []interface{} { + return []interface{}{ + operatorAddress, + } + }, + func(data []byte) { + var valOut staking.ValidatorOutput + err := s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorMethod, data) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(valOut.Validator.OperatorAddress, s.validators[0].OperatorAddress) + }, + 100000, + false, + "", + }, + { + name: "success - empty validator", + malleate: func(operatorAddress string) []interface{} { + newAddr, _ := testutiltx.NewAccAddressAndKey() + newValAddr := sdk.ValAddress(newAddr) + return []interface{}{ + newValAddr.String(), + } + }, + postCheck: func(data []byte) { + var valOut staking.ValidatorOutput + err := s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorMethod, data) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(valOut.Validator.OperatorAddress, "") + s.Require().Equal(valOut.Validator.Status, uint8(0)) + }, + gas: 100000, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + bz, err := s.precompile.Validator(s.ctx, &method, contract, tc.malleate(s.validators[0].OperatorAddress)) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotNil(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestValidators() { + method := s.precompile.Methods[staking.ValidatorsMethod] + + testCases := []struct { + name string + malleate func() []interface{} + postCheck func(bz []byte) + gas uint64 + expErr bool + errContains string + }{ + { + "fail - empty input args", + func() []interface{} { + return []interface{}{} + }, + func(_ []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 2, 0), + }, + { + "fail - invalid number of arguments", + func() []interface{} { + return []interface{}{ + stakingtypes.Bonded.String(), + } + }, + func(_ []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 2, 1), + }, + { + "success - bonded status & pagination w/countTotal", + func() []interface{} { + return []interface{}{ + stakingtypes.Bonded.String(), + query.PageRequest{ + Limit: 1, + CountTotal: true, + }, + } + }, + func(data []byte) { + const expLen = 1 + var valOut staking.ValidatorsOutput + err := s.precompile.UnpackIntoInterface(&valOut, staking.ValidatorsMethod, data) + s.Require().NoError(err, "failed to unpack output") + + s.Require().Len(valOut.Validators, expLen) + // passed CountTotal = true + s.Require().Equal(len(s.validators), int(valOut.PageResponse.Total)) + s.Require().NotEmpty(valOut.PageResponse.NextKey) + s.assertValidatorsResponse(valOut.Validators, expLen) + }, + 100000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + bz, err := s.precompile.Validators(s.ctx, &method, contract, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotNil(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestRedelegation() { + method := s.precompile.Methods[staking.RedelegationMethod] + redelegateMethod := s.precompile.Methods[staking.RedelegateMethod] + + testCases := []struct { + name string + malleate func(srcOperatorAddr, destOperatorAddr string) []interface{} + postCheck func(bz []byte) + gas uint64 + expErr bool + errContains string + }{ + { + "fail - empty input args", + func(srcOperatorAddr, destOperatorAddr string) []interface{} { + return []interface{}{} + }, + func(bz []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 3, 0), + }, + { + "fail - invalid delegator address", + func(srcOperatorAddr, destOperatorAddr string) []interface{} { + return []interface{}{ + "invalid", + srcOperatorAddr, + destOperatorAddr, + } + }, + func(bz []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, "invalid"), + }, + { + "fail - empty src validator addr", + func(srcOperatorAddr, destOperatorAddr string) []interface{} { + return []interface{}{ + s.address, + "", + destOperatorAddr, + } + }, + func(bz []byte) {}, + 100000, + true, + "empty address string is not allowed", + }, + { + "fail - empty destination addr", + func(srcOperatorAddr, destOperatorAddr string) []interface{} { + return []interface{}{ + s.address, + srcOperatorAddr, + "", + } + }, + func(bz []byte) {}, + 100000, + true, + "empty address string is not allowed", + }, + { + "success", + func(srcOperatorAddr, destOperatorAddr string) []interface{} { + return []interface{}{ + s.address, + srcOperatorAddr, + destOperatorAddr, + } + }, + func(data []byte) { + var redOut staking.RedelegationOutput + err := s.precompile.UnpackIntoInterface(&redOut, staking.RedelegationMethod, data) + s.Require().NoError(err, "failed to unpack output") + s.Require().Len(redOut.Redelegation.Entries, 1) + s.Require().Equal(redOut.Redelegation.Entries[0].CreationHeight, s.ctx.BlockHeight()) + s.Require().Equal(redOut.Redelegation.Entries[0].SharesDst, big.NewInt(1e18)) + }, + 100000, + false, + "", + }, + { + name: "success - no redelegation found", + malleate: func(srcOperatorAddr, _ string) []interface{} { + nonExistentOperator := sdk.ValAddress([]byte("non-existent-operator")) + return []interface{}{ + s.address, + srcOperatorAddr, + nonExistentOperator.String(), + } + }, + postCheck: func(data []byte) { + var redOut staking.RedelegationOutput + err := s.precompile.UnpackIntoInterface(&redOut, staking.RedelegationMethod, data) + s.Require().NoError(err, "failed to unpack output") + s.Require().Len(redOut.Redelegation.Entries, 0) + }, + gas: 100000, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + delegationArgs := []interface{}{ + s.address, + s.validators[0].OperatorAddress, + s.validators[1].OperatorAddress, + big.NewInt(1e18), + } + + err := s.CreateAuthorization(s.address, staking.RedelegateAuthz, nil) + s.Require().NoError(err) + + _, err = s.precompile.Redelegate(s.ctx, s.address, contract, s.stateDB, &redelegateMethod, delegationArgs) + s.Require().NoError(err) + + bz, err := s.precompile.Redelegation(s.ctx, &method, contract, tc.malleate(s.validators[0].OperatorAddress, s.validators[1].OperatorAddress)) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotNil(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestRedelegations() { + var ( + delAmt = big.NewInt(3e17) + redelTotalCount uint64 = 2 + method = s.precompile.Methods[staking.RedelegationsMethod] + ) + + testCases := []struct { + name string + malleate func() []interface{} + postCheck func(bz []byte) + gas uint64 + expErr bool + errContains string + }{ + { + "fail - empty input args", + func() []interface{} { + return []interface{}{} + }, + func(bz []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 4, 0), + }, + { + "fail - invalid delegator address", + func() []interface{} { + return []interface{}{ + common.BytesToAddress([]byte("invalid")), + s.validators[0].OperatorAddress, + s.validators[1].OperatorAddress, + query.PageRequest{}, + } + }, + func(bz []byte) {}, + 100000, + true, + "redelegation not found", + }, + { + "fail - invalid query | all empty args ", + func() []interface{} { + return []interface{}{ + common.Address{}, + "", + "", + query.PageRequest{}, + } + }, + func(data []byte) {}, + 100000, + true, + "invalid query. Need to specify at least a source validator address or delegator address", + }, + { + "fail - invalid query | only destination validator address", + func() []interface{} { + return []interface{}{ + common.Address{}, + "", + s.validators[1].OperatorAddress, + query.PageRequest{}, + } + }, + func(data []byte) {}, + 100000, + true, + "invalid query. Need to specify at least a source validator address or delegator address", + }, + { + "success - specified delegator, source & destination", + func() []interface{} { + return []interface{}{ + s.address, + s.validators[0].OperatorAddress, + s.validators[1].OperatorAddress, + query.PageRequest{}, + } + }, + func(data []byte) { + s.assertRedelegationsOutput(data, 0, delAmt, 2, false) + }, + 100000, + false, + "", + }, + { + "success - specifying only source w/pagination", + func() []interface{} { + return []interface{}{ + common.Address{}, + s.validators[0].OperatorAddress, + "", + query.PageRequest{ + Limit: 1, + CountTotal: true, + }, + } + }, + func(data []byte) { + s.assertRedelegationsOutput(data, redelTotalCount, delAmt, 2, true) + }, + 100000, + false, + "", + }, + { + "success - get all existing redelegations for a delegator w/pagination", + func() []interface{} { + return []interface{}{ + s.address, + "", + "", + query.PageRequest{ + Limit: 1, + CountTotal: true, + }, + } + }, + func(data []byte) { + s.assertRedelegationsOutput(data, redelTotalCount, delAmt, 2, true) + }, + 100000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + err := s.setupRedelegations(delAmt) + s.Require().NoError(err) + + // query redelegations + bz, err := s.precompile.Redelegations(s.ctx, &method, contract, tc.malleate()) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotNil(bz) + tc.postCheck(bz) + } + }) + } +} + +func (s *PrecompileTestSuite) TestAllowance() { + approvedCoin := sdk.Coin{Denom: s.bondDenom, Amount: math.NewInt(1e18)} + granteeAddr := testutiltx.GenerateAddress() + method := s.precompile.Methods[authorization.AllowanceMethod] + + testCases := []struct { + name string + malleate func() []interface{} + postCheck func(bz []byte) + gas uint64 + expErr bool + errContains string + }{ + { + "fail - empty input args", + func() []interface{} { + return []interface{}{} + }, + func(bz []byte) {}, + 100000, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 3, 0), + }, + { + "success - query delegate method allowance", + func() []interface{} { + err := s.CreateAuthorization(granteeAddr, staking.DelegateAuthz, &approvedCoin) + s.Require().NoError(err) + + return []interface{}{ + granteeAddr, + s.address, + staking.DelegateMsg, + } + }, + func(bz []byte) { + var amountsOut *big.Int + err := s.precompile.UnpackIntoInterface(&amountsOut, authorization.AllowanceMethod, bz) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(big.NewInt(1e18), amountsOut, "expected different allowed amount") + }, + 100000, + false, + "", + }, + { + "success - return empty allowance if authorization is not found", + func() []interface{} { + return []interface{}{ + granteeAddr, + s.address, + staking.UndelegateMsg, + } + }, + func(bz []byte) { + var amountsOut *big.Int + err := s.precompile.UnpackIntoInterface(&amountsOut, authorization.AllowanceMethod, bz) + s.Require().NoError(err, "failed to unpack output") + s.Require().Equal(int64(0), amountsOut.Int64(), "expected no allowance") + }, + 100000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() // reset + contract := vm.NewContract(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + + args := tc.malleate() + bz, err := s.precompile.Allowance(s.ctx, &method, contract, args) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotNil(bz) + tc.postCheck(bz) + } + }) + } +} diff --git a/precompiles/staking/setup_test.go b/precompiles/staking/setup_test.go new file mode 100644 index 00000000..08ae85b3 --- /dev/null +++ b/precompiles/staking/setup_test.go @@ -0,0 +1,56 @@ +package staking_test + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + evmosapp "github.com/evmos/evmos/v16/app" + "github.com/evmos/evmos/v16/precompiles/staking" + "github.com/evmos/evmos/v16/x/evm/statedb" + evmtypes "github.com/evmos/evmos/v16/x/evm/types" + + //nolint:revive // dot imports are fine for Ginkgo + . "github.com/onsi/ginkgo/v2" + //nolint:revive // dot imports are fine for Ginkgo + . "github.com/onsi/gomega" + + "github.com/stretchr/testify/suite" +) + +var s *PrecompileTestSuite + +type PrecompileTestSuite struct { + suite.Suite + + ctx sdk.Context + app *evmosapp.Evmos + address common.Address + validators []stakingtypes.Validator + ethSigner ethtypes.Signer + privKey cryptotypes.PrivKey + signer keyring.Signer + bondDenom string + + precompile *staking.Precompile + stateDB *statedb.StateDB + + queryClientEVM evmtypes.QueryClient +} + +func TestPrecompileTestSuite(t *testing.T) { + s = new(PrecompileTestSuite) + suite.Run(t, s) + + // Run Ginkgo integration tests + RegisterFailHandler(Fail) + RunSpecs(t, "Precompile Test Suite") +} + +func (s *PrecompileTestSuite) SetupTest() { + s.DoSetupTest() +} diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go new file mode 100644 index 00000000..efa998d6 --- /dev/null +++ b/precompiles/staking/staking.go @@ -0,0 +1,187 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package staking + +import ( + "embed" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cometbft/cometbft/libs/log" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/evmos/evmos/v16/precompiles/authorization" + cmn "github.com/evmos/evmos/v16/precompiles/common" +) + +var _ vm.PrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +// PrecompileAddress defines the contract address of the staking precompile. +const PrecompileAddress = "0x0000000000000000000000000000000000000800" + +// Precompile defines the precompiled contract for staking. +type Precompile struct { + cmn.Precompile + stakingKeeper stakingkeeper.Keeper +} + +// LoadABI loads the staking ABI from the embedded abi.json file +// for the staking precompile. +func LoadABI() (abi.ABI, error) { + return cmn.LoadABI(f, "abi.json") +} + +// NewPrecompile creates a new staking Precompile instance as a +// PrecompiledContract interface. +func NewPrecompile( + stakingKeeper stakingkeeper.Keeper, + authzKeeper authzkeeper.Keeper, +) (*Precompile, error) { + abi, err := LoadABI() + if err != nil { + return nil, err + } + + return &Precompile{ + Precompile: cmn.Precompile{ + ABI: abi, + AuthzKeeper: authzKeeper, + KvGasConfig: storetypes.KVGasConfig(), + TransientKVGasConfig: storetypes.TransientGasConfig(), + ApprovalExpiration: cmn.DefaultExpirationDuration, // should be configurable in the future. + }, + stakingKeeper: stakingKeeper, + }, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID := input[:4] + + method, err := p.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return 0 + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +// Address defines the address of the staking compile contract. +// address: 0x0000000000000000000000000000000000000800 +func (Precompile) Address() common.Address { + return common.HexToAddress(PrecompileAddress) +} + +// Run executes the precompiled contract staking methods defined in the ABI. +func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) { + ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction) + if err != nil { + return nil, err + } + + // This handles any out of gas errors that may occur during the execution of a precompile tx or query. + // It avoids panics and returns the out of gas error so the EVM can continue gracefully. + defer cmn.HandleGasError(ctx, contract, initialGas, &err)() + + if err := stateDB.Commit(); err != nil { + return nil, err + } + + switch method.Name { + // Authorization transactions + case authorization.ApproveMethod: + bz, err = p.Approve(ctx, evm.Origin, stateDB, method, args) + case authorization.RevokeMethod: + bz, err = p.Revoke(ctx, evm.Origin, stateDB, method, args) + case authorization.IncreaseAllowanceMethod: + bz, err = p.IncreaseAllowance(ctx, evm.Origin, stateDB, method, args) + case authorization.DecreaseAllowanceMethod: + bz, err = p.DecreaseAllowance(ctx, evm.Origin, stateDB, method, args) + // Staking transactions + case CreateValidatorMethod: + bz, err = p.CreateValidator(ctx, evm.Origin, contract, stateDB, method, args) + case DelegateMethod: + bz, err = p.Delegate(ctx, evm.Origin, contract, stateDB, method, args) + case UndelegateMethod: + bz, err = p.Undelegate(ctx, evm.Origin, contract, stateDB, method, args) + case RedelegateMethod: + bz, err = p.Redelegate(ctx, evm.Origin, contract, stateDB, method, args) + case CancelUnbondingDelegationMethod: + bz, err = p.CancelUnbondingDelegation(ctx, evm.Origin, contract, stateDB, method, args) + // Staking queries + case DelegationMethod: + bz, err = p.Delegation(ctx, contract, method, args) + case UnbondingDelegationMethod: + bz, err = p.UnbondingDelegation(ctx, contract, method, args) + case ValidatorMethod: + bz, err = p.Validator(ctx, method, contract, args) + case ValidatorsMethod: + bz, err = p.Validators(ctx, method, contract, args) + case RedelegationMethod: + bz, err = p.Redelegation(ctx, method, contract, args) + case RedelegationsMethod: + bz, err = p.Redelegations(ctx, method, contract, args) + // Authorization queries + case authorization.AllowanceMethod: + bz, err = p.Allowance(ctx, method, contract, args) + } + + if err != nil { + return nil, err + } + + cost := ctx.GasMeter().GasConsumed() - initialGas + + if !contract.UseGas(cost) { + return nil, vm.ErrOutOfGas + } + + return bz, nil +} + +// IsTransaction checks if the given method name corresponds to a transaction or query. +// +// Available staking transactions are: +// - CreateValidator +// - Delegate +// - Undelegate +// - Redelegate +// - CancelUnbondingDelegation +// +// Available authorization transactions are: +// - Approve +// - Revoke +// - IncreaseAllowance +// - DecreaseAllowance +func (Precompile) IsTransaction(method string) bool { + switch method { + case CreateValidatorMethod, + DelegateMethod, + UndelegateMethod, + RedelegateMethod, + CancelUnbondingDelegationMethod, + authorization.ApproveMethod, + authorization.RevokeMethod, + authorization.IncreaseAllowanceMethod, + authorization.DecreaseAllowanceMethod: + return true + default: + return false + } +} + +// Logger returns a precompile-specific logger. +func (p Precompile) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("evm extension", "staking") +} diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go new file mode 100644 index 00000000..e5c032f2 --- /dev/null +++ b/precompiles/staking/staking_test.go @@ -0,0 +1,474 @@ +package staking_test + +import ( + "math/big" + "time" + + "cosmossdk.io/math" + "github.com/evmos/evmos/v16/app" + + "github.com/evmos/evmos/v16/precompiles/authorization" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/evmos/evmos/v16/precompiles/staking" + "github.com/evmos/evmos/v16/utils" + evmtypes "github.com/evmos/evmos/v16/x/evm/types" +) + +func (s *PrecompileTestSuite) TestIsTransaction() { + testCases := []struct { + name string + method string + isTx bool + }{ + { + authorization.ApproveMethod, + s.precompile.Methods[authorization.ApproveMethod].Name, + true, + }, + { + authorization.IncreaseAllowanceMethod, + s.precompile.Methods[authorization.IncreaseAllowanceMethod].Name, + true, + }, + { + authorization.DecreaseAllowanceMethod, + s.precompile.Methods[authorization.DecreaseAllowanceMethod].Name, + true, + }, + { + staking.CreateValidatorMethod, + s.precompile.Methods[staking.CreateValidatorMethod].Name, + true, + }, + { + staking.DelegateMethod, + s.precompile.Methods[staking.DelegateMethod].Name, + true, + }, + { + staking.UndelegateMethod, + s.precompile.Methods[staking.UndelegateMethod].Name, + true, + }, + { + staking.RedelegateMethod, + s.precompile.Methods[staking.RedelegateMethod].Name, + true, + }, + { + staking.CancelUnbondingDelegationMethod, + s.precompile.Methods[staking.CancelUnbondingDelegationMethod].Name, + true, + }, + { + staking.DelegationMethod, + s.precompile.Methods[staking.DelegationMethod].Name, + false, + }, + { + "invalid", + "invalid", + false, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.Require().Equal(s.precompile.IsTransaction(tc.method), tc.isTx) + }) + } +} + +func (s *PrecompileTestSuite) TestRequiredGas() { + testcases := []struct { + name string + malleate func() []byte + expGas uint64 + }{ + { + "success - delegate transaction with correct gas estimation", + func() []byte { + input, err := s.precompile.Pack( + staking.DelegateMethod, + s.address, + s.validators[0].GetOperator().String(), + big.NewInt(10000000000), + ) + s.Require().NoError(err) + return input + }, + 7760, + }, + { + "success - undelegate transaction with correct gas estimation", + func() []byte { + input, err := s.precompile.Pack( + staking.UndelegateMethod, + s.address, + s.validators[0].GetOperator().String(), + big.NewInt(1), + ) + s.Require().NoError(err) + return input + }, + 7760, + }, + } + + for _, tc := range testcases { + s.Run(tc.name, func() { + s.SetupTest() + + // malleate contract input + input := tc.malleate() + gas := s.precompile.RequiredGas(input) + + s.Require().Equal(gas, tc.expGas) + }) + } +} + +// TestRun tests the precompile's Run method. +func (s *PrecompileTestSuite) TestRun() { + testcases := []struct { + name string + malleate func() []byte + gas uint64 + readOnly bool + expPass bool + errContains string + }{ + { + "fail - contract gas limit is < gas cost to run a query / tx", + func() []byte { + err := s.CreateAuthorization(s.address, staking.DelegateAuthz, nil) + s.Require().NoError(err) + + input, err := s.precompile.Pack( + staking.DelegateMethod, + s.address, + s.validators[0].GetOperator().String(), + big.NewInt(1000), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 8000, + false, + false, + "out of gas", + }, + { + "pass - delegate transaction", + func() []byte { + err := s.CreateAuthorization(s.address, staking.DelegateAuthz, nil) + s.Require().NoError(err) + + input, err := s.precompile.Pack( + staking.DelegateMethod, + s.address, + s.validators[0].GetOperator().String(), + big.NewInt(1000), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + false, + true, + "", + }, + { + "pass - undelegate transaction", + func() []byte { + err := s.CreateAuthorization(s.address, staking.UndelegateAuthz, nil) + s.Require().NoError(err) + + input, err := s.precompile.Pack( + staking.UndelegateMethod, + s.address, + s.validators[0].GetOperator().String(), + big.NewInt(1), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + false, + true, + "", + }, + { + "pass - redelegate transaction", + func() []byte { + err := s.CreateAuthorization(s.address, staking.RedelegateAuthz, nil) + s.Require().NoError(err) + + input, err := s.precompile.Pack( + staking.RedelegateMethod, + s.address, + s.validators[0].GetOperator().String(), + s.validators[1].GetOperator().String(), + big.NewInt(1), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + false, + true, + "failed to redelegate tokens", + }, + { + "pass - cancel unbonding delegation transaction", + func() []byte { + // add unbonding delegation to staking keeper + ubd := stakingtypes.NewUnbondingDelegation( + s.address.Bytes(), + s.validators[0].GetOperator(), + 1000, + time.Now().Add(time.Hour), + math.NewInt(1000), + 0, + ) + s.app.StakingKeeper.SetUnbondingDelegation(s.ctx, ubd) + + err := s.CreateAuthorization(s.address, staking.CancelUnbondingDelegationAuthz, nil) + s.Require().NoError(err) + + // Needs to be called after setting unbonding delegation + // In order to mimic the coins being added to the unboding pool + coin := sdk.NewCoin(utils.BaseDenom, math.NewInt(1000)) + err = s.app.BankKeeper.SendCoinsFromModuleToModule(s.ctx, stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, sdk.Coins{coin}) + s.Require().NoError(err, "failed to send coins from module to module") + + input, err := s.precompile.Pack( + staking.CancelUnbondingDelegationMethod, + s.address, + s.validators[0].GetOperator().String(), + big.NewInt(1000), + big.NewInt(1000), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + false, + true, + "", + }, + { + "pass - delegation query", + func() []byte { + input, err := s.precompile.Pack( + staking.DelegationMethod, + s.address, + s.validators[0].GetOperator().String(), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + false, + true, + "", + }, + { + "pass - validator query", + func() []byte { + input, err := s.precompile.Pack( + staking.ValidatorMethod, + s.validators[0].OperatorAddress, + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + false, + true, + "", + }, + { + "pass - redelgation query", + func() []byte { + // add redelegation to staking keeper + redelegation := stakingtypes.NewRedelegation( + s.address.Bytes(), + s.validators[0].GetOperator(), + s.validators[1].GetOperator(), + 1000, + time.Now().Add(time.Hour), + math.NewInt(1000), + math.LegacyNewDec(1), + 0, + ) + + s.app.StakingKeeper.SetRedelegation(s.ctx, redelegation) + + input, err := s.precompile.Pack( + staking.RedelegationMethod, + s.address, + s.validators[0].GetOperator().String(), + s.validators[1].GetOperator().String(), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + false, + true, + "", + }, + { + "pass - delegation query - read only", + func() []byte { + input, err := s.precompile.Pack( + staking.DelegationMethod, + s.address, + s.validators[0].GetOperator().String(), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + true, + true, + "", + }, + { + "pass - unbonding delegation query", + func() []byte { + // add unbonding delegation to staking keeper + ubd := stakingtypes.NewUnbondingDelegation( + s.address.Bytes(), + s.validators[0].GetOperator(), + 1000, + time.Now().Add(time.Hour), + math.NewInt(1000), + 0, + ) + s.app.StakingKeeper.SetUnbondingDelegation(s.ctx, ubd) + + // Needs to be called after setting unbonding delegation + // In order to mimic the coins being added to the unboding pool + coin := sdk.NewCoin(utils.BaseDenom, math.NewInt(1000)) + err := s.app.BankKeeper.SendCoinsFromModuleToModule(s.ctx, stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, sdk.Coins{coin}) + s.Require().NoError(err, "failed to send coins from module to module") + + input, err := s.precompile.Pack( + staking.UnbondingDelegationMethod, + s.address, + s.validators[0].GetOperator().String(), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + true, + true, + "", + }, + { + "fail - delegate method - read only", + func() []byte { + input, err := s.precompile.Pack( + staking.DelegateMethod, + s.address, + s.validators[0].GetOperator().String(), + big.NewInt(1000), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 0, + true, + false, + "write protection", + }, + { + "fail - invalid method", + func() []byte { + return []byte("invalid") + }, + 0, + false, + false, + "no method with id", + }, + } + + for _, tc := range testcases { + s.Run(tc.name, func() { + // setup basic test suite + s.SetupTest() + + baseFee := s.app.FeeMarketKeeper.GetBaseFee(s.ctx) + + contract := vm.NewPrecompile(vm.AccountRef(s.address), s.precompile, big.NewInt(0), tc.gas) + contractAddr := contract.Address() + + // malleate testcase + contract.Input = tc.malleate() + + // Build and sign Ethereum transaction + txArgs := evmtypes.EvmTxArgs{ + ChainID: s.app.EvmKeeper.ChainID(), + Nonce: 0, + To: &contractAddr, + Amount: nil, + GasLimit: tc.gas, + GasPrice: app.MainnetMinGasPrices.BigInt(), + GasFeeCap: baseFee, + GasTipCap: big.NewInt(1), + Accesses: ðtypes.AccessList{}, + } + msgEthereumTx := evmtypes.NewTx(&txArgs) + + msgEthereumTx.From = s.address.String() + err := msgEthereumTx.Sign(s.ethSigner, s.signer) + s.Require().NoError(err, "failed to sign Ethereum message") + + // Instantiate config + proposerAddress := s.ctx.BlockHeader().ProposerAddress + cfg, err := s.app.EvmKeeper.EVMConfig(s.ctx, proposerAddress, s.app.EvmKeeper.ChainID()) + s.Require().NoError(err, "failed to instantiate EVM config") + + msg, err := msgEthereumTx.AsMessage(s.ethSigner, baseFee) + s.Require().NoError(err, "failed to instantiate Ethereum message") + + // Instantiate EVM + evm := s.app.EvmKeeper.NewEVM( + s.ctx, msg, cfg, nil, s.stateDB, + ) + + params := s.app.EvmKeeper.GetParams(s.ctx) + activePrecompiles := params.GetActivePrecompilesAddrs() + precompileMap := s.app.EvmKeeper.Precompiles(activePrecompiles...) + err = vm.ValidatePrecompiles(precompileMap, activePrecompiles) + s.Require().NoError(err, "invalid precompiles", activePrecompiles) + evm.WithPrecompiles(precompileMap, activePrecompiles) + + // Run precompiled contract + bz, err := s.precompile.Run(evm, contract, tc.readOnly) + + // Check results + if tc.expPass { + s.Require().NoError(err, "expected no error when running the precompile") + s.Require().NotNil(bz, "expected returned bytes not to be nil") + } else { + s.Require().Error(err, "expected error to be returned when running the precompile") + s.Require().Nil(bz, "expected returned bytes to be nil") + s.Require().ErrorContains(err, tc.errContains) + consumed := s.ctx.GasMeter().GasConsumed() + // LessThanOrEqual because the gas is consumed before the error is returned + s.Require().LessOrEqual(tc.gas, consumed, "expected gas consumed to be equal to gas limit") + + } + }) + } +} diff --git a/precompiles/staking/testdata/StakingCaller.json b/precompiles/staking/testdata/StakingCaller.json new file mode 100644 index 00000000..cbbeee53 --- /dev/null +++ b/precompiles/staking/testdata/StakingCaller.json @@ -0,0 +1,4 @@ +{ + "abi": "[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"}],\"name\":\"approveDepositAndDelegate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"}],\"name\":\"approveDepositAndDelegateExceedingAllowance\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"}],\"name\":\"approveDepositDelegateAndFailCustomLogic\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_contract\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"callERC20AndDelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"counter\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_grantee\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"method\",\"type\":\"string\"}],\"name\":\"getAllowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"allowance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"}],\"name\":\"getDelegation\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"string\",\"name\":\"denom\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"struct Coin\",\"name\":\"balance\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"_validatorSrcAddr\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_validatorDstAddr\",\"type\":\"string\"}],\"name\":\"getRedelegation\",\"outputs\":[{\"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\":\"uint256\",\"name\":\"initialBalance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"sharesDst\",\"type\":\"uint256\"}],\"internalType\":\"struct RedelegationEntry[]\",\"name\":\"entries\",\"type\":\"tuple[]\"}],\"internalType\":\"struct RedelegationOutput\",\"name\":\"redelegation\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_delegatorAddr\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"_validatorSrcAddr\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_validatorDstAddr\",\"type\":\"string\"},{\"components\":[{\"internalType\":\"bytes\",\"name\":\"key\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"offset\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"limit\",\"type\":\"uint64\"},{\"internalType\":\"bool\",\"name\":\"countTotal\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"reverse\",\"type\":\"bool\"}],\"internalType\":\"struct PageRequest\",\"name\":\"_pageRequest\",\"type\":\"tuple\"}],\"name\":\"getRedelegations\",\"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\":\"uint256\",\"name\":\"initialBalance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"sharesDst\",\"type\":\"uint256\"}],\"internalType\":\"struct RedelegationEntry[]\",\"name\":\"entries\",\"type\":\"tuple[]\"}],\"internalType\":\"struct Redelegation\",\"name\":\"redelegation\",\"type\":\"tuple\"},{\"components\":[{\"components\":[{\"internalType\":\"int64\",\"name\":\"creationHeight\",\"type\":\"int64\"},{\"internalType\":\"int64\",\"name\":\"completionTime\",\"type\":\"int64\"},{\"internalType\":\"uint256\",\"name\":\"initialBalance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"sharesDst\",\"type\":\"uint256\"}],\"internalType\":\"struct RedelegationEntry\",\"name\":\"redelegationEntry\",\"type\":\"tuple\"},{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"internalType\":\"struct RedelegationEntryResponse[]\",\"name\":\"entries\",\"type\":\"tuple[]\"}],\"internalType\":\"struct RedelegationResponse[]\",\"name\":\"response\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"bytes\",\"name\":\"nextKey\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"total\",\"type\":\"uint64\"}],\"internalType\":\"struct PageResponse\",\"name\":\"pageResponse\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"}],\"name\":\"getUnbondingDelegation\",\"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\":\"uint256\",\"name\":\"initialBalance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"uint64\",\"name\":\"unbondingId\",\"type\":\"uint64\"},{\"internalType\":\"int64\",\"name\":\"unbondingOnHoldRefCount\",\"type\":\"int64\"}],\"internalType\":\"struct UnbondingDelegationEntry[]\",\"name\":\"entries\",\"type\":\"tuple[]\"}],\"internalType\":\"struct UnbondingDelegationOutput\",\"name\":\"unbondingDelegation\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"}],\"name\":\"getValidator\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"operatorAddress\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"consensusPubkey\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"jailed\",\"type\":\"bool\"},{\"internalType\":\"enum BondStatus\",\"name\":\"status\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"tokens\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"delegatorShares\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"},{\"internalType\":\"int64\",\"name\":\"unbondingHeight\",\"type\":\"int64\"},{\"internalType\":\"int64\",\"name\":\"unbondingTime\",\"type\":\"int64\"},{\"internalType\":\"uint256\",\"name\":\"commission\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSelfDelegation\",\"type\":\"uint256\"}],\"internalType\":\"struct Validator\",\"name\":\"validator\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_status\",\"type\":\"string\"},{\"components\":[{\"internalType\":\"bytes\",\"name\":\"key\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"offset\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"limit\",\"type\":\"uint64\"},{\"internalType\":\"bool\",\"name\":\"countTotal\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"reverse\",\"type\":\"bool\"}],\"internalType\":\"struct PageRequest\",\"name\":\"_pageRequest\",\"type\":\"tuple\"}],\"name\":\"getValidators\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"operatorAddress\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"consensusPubkey\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"jailed\",\"type\":\"bool\"},{\"internalType\":\"enum BondStatus\",\"name\":\"status\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"tokens\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"delegatorShares\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"},{\"internalType\":\"int64\",\"name\":\"unbondingHeight\",\"type\":\"int64\"},{\"internalType\":\"int64\",\"name\":\"unbondingTime\",\"type\":\"int64\"},{\"internalType\":\"uint256\",\"name\":\"commission\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minSelfDelegation\",\"type\":\"uint256\"}],\"internalType\":\"struct Validator[]\",\"name\":\"validators\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"bytes\",\"name\":\"nextKey\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"total\",\"type\":\"uint64\"}],\"internalType\":\"struct PageResponse\",\"name\":\"pageResponse\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"internalType\":\"string[]\",\"name\":\"_methods\",\"type\":\"string[]\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"testApprove\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_approveAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_undelegateAmount\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"}],\"name\":\"testApproveAndThenUndelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_calltype\",\"type\":\"string\"}],\"name\":\"testCallDelegation\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"shares\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"string\",\"name\":\"denom\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"struct Coin\",\"name\":\"coin\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"_calltype\",\"type\":\"string\"}],\"name\":\"testCallUndelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_creationHeight\",\"type\":\"uint256\"}],\"name\":\"testCancelUnbonding\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"testDelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"testDelegateIncrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"_validatorSrcAddr\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_validatorDstAddr\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"testRedelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_grantee\",\"type\":\"address\"},{\"internalType\":\"string[]\",\"name\":\"_methods\",\"type\":\"string[]\"}],\"name\":\"testRevoke\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"_validatorAddr\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"testUndelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + "bin": "6080604052604051806020016040528060405180606001604052806023815260200162005c026023913981525060019060016200003e92919062000053565b503480156200004c57600080fd5b50620004a1565b828054828255906000526020600020908101928215620000a0579160200282015b828111156200009f5782518290816200008e9190620003ba565b509160200191906001019062000074565b5b509050620000af9190620000b3565b5090565b5b80821115620000d75760008181620000cd9190620000db565b50600101620000b4565b5090565b508054620000e990620001a9565b6000825580601f10620000fd57506200011e565b601f0160209004906000526020600020908101906200011d919062000121565b5b50565b5b808211156200013c57600081600090555060010162000122565b5090565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620001c257607f821691505b602082108103620001d857620001d76200017a565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620002427fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000203565b6200024e868362000203565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006200029b620002956200028f8462000266565b62000270565b62000266565b9050919050565b6000819050919050565b620002b7836200027a565b620002cf620002c682620002a2565b84845462000210565b825550505050565b600090565b620002e6620002d7565b620002f3818484620002ac565b505050565b5b818110156200031b576200030f600082620002dc565b600181019050620002f9565b5050565b601f8211156200036a576200033481620001de565b6200033f84620001f3565b810160208510156200034f578190505b620003676200035e85620001f3565b830182620002f8565b50505b505050565b600082821c905092915050565b60006200038f600019846008026200036f565b1980831691505092915050565b6000620003aa83836200037c565b9150826002028217905092915050565b620003c58262000140565b67ffffffffffffffff811115620003e157620003e06200014b565b5b620003ed8254620001a9565b620003fa8282856200031f565b600060209050601f8311600181146200043257600084156200041d578287015190505b6200042985826200039c565b86555062000499565b601f1984166200044286620001de565b60005b828110156200046c5784890151825560018201915060208501945060208101905062000445565b868310156200048c578489015162000488601f8916826200037c565b8355505b6001600288020188555050505b505050505050565b61575180620004b16000396000f3fe6080604052600436106101355760003560e01c80638939e783116100ab578063cf2753cf1161006f578063cf2753cf14610418578063ec9485df14610456578063f40a214614610493578063f5714e64146104bc578063f700dbd2146104d8578063f732b0651461050157610135565b80638939e7831461032f5780638edb3f8b146103585780638fa111a5146103745780639eab6711146103b1578063b13d4242146103da57610135565b806355dc4b22116100fd57806355dc4b2214610230578063570467ac146102595780635e269bfe1461029657806360deaa2a146102bf57806361bc221a146102db5780637e51b8111461030657610135565b80630a4433e21461013a57806319b16c4c1461016357806331bcbcb3146101a15780633566cb95146101ca578063455b8551146101f3575b600080fd5b34801561014657600080fd5b50610161600480360381019061015c919061221e565b61053f565b005b34801561016f57600080fd5b5061018a600480360381019061018591906123d3565b61060e565b604051610198929190612538565b60405180910390f35b3480156101ad57600080fd5b506101c860048036038101906101c39190612568565b610b0d565b005b3480156101d657600080fd5b506101f160048036038101906101ec91906125d7565b610d9a565b005b3480156101ff57600080fd5b5061021a60048036038101906102159190612633565b610f02565b6040516102279190612856565b60405180910390f35b34801561023c57600080fd5b5061025760048036038101906102529190612878565b610f95565b005b34801561026557600080fd5b50610280600480360381019061027b91906123d3565b611387565b60405161028d9190612a93565b60405180910390f35b3480156102a257600080fd5b506102bd60048036038101906102b89190612ab5565b61141d565b005b6102d960048036038101906102d49190612b38565b6114a8565b005b3480156102e757600080fd5b506102f06115f6565b6040516102fd9190612b81565b60405180910390f35b34801561031257600080fd5b5061032d60048036038101906103289190612568565b6115fc565b005b34801561033b57600080fd5b5061035660048036038101906103519190612568565b611684565b005b610372600480360381019061036d9190612b38565b61170c565b005b34801561038057600080fd5b5061039b60048036038101906103969190612b38565b6118a1565b6040516103a89190612d2b565b60405180910390f35b3480156103bd57600080fd5b506103d860048036038101906103d39190612d4d565b611931565b005b3480156103e657600080fd5b5061040160048036038101906103fc9190612e10565b6119bc565b60405161040f9291906130d9565b60405180910390f35b34801561042457600080fd5b5061043f600480360381019061043a9190612633565b611a54565b60405161044d929190612538565b60405180910390f35b34801561046257600080fd5b5061047d60048036038101906104789190612633565b611aec565b60405161048a9190612b81565b60405180910390f35b34801561049f57600080fd5b506104ba60048036038101906104b59190613110565b611b76565b005b6104d660048036038101906104d19190612b38565b611d52565b005b3480156104e457600080fd5b506104ff60048036038101906104fa9190613193565b611eac565b005b34801561050d57600080fd5b506105286004803603810190610523919061339e565b611f78565b6040516105369291906136b5565b60405180910390f35b600061080073ffffffffffffffffffffffffffffffffffffffff1663b6039895868487876040518563ffffffff1660e01b8152600401610582949392919061384e565b6020604051808303816000875af11580156105a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105c591906138a3565b905080610607576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105fe90613953565b60405180910390fd5b5050505050565b6000610618612016565b60006108009050600086866040516024016106349291906139ac565b6040516020818303038152906040527f241774e6000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090506000856040516020016106c69190613a18565b6040516020818303038152906040528051906020012090506040516020016106ed90613a7b565b6040516020818303038152906040528051906020012081036107d9576000808473ffffffffffffffffffffffffffffffffffffffff16846040516107319190613acc565b600060405180830381855af49150503d806000811461076c576040519150601f19603f3d011682016040523d82523d6000602084013e610771565b606091505b5091509150816107b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107ad90613b55565b60405180910390fd5b808060200190518101906107ca9190613c66565b80975081985050505050610b02565b6040516020016107e890613d0e565b6040516020818303038152906040528051906020012081036108d4576000808473ffffffffffffffffffffffffffffffffffffffff168460405161082c9190613acc565b600060405180830381855afa9150503d8060008114610867576040519150601f19603f3d011682016040523d82523d6000602084013e61086c565b606091505b5091509150816108b1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108a890613d6f565b60405180910390fd5b808060200190518101906108c59190613c66565b80975081985050505050610b01565b6040516020016108e390613ddb565b6040516020818303038152906040528051906020012081036109d1576000808473ffffffffffffffffffffffffffffffffffffffff16846040516109279190613acc565b6000604051808303816000865af19150503d8060008114610964576040519150601f19603f3d011682016040523d82523d6000602084013e610969565b606091505b5091509150816109ae576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109a590613e3c565b60405180910390fd5b808060200190518101906109c29190613c66565b80975081985050505050610b00565b6040516020016109e090613ea8565b604051602081830303815290604052805190602001208103610ac45760006040518060400160405280601a81526020017f64656c65676174696f6e28616464726573732c737472696e6729000000000000815250805190602001209050600060a490506060600060208b01516020808d0101516040518681528e6004820152604060248201526033604482015282606482015281608482015260c081878360008e5af281519c5060608201519450610100820160405280610aa057600080fd5b50505050604051806040016040528083815260200182815250975050505050610aff565b6040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610af690613f09565b60405180910390fd5b5b5b5b505050935093915050565b600061080073ffffffffffffffffffffffffffffffffffffffff1663b6039895308460016040518463ffffffff1660e01b8152600401610b4f939291906140d7565b6020604051808303816000875af1158015610b6e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b9291906138a3565b905080610bd4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610bcb90614161565b60405180910390fd5b60008473ffffffffffffffffffffffffffffffffffffffff163384604051602401610c00929190614181565b6040516020818303038152906040527fa9059cbb000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c8a9190613acc565b6000604051808303816000865af19150503d8060008114610cc7576040519150601f19603f3d011682016040523d82523d6000602084013e610ccc565b606091505b5050905080610d10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d07906141f6565b60405180910390fd5b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb3386866040518463ffffffff1660e01b8152600401610d4f93929190614216565b6020604051808303816000875af1158015610d6e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d9291906138a3565b505050505050565b600061080073ffffffffffffffffffffffffffffffffffffffff1663b6039895308460016040518463ffffffff1660e01b8152600401610ddc939291906140d7565b6020604051808303816000875af1158015610dfb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e1f91906138a3565b905080610e61576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e58906142a0565b60405180910390fd5b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb3085856040518463ffffffff1660e01b8152600401610ea093929190614216565b6020604051808303816000875af1158015610ebf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ee391906138a3565b506001600080828254610ef691906142ef565b92505081905550505050565b610f0a612030565b61080073ffffffffffffffffffffffffffffffffffffffff1663a03ffee184846040518363ffffffff1660e01b8152600401610f479291906139ac565b600060405180830381865afa158015610f64573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f82011682018060405250810190610f8d919061457f565b905092915050565b600061080090506000858585604051602401610fb393929190614216565b6040516020818303038152906040527f3edab33c000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090506000836040516020016110459190613a18565b60405160208183030381529060405280519060200120905060405160200161106c90613a7b565b6040516020818303038152906040528051906020012081036111395760008373ffffffffffffffffffffffffffffffffffffffff16836040516110af9190613acc565b600060405180830381855af49150503d80600081146110ea576040519150601f19603f3d011682016040523d82523d6000602084013e6110ef565b606091505b5050905080611133576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161112a90613b55565b60405180910390fd5b5061137e565b60405160200161114890613d0e565b6040516020818303038152906040528051906020012081036112155760008373ffffffffffffffffffffffffffffffffffffffff168360405161118b9190613acc565b600060405180830381855afa9150503d80600081146111c6576040519150601f19603f3d011682016040523d82523d6000602084013e6111cb565b606091505b505090508061120f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161120690613d6f565b60405180910390fd5b5061137d565b60405160200161122490613ddb565b6040516020818303038152906040528051906020012081036112f35760008373ffffffffffffffffffffffffffffffffffffffff16836040516112679190613acc565b6000604051808303816000865af19150503d80600081146112a4576040519150601f19603f3d011682016040523d82523d6000602084013e6112a9565b606091505b50509050806112ed576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112e490613e3c565b60405180910390fd5b5061137c565b60405160200161130290613ea8565b6040516020818303038152906040528051906020012081036113405760208201825160008082846000895af28061133857600080fd5b50505061137b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161137290613f09565b60405180910390fd5b5b5b5b50505050505050565b61138f612051565b61080073ffffffffffffffffffffffffffffffffffffffff16637d9f939c8585856040518463ffffffff1660e01b81526004016113ce939291906145c8565b600060405180830381865afa1580156113eb573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906114149190614830565b90509392505050565b61080073ffffffffffffffffffffffffffffffffffffffff166312d58dfe858585856040518563ffffffff1660e01b815260040161145e9493929190614879565b6020604051808303816000875af115801561147d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114a191906138a3565b5050505050565b600061080073ffffffffffffffffffffffffffffffffffffffff1663b6039895303460016040518463ffffffff1660e01b81526004016114ea939291906140d7565b6020604051808303816000875af1158015611509573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061152d91906138a3565b90508061156f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161156690614911565b60405180910390fd5b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb3084346040518463ffffffff1660e01b81526004016115ae93929190614216565b6020604051808303816000875af11580156115cd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115f191906138a3565b505050565b60005481565b61080073ffffffffffffffffffffffffffffffffffffffff16633edab33c8484846040518463ffffffff1660e01b815260040161163b93929190614216565b6020604051808303816000875af115801561165a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061167e9190614931565b50505050565b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb8484846040518463ffffffff1660e01b81526004016116c393929190614216565b6020604051808303816000875af11580156116e2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061170691906138a3565b50505050565b600061080073ffffffffffffffffffffffffffffffffffffffff1663b6039895323460016040518463ffffffff1660e01b815260040161174e939291906140d7565b6020604051808303816000875af115801561176d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061179191906138a3565b9050806117d3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016117ca90614911565b60405180910390fd5b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb3084346040518463ffffffff1660e01b815260040161181293929190614216565b6020604051808303816000875af1158015611831573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061185591906138a3565b503373ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f1935050505015801561189c573d6000803e3d6000fd5b505050565b6118a9612079565b61080073ffffffffffffffffffffffffffffffffffffffff16630bc82a17836040518263ffffffff1660e01b81526004016118e4919061495e565b600060405180830381865afa158015611901573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f8201168201806040525081019061192a9190614b05565b9050919050565b61080073ffffffffffffffffffffffffffffffffffffffff166354b826f5858585856040518563ffffffff1660e01b81526004016119729493929190614b4e565b6020604051808303816000875af1158015611991573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119b59190614931565b5050505050565b60606119c66120ed565b61080073ffffffffffffffffffffffffffffffffffffffff1663186b216785856040518363ffffffff1660e01b8152600401611a03929190614cff565b600060405180830381865afa158015611a20573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f82011682018060405250810190611a499190614ef3565b915091509250929050565b6000611a5e612016565b61080073ffffffffffffffffffffffffffffffffffffffff1663241774e685856040518363ffffffff1660e01b8152600401611a9b9291906139ac565b600060405180830381865afa158015611ab8573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f82011682018060405250810190611ae19190613c66565b915091509250929050565b600061080073ffffffffffffffffffffffffffffffffffffffff1663fc08930c8433856040518463ffffffff1660e01b8152600401611b2d93929190614f6b565b602060405180830381865afa158015611b4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b6e9190614fa9565b905092915050565b6000600167ffffffffffffffff811115611b9357611b926122a8565b5b604051908082528060200260200182016040528015611bc657816020015b6060815260200190600190039081611bb15790505b5090506040518060600160405280602581526020016156f76025913981600081518110611bf657611bf5614fd6565b5b6020026020010181905250600061080073ffffffffffffffffffffffffffffffffffffffff1663b60398958787856040518463ffffffff1660e01b8152600401611c42939291906150b6565b6020604051808303816000875af1158015611c61573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c8591906138a3565b905080611cc7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611cbe90615166565b60405180910390fd5b61080073ffffffffffffffffffffffffffffffffffffffff16633edab33c3285876040518463ffffffff1660e01b8152600401611d0693929190614216565b6020604051808303816000875af1158015611d25573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d499190614931565b50505050505050565b600061080073ffffffffffffffffffffffffffffffffffffffff1663b6039895323460016040518463ffffffff1660e01b8152600401611d94939291906140d7565b6020604051808303816000875af1158015611db3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dd791906138a3565b905080611e19576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e1090614911565b60405180910390fd5b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb3084600134611e4691906142ef565b6040518463ffffffff1660e01b8152600401611e6493929190614216565b6020604051808303816000875af1158015611e83573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ea791906138a3565b505050565b600061080073ffffffffffffffffffffffffffffffffffffffff166361dc5c3b8585856040518463ffffffff1660e01b8152600401611eed93929190615186565b6020604051808303816000875af1158015611f0c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f3091906138a3565b905080611f72576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f699061522a565b60405180910390fd5b50505050565b6060611f826120ed565b61080073ffffffffffffffffffffffffffffffffffffffff166310a2851c878787876040518563ffffffff1660e01b8152600401611fc394939291906152c0565b600060405180830381865afa158015611fe0573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f82011682018060405250810190612009919061567e565b9150915094509492505050565b604051806040016040528060608152602001600081525090565b60405180606001604052806060815260200160608152602001606081525090565b6040518060800160405280606081526020016060815260200160608152602001606081525090565b6040518061016001604052806060815260200160608152602001600015158152602001600060038111156120b0576120af612bb7565b5b8152602001600081526020016000815260200160608152602001600060070b8152602001600060070b815260200160008152602001600081525090565b604051806040016040528060608152602001600067ffffffffffffffff1681525090565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061215082612125565b9050919050565b61216081612145565b811461216b57600080fd5b50565b60008135905061217d81612157565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f8401126121a8576121a7612183565b5b8235905067ffffffffffffffff8111156121c5576121c4612188565b5b6020830191508360208202830111156121e1576121e061218d565b5b9250929050565b6000819050919050565b6121fb816121e8565b811461220657600080fd5b50565b600081359050612218816121f2565b92915050565b600080600080606085870312156122385761223761211b565b5b60006122468782880161216e565b945050602085013567ffffffffffffffff81111561226757612266612120565b5b61227387828801612192565b9350935050604061228687828801612209565b91505092959194509250565b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6122e082612297565b810181811067ffffffffffffffff821117156122ff576122fe6122a8565b5b80604052505050565b6000612312612111565b905061231e82826122d7565b919050565b600067ffffffffffffffff82111561233e5761233d6122a8565b5b61234782612297565b9050602081019050919050565b82818337600083830152505050565b600061237661237184612323565b612308565b90508281526020810184848401111561239257612391612292565b5b61239d848285612354565b509392505050565b600082601f8301126123ba576123b9612183565b5b81356123ca848260208601612363565b91505092915050565b6000806000606084860312156123ec576123eb61211b565b5b60006123fa8682870161216e565b935050602084013567ffffffffffffffff81111561241b5761241a612120565b5b612427868287016123a5565b925050604084013567ffffffffffffffff81111561244857612447612120565b5b612454868287016123a5565b9150509250925092565b612467816121e8565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b838110156124a757808201518184015260208101905061248c565b60008484015250505050565b60006124be8261246d565b6124c88185612478565b93506124d8818560208601612489565b6124e181612297565b840191505092915050565b6124f5816121e8565b82525050565b6000604083016000830151848203600086015261251882826124b3565b915050602083015161252d60208601826124ec565b508091505092915050565b600060408201905061254d600083018561245e565b818103602083015261255f81846124fb565b90509392505050565b6000806000606084860312156125815761258061211b565b5b600061258f8682870161216e565b935050602084013567ffffffffffffffff8111156125b0576125af612120565b5b6125bc868287016123a5565b92505060406125cd86828701612209565b9150509250925092565b600080604083850312156125ee576125ed61211b565b5b600083013567ffffffffffffffff81111561260c5761260b612120565b5b612618858286016123a5565b925050602061262985828601612209565b9150509250929050565b6000806040838503121561264a5761264961211b565b5b60006126588582860161216e565b925050602083013567ffffffffffffffff81111561267957612678612120565b5b612685858286016123a5565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60008160070b9050919050565b6126d1816126bb565b82525050565b600067ffffffffffffffff82169050919050565b6126f4816126d7565b82525050565b60c08201600082015161271060008501826126c8565b50602082015161272360208501826126c8565b50604082015161273660408501826124ec565b50606082015161274960608501826124ec565b50608082015161275c60808501826126eb565b5060a082015161276f60a08501826126c8565b50505050565b600061278183836126fa565b60c08301905092915050565b6000602082019050919050565b60006127a58261268f565b6127af818561269a565b93506127ba836126ab565b8060005b838110156127eb5781516127d28882612775565b97506127dd8361278d565b9250506001810190506127be565b5085935050505092915050565b6000606083016000830151848203600086015261281582826124b3565b9150506020830151848203602086015261282f82826124b3565b91505060408301518482036040860152612849828261279a565b9150508091505092915050565b6000602082019050818103600083015261287081846127f8565b905092915050565b600080600080608085870312156128925761289161211b565b5b60006128a08782880161216e565b945050602085013567ffffffffffffffff8111156128c1576128c0612120565b5b6128cd878288016123a5565b93505060406128de87828801612209565b925050606085013567ffffffffffffffff8111156128ff576128fe612120565b5b61290b878288016123a5565b91505092959194509250565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60808201600082015161295960008501826126c8565b50602082015161296c60208501826126c8565b50604082015161297f60408501826124ec565b50606082015161299260608501826124ec565b50505050565b60006129a48383612943565b60808301905092915050565b6000602082019050919050565b60006129c882612917565b6129d28185612922565b93506129dd83612933565b8060005b83811015612a0e5781516129f58882612998565b9750612a00836129b0565b9250506001810190506129e1565b5085935050505092915050565b60006080830160008301518482036000860152612a3882826124b3565b91505060208301518482036020860152612a5282826124b3565b91505060408301518482036040860152612a6c82826124b3565b91505060608301518482036060860152612a8682826129bd565b9150508091505092915050565b60006020820190508181036000830152612aad8184612a1b565b905092915050565b60008060008060808587031215612acf57612ace61211b565b5b6000612add8782880161216e565b945050602085013567ffffffffffffffff811115612afe57612afd612120565b5b612b0a878288016123a5565b9350506040612b1b87828801612209565b9250506060612b2c87828801612209565b91505092959194509250565b600060208284031215612b4e57612b4d61211b565b5b600082013567ffffffffffffffff811115612b6c57612b6b612120565b5b612b78848285016123a5565b91505092915050565b6000602082019050612b96600083018461245e565b92915050565b60008115159050919050565b612bb181612b9c565b82525050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60048110612bf757612bf6612bb7565b5b50565b6000819050612c0882612be6565b919050565b6000612c1882612bfa565b9050919050565b612c2881612c0d565b82525050565b6000610160830160008301518482036000860152612c4c82826124b3565b91505060208301518482036020860152612c6682826124b3565b9150506040830151612c7b6040860182612ba8565b506060830151612c8e6060860182612c1f565b506080830151612ca160808601826124ec565b5060a0830151612cb460a08601826124ec565b5060c083015184820360c0860152612ccc82826124b3565b91505060e0830151612ce160e08601826126c8565b50610100830151612cf66101008601826126c8565b50610120830151612d0b6101208601826124ec565b50610140830151612d206101408601826124ec565b508091505092915050565b60006020820190508181036000830152612d458184612c2e565b905092915050565b60008060008060808587031215612d6757612d6661211b565b5b6000612d758782880161216e565b945050602085013567ffffffffffffffff811115612d9657612d95612120565b5b612da2878288016123a5565b935050604085013567ffffffffffffffff811115612dc357612dc2612120565b5b612dcf878288016123a5565b9250506060612de087828801612209565b91505092959194509250565b600080fd5b600060a08284031215612e0757612e06612dec565b5b81905092915050565b60008060408385031215612e2757612e2661211b565b5b600083013567ffffffffffffffff811115612e4557612e44612120565b5b612e51858286016123a5565b925050602083013567ffffffffffffffff811115612e7257612e71612120565b5b612e7e85828601612df1565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6000610160830160008301518482036000860152612ed282826124b3565b91505060208301518482036020860152612eec82826124b3565b9150506040830151612f016040860182612ba8565b506060830151612f146060860182612c1f565b506080830151612f2760808601826124ec565b5060a0830151612f3a60a08601826124ec565b5060c083015184820360c0860152612f5282826124b3565b91505060e0830151612f6760e08601826126c8565b50610100830151612f7c6101008601826126c8565b50610120830151612f916101208601826124ec565b50610140830151612fa66101408601826124ec565b508091505092915050565b6000612fbd8383612eb4565b905092915050565b6000602082019050919050565b6000612fdd82612e88565b612fe78185612e93565b935083602082028501612ff985612ea4565b8060005b8581101561303557848403895281516130168582612fb1565b945061302183612fc5565b925060208a01995050600181019050612ffd565b50829750879550505050505092915050565b600081519050919050565b600082825260208201905092915050565b600061306e82613047565b6130788185613052565b9350613088818560208601612489565b61309181612297565b840191505092915050565b600060408301600083015184820360008601526130b98282613063565b91505060208301516130ce60208601826126eb565b508091505092915050565b600060408201905081810360008301526130f38185612fd2565b90508181036020830152613107818461309c565b90509392505050565b6000806000806080858703121561312a5761312961211b565b5b60006131388782880161216e565b945050602061314987828801612209565b935050604061315a87828801612209565b925050606085013567ffffffffffffffff81111561317b5761317a612120565b5b613187878288016123a5565b91505092959194509250565b6000806000604084860312156131ac576131ab61211b565b5b60006131ba8682870161216e565b935050602084013567ffffffffffffffff8111156131db576131da612120565b5b6131e786828701612192565b92509250509250925092565b600080fd5b600080fd5b600067ffffffffffffffff821115613218576132176122a8565b5b61322182612297565b9050602081019050919050565b600061324161323c846131fd565b612308565b90508281526020810184848401111561325d5761325c612292565b5b613268848285612354565b509392505050565b600082601f83011261328557613284612183565b5b813561329584826020860161322e565b91505092915050565b6132a7816126d7565b81146132b257600080fd5b50565b6000813590506132c48161329e565b92915050565b6132d381612b9c565b81146132de57600080fd5b50565b6000813590506132f0816132ca565b92915050565b600060a0828403121561330c5761330b6131f3565b5b61331660a0612308565b9050600082013567ffffffffffffffff811115613336576133356131f8565b5b61334284828501613270565b6000830152506020613356848285016132b5565b602083015250604061336a848285016132b5565b604083015250606061337e848285016132e1565b6060830152506080613392848285016132e1565b60808301525092915050565b600080600080608085870312156133b8576133b761211b565b5b60006133c68782880161216e565b945050602085013567ffffffffffffffff8111156133e7576133e6612120565b5b6133f3878288016123a5565b935050604085013567ffffffffffffffff81111561341457613413612120565b5b613420878288016123a5565b925050606085013567ffffffffffffffff81111561344157613440612120565b5b61344d878288016132f6565b91505092959194509250565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600060808301600083015184820360008601526134a282826124b3565b915050602083015184820360208601526134bc82826124b3565b915050604083015184820360408601526134d682826124b3565b915050606083015184820360608601526134f082826129bd565b9150508091505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60a08201600082015161353f6000850182612943565b50602082015161355260808501826124ec565b50505050565b60006135648383613529565b60a08301905092915050565b6000602082019050919050565b6000613588826134fd565b6135928185613508565b935061359d83613519565b8060005b838110156135ce5781516135b58882613558565b97506135c083613570565b9250506001810190506135a1565b5085935050505092915050565b600060408301600083015184820360008601526135f88282613485565b91505060208301518482036020860152613612828261357d565b9150508091505092915050565b600061362b83836135db565b905092915050565b6000602082019050919050565b600061364b82613459565b6136558185613464565b93508360208202850161366785613475565b8060005b858110156136a35784840389528151613684858261361f565b945061368f83613633565b925060208a0199505060018101905061366b565b50829750879550505050505092915050565b600060408201905081810360008301526136cf8185613640565b905081810360208301526136e3818461309c565b90509392505050565b6136f581612145565b82525050565b600082825260208201905092915050565b6000819050919050565b60006137228385612478565b935061372f838584612354565b61373883612297565b840190509392505050565b6000613750848484613716565b90509392505050565b600080fd5b600080fd5b600080fd5b6000808335600160200384360303811261378557613784613763565b5b83810192508235915060208301925067ffffffffffffffff8211156137ad576137ac613759565b5b6001820236038313156137c3576137c261375e565b5b509250929050565b6000602082019050919050565b60006137e483856136fb565b9350836020840285016137f68461370c565b8060005b8781101561383c5784840389526138118284613768565b61381c868284613743565b9550613827846137cb565b935060208b019a5050506001810190506137fa565b50829750879450505050509392505050565b600060608201905061386360008301876136ec565b613870602083018661245e565b81810360408301526138838184866137d8565b905095945050505050565b60008151905061389d816132ca565b92915050565b6000602082840312156138b9576138b861211b565b5b60006138c78482850161388e565b91505092915050565b600082825260208201905092915050565b7f4661696c656420746f20617070726f7665207374616b696e67206d6574686f6460008201527f7300000000000000000000000000000000000000000000000000000000000000602082015250565b600061393d6021836138d0565b9150613948826138e1565b604082019050919050565b6000602082019050818103600083015261396c81613930565b9050919050565b600061397e8261246d565b61398881856138d0565b9350613998818560208601612489565b6139a181612297565b840191505092915050565b60006040820190506139c160008301856136ec565b81810360208301526139d38184613973565b90509392505050565b600081905092915050565b60006139f28261246d565b6139fc81856139dc565b9350613a0c818560208601612489565b80840191505092915050565b6000613a2482846139e7565b915081905092915050565b7f64656c656761746563616c6c0000000000000000000000000000000000000000600082015250565b6000613a65600c836139dc565b9150613a7082613a2f565b600c82019050919050565b6000613a8682613a58565b9150819050919050565b600081905092915050565b6000613aa682613047565b613ab08185613a90565b9350613ac0818560208601612489565b80840191505092915050565b6000613ad88284613a9b565b915081905092915050565b7f6661696c65642064656c656761746563616c6c20746f20707265636f6d70696c60008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b6000613b3f6021836138d0565b9150613b4a82613ae3565b604082019050919050565b60006020820190508181036000830152613b6e81613b32565b9050919050565b600081519050613b84816121f2565b92915050565b6000613b9d613b9884612323565b612308565b905082815260208101848484011115613bb957613bb8612292565b5b613bc4848285612489565b509392505050565b600082601f830112613be157613be0612183565b5b8151613bf1848260208601613b8a565b91505092915050565b600060408284031215613c1057613c0f6131f3565b5b613c1a6040612308565b9050600082015167ffffffffffffffff811115613c3a57613c396131f8565b5b613c4684828501613bcc565b6000830152506020613c5a84828501613b75565b60208301525092915050565b60008060408385031215613c7d57613c7c61211b565b5b6000613c8b85828601613b75565b925050602083015167ffffffffffffffff811115613cac57613cab612120565b5b613cb885828601613bfa565b9150509250929050565b7f73746174696363616c6c00000000000000000000000000000000000000000000600082015250565b6000613cf8600a836139dc565b9150613d0382613cc2565b600a82019050919050565b6000613d1982613ceb565b9150819050919050565b7f6661696c65642073746174696363616c6c20746f20707265636f6d70696c6500600082015250565b6000613d59601f836138d0565b9150613d6482613d23565b602082019050919050565b60006020820190508181036000830152613d8881613d4c565b9050919050565b7f63616c6c00000000000000000000000000000000000000000000000000000000600082015250565b6000613dc56004836139dc565b9150613dd082613d8f565b600482019050919050565b6000613de682613db8565b9150819050919050565b7f6661696c65642063616c6c20746f20707265636f6d70696c6500000000000000600082015250565b6000613e266019836138d0565b9150613e3182613df0565b602082019050919050565b60006020820190508181036000830152613e5581613e19565b9050919050565b7f63616c6c636f6465000000000000000000000000000000000000000000000000600082015250565b6000613e926008836139dc565b9150613e9d82613e5c565b600882019050919050565b6000613eb382613e85565b9150819050919050565b7f696e76616c69642063616c6c7479706500000000000000000000000000000000600082015250565b6000613ef36010836138d0565b9150613efe82613ebd565b602082019050919050565b60006020820190508181036000830152613f2281613ee6565b9050919050565b600081549050919050565b60008190508160005260206000209050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680613f9057607f821691505b602082108103613fa357613fa2613f49565b5b50919050565b60008190508160005260206000209050919050565b60008154613fcb81613f78565b613fd58186612478565b94506001821660008114613ff0576001811461400657614039565b60ff198316865281151560200286019350614039565b61400f85613fa9565b60005b8381101561403157815481890152600182019150602081019050614012565b808801955050505b50505092915050565b600061404e8383613fbe565b905092915050565b6000600182019050919050565b600061406e82613f29565b61407881856136fb565b93508360208202850161408a85613f34565b8060005b858110156140c5578484038952816140a68582614042565b94506140b183614056565b925060208a0199505060018101905061408e565b50829750879550505050505092915050565b60006060820190506140ec60008301866136ec565b6140f9602083018561245e565b818103604083015261410b8184614063565b9050949350505050565b7f64656c65676174696f6e20617070726f76616c206661696c6564000000000000600082015250565b600061414b601a836138d0565b915061415682614115565b602082019050919050565b6000602082019050818103600083015261417a8161413e565b9050919050565b600060408201905061419660008301856136ec565b6141a3602083018461245e565b9392505050565b7f7472616e73666572206661696c65640000000000000000000000000000000000600082015250565b60006141e0600f836138d0565b91506141eb826141aa565b602082019050919050565b6000602082019050818103600083015261420f816141d3565b9050919050565b600060608201905061422b60008301866136ec565b818103602083015261423d8185613973565b905061424c604083018461245e565b949350505050565b7f5374616b696e6720417070726f7665206661696c656400000000000000000000600082015250565b600061428a6016836138d0565b915061429582614254565b602082019050919050565b600060208201905081810360008301526142b98161427d565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006142fa826121e8565b9150614305836121e8565b925082820190508082111561431d5761431c6142c0565b5b92915050565b600067ffffffffffffffff82111561433e5761433d6122a8565b5b602082029050602081019050919050565b614358816126bb565b811461436357600080fd5b50565b6000815190506143758161434f565b92915050565b60008151905061438a8161329e565b92915050565b600060c082840312156143a6576143a56131f3565b5b6143b060c0612308565b905060006143c084828501614366565b60008301525060206143d484828501614366565b60208301525060406143e884828501613b75565b60408301525060606143fc84828501613b75565b60608301525060806144108482850161437b565b60808301525060a061442484828501614366565b60a08301525092915050565b600061444361443e84614323565b612308565b90508083825260208201905060c084028301858111156144665761446561218d565b5b835b8181101561448f578061447b8882614390565b84526020840193505060c081019050614468565b5050509392505050565b600082601f8301126144ae576144ad612183565b5b81516144be848260208601614430565b91505092915050565b6000606082840312156144dd576144dc6131f3565b5b6144e76060612308565b9050600082015167ffffffffffffffff811115614507576145066131f8565b5b61451384828501613bcc565b600083015250602082015167ffffffffffffffff811115614537576145366131f8565b5b61454384828501613bcc565b602083015250604082015167ffffffffffffffff811115614567576145666131f8565b5b61457384828501614499565b60408301525092915050565b6000602082840312156145955761459461211b565b5b600082015167ffffffffffffffff8111156145b3576145b2612120565b5b6145bf848285016144c7565b91505092915050565b60006060820190506145dd60008301866136ec565b81810360208301526145ef8185613973565b905081810360408301526146038184613973565b9050949350505050565b600067ffffffffffffffff821115614628576146276122a8565b5b602082029050602081019050919050565b60006080828403121561464f5761464e6131f3565b5b6146596080612308565b9050600061466984828501614366565b600083015250602061467d84828501614366565b602083015250604061469184828501613b75565b60408301525060606146a584828501613b75565b60608301525092915050565b60006146c46146bf8461460d565b612308565b905080838252602082019050608084028301858111156146e7576146e661218d565b5b835b8181101561471057806146fc8882614639565b8452602084019350506080810190506146e9565b5050509392505050565b600082601f83011261472f5761472e612183565b5b815161473f8482602086016146b1565b91505092915050565b60006080828403121561475e5761475d6131f3565b5b6147686080612308565b9050600082015167ffffffffffffffff811115614788576147876131f8565b5b61479484828501613bcc565b600083015250602082015167ffffffffffffffff8111156147b8576147b76131f8565b5b6147c484828501613bcc565b602083015250604082015167ffffffffffffffff8111156147e8576147e76131f8565b5b6147f484828501613bcc565b604083015250606082015167ffffffffffffffff811115614818576148176131f8565b5b6148248482850161471a565b60608301525092915050565b6000602082840312156148465761484561211b565b5b600082015167ffffffffffffffff81111561486457614863612120565b5b61487084828501614748565b91505092915050565b600060808201905061488e60008301876136ec565b81810360208301526148a08186613973565b90506148af604083018561245e565b6148bc606083018461245e565b95945050505050565b7f44656c656761746520417070726f7665206661696c6564000000000000000000600082015250565b60006148fb6017836138d0565b9150614906826148c5565b602082019050919050565b6000602082019050818103600083015261492a816148ee565b9050919050565b6000602082840312156149475761494661211b565b5b600061495584828501614366565b91505092915050565b600060208201905081810360008301526149788184613973565b905092915050565b6004811061498d57600080fd5b50565b60008151905061499f81614980565b92915050565b600061016082840312156149bc576149bb6131f3565b5b6149c7610160612308565b9050600082015167ffffffffffffffff8111156149e7576149e66131f8565b5b6149f384828501613bcc565b600083015250602082015167ffffffffffffffff811115614a1757614a166131f8565b5b614a2384828501613bcc565b6020830152506040614a378482850161388e565b6040830152506060614a4b84828501614990565b6060830152506080614a5f84828501613b75565b60808301525060a0614a7384828501613b75565b60a08301525060c082015167ffffffffffffffff811115614a9757614a966131f8565b5b614aa384828501613bcc565b60c08301525060e0614ab784828501614366565b60e083015250610100614acc84828501614366565b61010083015250610120614ae284828501613b75565b61012083015250610140614af884828501613b75565b6101408301525092915050565b600060208284031215614b1b57614b1a61211b565b5b600082015167ffffffffffffffff811115614b3957614b38612120565b5b614b45848285016149a5565b91505092915050565b6000608082019050614b6360008301876136ec565b8181036020830152614b758186613973565b90508181036040830152614b898185613973565b9050614b98606083018461245e565b95945050505050565b60008083356001602003843603038112614bbe57614bbd613763565b5b83810192508235915060208301925067ffffffffffffffff821115614be657614be5613759565b5b600182023603831315614bfc57614bfb61375e565b5b509250929050565b6000614c108385613052565b9350614c1d838584612354565b614c2683612297565b840190509392505050565b6000614c4060208401846132b5565b905092915050565b6000614c5760208401846132e1565b905092915050565b600060a08301614c726000840184614ba1565b8583036000870152614c85838284614c04565b92505050614c966020840184614c31565b614ca360208601826126eb565b50614cb16040840184614c31565b614cbe60408601826126eb565b50614ccc6060840184614c48565b614cd96060860182612ba8565b50614ce76080840184614c48565b614cf46080860182612ba8565b508091505092915050565b60006040820190508181036000830152614d198185613973565b90508181036020830152614d2d8184614c5f565b90509392505050565b600067ffffffffffffffff821115614d5157614d506122a8565b5b602082029050602081019050919050565b6000614d75614d7084614d36565b612308565b90508083825260208201905060208402830185811115614d9857614d9761218d565b5b835b81811015614ddf57805167ffffffffffffffff811115614dbd57614dbc612183565b5b808601614dca89826149a5565b85526020850194505050602081019050614d9a565b5050509392505050565b600082601f830112614dfe57614dfd612183565b5b8151614e0e848260208601614d62565b91505092915050565b6000614e2a614e25846131fd565b612308565b905082815260208101848484011115614e4657614e45612292565b5b614e51848285612489565b509392505050565b600082601f830112614e6e57614e6d612183565b5b8151614e7e848260208601614e17565b91505092915050565b600060408284031215614e9d57614e9c6131f3565b5b614ea76040612308565b9050600082015167ffffffffffffffff811115614ec757614ec66131f8565b5b614ed384828501614e59565b6000830152506020614ee78482850161437b565b60208301525092915050565b60008060408385031215614f0a57614f0961211b565b5b600083015167ffffffffffffffff811115614f2857614f27612120565b5b614f3485828601614de9565b925050602083015167ffffffffffffffff811115614f5557614f54612120565b5b614f6185828601614e87565b9150509250929050565b6000606082019050614f8060008301866136ec565b614f8d60208301856136ec565b8181036040830152614f9f8184613973565b9050949350505050565b600060208284031215614fbf57614fbe61211b565b5b6000614fcd84828501613b75565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600081519050919050565b6000819050602082019050919050565b600061502c83836124b3565b905092915050565b6000602082019050919050565b600061504c82615005565b61505681856136fb565b93508360208202850161506885615010565b8060005b858110156150a457848403895281516150858582615020565b945061509083615034565b925060208a0199505060018101905061506c565b50829750879550505050505092915050565b60006060820190506150cb60008301866136ec565b6150d8602083018561245e565b81810360408301526150ea8184615041565b9050949350505050565b7f6661696c656420746f20617070726f766520756e64656c65676174696f6e206d60008201527f6574686f64000000000000000000000000000000000000000000000000000000602082015250565b60006151506025836138d0565b915061515b826150f4565b604082019050919050565b6000602082019050818103600083015261517f81615143565b9050919050565b600060408201905061519b60008301866136ec565b81810360208301526151ae8184866137d8565b9050949350505050565b7f4661696c656420746f207265766f6b6520617070726f76616c20666f7220737460008201527f616b696e67206d6574686f647300000000000000000000000000000000000000602082015250565b6000615214602d836138d0565b915061521f826151b8565b604082019050919050565b6000602082019050818103600083015261524381615207565b9050919050565b600060a08301600083015184820360008601526152678282613063565b915050602083015161527c60208601826126eb565b50604083015161528f60408601826126eb565b5060608301516152a26060860182612ba8565b5060808301516152b56080860182612ba8565b508091505092915050565b60006080820190506152d560008301876136ec565b81810360208301526152e78186613973565b905081810360408301526152fb8185613973565b9050818103606083015261530f818461524a565b905095945050505050565b600067ffffffffffffffff821115615335576153346122a8565b5b602082029050602081019050919050565b60006080828403121561535c5761535b6131f3565b5b6153666080612308565b9050600082015167ffffffffffffffff811115615386576153856131f8565b5b61539284828501613bcc565b600083015250602082015167ffffffffffffffff8111156153b6576153b56131f8565b5b6153c284828501613bcc565b602083015250604082015167ffffffffffffffff8111156153e6576153e56131f8565b5b6153f284828501613bcc565b604083015250606082015167ffffffffffffffff811115615416576154156131f8565b5b6154228482850161471a565b60608301525092915050565b600067ffffffffffffffff821115615449576154486122a8565b5b602082029050602081019050919050565b600060a082840312156154705761546f6131f3565b5b61547a6040612308565b9050600061548a84828501614639565b600083015250608061549e84828501613b75565b60208301525092915050565b60006154bd6154b88461542e565b612308565b90508083825260208201905060a084028301858111156154e0576154df61218d565b5b835b8181101561550957806154f5888261545a565b84526020840193505060a0810190506154e2565b5050509392505050565b600082601f83011261552857615527612183565b5b81516155388482602086016154aa565b91505092915050565b600060408284031215615557576155566131f3565b5b6155616040612308565b9050600082015167ffffffffffffffff811115615581576155806131f8565b5b61558d84828501615346565b600083015250602082015167ffffffffffffffff8111156155b1576155b06131f8565b5b6155bd84828501615513565b60208301525092915050565b60006155dc6155d78461531a565b612308565b905080838252602082019050602084028301858111156155ff576155fe61218d565b5b835b8181101561564657805167ffffffffffffffff81111561562457615623612183565b5b8086016156318982615541565b85526020850194505050602081019050615601565b5050509392505050565b600082601f83011261566557615664612183565b5b81516156758482602086016155c9565b91505092915050565b600080604083850312156156955761569461211b565b5b600083015167ffffffffffffffff8111156156b3576156b2612120565b5b6156bf85828601615650565b925050602083015167ffffffffffffffff8111156156e0576156df612120565b5b6156ec85828601614e87565b915050925092905056fe2f636f736d6f732e7374616b696e672e763162657461312e4d7367556e64656c6567617465a26469706673582212209f5ff7a1cd36ef190a842d0328bc0ab39b30a165eafcd11a415c09348da2a2d464736f6c634300081200332f636f736d6f732e7374616b696e672e763162657461312e4d736744656c6567617465" +} diff --git a/precompiles/staking/testdata/StakingCaller.sol b/precompiles/staking/testdata/StakingCaller.sol new file mode 100644 index 00000000..a1f08562 --- /dev/null +++ b/precompiles/staking/testdata/StakingCaller.sol @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.17; + +import "../StakingI.sol" as staking; + +/// @title StakingCaller +/// @author Evmos Core Team +/// @dev This contract is used to test external contract calls to the staking precompile. +contract StakingCaller { + /// counter is used to test the state persistence bug, when EVM and Cosmos state were both changed in the same function. + uint256 public counter; + string[] private delegateMethod = [staking.MSG_DELEGATE]; + + /// @dev This function calls the staking precompile's approve method. + /// @param _addr The address to approve. + /// @param _methods The methods to approve. + function testApprove( + address _addr, + string[] calldata _methods, + uint256 _amount + ) public { + bool success = staking.STAKING_CONTRACT.approve( + _addr, + _amount, + _methods + ); + require(success, "Failed to approve staking methods"); + } + + /// @dev This function calls the staking precompile's revoke method. + /// @param _grantee The address that was approved to spend the funds. + /// @param _methods The methods to revoke. + function testRevoke( + address _grantee, + string[] calldata _methods + ) public { + bool success = staking.STAKING_CONTRACT.revoke( + _grantee, + _methods + ); + require(success, "Failed to revoke approval for staking methods"); + } + + /// @dev This function calls the staking precompile's delegate method. + /// @param _addr The address to approve. + /// @param _validatorAddr The validator address to delegate to. + /// @param _amount The amount to delegate. + function testDelegate( + address _addr, + string memory _validatorAddr, + uint256 _amount + ) public { + staking.STAKING_CONTRACT.delegate(_addr, _validatorAddr, _amount); + } + + /// @dev This function calls the staking precompile's undelegate method. + /// @param _addr The address to approve. + /// @param _validatorAddr The validator address to delegate to. + /// @param _amount The amount to delegate. + function testUndelegate( + address _addr, + string memory _validatorAddr, + uint256 _amount + ) public { + staking.STAKING_CONTRACT.undelegate(_addr, _validatorAddr, _amount); + } + + /// @dev This function calls the staking precompile's redelegate method. + /// @param _addr The address to approve. + /// @param _validatorSrcAddr The validator address to delegate from. + /// @param _validatorDstAddr The validator address to delegate to. + /// @param _amount The amount to delegate. + function testRedelegate( + address _addr, + string memory _validatorSrcAddr, + string memory _validatorDstAddr, + uint256 _amount + ) public { + staking.STAKING_CONTRACT.redelegate( + _addr, + _validatorSrcAddr, + _validatorDstAddr, + _amount + ); + } + + /// @dev This function calls the staking precompile's cancel unbonding delegation method. + /// @param _addr The address to approve. + /// @param _validatorAddr The validator address to delegate from. + /// @param _amount The amount to delegate. + /// @param _creationHeight The creation height of the unbonding delegation. + function testCancelUnbonding( + address _addr, + string memory _validatorAddr, + uint256 _amount, + uint256 _creationHeight + ) public { + staking.STAKING_CONTRACT.cancelUnbondingDelegation( + _addr, + _validatorAddr, + _amount, + _creationHeight + ); + } + + /// @dev This function calls the staking precompile's allowance query method. + /// @param _grantee The address that received the grant. + /// @param method The method to query. + /// @return allowance The allowance. + function getAllowance( + address _grantee, + string memory method + ) public view returns (uint256 allowance) { + return staking.STAKING_CONTRACT.allowance(_grantee, msg.sender, method); + } + + /// @dev This function calls the staking precompile's validator query method. + /// @param _validatorAddr The validator address to query. + /// @return validator The validator. + function getValidator( + string memory _validatorAddr + ) public view returns (staking.Validator memory validator) { + return staking.STAKING_CONTRACT.validator(_validatorAddr); + } + + /// @dev This function calls the staking precompile's validators query method. + /// @param _status The status of the validators to query. + /// @param _pageRequest The page request to query. + /// @return validators The validators. + /// @return pageResponse The page response. + function getValidators( + string memory _status, + staking.PageRequest calldata _pageRequest + ) + public + view + returns ( + staking.Validator[] memory validators, + staking.PageResponse memory pageResponse + ) + { + return staking.STAKING_CONTRACT.validators(_status, _pageRequest); + } + + /// @dev This function calls the staking precompile's delegation query method. + /// @param _addr The address to approve. + /// @param _validatorAddr The validator address to delegate from. + /// @return shares The shares of the delegation. + /// @return balance The balance of the delegation. + function getDelegation( + address _addr, + string memory _validatorAddr + ) public view returns (uint256 shares, staking.Coin memory balance) { + return staking.STAKING_CONTRACT.delegation(_addr, _validatorAddr); + } + + /// @dev This function calls the staking precompile's redelegations query method. + /// @param _addr The address to approve. + /// @param _validatorSrcAddr The validator address to delegate from. + /// @param _validatorDstAddr The validator address to delegate to. + /// @return redelegation The redelegation output. + function getRedelegation( + address _addr, + string memory _validatorSrcAddr, + string memory _validatorDstAddr + ) public view returns (staking.RedelegationOutput memory redelegation) { + return + staking.STAKING_CONTRACT.redelegation( + _addr, + _validatorSrcAddr, + _validatorDstAddr + ); + } + + /// @dev This function calls the staking precompile's redelegations query method. + /// @param _delegatorAddr The delegator address. + /// @param _validatorSrcAddr The validator address to delegate from. + /// @param _validatorDstAddr The validator address to delegate to. + /// @param _pageRequest The page request to query. + /// @return response The redelegation response. + function getRedelegations( + address _delegatorAddr, + string memory _validatorSrcAddr, + string memory _validatorDstAddr, + staking.PageRequest memory _pageRequest + ) + public + view + returns ( + staking.RedelegationResponse[] memory response, + staking.PageResponse memory pageResponse + ) + { + return + staking.STAKING_CONTRACT.redelegations( + _delegatorAddr, + _validatorSrcAddr, + _validatorDstAddr, + _pageRequest + ); + } + + /// @dev This function calls the staking precompile's unbonding delegation query method. + /// @param _addr The address to approve. + /// @param _validatorAddr The validator address to delegate from. + /// @return unbondingDelegation The unbonding delegation output. + function getUnbondingDelegation( + address _addr, + string memory _validatorAddr + ) public view returns (staking.UnbondingDelegationOutput memory unbondingDelegation) { + return + staking.STAKING_CONTRACT.unbondingDelegation(_addr, _validatorAddr); + } + + /// @dev This function calls the staking precompile's approve method to grant approval for an undelegation. + /// Next, the undelegate method is called to execute an unbonding. + /// @param _addr The address to approve. + /// @param _approveAmount The amount to approve. + /// @param _undelegateAmount The amount to undelegate. + /// @param _validatorAddr The validator address to delegate from. + function testApproveAndThenUndelegate( + address _addr, + uint256 _approveAmount, + uint256 _undelegateAmount, + string memory _validatorAddr + ) public { + string[] memory approvedMethods = new string[](1); + approvedMethods[0] = staking.MSG_UNDELEGATE; + bool success = staking.STAKING_CONTRACT.approve( + _addr, + _approveAmount, + approvedMethods + ); + require(success, "failed to approve undelegation method"); + staking.STAKING_CONTRACT.undelegate( + tx.origin, + _validatorAddr, + _undelegateAmount + ); + } + + /// @dev This function is used to test the behaviour when executing transactions using special function calling opcodes, + /// like call, delegatecall, staticcall, and callcode. + /// @param _addr The address to approve. + /// @param _validatorAddr The validator address to delegate from. + /// @param _amount The amount to undelegate. + /// @param _calltype The opcode to use. + function testCallUndelegate( + address _addr, + string memory _validatorAddr, + uint256 _amount, + string memory _calltype + ) public { + address calledContractAddress = staking.STAKING_PRECOMPILE_ADDRESS; + bytes memory payload = abi.encodeWithSignature( + "undelegate(address,string,uint256)", + _addr, + _validatorAddr, + _amount + ); + bytes32 calltypeHash = keccak256(abi.encodePacked(_calltype)); + + if (calltypeHash == keccak256(abi.encodePacked("delegatecall"))) { + (bool success, ) = calledContractAddress.delegatecall(payload); + require(success, "failed delegatecall to precompile"); + } else if (calltypeHash == keccak256(abi.encodePacked("staticcall"))) { + (bool success, ) = calledContractAddress.staticcall(payload); + require(success, "failed staticcall to precompile"); + } else if (calltypeHash == keccak256(abi.encodePacked("call"))) { + (bool success, ) = calledContractAddress.call(payload); + require(success, "failed call to precompile"); + } else if (calltypeHash == keccak256(abi.encodePacked("callcode"))) { + // NOTE: callcode is deprecated and now only available via inline assembly + assembly { + // Load the function signature and argument data onto the stack + let ptr := add(payload, 0x20) + let len := mload(payload) + + // Invoke the contract at calledContractAddress in the context of the current contract + // using CALLCODE opcode and the loaded function signature and argument data + let success := callcode( + gas(), + calledContractAddress, + 0, + ptr, + len, + 0, + 0 + ) + + // Check if the call was successful and revert the transaction if it failed + if iszero(success) { + revert(0, 0) + } + } + } else { + revert("invalid calltype"); + } + } + + /// @dev This function is used to test the behaviour when executing queries using special function calling opcodes, + /// like call, delegatecall, staticcall, and callcode. + /// @param _addr The address of the delegator. + /// @param _validatorAddr The validator address to query for. + /// @param _calltype The opcode to use. + function testCallDelegation( + address _addr, + string memory _validatorAddr, + string memory _calltype + ) public returns (uint256 shares, staking.Coin memory coin) { + address calledContractAddress = staking.STAKING_PRECOMPILE_ADDRESS; + bytes memory payload = abi.encodeWithSignature( + "delegation(address,string)", + _addr, + _validatorAddr + ); + bytes32 calltypeHash = keccak256(abi.encodePacked(_calltype)); + + if (calltypeHash == keccak256(abi.encodePacked("delegatecall"))) { + (bool success, bytes memory data) = calledContractAddress + .delegatecall(payload); + require(success, "failed delegatecall to precompile"); + (shares, coin) = abi.decode(data, (uint256, staking.Coin)); + } else if (calltypeHash == keccak256(abi.encodePacked("staticcall"))) { + (bool success, bytes memory data) = calledContractAddress + .staticcall(payload); + require(success, "failed staticcall to precompile"); + (shares, coin) = abi.decode(data, (uint256, staking.Coin)); + } else if (calltypeHash == keccak256(abi.encodePacked("call"))) { + (bool success, bytes memory data) = calledContractAddress.call( + payload + ); + require(success, "failed call to precompile"); + (shares, coin) = abi.decode(data, (uint256, staking.Coin)); + } else if (calltypeHash == keccak256(abi.encodePacked("callcode"))) { + //Function signature + bytes4 sig = bytes4(keccak256(bytes("delegation(address,string)"))); + // Length of the input data is 164 bytes on 32bytes chunks: + // Memory location + // 0 - 4 byte signature x + // 1 - 0x0000..address x + 0x04 + // 2 - 0x0000..00 x + 0x24 + // 3 - 0x40..0000 x + 0x44 + // 4 - val_addr_chunk1 x + 0x64 + // 5 - val_addr_chunk2..000 x + 0x84 + uint256 len = 164; + // Coin type includes denom & amount + // need to get these separately from the bytes response + string memory denom; + uint256 amt; + + // NOTE: callcode is deprecated and now only available via inline assembly + assembly { + let chunk1 := mload(add(_validatorAddr, 32)) // first 32 bytes of validator address string + let chunk2 := mload(add(add(_validatorAddr, 32), 32)) // remaining 19 bytes of val address string + + // Load the function signature and argument data onto the stack + let x := mload(0x40) // Find empty storage location using "free memory pointer" + mstore(x, sig) // Place function signature at begining of empty storage + mstore(add(x, 0x04), _addr) // Place the address (input param) after the function sig + mstore(add(x, 0x24), 0x40) // These are needed for + mstore(add(x, 0x44), 0x33) // bytes unpacking + mstore(add(x, 0x64), chunk1) // Place the validator address in 2 chunks (input param) + mstore(add(x, 0x84), chunk2) // because mstore stores 32bytes + + // Invoke the contract at calledContractAddress in the context of the current contract + // using CALLCODE opcode and the loaded function signature and argument data + let success := callcode( + gas(), + calledContractAddress, // to addr + 0, // no value + x, // inputs are stored at location x + len, // inputs length + x, //store output over input (saves space) + 0xC0 // output length for this call + ) + + // output length for this call is 192 bytes splitted on these 32 bytes chunks: + // 1 - 0x00..amt -> @ 0x40 + // 2 - 0x000..00 -> @ 0x60 + // 3 - 0x40..000 -> @ 0x80 + // 4 - 0x00..amt -> @ 0xC0 + // 5 - 0x00..denom -> @ 0xE0 TODO: cannot get the return value + + shares := mload(x) // Assign shares output value - 32 bytes long + amt := mload(add(x, 0x60)) // Assign output value to c - 64 bytes long (string & uint256) + + mstore(0x40, add(x, 0x100)) // Set storage pointer to empty space + + // Check if the call was successful and revert the transaction if it failed + if iszero(success) { + revert(0, 0) + } + } + coin = staking.Coin(denom, amt); // NOTE: this is returning a blank denom because unpacking the denom is not straightforward and hasn't been solved, which is okay for this generic test case + } else { + revert("invalid calltype"); + } + + return (shares, coin); + } + + /// @dev This function showcased, that there was a bug in the EVM implementation, that occured when + /// Cosmos state is modified in the same transaction as state information inside + /// the EVM. + /// @param _validatorAddr Address of the validator to delegate to + /// @param _amount Amount to delegate + function testDelegateIncrementCounter( + string memory _validatorAddr, + uint256 _amount + ) public { + bool successStk = staking.STAKING_CONTRACT.approve( + address(this), + _amount, + delegateMethod + ); + require(successStk, "Staking Approve failed"); + staking.STAKING_CONTRACT.delegate( + address(this), + _validatorAddr, + _amount + ); + counter += 1; + } + + /// @dev This function showcases the possibility to deposit into the contract + /// and immediately delegate to a validator using the same balance in the same transaction. + function approveDepositAndDelegate(string memory _validatorAddr) payable public { + bool successTx = staking.STAKING_CONTRACT.approve( + address(this), + msg.value, + delegateMethod + ); + require(successTx, "Delegate Approve failed"); + staking.STAKING_CONTRACT.delegate( + address(this), + _validatorAddr, + msg.value + ); + } + + /// @dev This function is suppose to fail because the amount to delegate is + /// higher than the amount approved. + function approveDepositAndDelegateExceedingAllowance(string memory _validatorAddr) payable public { + bool successTx = staking.STAKING_CONTRACT.approve( + tx.origin, + msg.value, + delegateMethod + ); + require(successTx, "Delegate Approve failed"); + staking.STAKING_CONTRACT.delegate( + address(this), + _validatorAddr, + msg.value + 1 + ); + } + + /// @dev This function is suppose to fail because the amount to delegate is + /// higher than the amount approved. + function approveDepositDelegateAndFailCustomLogic(string memory _validatorAddr) payable public { + bool successTx = staking.STAKING_CONTRACT.approve( + tx.origin, + msg.value, + delegateMethod + ); + require(successTx, "Delegate Approve failed"); + staking.STAKING_CONTRACT.delegate( + address(this), + _validatorAddr, + msg.value + ); + // This should fail since the balance is already spent in the previous call + payable(msg.sender).transfer(msg.value); + } + + /// @dev This function is used to check that both the cosmos and evm state are correctly + /// updated for a successful transaction or reverted for a failed transaction. + /// To test this, deploy an ERC20 token contract to chain and mint some tokens to this + /// contract's address. + /// This contract will then transfer some tokens to the msg.sender address as well as + /// set up a delegation approval and stake funds using the staking EVM extension. + /// @param _contract Address of the ERC20 to call + /// @param _validatorAddr Address of the validator to delegate to + function callERC20AndDelegate( + address _contract, + string memory _validatorAddr, + uint256 _amount + ) public { + bool successApprove = staking.STAKING_CONTRACT.approve( + address(this), + _amount, + delegateMethod + ); + require(successApprove, "delegation approval failed"); + + (bool success, ) = _contract.call( + abi.encodeWithSignature("transfer(address,uint256)", msg.sender, _amount) + ); + require(success, "transfer failed"); + + staking.STAKING_CONTRACT.delegate( + msg.sender, + _validatorAddr, + _amount + ); + } +} diff --git a/precompiles/staking/testdata/staking_caller.go b/precompiles/staking/testdata/staking_caller.go new file mode 100644 index 00000000..8545296f --- /dev/null +++ b/precompiles/staking/testdata/staking_caller.go @@ -0,0 +1,30 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package testdata + +import ( + _ "embed" // embed compiled smart contract + "encoding/json" + + evmtypes "github.com/evmos/evmos/v16/x/evm/types" +) + +var ( + //go:embed StakingCaller.json + StakingCallerJSON []byte + + // StakingCallerContract is the compiled contract calling the staking precompile + StakingCallerContract evmtypes.CompiledContract +) + +func init() { + err := json.Unmarshal(StakingCallerJSON, &StakingCallerContract) + if err != nil { + panic(err) + } + + if len(StakingCallerContract.Bin) == 0 { + panic("failed to load smart contract that calls staking precompile") + } +} diff --git a/precompiles/staking/tx.go b/precompiles/staking/tx.go new file mode 100644 index 00000000..b4fed3d0 --- /dev/null +++ b/precompiles/staking/tx.go @@ -0,0 +1,422 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package staking + +import ( + "fmt" + "math/big" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/evmos/evmos/v16/precompiles/authorization" + "github.com/evmos/evmos/v16/x/evm/statedb" +) + +const ( + // CreateValidatorMethod defines the ABI method name for the staking create validator transaction + CreateValidatorMethod = "createValidator" + // DelegateMethod defines the ABI method name for the staking Delegate + // transaction. + DelegateMethod = "delegate" + // UndelegateMethod defines the ABI method name for the staking Undelegate + // transaction. + UndelegateMethod = "undelegate" + // RedelegateMethod defines the ABI method name for the staking Redelegate + // transaction. + RedelegateMethod = "redelegate" + // CancelUnbondingDelegationMethod defines the ABI method name for the staking + // CancelUnbondingDelegation transaction. + CancelUnbondingDelegationMethod = "cancelUnbondingDelegation" +) + +const ( + // DelegateAuthz defines the authorization type for the staking Delegate + DelegateAuthz = stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE + // UndelegateAuthz defines the authorization type for the staking Undelegate + UndelegateAuthz = stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE + // RedelegateAuthz defines the authorization type for the staking Redelegate + RedelegateAuthz = stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE + // CancelUnbondingDelegationAuthz defines the authorization type for the staking + CancelUnbondingDelegationAuthz = stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION +) + +// CreateValidator performs create validator. +func (p Precompile) CreateValidator( + ctx sdk.Context, + origin common.Address, + _ *vm.Contract, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + msg, delegatorHexAddr, err := NewMsgCreateValidator(args, p.stakingKeeper.BondDenom(ctx)) + if err != nil { + return nil, err + } + + p.Logger(ctx).Debug( + "tx called", + "method", method.Name, + "commission", msg.Commission.String(), + "min_self_delegation", msg.MinSelfDelegation.String(), + "delegator_address", delegatorHexAddr.String(), + "validator_address", msg.ValidatorAddress, + "pubkey", msg.Pubkey.String(), + "value", msg.Value.Amount.String(), + ) + + // we only allow the tx signer "origin" to create their own validator. + if origin != delegatorHexAddr { + return nil, fmt.Errorf(ErrDifferentOriginFromDelegator, origin.String(), delegatorHexAddr.String()) + } + + // Execute the transaction using the message server + msgSrv := stakingkeeper.NewMsgServerImpl(&p.stakingKeeper) + if _, err = msgSrv.CreateValidator(sdk.WrapSDKContext(ctx), msg); err != nil { + return nil, err + } + + // Emit the event for the delegate transaction + if err = p.EmitCreateValidatorEvent(ctx, stateDB, msg, delegatorHexAddr); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +// Delegate performs a delegation of coins from a delegator to a validator. +func (p Precompile) Delegate( + ctx sdk.Context, + origin common.Address, + contract *vm.Contract, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + msg, delegatorHexAddr, err := NewMsgDelegate(args, p.stakingKeeper.BondDenom(ctx)) + if err != nil { + return nil, err + } + + p.Logger(ctx).Debug( + "tx called", + "method", method.Name, + "args", fmt.Sprintf( + "{ delegator_address: %s, validator_address: %s, amount: %s }", + delegatorHexAddr, + msg.ValidatorAddress, + msg.Amount.Amount, + ), + ) + + var ( + // stakeAuthz is the authorization grant for the caller and the delegator address + stakeAuthz *stakingtypes.StakeAuthorization + // expiration is the expiration time of the authorization grant + expiration *time.Time + + // isCallerOrigin is true when the contract caller is the same as the origin + isCallerOrigin = contract.CallerAddress == origin + // isCallerDelegator is true when the contract caller is the same as the delegator + isCallerDelegator = contract.CallerAddress == delegatorHexAddr + ) + + // The provided delegator address should always be equal to the origin address. + // In case the contract caller address is the same as the delegator address provided, + // update the delegator address to be equal to the origin address. + // Otherwise, if the provided delegator address is different from the origin address, + // return an error because is a forbidden operation + if isCallerDelegator { + delegatorHexAddr = origin + } else if origin != delegatorHexAddr { + return nil, fmt.Errorf(ErrDifferentOriginFromDelegator, origin.String(), delegatorHexAddr.String()) + } + + // no need to have authorization when the contract caller is the same as origin (owner of funds) + if !isCallerOrigin { + // Check if the authorization grant exists for the caller and the origin + stakeAuthz, expiration, err = authorization.CheckAuthzAndAllowanceForGranter(ctx, p.AuthzKeeper, contract.CallerAddress, delegatorHexAddr, &msg.Amount, DelegateMsg) + if err != nil { + return nil, err + } + } + + // Execute the transaction using the message server + msgSrv := stakingkeeper.NewMsgServerImpl(&p.stakingKeeper) + if _, err = msgSrv.Delegate(sdk.WrapSDKContext(ctx), msg); err != nil { + p.Logger(ctx).Error("failed to delegate", "error", err) + return nil, err + } + + // Only update the authorization if the contract caller is different from the origin + if !isCallerOrigin { + if err := p.UpdateStakingAuthorization(ctx, contract.CallerAddress, delegatorHexAddr, stakeAuthz, expiration, DelegateMsg, msg); err != nil { + return nil, err + } + } + + // TODO: recheck whether this is necessary + // Emit the event for the delegate transaction + // if err = p.EmitDelegateEvent(ctx, stateDB, msg, delegatorHexAddr); err != nil { + // return nil, err + // } + + // NOTE: This ensures that the changes in the bank keeper are correctly mirrored to the EVM stateDB. + // This prevents the stateDB from overwriting the changed balance in the bank keeper when committing the EVM state. + if isCallerDelegator { + var convertedAmount big.Int + convertedAmount.Mul(msg.Amount.Amount.BigInt(), big.NewInt(1e12)) + p.Logger(ctx).Info("Subtracting balance from the delegator", "address", contract.CallerAddress, "amount", convertedAmount) + stateDB.(*statedb.StateDB).SubBalance(contract.CallerAddress, &convertedAmount) + } + + return method.Outputs.Pack(true) +} + +// Undelegate performs the undelegation of coins from a validator for a delegate. +// The provided amount cannot be negative. This is validated in the msg.ValidateBasic() function. +func (p Precompile) Undelegate( + ctx sdk.Context, + origin common.Address, + contract *vm.Contract, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + msg, delegatorHexAddr, err := NewMsgUndelegate(args, p.stakingKeeper.BondDenom(ctx)) + if err != nil { + return nil, err + } + + p.Logger(ctx).Debug( + "tx called", + "method", method.Name, + "args", fmt.Sprintf( + "{ delegator_address: %s, validator_address: %s, amount: %s }", + delegatorHexAddr, + msg.ValidatorAddress, + msg.Amount.Amount, + ), + ) + + var ( + // stakeAuthz is the authorization grant for the caller and the delegator address + stakeAuthz *stakingtypes.StakeAuthorization + // expiration is the expiration time of the authorization grant + expiration *time.Time + + // isCallerOrigin is true when the contract caller is the same as the origin + isCallerOrigin = contract.CallerAddress == origin + // isCallerDelegator is true when the contract caller is the same as the delegator + isCallerDelegator = contract.CallerAddress == delegatorHexAddr + ) + + // The provided delegator address should always be equal to the origin address. + // In case the contract caller address is the same as the delegator address provided, + // update the delegator address to be equal to the origin address. + // Otherwise, if the provided delegator address is different from the origin address, + // return an error because is a forbidden operation + if isCallerDelegator { + delegatorHexAddr = origin + } else if origin != delegatorHexAddr { + return nil, fmt.Errorf(ErrDifferentOriginFromDelegator, origin.String(), delegatorHexAddr.String()) + } + + // no need to have authorization when the contract caller is the same as origin (owner of funds) + if !isCallerOrigin { + // Check if the authorization grant exists for the caller and the origin + stakeAuthz, expiration, err = authorization.CheckAuthzAndAllowanceForGranter(ctx, p.AuthzKeeper, contract.CallerAddress, delegatorHexAddr, &msg.Amount, UndelegateMsg) + if err != nil { + return nil, err + } + } + + // Execute the transaction using the message server + msgSrv := stakingkeeper.NewMsgServerImpl(&p.stakingKeeper) + res, err := msgSrv.Undelegate(sdk.WrapSDKContext(ctx), msg) + if err != nil { + return nil, err + } + + // Only update the authorization if the contract caller is different from the origin + if !isCallerOrigin { + if err := p.UpdateStakingAuthorization(ctx, contract.CallerAddress, delegatorHexAddr, stakeAuthz, expiration, UndelegateMsg, msg); err != nil { + return nil, err + } + } + + // TODO: recheck whether this is necessary + // Emit the event for the undelegate transaction + // if err = p.EmitUnbondEvent(ctx, stateDB, msg, delegatorHexAddr, res.CompletionTime.UTC().Unix()); err != nil { + // return nil, err + // } + + return method.Outputs.Pack(res.CompletionTime.UTC().Unix()) +} + +// Redelegate performs a redelegation of coins for a delegate from a source validator +// to a destination validator. +// The provided amount cannot be negative. This is validated in the msg.ValidateBasic() function. +func (p Precompile) Redelegate( + ctx sdk.Context, + origin common.Address, + contract *vm.Contract, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + msg, delegatorHexAddr, err := NewMsgRedelegate(args, p.stakingKeeper.BondDenom(ctx)) + if err != nil { + return nil, err + } + + p.Logger(ctx).Debug( + "tx called", + "method", method.Name, + "args", fmt.Sprintf( + "{ delegator_address: %s, validator_src_address: %s, validator_dst_address: %s, amount: %s }", + delegatorHexAddr, + msg.ValidatorSrcAddress, + msg.ValidatorDstAddress, + msg.Amount.Amount, + ), + ) + + var ( + // stakeAuthz is the authorization grant for the caller and the delegator address + stakeAuthz *stakingtypes.StakeAuthorization + // expiration is the expiration time of the authorization grant + expiration *time.Time + + // isCallerOrigin is true when the contract caller is the same as the origin + isCallerOrigin = contract.CallerAddress == origin + // isCallerDelegator is true when the contract caller is the same as the delegator + isCallerDelegator = contract.CallerAddress == delegatorHexAddr + ) + + // The provided delegator address should always be equal to the origin address. + // In case the contract caller address is the same as the delegator address provided, + // update the delegator address to be equal to the origin address. + // Otherwise, if the provided delegator address is different from the origin address, + // return an error because is a forbidden operation + if isCallerDelegator { + delegatorHexAddr = origin + } else if origin != delegatorHexAddr { + return nil, fmt.Errorf(ErrDifferentOriginFromDelegator, origin.String(), delegatorHexAddr.String()) + } + + // no need to have authorization when the contract caller is the same as origin (owner of funds) + if !isCallerOrigin { + // Check if the authorization grant exists for the caller and the origin + stakeAuthz, expiration, err = authorization.CheckAuthzAndAllowanceForGranter(ctx, p.AuthzKeeper, contract.CallerAddress, delegatorHexAddr, &msg.Amount, RedelegateMsg) + if err != nil { + return nil, err + } + } + + msgSrv := stakingkeeper.NewMsgServerImpl(&p.stakingKeeper) + res, err := msgSrv.BeginRedelegate(sdk.WrapSDKContext(ctx), msg) + if err != nil { + return nil, err + } + + // Only update the authorization if the contract caller is different from the origin + if !isCallerOrigin { + if err := p.UpdateStakingAuthorization(ctx, contract.CallerAddress, delegatorHexAddr, stakeAuthz, expiration, RedelegateMsg, msg); err != nil { + return nil, err + } + } + + // TODO: recheck whether this is necessary + // if err = p.EmitRedelegateEvent(ctx, stateDB, msg, delegatorHexAddr, res.CompletionTime.UTC().Unix()); err != nil { + // return nil, err + // } + + return method.Outputs.Pack(res.CompletionTime.UTC().Unix()) +} + +// CancelUnbondingDelegation will cancel the unbonding of a delegation and delegate +// back to the validator being unbonded from. +// The provided amount cannot be negative. This is validated in the msg.ValidateBasic() function. +func (p Precompile) CancelUnbondingDelegation( + ctx sdk.Context, + origin common.Address, + contract *vm.Contract, + stateDB vm.StateDB, + method *abi.Method, + args []interface{}, +) ([]byte, error) { + msg, delegatorHexAddr, err := NewMsgCancelUnbondingDelegation(args, p.stakingKeeper.BondDenom(ctx)) + if err != nil { + return nil, err + } + + p.Logger(ctx).Debug( + "tx called", + "method", method.Name, + "args", fmt.Sprintf( + "{ delegator_address: %s, validator_address: %s, amount: %s, creation_height: %d }", + delegatorHexAddr, + msg.ValidatorAddress, + msg.Amount.Amount, + msg.CreationHeight, + ), + ) + + var ( + // stakeAuthz is the authorization grant for the caller and the delegator address + stakeAuthz *stakingtypes.StakeAuthorization + // expiration is the expiration time of the authorization grant + expiration *time.Time + + // isCallerOrigin is true when the contract caller is the same as the origin + isCallerOrigin = contract.CallerAddress == origin + // isCallerDelegator is true when the contract caller is the same as the delegator + isCallerDelegator = contract.CallerAddress == delegatorHexAddr + ) + + // The provided delegator address should always be equal to the origin address. + // In case the contract caller address is the same as the delegator address provided, + // update the delegator address to be equal to the origin address. + // Otherwise, if the provided delegator address is different from the origin address, + // return an error because is a forbidden operation + if isCallerDelegator { + delegatorHexAddr = origin + } else if origin != delegatorHexAddr { + return nil, fmt.Errorf(ErrDifferentOriginFromDelegator, origin.String(), delegatorHexAddr.String()) + } + + // no need to have authorization when the contract caller is the same as origin (owner of funds) + if !isCallerOrigin { + // Check if the authorization grant exists for the caller and the origin + stakeAuthz, expiration, err = authorization.CheckAuthzAndAllowanceForGranter(ctx, p.AuthzKeeper, contract.CallerAddress, delegatorHexAddr, &msg.Amount, CancelUnbondingDelegationMsg) + if err != nil { + return nil, err + } + } + + msgSrv := stakingkeeper.NewMsgServerImpl(&p.stakingKeeper) + if _, err = msgSrv.CancelUnbondingDelegation(sdk.WrapSDKContext(ctx), msg); err != nil { + return nil, err + } + + // Only update the authorization if the contract caller is different from the origin + if !isCallerOrigin { + if err := p.UpdateStakingAuthorization(ctx, contract.CallerAddress, delegatorHexAddr, stakeAuthz, expiration, CancelUnbondingDelegationMsg, msg); err != nil { + return nil, err + } + } + + // TODO: recheck whether this is necessary + // if err = p.EmitCancelUnbondingDelegationEvent(ctx, stateDB, msg, delegatorHexAddr); err != nil { + // return nil, err + // } + + return method.Outputs.Pack(true) +} diff --git a/precompiles/staking/tx_test.go b/precompiles/staking/tx_test.go new file mode 100644 index 00000000..187f7273 --- /dev/null +++ b/precompiles/staking/tx_test.go @@ -0,0 +1,972 @@ +package staking_test + +import ( + "encoding/base64" + "fmt" + "math/big" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + geth "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + cmn "github.com/evmos/evmos/v16/precompiles/common" + "github.com/evmos/evmos/v16/precompiles/staking" + "github.com/evmos/evmos/v16/precompiles/testutil" + evmosutiltx "github.com/evmos/evmos/v16/testutil/tx" +) + +func (s *PrecompileTestSuite) TestCreateValidator() { + var ( + method = s.precompile.Methods[staking.CreateValidatorMethod] + description = staking.Description{ + Moniker: "node0", + Identity: "", + Website: "", + SecurityContact: "", + Details: "", + } + commission = staking.Commission{ + Rate: math.LegacyOneDec().BigInt(), + MaxRate: math.LegacyOneDec().BigInt(), + MaxChangeRate: math.LegacyOneDec().BigInt(), + } + minSelfDelegation = big.NewInt(1) + delegatorAddress = s.address + validatorAddress = sdk.ValAddress(s.address.Bytes()).String() + pubkey = "nfJ0axJC9dhta1MAE1EBFaVdxxkYzxYrBaHuJVjG//M=" + value = big.NewInt(1205000000000000000) + ) + + testCases := []struct { + name string + malleate func() []interface{} + gas uint64 + postCheck func(data []byte) + expError bool + errContains string + }{ + { + "fail - empty input args", + func() []interface{} { + return []interface{}{} + }, + 200000, + func(data []byte) {}, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 7, 0), + }, + { + "fail - different origin than delegator", + func() []interface{} { + differentAddr := evmosutiltx.GenerateAddress() + return []interface{}{ + description, + commission, + minSelfDelegation, + differentAddr, + sdk.ValAddress(differentAddr.Bytes()).String(), + pubkey, + value, + } + }, + 200000, + func(data []byte) {}, + true, + "is not the same as delegator address", + }, + { + "fail - invalid description", + func() []interface{} { + return []interface{}{ + "", + commission, + minSelfDelegation, + delegatorAddress, + validatorAddress, + pubkey, + value, + } + }, + 200000, + func(data []byte) {}, + true, + "invalid description", + }, + { + "fail - invalid commission", + func() []interface{} { + return []interface{}{ + description, + "", + minSelfDelegation, + delegatorAddress, + validatorAddress, + pubkey, + value, + } + }, + 200000, + func(data []byte) {}, + true, + "invalid commission", + }, + { + "fail - invalid min self delegation", + func() []interface{} { + return []interface{}{ + description, + commission, + "", + delegatorAddress, + validatorAddress, + pubkey, + value, + } + }, + 200000, + func(data []byte) {}, + true, + "invalid amount", + }, + { + "fail - invalid delegator address", + func() []interface{} { + return []interface{}{ + description, + commission, + minSelfDelegation, + "", + validatorAddress, + pubkey, + value, + } + }, + 200000, + func(data []byte) {}, + true, + "invalid delegator address", + }, + { + "fail - invalid validator address", + func() []interface{} { + return []interface{}{ + description, + commission, + minSelfDelegation, + delegatorAddress, + 1205, + pubkey, + value, + } + }, + 200000, + func(data []byte) {}, + true, + "invalid type for", + }, + { + "fail - invalid pubkey", + func() []interface{} { + return []interface{}{ + description, + commission, + minSelfDelegation, + delegatorAddress, + validatorAddress, + 1205, + value, + } + }, + 200000, + func(data []byte) {}, + true, + "invalid type for", + }, + { + "fail - pubkey decoding error", + func() []interface{} { + return []interface{}{ + description, + commission, + minSelfDelegation, + delegatorAddress, + validatorAddress, + "bHVrZQ=", // base64.StdEncoding.DecodeString error + value, + } + }, + 200000, + func(data []byte) {}, + true, + "illegal base64 data", + }, + { + "fail - invalid value", + func() []interface{} { + return []interface{}{ + description, + commission, + minSelfDelegation, + delegatorAddress, + validatorAddress, + pubkey, + "", + } + }, + 200000, + func(data []byte) {}, + true, + "invalid amount", + }, + { + "success", + func() []interface{} { + return []interface{}{ + description, + commission, + minSelfDelegation, + delegatorAddress, + validatorAddress, + pubkey, + value, + } + }, + 200000, + func(data []byte) { + success, err := s.precompile.Unpack(staking.CreateValidatorMethod, data) + s.Require().NoError(err) + s.Require().Equal(success[0], true) + + log := s.stateDB.Logs()[0] + s.Require().Equal(log.Address, s.precompile.Address()) + + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[staking.EventTypeCreateValidator] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), geth.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + // Check the fully unpacked event matches the one emitted + var createValidatorEvent staking.EventCreateValidator + err = cmn.UnpackLog(s.precompile.ABI, &createValidatorEvent, staking.EventTypeCreateValidator, *log) + s.Require().NoError(err) + s.Require().Equal(s.address, createValidatorEvent.DelegatorAddress) + s.Require().Equal(s.address, createValidatorEvent.ValidatorAddress) + s.Require().Equal(value, createValidatorEvent.Value) + }, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + // reset sender + delegatorAddress = s.address + validatorAddress = sdk.ValAddress(s.address.Bytes()).String() + + var contract *vm.Contract + contract, s.ctx = testutil.NewPrecompileContract(s.T(), s.ctx, s.address, s.precompile, tc.gas) + + bz, err := s.precompile.CreateValidator(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate()) + + // query the validator in the staking keeper + validator := s.app.StakingKeeper.Validator(s.ctx, s.address.Bytes()) + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + s.Require().Empty(bz) + s.Require().Nil(validator) + } else { + s.Require().NoError(err) + s.Require().NotNil(validator, "expected validator not to be nil") + tc.postCheck(bz) + + isBonded := validator.IsBonded() + s.Require().Equal(false, isBonded, "expected validator bonded to be %t; got %t", false, isBonded) + + consPubKey, err := validator.ConsPubKey() + s.Require().NoError(err) + consPubKeyBase64 := base64.StdEncoding.EncodeToString(consPubKey.Bytes()) + s.Require().Equal(pubkey, consPubKeyBase64, "expected validator pubkey to be %s; got %s", pubkey, consPubKeyBase64) + + operator := validator.GetOperator().String() + s.Require().Equal(validatorAddress, operator, "expected validator operator to be %s; got %s", validatorAddress, operator) + + commissionRate := validator.GetCommission() + s.Require().Equal(commission.Rate.String(), commissionRate.BigInt().String(), "expected validator commission rate to be %s; got %s", commission.Rate.String(), commissionRate.String()) + + valMinSelfDelegation := validator.GetMinSelfDelegation() + s.Require().Equal(minSelfDelegation.String(), valMinSelfDelegation.String(), "expected validator min self delegation to be %s; got %s", minSelfDelegation.String(), valMinSelfDelegation.String()) + + moniker := validator.GetMoniker() + s.Require().Equal(description.Moniker, moniker, "expected validator moniker to be %s; got %s", description.Moniker, moniker) + + jailed := validator.IsJailed() + s.Require().Equal(false, jailed, "expected validator jailed to be %t; got %t", false, jailed) + } + }) + } +} + +func (s *PrecompileTestSuite) TestDelegate() { + method := s.precompile.Methods[staking.DelegateMethod] + + testCases := []struct { + name string + malleate func(operatorAddress string) []interface{} + gas uint64 + expDelegationShares *big.Int + postCheck func(data []byte) + expError bool + errContains string + }{ + { + "fail - empty input args", + func(operatorAddress string) []interface{} { + return []interface{}{} + }, + 200000, + big.NewInt(0), + func(data []byte) {}, + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 3, 0), + }, + // TODO: check case if authorization does not exist + { + name: "fail - different origin than delegator", + malleate: func(operatorAddress string) []interface{} { + differentAddr := evmosutiltx.GenerateAddress() + return []interface{}{ + differentAddr, + operatorAddress, + big.NewInt(1e18), + } + }, + gas: 200000, + expError: true, + errContains: "is not the same as delegator address", + }, + { + "fail - invalid delegator address", + func(operatorAddress string) []interface{} { + return []interface{}{ + "", + operatorAddress, + big.NewInt(1), + } + }, + 200000, + big.NewInt(1), + func(data []byte) {}, + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, ""), + }, + { + "fail - invalid amount", + func(operatorAddress string) []interface{} { + return []interface{}{ + s.address, + operatorAddress, + nil, + } + }, + 200000, + big.NewInt(1), + func(data []byte) {}, + true, + fmt.Sprintf(cmn.ErrInvalidAmount, nil), + }, + { + "fail - delegation failed because of insufficient funds", + func(operatorAddress string) []interface{} { + err := s.CreateAuthorization(s.address, staking.DelegateAuthz, nil) + s.Require().NoError(err) + return []interface{}{ + s.address, + operatorAddress, + big.NewInt(9e18), + } + }, + 200000, + big.NewInt(15), + func(data []byte) {}, + true, + "insufficient funds", + }, + // TODO: adjust tests to work with authorizations (currently does not work because origin == precompile caller which needs no authorization) + // { + // "fail - delegation should not be possible to validators outside of the allow list", + // func(string) []interface{} { + // err := s.CreateAuthorization(s.address, staking.DelegateAuthz, nil) + // s.Require().NoError(err) + // + // // Create new validator --> this is not included in the authorized allow list + // testutil.CreateValidator(s.ctx, s.T(), s.privKey.PubKey(), s.app.StakingKeeper, math.NewInt(100)) + // newValAddr := sdk.ValAddress(s.address.Bytes()) + // + // return []interface{}{ + // s.address, + // newValAddr.String(), + // big.NewInt(1e18), + // } + // }, + // 200000, + // big.NewInt(15), + // func(data []byte) {}, + // true, + // "cannot delegate/undelegate", + // }, + { + "success", + func(operatorAddress string) []interface{} { + err := s.CreateAuthorization(s.address, staking.DelegateAuthz, nil) + s.Require().NoError(err) + return []interface{}{ + s.address, + operatorAddress, + big.NewInt(1e18), + } + }, + 20000, + big.NewInt(2), + func(data []byte) { + success, err := s.precompile.Unpack(staking.DelegateMethod, data) + s.Require().NoError(err) + s.Require().Equal(success[0], true) + + log := s.stateDB.Logs()[0] + s.Require().Equal(log.Address, s.precompile.Address()) + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[staking.EventTypeDelegate] + s.Require().Equal(crypto.Keccak256Hash([]byte(event.Sig)), geth.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + }, + false, + "", + }, + // TODO: adjust tests to work with authorizations (currently does not work because origin == precompile caller which needs no authorization) + // { + // "success - delegate and update the authorization for the delegator", + // func(operatorAddress string) []interface{} { + // err := s.CreateAuthorization(s.address, staking.DelegateAuthz, &sdk.Coin{Denom: utils.BaseDenom, Amount: math.NewInt(2e18)}) + // s.Require().NoError(err) + // return []interface{}{ + // s.address, + // operatorAddress, + // big.NewInt(1e18), + // } + // }, + // 20000, + // big.NewInt(2), + // func(data []byte) { + // authorization, _ := s.app.AuthzKeeper.GetAuthorization(s.ctx, s.address.Bytes(), s.address.Bytes(), staking.DelegateMsg) + // s.Require().NotNil(authorization) + // stakeAuthorization := authorization.(*stakingtypes.StakeAuthorization) + // s.Require().Equal(math.NewInt(1e18), stakeAuthorization.MaxTokens.Amount) + // }, + // false, + // "", + // }, + // { + // "success - delegate and delete the authorization for the delegator", + // func(operatorAddress string) []interface{} { + // err := s.CreateAuthorization(s.address, staking.DelegateAuthz, &sdk.Coin{Denom: utils.BaseDenom, Amount: math.NewInt(1e18)}) + // s.Require().NoError(err) + // return []interface{}{ + // s.address, + // operatorAddress, + // big.NewInt(1e18), + // } + // }, + // 20000, + // big.NewInt(2), + // func(data []byte) { + // authorization, _ := s.app.AuthzKeeper.GetAuthorization(s.ctx, s.address.Bytes(), s.address.Bytes(), staking.DelegateMsg) + // s.Require().Nil(authorization) + // }, + // false, + // "", + // }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + var contract *vm.Contract + contract, s.ctx = testutil.NewPrecompileContract(s.T(), s.ctx, s.address, s.precompile, tc.gas) + + bz, err := s.precompile.Delegate(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate(s.validators[0].OperatorAddress)) + + // query the delegation in the staking keeper + delegation := s.app.StakingKeeper.Delegation(s.ctx, s.address.Bytes(), s.validators[0].GetOperator()) + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + s.Require().Empty(bz) + s.Require().Equal(s.validators[0].DelegatorShares, delegation.GetShares()) + } else { + s.Require().NoError(err) + s.Require().NotNil(delegation, "expected delegation not to be nil") + tc.postCheck(bz) + + expDelegationAmt := math.NewIntFromBigInt(tc.expDelegationShares) + delegationAmt := delegation.GetShares().TruncateInt() + + s.Require().Equal(expDelegationAmt, delegationAmt, "expected delegation amount to be %d; got %d", expDelegationAmt, delegationAmt) + } + }) + } +} + +func (s *PrecompileTestSuite) TestUndelegate() { + method := s.precompile.Methods[staking.UndelegateMethod] + + testCases := []struct { + name string + malleate func(operatorAddress string) []interface{} + postCheck func(data []byte) + gas uint64 + expUndelegationShares *big.Int + expError bool + errContains string + }{ + { + "fail - empty input args", + func(operatorAddress string) []interface{} { + return []interface{}{} + }, + func(data []byte) {}, + 200000, + big.NewInt(0), + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 3, 0), + }, + // TODO: check case if authorization does not exist + { + name: "fail - different origin than delegator", + malleate: func(operatorAddress string) []interface{} { + differentAddr := evmosutiltx.GenerateAddress() + return []interface{}{ + differentAddr, + operatorAddress, + big.NewInt(1000000000000000000), + } + }, + gas: 200000, + expError: true, + errContains: "is not the same as delegator", + }, + { + "fail - invalid delegator address", + func(operatorAddress string) []interface{} { + return []interface{}{ + "", + operatorAddress, + big.NewInt(1), + } + }, + func(data []byte) {}, + 200000, + big.NewInt(1), + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, ""), + }, + { + "fail - invalid amount", + func(operatorAddress string) []interface{} { + return []interface{}{ + s.address, + operatorAddress, + nil, + } + }, + func(data []byte) {}, + 200000, + big.NewInt(1), + true, + fmt.Sprintf(cmn.ErrInvalidAmount, nil), + }, + { + "success", + func(operatorAddress string) []interface{} { + err := s.CreateAuthorization(s.address, staking.UndelegateAuthz, nil) + s.Require().NoError(err) + return []interface{}{ + s.address, + operatorAddress, + big.NewInt(1000000000000000000), + } + }, + func(data []byte) { + args, err := s.precompile.Unpack(staking.UndelegateMethod, data) + s.Require().NoError(err, "failed to unpack output") + s.Require().Len(args, 1) + completionTime, ok := args[0].(int64) + s.Require().True(ok, "completion time type %T", args[0]) + params := s.app.StakingKeeper.GetParams(s.ctx) + expCompletionTime := s.ctx.BlockTime().Add(params.UnbondingTime).UTC().Unix() + s.Require().Equal(expCompletionTime, completionTime) + // Check the event emitted + log := s.stateDB.Logs()[0] + s.Require().Equal(log.Address, s.precompile.Address()) + }, + 20000, + big.NewInt(1000000000000000000), + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + var contract *vm.Contract + contract, s.ctx = testutil.NewPrecompileContract(s.T(), s.ctx, s.address, s.precompile, tc.gas) + + bz, err := s.precompile.Undelegate(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate(s.validators[0].OperatorAddress)) + + // query the unbonding delegations in the staking keeper + undelegations := s.app.StakingKeeper.GetAllUnbondingDelegations(s.ctx, s.address.Bytes()) + + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + s.Require().Empty(bz) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + tc.postCheck(bz) + + bech32Addr, err := sdk.Bech32ifyAddressBytes("evmos", s.address.Bytes()) + s.Require().NoError(err) + s.Require().Equal(undelegations[0].DelegatorAddress, bech32Addr) + s.Require().Equal(undelegations[0].ValidatorAddress, s.validators[0].OperatorAddress) + s.Require().Equal(undelegations[0].Entries[0].Balance, math.NewIntFromBigInt(tc.expUndelegationShares)) + } + }) + } +} + +func (s *PrecompileTestSuite) TestRedelegate() { + method := s.precompile.Methods[staking.RedelegateMethod] + + testCases := []struct { + name string + malleate func(srcOperatorAddr, dstOperatorAddr string) []interface{} + postCheck func(data []byte) + gas uint64 + expRedelegationShares *big.Int + expError bool + errContains string + }{ + { + "fail - empty input args", + func(srcOperatorAddr, dstOperatorAddr string) []interface{} { + return []interface{}{} + }, + func(data []byte) {}, + 200000, + big.NewInt(0), + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 4, 0), + }, + // TODO: check case if authorization does not exist + { + name: "fail - different origin than delegator", + malleate: func(srcOperatorAddr, dstOperatorAddr string) []interface{} { + differentAddr := evmosutiltx.GenerateAddress() + return []interface{}{ + differentAddr, + srcOperatorAddr, + dstOperatorAddr, + big.NewInt(1000000000000000000), + } + }, + gas: 200000, + expError: true, + errContains: "is not the same as delegator", + }, + { + "fail - invalid delegator address", + func(srcOperatorAddr, dstOperatorAddr string) []interface{} { + return []interface{}{ + "", + srcOperatorAddr, + dstOperatorAddr, + big.NewInt(1), + } + }, + func(data []byte) {}, + 200000, + big.NewInt(1), + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, ""), + }, + { + "fail - invalid amount", + func(srcOperatorAddr, dstOperatorAddr string) []interface{} { + return []interface{}{ + s.address, + srcOperatorAddr, + dstOperatorAddr, + nil, + } + }, + func(data []byte) {}, + 200000, + big.NewInt(1), + true, + fmt.Sprintf(cmn.ErrInvalidAmount, nil), + }, + { + "fail - invalid shares amount", + func(srcOperatorAddr, dstOperatorAddr string) []interface{} { + return []interface{}{ + s.address, + srcOperatorAddr, + dstOperatorAddr, + big.NewInt(-1), + } + }, + func(data []byte) {}, + 200000, + big.NewInt(1), + true, + "invalid shares amount", + }, + { + "success", + func(srcOperatorAddr, dstOperatorAddr string) []interface{} { + err := s.CreateAuthorization(s.address, staking.RedelegateAuthz, nil) + s.Require().NoError(err) + return []interface{}{ + s.address, + srcOperatorAddr, + dstOperatorAddr, + big.NewInt(1000000000000000000), + } + }, + func(data []byte) { + args, err := s.precompile.Unpack(staking.RedelegateMethod, data) + s.Require().NoError(err, "failed to unpack output") + s.Require().Len(args, 1) + completionTime, ok := args[0].(int64) + s.Require().True(ok, "completion time type %T", args[0]) + params := s.app.StakingKeeper.GetParams(s.ctx) + expCompletionTime := s.ctx.BlockTime().Add(params.UnbondingTime).UTC().Unix() + s.Require().Equal(expCompletionTime, completionTime) + }, + 200000, + big.NewInt(1), + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + var contract *vm.Contract + contract, s.ctx = testutil.NewPrecompileContract(s.T(), s.ctx, s.address, s.precompile, tc.gas) + + bz, err := s.precompile.Redelegate(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate(s.validators[0].OperatorAddress, s.validators[1].OperatorAddress)) + + // query the redelegations in the staking keeper + redelegations := s.app.StakingKeeper.GetRedelegations(s.ctx, s.address.Bytes(), 5) + + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + s.Require().Empty(bz) + } else { + s.Require().NoError(err) + s.Require().NotEmpty(bz) + + bech32Addr, err := sdk.Bech32ifyAddressBytes("evmos", s.address.Bytes()) + s.Require().NoError(err) + s.Require().Equal(redelegations[0].DelegatorAddress, bech32Addr) + s.Require().Equal(redelegations[0].ValidatorSrcAddress, s.validators[0].OperatorAddress) + s.Require().Equal(redelegations[0].ValidatorDstAddress, s.validators[1].OperatorAddress) + s.Require().Equal(redelegations[0].Entries[0].SharesDst, math.LegacyNewDecFromBigInt(tc.expRedelegationShares)) + } + }) + } +} + +func (s *PrecompileTestSuite) TestCancelUnbondingDelegation() { + method := s.precompile.Methods[staking.CancelUnbondingDelegationMethod] + undelegateMethod := s.precompile.Methods[staking.UndelegateMethod] + + testCases := []struct { + name string + malleate func(operatorAddress string) []interface{} + postCheck func(data []byte) + gas uint64 + expDelegatedShares *big.Int + expError bool + errContains string + }{ + { + "fail - empty input args", + func(operatorAddress string) []interface{} { + return []interface{}{} + }, + func(data []byte) {}, + 200000, + big.NewInt(0), + true, + fmt.Sprintf(cmn.ErrInvalidNumberOfArgs, 4, 0), + }, + { + "fail - invalid delegator address", + func(operatorAddress string) []interface{} { + return []interface{}{ + "", + operatorAddress, + big.NewInt(1), + big.NewInt(1), + } + }, + func(data []byte) {}, + 200000, + big.NewInt(1), + true, + fmt.Sprintf(cmn.ErrInvalidDelegator, ""), + }, + { + "fail - creation height", + func(operatorAddress string) []interface{} { + return []interface{}{ + s.address, + operatorAddress, + big.NewInt(1), + nil, + } + }, + func(data []byte) {}, + 200000, + big.NewInt(1), + true, + "invalid creation height", + }, + { + "fail - invalid amount", + func(operatorAddress string) []interface{} { + return []interface{}{ + s.address, + operatorAddress, + nil, + big.NewInt(1), + } + }, + func(data []byte) {}, + 200000, + big.NewInt(1), + true, + fmt.Sprintf(cmn.ErrInvalidAmount, nil), + }, + { + "fail - invalid amount", + func(operatorAddress string) []interface{} { + return []interface{}{ + s.address, + operatorAddress, + nil, + big.NewInt(1), + } + }, + func(data []byte) {}, + 200000, + big.NewInt(1), + true, + fmt.Sprintf(cmn.ErrInvalidAmount, nil), + }, + { + "fail - invalid shares amount", + func(operatorAddress string) []interface{} { + return []interface{}{ + s.address, + operatorAddress, + big.NewInt(-1), + big.NewInt(1), + } + }, + func(data []byte) {}, + 200000, + big.NewInt(1), + true, + "invalid amount: invalid request", + }, + { + "success", + func(operatorAddress string) []interface{} { + err := s.CreateAuthorization(s.address, staking.DelegateAuthz, nil) + s.Require().NoError(err) + return []interface{}{ + s.address, + operatorAddress, + big.NewInt(1), + big.NewInt(2), + } + }, + func(data []byte) { + success, err := s.precompile.Unpack(staking.CancelUnbondingDelegationMethod, data) + s.Require().NoError(err) + s.Require().Equal(success[0], true) + }, + 200000, + big.NewInt(1), + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + var contract *vm.Contract + contract, s.ctx = testutil.NewPrecompileContract(s.T(), s.ctx, s.address, s.precompile, tc.gas) + + if tc.expError { + bz, err := s.precompile.CancelUnbondingDelegation(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate(s.validators[0].OperatorAddress)) + s.Require().ErrorContains(err, tc.errContains) + s.Require().Empty(bz) + } else { + undelegateArgs := []interface{}{ + s.address, + s.validators[0].OperatorAddress, + big.NewInt(1000000000000000000), + } + + err := s.CreateAuthorization(s.address, staking.UndelegateAuthz, nil) + s.Require().NoError(err) + + _, err = s.precompile.Undelegate(s.ctx, s.address, contract, s.stateDB, &undelegateMethod, undelegateArgs) + s.Require().NoError(err) + + _, found := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), s.validators[0].GetOperator()) + s.Require().False(found) + + err = s.CreateAuthorization(s.address, staking.CancelUnbondingDelegationAuthz, nil) + s.Require().NoError(err) + + bz, err := s.precompile.CancelUnbondingDelegation(s.ctx, s.address, contract, s.stateDB, &method, tc.malleate(s.validators[0].OperatorAddress)) + s.Require().NoError(err) + tc.postCheck(bz) + + delegation, found := s.app.StakingKeeper.GetDelegation(s.ctx, s.address.Bytes(), s.validators[0].GetOperator()) + s.Require().True(found) + + bech32Addr, err := sdk.Bech32ifyAddressBytes("evmos", s.address.Bytes()) + s.Require().NoError(err) + s.Require().Equal(delegation.DelegatorAddress, bech32Addr) + s.Require().Equal(delegation.ValidatorAddress, s.validators[0].OperatorAddress) + s.Require().Equal(delegation.Shares, math.LegacyNewDecFromBigInt(tc.expDelegatedShares)) + + } + }) + } +} diff --git a/precompiles/staking/types.go b/precompiles/staking/types.go new file mode 100644 index 00000000..86c554c0 --- /dev/null +++ b/precompiles/staking/types.go @@ -0,0 +1,844 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package staking + +import ( + "encoding/base64" + "errors" + "fmt" + "math/big" + + "cosmossdk.io/math" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + 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" + cmn "github.com/evmos/evmos/v16/precompiles/common" + + "github.com/aura-nw/aura/precompiles/util" +) + +// EventCreateValidator defines the event data for the staking CreateValidator transaction. +type EventCreateValidator struct { + DelegatorAddress common.Address + ValidatorAddress common.Address + Value *big.Int +} + +// EventDelegate defines the event data for the staking Delegate transaction. +type EventDelegate struct { + DelegatorAddress common.Address + ValidatorAddress common.Address + Amount *big.Int + NewShares *big.Int +} + +// EventUnbond defines the event data for the staking Undelegate transaction. +type EventUnbond struct { + DelegatorAddress common.Address + ValidatorAddress common.Address + Amount *big.Int + CompletionTime *big.Int +} + +// EventRedelegate defines the event data for the staking Redelegate transaction. +type EventRedelegate struct { + DelegatorAddress common.Address + ValidatorSrcAddress common.Address + ValidatorDstAddress common.Address + Amount *big.Int + CompletionTime *big.Int +} + +// EventCancelUnbonding defines the event data for the staking CancelUnbond transaction. +type EventCancelUnbonding struct { + DelegatorAddress common.Address + ValidatorAddress common.Address + Amount *big.Int + CreationHeight *big.Int +} + +// Description use golang type alias defines a validator description. +type Description = struct { + Moniker string "json:\"moniker\"" + Identity string "json:\"identity\"" + Website string "json:\"website\"" + SecurityContact string "json:\"securityContact\"" + Details string "json:\"details\"" +} + +// Commission use golang type alias defines a validator commission. +// since solidity does not support decimals, after passing in the big int, convert the big int into a decimal with a precision of 18 +type Commission = struct { + Rate *big.Int "json:\"rate\"" + MaxRate *big.Int "json:\"maxRate\"" + MaxChangeRate *big.Int "json:\"maxChangeRate\"" +} + +// NewMsgCreateValidator creates a new MsgCreateValidator instance and does sanity checks +// on the given arguments before populating the message. +func NewMsgCreateValidator(args []interface{}, denom string) (*stakingtypes.MsgCreateValidator, common.Address, error) { + if len(args) != 7 { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 7, len(args)) + } + + description := stakingtypes.Description{} + if descriptionInput, ok := args[0].(Description); ok { + description.Moniker = descriptionInput.Moniker + description.Identity = descriptionInput.Identity + description.Website = descriptionInput.Website + description.SecurityContact = descriptionInput.SecurityContact + description.Details = descriptionInput.Details + } else { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidDescription, args[0]) + } + + commission := stakingtypes.CommissionRates{} + if commissionInput, ok := args[1].(Commission); ok { + commission.Rate = math.LegacyNewDecFromBigIntWithPrec(commissionInput.Rate, math.LegacyPrecision) + commission.MaxRate = math.LegacyNewDecFromBigIntWithPrec(commissionInput.MaxRate, math.LegacyPrecision) + commission.MaxChangeRate = math.LegacyNewDecFromBigIntWithPrec(commissionInput.MaxChangeRate, math.LegacyPrecision) + } else { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidCommission, args[1]) + } + + minSelfDelegation, ok := args[2].(*big.Int) + if !ok { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidAmount, args[2]) + } + + delegatorAddress, ok := args[3].(common.Address) + if !ok || delegatorAddress == (common.Address{}) { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidDelegator, args[3]) + } + + validatorAddress, ok := args[4].(string) + if !ok { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidType, "validatorAddress", "string", args[4]) + } + + // use cli `evmosd tendermint show-validator` get pubkey + pubkeyBase64Str, ok := args[5].(string) + if !ok { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidType, "pubkey", "string", args[5]) + } + pubkeyBytes, err := base64.StdEncoding.DecodeString(pubkeyBase64Str) + if err != nil { + return nil, common.Address{}, err + } + + var ed25519pk cryptotypes.PubKey = &ed25519.PubKey{Key: pubkeyBytes} + pubkey, err := codectypes.NewAnyWithValue(ed25519pk) + if err != nil { + return nil, common.Address{}, err + } + + value, ok := args[6].(*big.Int) + if !ok { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidAmount, args[6]) + } + + msg := &stakingtypes.MsgCreateValidator{ + Description: description, + Commission: commission, + MinSelfDelegation: math.NewIntFromBigInt(minSelfDelegation), + DelegatorAddress: sdk.AccAddress(delegatorAddress.Bytes()).String(), + ValidatorAddress: validatorAddress, + Pubkey: pubkey, + Value: sdk.Coin{Denom: denom, Amount: math.NewIntFromBigInt(value)}, + } + + if err := msg.ValidateBasic(); err != nil { + return nil, common.Address{}, err + } + + return msg, delegatorAddress, nil +} + +// convertAmountForAura converts the given amount to a valid amount for Aura. +// Aura has a decimal of 6, while Evmos has a decimal of 18. +// So we need to convert the amount to a valid amount for Aura. +// The amount must be divisible by 10^12. +func convertAmountForAura(amount *big.Int) (math.Int, error) { + var m big.Int + // check if amount is divisible by 10^12 + m.Mod(amount, big.NewInt(1e12)) + if m.Cmp(big.NewInt(0)) != 0 { + return math.NewInt(0), fmt.Errorf("Amount should be divisible by 10^12: %s", amount) + } + + var newAmount big.Int + newAmount.Div(amount, big.NewInt(1e12)) + + return math.NewIntFromBigInt(&newAmount), nil +} + +// NewMsgDelegate creates a new MsgDelegate instance and does sanity checks +// on the given arguments before populating the message. +func NewMsgDelegate(args []interface{}, denom string) (*stakingtypes.MsgDelegate, common.Address, error) { + delegatorAddr, validatorAddress, amount, err := checkDelegationUndelegationArgs(args) + if err != nil { + return nil, common.Address{}, err + } + + msgAmount, err := convertAmountForAura(amount) + if err != nil { + return nil, common.Address{}, err + } + + msg := &stakingtypes.MsgDelegate{ + DelegatorAddress: sdk.AccAddress(delegatorAddr.Bytes()).String(), + ValidatorAddress: validatorAddress, + Amount: sdk.Coin{ + Denom: denom, + Amount: msgAmount, + }, + } + + if err = msg.ValidateBasic(); err != nil { + return nil, common.Address{}, err + } + + return msg, delegatorAddr, nil +} + +// NewMsgUndelegate creates a new MsgUndelegate instance and does sanity checks +// on the given arguments before populating the message. +func NewMsgUndelegate(args []interface{}, denom string) (*stakingtypes.MsgUndelegate, common.Address, error) { + delegatorAddr, validatorAddress, amount, err := checkDelegationUndelegationArgs(args) + if err != nil { + return nil, common.Address{}, err + } + + msgAmount, err := convertAmountForAura(amount) + if err != nil { + return nil, common.Address{}, err + } + + msg := &stakingtypes.MsgUndelegate{ + DelegatorAddress: sdk.AccAddress(delegatorAddr.Bytes()).String(), + ValidatorAddress: validatorAddress, + Amount: sdk.Coin{ + Denom: denom, + Amount: msgAmount, + }, + } + + if err = msg.ValidateBasic(); err != nil { + return nil, common.Address{}, err + } + + return msg, delegatorAddr, nil +} + +// NewMsgRedelegate creates a new MsgRedelegate instance and does sanity checks +// on the given arguments before populating the message. +func NewMsgRedelegate(args []interface{}, denom string) (*stakingtypes.MsgBeginRedelegate, common.Address, error) { + if len(args) != 4 { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 4, len(args)) + } + + delegatorAddr, ok := args[0].(common.Address) + if !ok || delegatorAddr == (common.Address{}) { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + validatorSrcAddress, ok := args[1].(string) + if !ok { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidType, "validatorSrcAddress", "string", args[1]) + } + + validatorDstAddress, ok := args[2].(string) + if !ok { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidType, "validatorDstAddress", "string", args[2]) + } + + amount, ok := args[3].(*big.Int) + if !ok { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidAmount, args[3]) + } + + msgAmount, err := convertAmountForAura(amount) + if err != nil { + return nil, common.Address{}, err + } + + msg := &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: sdk.AccAddress(delegatorAddr.Bytes()).String(), // bech32 formatted + ValidatorSrcAddress: validatorSrcAddress, + ValidatorDstAddress: validatorDstAddress, + Amount: sdk.Coin{ + Denom: denom, + Amount: msgAmount, + }, + } + + if err := msg.ValidateBasic(); err != nil { + return nil, common.Address{}, err + } + + return msg, delegatorAddr, nil +} + +// NewMsgCancelUnbondingDelegation creates a new MsgCancelUnbondingDelegation instance and does sanity checks +// on the given arguments before populating the message. +func NewMsgCancelUnbondingDelegation(args []interface{}, denom string) (*stakingtypes.MsgCancelUnbondingDelegation, common.Address, error) { + if len(args) != 4 { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 4, len(args)) + } + + delegatorAddr, ok := args[0].(common.Address) + if !ok || delegatorAddr == (common.Address{}) { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + validatorAddress, ok := args[1].(string) + if !ok { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidType, "validatorAddress", "string", args[1]) + } + + amount, ok := args[2].(*big.Int) + if !ok { + return nil, common.Address{}, fmt.Errorf(cmn.ErrInvalidAmount, args[2]) + } + + msgAmount, err := convertAmountForAura(amount) + if err != nil { + return nil, common.Address{}, err + } + + creationHeight, ok := args[3].(*big.Int) + if !ok { + return nil, common.Address{}, fmt.Errorf("invalid creation height") + } + + msg := &stakingtypes.MsgCancelUnbondingDelegation{ + DelegatorAddress: sdk.AccAddress(delegatorAddr.Bytes()).String(), // bech32 formatted + ValidatorAddress: validatorAddress, + Amount: sdk.Coin{ + Denom: denom, + Amount: msgAmount, + }, + CreationHeight: creationHeight.Int64(), + } + + if err := msg.ValidateBasic(); err != nil { + return nil, common.Address{}, err + } + + return msg, delegatorAddr, nil +} + +// NewDelegationRequest creates a new QueryDelegationRequest instance and does sanity checks +// on the given arguments before populating the request. +func NewDelegationRequest(args []interface{}) (*stakingtypes.QueryDelegationRequest, error) { + if len(args) != 2 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 2, len(args)) + } + + delegatorAddr, ok := args[0].(common.Address) + if !ok || delegatorAddr == (common.Address{}) { + return nil, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + validatorAddress, ok := args[1].(string) + if !ok { + return nil, fmt.Errorf(cmn.ErrInvalidType, "validatorAddress", "string", args[1]) + } + + return &stakingtypes.QueryDelegationRequest{ + DelegatorAddr: sdk.AccAddress(delegatorAddr.Bytes()).String(), // bech32 formatted + ValidatorAddr: validatorAddress, + }, nil +} + +// NewValidatorRequest create a new QueryValidatorRequest instance and does sanity checks +// on the given arguments before populating the request. +func NewValidatorRequest(args []interface{}) (*stakingtypes.QueryValidatorRequest, error) { + if len(args) != 1 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 1, len(args)) + } + + validatorAddress, ok := args[0].(string) + if !ok { + return nil, fmt.Errorf(cmn.ErrInvalidType, "validatorAddress", "string", args[0]) + } + + return &stakingtypes.QueryValidatorRequest{ValidatorAddr: validatorAddress}, nil +} + +// NewValidatorsRequest create a new QueryValidatorsRequest instance and does sanity checks +// on the given arguments before populating the request. +func NewValidatorsRequest(method *abi.Method, args []interface{}) (*stakingtypes.QueryValidatorsRequest, error) { + if len(args) != 2 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 2, len(args)) + } + + var input ValidatorsInput + if err := method.Inputs.Copy(&input, args); err != nil { + return nil, fmt.Errorf("error while unpacking args to ValidatorsInput struct: %s", err) + } + + return &stakingtypes.QueryValidatorsRequest{ + Status: input.Status, + Pagination: &input.PageRequest, + }, nil +} + +// NewRedelegationRequest create a new QueryRedelegationRequest instance and does sanity checks +// on the given arguments before populating the request. +func NewRedelegationRequest(args []interface{}) (*RedelegationRequest, error) { + if len(args) != 3 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 3, len(args)) + } + + delegatorAddr, ok := args[0].(common.Address) + if !ok || delegatorAddr == (common.Address{}) { + return nil, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + validatorSrcAddress, ok := args[1].(string) + if !ok { + return nil, fmt.Errorf(cmn.ErrInvalidType, "validatorSrcAddress", "string", args[1]) + } + + validatorSrcAddr, err := sdk.ValAddressFromBech32(validatorSrcAddress) + if err != nil { + return nil, err + } + + validatorDstAddress, ok := args[2].(string) + if !ok { + return nil, fmt.Errorf(cmn.ErrInvalidType, "validatorDstAddress", "string", args[2]) + } + + validatorDstAddr, err := sdk.ValAddressFromBech32(validatorDstAddress) + if err != nil { + return nil, err + } + + return &RedelegationRequest{ + DelegatorAddress: delegatorAddr.Bytes(), // bech32 formatted + ValidatorSrcAddress: validatorSrcAddr, + ValidatorDstAddress: validatorDstAddr, + }, nil +} + +// NewRedelegationsRequest create a new QueryRedelegationsRequest instance and does sanity checks +// on the given arguments before populating the request. +func NewRedelegationsRequest(method *abi.Method, args []interface{}) (*stakingtypes.QueryRedelegationsRequest, error) { + if len(args) != 4 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 4, len(args)) + } + + // delAddr, srcValAddr & dstValAddr + // can be empty strings. The query will return the + // corresponding redelegations according to the addresses specified + // however, cannot pass all as empty strings, need to provide at least + // the delegator address or the source validator address + var input RedelegationsInput + if err := method.Inputs.Copy(&input, args); err != nil { + return nil, fmt.Errorf("error while unpacking args to RedelegationsInput struct: %s", err) + } + + var ( + // delegatorAddr is the string representation of the delegator address + delegatorAddr = "" + // emptyAddr is an empty address + emptyAddr = common.Address{}.Hex() + ) + if input.DelegatorAddress.Hex() != emptyAddr { + delegatorAddr = sdk.AccAddress(input.DelegatorAddress.Bytes()).String() // bech32 formatted + } + + if delegatorAddr == "" && input.SrcValidatorAddress == "" && input.DstValidatorAddress == "" || + delegatorAddr == "" && input.SrcValidatorAddress == "" && input.DstValidatorAddress != "" { + return nil, errors.New("invalid query. Need to specify at least a source validator address or delegator address") + } + + return &stakingtypes.QueryRedelegationsRequest{ + DelegatorAddr: delegatorAddr, // bech32 formatted + SrcValidatorAddr: input.SrcValidatorAddress, + DstValidatorAddr: input.DstValidatorAddress, + Pagination: &input.PageRequest, + }, nil +} + +// RedelegationRequest is a struct that contains the information to pass into a redelegation query. +type RedelegationRequest struct { + DelegatorAddress sdk.AccAddress + ValidatorSrcAddress sdk.ValAddress + ValidatorDstAddress sdk.ValAddress +} + +// RedelegationsRequest is a struct that contains the information to pass into a redelegations query. +type RedelegationsRequest struct { + DelegatorAddress sdk.AccAddress + MaxRetrieve int64 +} + +// UnbondingDelegationEntry is a struct that contains the information about an unbonding delegation entry. +type UnbondingDelegationEntry struct { + CreationHeight int64 + CompletionTime int64 + InitialBalance *big.Int + Balance *big.Int + UnbondingId uint64 //nolint + UnbondingOnHoldRefCount int64 +} + +// UnbondingDelegationResponse is a struct that contains the information about an unbonding delegation. +type UnbondingDelegationResponse struct { + DelegatorAddress string + ValidatorAddress string + Entries []UnbondingDelegationEntry +} + +// UnbondingDelegationOutput is the output response returned by the query method. +type UnbondingDelegationOutput struct { + UnbondingDelegation UnbondingDelegationResponse +} + +// FromResponse populates the DelegationOutput from a QueryDelegationResponse. +func (do *UnbondingDelegationOutput) FromResponse(res *stakingtypes.QueryUnbondingDelegationResponse) *UnbondingDelegationOutput { + do.UnbondingDelegation.Entries = make([]UnbondingDelegationEntry, len(res.Unbond.Entries)) + do.UnbondingDelegation.ValidatorAddress = res.Unbond.ValidatorAddress + do.UnbondingDelegation.DelegatorAddress = res.Unbond.DelegatorAddress + for i, entry := range res.Unbond.Entries { + do.UnbondingDelegation.Entries[i] = UnbondingDelegationEntry{ + UnbondingId: entry.UnbondingId, + UnbondingOnHoldRefCount: entry.UnbondingOnHoldRefCount, + CreationHeight: entry.CreationHeight, + CompletionTime: entry.CompletionTime.UTC().Unix(), + InitialBalance: util.AuraToEvmBigInt(entry.InitialBalance.BigInt()), + Balance: util.AuraToEvmBigInt(entry.Balance.BigInt()), + } + } + return do +} + +// DelegationOutput is a struct to represent the key information from +// a delegation response. +type DelegationOutput struct { + Shares *big.Int + Balance cmn.Coin +} + +// FromResponse populates the DelegationOutput from a QueryDelegationResponse. +func (do *DelegationOutput) FromResponse(res *stakingtypes.QueryDelegationResponse) *DelegationOutput { + do.Shares = res.DelegationResponse.Delegation.Shares.BigInt() + do.Balance = cmn.Coin{ + // TODO: for consistency, we will change type of balance to BigInt + // the unit will be the same as the token unit with 18 decimals + // the denom here is just for compatibility with the current implementation and should not be used + Denom: res.DelegationResponse.Balance.Denom, + Amount: util.AuraToEvmBigInt(res.DelegationResponse.Balance.Amount.BigInt()), + } + return do +} + +// Pack packs a given slice of abi arguments into a byte array. +func (do *DelegationOutput) Pack(args abi.Arguments) ([]byte, error) { + return args.Pack(do.Shares, do.Balance) +} + +// ValidatorInfo is a struct to represent the key information from +// a validator response. +type ValidatorInfo struct { + OperatorAddress string `abi:"operatorAddress"` + ConsensusPubkey string `abi:"consensusPubkey"` + Jailed bool `abi:"jailed"` + Status uint8 `abi:"status"` + Tokens *big.Int `abi:"tokens"` + DelegatorShares *big.Int `abi:"delegatorShares"` // TODO: Decimal + Description string `abi:"description"` + UnbondingHeight int64 `abi:"unbondingHeight"` + UnbondingTime int64 `abi:"unbondingTime"` + Commission *big.Int `abi:"commission"` + MinSelfDelegation *big.Int `abi:"minSelfDelegation"` +} + +type ValidatorOutput struct { + Validator ValidatorInfo +} + +// DefaultValidatorOutput returns a ValidatorOutput with default values. +func DefaultValidatorOutput() ValidatorOutput { + return ValidatorOutput{ + ValidatorInfo{ + OperatorAddress: "", + ConsensusPubkey: "", + Jailed: false, + Status: uint8(0), + Tokens: big.NewInt(0), + DelegatorShares: big.NewInt(0), + Description: "", + UnbondingHeight: int64(0), + UnbondingTime: int64(0), + Commission: big.NewInt(0), + MinSelfDelegation: big.NewInt(0), + }, + } +} + +// FromResponse populates the ValidatorOutput from a QueryValidatorResponse. +func (vo *ValidatorOutput) FromResponse(res *stakingtypes.QueryValidatorResponse) ValidatorOutput { + return ValidatorOutput{ + Validator: ValidatorInfo{ + OperatorAddress: res.Validator.OperatorAddress, + ConsensusPubkey: FormatConsensusPubkey(res.Validator.ConsensusPubkey), + Jailed: res.Validator.Jailed, + Status: uint8(stakingtypes.BondStatus_value[res.Validator.Status.String()]), + Tokens: util.AuraToEvmBigInt(res.Validator.Tokens.BigInt()), + DelegatorShares: res.Validator.DelegatorShares.BigInt(), // TODO: Decimal + // TODO: create description type, + Description: res.Validator.Description.Details, + UnbondingHeight: res.Validator.UnbondingHeight, + UnbondingTime: res.Validator.UnbondingTime.UTC().Unix(), + Commission: res.Validator.Commission.CommissionRates.Rate.BigInt(), + MinSelfDelegation: util.AuraToEvmBigInt(res.Validator.MinSelfDelegation.BigInt()), + }, + } +} + +// ValidatorsInput is a struct to represent the input information for +// the validators query. Needed to unpack arguments into the PageRequest struct. +type ValidatorsInput struct { + Status string + PageRequest query.PageRequest +} + +// ValidatorsOutput is a struct to represent the key information from +// a validators response. +type ValidatorsOutput struct { + Validators []ValidatorInfo + PageResponse query.PageResponse +} + +// FromResponse populates the ValidatorsOutput from a QueryValidatorsResponse. +func (vo *ValidatorsOutput) FromResponse(res *stakingtypes.QueryValidatorsResponse) *ValidatorsOutput { + vo.Validators = make([]ValidatorInfo, len(res.Validators)) + for i, v := range res.Validators { + vo.Validators[i] = ValidatorInfo{ + OperatorAddress: v.OperatorAddress, + ConsensusPubkey: FormatConsensusPubkey(v.ConsensusPubkey), + Jailed: v.Jailed, + Status: uint8(stakingtypes.BondStatus_value[v.Status.String()]), + Tokens: util.AuraToEvmBigInt(v.Tokens.BigInt()), + DelegatorShares: v.DelegatorShares.BigInt(), + Description: v.Description.Details, + UnbondingHeight: v.UnbondingHeight, + UnbondingTime: v.UnbondingTime.UTC().Unix(), + Commission: v.Commission.CommissionRates.Rate.BigInt(), + MinSelfDelegation: util.AuraToEvmBigInt(v.MinSelfDelegation.BigInt()), + } + } + + if res.Pagination != nil { + vo.PageResponse.Total = res.Pagination.Total + vo.PageResponse.NextKey = res.Pagination.NextKey + } + + return vo +} + +// Pack packs a given slice of abi arguments into a byte array. +func (vo *ValidatorsOutput) Pack(args abi.Arguments) ([]byte, error) { + return args.Pack(vo.Validators, vo.PageResponse) +} + +// RedelegationEntry is a struct to represent the key information from +// a redelegation entry response. +type RedelegationEntry struct { + CreationHeight int64 + CompletionTime int64 + InitialBalance *big.Int + SharesDst *big.Int +} + +// RedelegationValues is a struct to represent the key information from +// a redelegation response. +type RedelegationValues struct { + DelegatorAddress string + ValidatorSrcAddress string + ValidatorDstAddress string + Entries []RedelegationEntry +} + +// RedelegationOutput returns the output for a redelegation query. +type RedelegationOutput struct { + Redelegation RedelegationValues +} + +// FromResponse populates the RedelegationOutput from a QueryRedelegationsResponse. +func (ro *RedelegationOutput) FromResponse(res stakingtypes.Redelegation) *RedelegationOutput { + ro.Redelegation.Entries = make([]RedelegationEntry, len(res.Entries)) + ro.Redelegation.DelegatorAddress = res.DelegatorAddress + ro.Redelegation.ValidatorSrcAddress = res.ValidatorSrcAddress + ro.Redelegation.ValidatorDstAddress = res.ValidatorDstAddress + for i, entry := range res.Entries { + ro.Redelegation.Entries[i] = RedelegationEntry{ + CreationHeight: entry.CreationHeight, + CompletionTime: entry.CompletionTime.UTC().Unix(), + InitialBalance: util.AuraToEvmBigInt(entry.InitialBalance.BigInt()), + SharesDst: entry.SharesDst.BigInt(), + } + } + return ro +} + +// RedelegationEntryResponse is equivalent to a RedelegationEntry except that it +// contains a balance in addition to shares which is more suitable for client +// responses. +type RedelegationEntryResponse struct { + RedelegationEntry RedelegationEntry + Balance *big.Int +} + +// Redelegation contains the list of a particular delegator's redelegating bonds +// from a particular source validator to a particular destination validator. +type Redelegation struct { + DelegatorAddress string + ValidatorSrcAddress string + ValidatorDstAddress string + Entries []RedelegationEntry +} + +// RedelegationResponse is equivalent to a Redelegation except that its entries +// contain a balance in addition to shares which is more suitable for client +// responses. +type RedelegationResponse struct { + Redelegation Redelegation + Entries []RedelegationEntryResponse +} + +// RedelegationsInput is a struct to represent the input information for +// the redelegations query. Needed to unpack arguments into the PageRequest struct. +type RedelegationsInput struct { + DelegatorAddress common.Address + SrcValidatorAddress string + DstValidatorAddress string + PageRequest query.PageRequest +} + +// RedelegationsOutput is a struct to represent the key information from +// a redelegations response. +type RedelegationsOutput struct { + Response []RedelegationResponse + PageResponse query.PageResponse +} + +// FromResponse populates the RedelgationsOutput from a QueryRedelegationsResponse. +func (ro *RedelegationsOutput) FromResponse(res *stakingtypes.QueryRedelegationsResponse) *RedelegationsOutput { + ro.Response = make([]RedelegationResponse, len(res.RedelegationResponses)) + for i, resp := range res.RedelegationResponses { + // for each RedelegationResponse + // there's a RedelegationEntryResponse array ('Entries' field) + entries := make([]RedelegationEntryResponse, len(resp.Entries)) + for j, e := range resp.Entries { + entries[j] = RedelegationEntryResponse{ + RedelegationEntry: RedelegationEntry{ + CreationHeight: e.RedelegationEntry.CreationHeight, + CompletionTime: e.RedelegationEntry.CompletionTime.Unix(), + InitialBalance: util.AuraToEvmBigInt(e.RedelegationEntry.InitialBalance.BigInt()), + SharesDst: e.RedelegationEntry.SharesDst.BigInt(), + }, + Balance: util.AuraToEvmBigInt(e.Balance.BigInt()), + } + } + + // the Redelegation field has also an 'Entries' field of type RedelegationEntry + redelEntries := make([]RedelegationEntry, len(resp.Redelegation.Entries)) + for j, e := range resp.Redelegation.Entries { + redelEntries[j] = RedelegationEntry{ + CreationHeight: e.CreationHeight, + CompletionTime: e.CompletionTime.Unix(), + InitialBalance: util.AuraToEvmBigInt(e.InitialBalance.BigInt()), + SharesDst: e.SharesDst.BigInt(), + } + } + + ro.Response[i] = RedelegationResponse{ + Entries: entries, + Redelegation: Redelegation{ + DelegatorAddress: resp.Redelegation.DelegatorAddress, + ValidatorSrcAddress: resp.Redelegation.ValidatorSrcAddress, + ValidatorDstAddress: resp.Redelegation.ValidatorDstAddress, + Entries: redelEntries, + }, + } + } + + if res.Pagination != nil { + ro.PageResponse.Total = res.Pagination.Total + ro.PageResponse.NextKey = res.Pagination.NextKey + } + + return ro +} + +// Pack packs a given slice of abi arguments into a byte array. +func (ro *RedelegationsOutput) Pack(args abi.Arguments) ([]byte, error) { + return args.Pack(ro.Response, ro.PageResponse) +} + +// NewUnbondingDelegationRequest creates a new QueryUnbondingDelegationRequest instance and does sanity checks +// on the given arguments before populating the request. +func NewUnbondingDelegationRequest(args []interface{}) (*stakingtypes.QueryUnbondingDelegationRequest, error) { + if len(args) != 2 { + return nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 2, len(args)) + } + + delegatorAddr, ok := args[0].(common.Address) + if !ok || delegatorAddr == (common.Address{}) { + return nil, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + validatorAddress, ok := args[1].(string) + if !ok { + return nil, fmt.Errorf(cmn.ErrInvalidType, "validatorAddress", "string", args[1]) + } + + return &stakingtypes.QueryUnbondingDelegationRequest{ + DelegatorAddr: sdk.AccAddress(delegatorAddr.Bytes()).String(), // bech32 formatted + ValidatorAddr: validatorAddress, + }, nil +} + +// checkDelegationUndelegationArgs checks the arguments for the delegation and undelegation functions. +func checkDelegationUndelegationArgs(args []interface{}) (common.Address, string, *big.Int, error) { + if len(args) != 3 { + return common.Address{}, "", nil, fmt.Errorf(cmn.ErrInvalidNumberOfArgs, 3, len(args)) + } + + delegatorAddr, ok := args[0].(common.Address) + if !ok || delegatorAddr == (common.Address{}) { + return common.Address{}, "", nil, fmt.Errorf(cmn.ErrInvalidDelegator, args[0]) + } + + validatorAddress, ok := args[1].(string) + if !ok { + return common.Address{}, "", nil, fmt.Errorf(cmn.ErrInvalidType, "validatorAddress", "string", args[1]) + } + + amount, ok := args[2].(*big.Int) + if !ok { + return common.Address{}, "", nil, fmt.Errorf(cmn.ErrInvalidAmount, args[2]) + } + + return delegatorAddr, validatorAddress, amount, nil +} + +// FormatConsensusPubkey format ConsensusPubkey into a base64 string +func FormatConsensusPubkey(consensusPubkey *codectypes.Any) string { + ed25519pk, ok := consensusPubkey.GetCachedValue().(cryptotypes.PubKey) + if ok { + return base64.StdEncoding.EncodeToString(ed25519pk.Bytes()) + } + return consensusPubkey.String() +} diff --git a/precompiles/staking/utils_test.go b/precompiles/staking/utils_test.go new file mode 100644 index 00000000..98d40462 --- /dev/null +++ b/precompiles/staking/utils_test.go @@ -0,0 +1,516 @@ +package staking_test + +import ( + "encoding/json" + "math/big" + "time" + + //nolint:revive // dot imports are fine for Ginkgo + . "github.com/onsi/gomega" + + "cosmossdk.io/math" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/crypto/tmhash" + tmtypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/baseapp" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil/mock" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + evmosapp "github.com/evmos/evmos/v16/app" + "github.com/evmos/evmos/v16/precompiles/authorization" + cmn "github.com/evmos/evmos/v16/precompiles/common" + "github.com/evmos/evmos/v16/precompiles/staking" + "github.com/evmos/evmos/v16/precompiles/testutil" + "github.com/evmos/evmos/v16/precompiles/testutil/contracts" + evmosutil "github.com/evmos/evmos/v16/testutil" + testutiltx "github.com/evmos/evmos/v16/testutil/tx" + evmostypes "github.com/evmos/evmos/v16/types" + "github.com/evmos/evmos/v16/utils" + "github.com/evmos/evmos/v16/x/evm/statedb" + evmtypes "github.com/evmos/evmos/v16/x/evm/types" + inflationtypes "github.com/evmos/evmos/v16/x/inflation/v1/types" + "golang.org/x/exp/slices" +) + +// SetupWithGenesisValSet initializes a new EvmosApp with a validator set and genesis accounts +// that also act as delegators. For simplicity, each validator is bonded with a delegation +// of one consensus engine unit (10^6) in the default token of the simapp from first genesis +// account. A Nop logger is set in SimApp. +func (s *PrecompileTestSuite) SetupWithGenesisValSet(valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) { + appI, genesisState := evmosapp.SetupTestingApp(cmn.DefaultChainID)() + app, ok := appI.(*evmosapp.Evmos) + s.Require().True(ok) + + // set genesis accounts + authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) + genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis) + + validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) + delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) + + bondAmt := sdk.TokensFromConsensusPower(1, evmostypes.PowerReduction) + + for _, val := range valSet.Validators { + pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey) + s.Require().NoError(err) + pkAny, err := codectypes.NewAnyWithValue(pk) + s.Require().NoError(err) + validator := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: bondAmt, + DelegatorShares: math.LegacyOneDec(), + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()), + MinSelfDelegation: math.ZeroInt(), + } + validators = append(validators, validator) + delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), math.LegacyOneDec())) + } + s.validators = validators + + // set validators and delegations + stakingParams := stakingtypes.DefaultParams() + // set bond demon to be aevmos + stakingParams.BondDenom = utils.BaseDenom + stakingGenesis := stakingtypes.NewGenesisState(stakingParams, validators, delegations) + genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis) + + totalBondAmt := math.ZeroInt() + for range validators { + totalBondAmt = totalBondAmt.Add(bondAmt) + } + totalSupply := sdk.NewCoins() + for _, b := range balances { + // add genesis acc tokens and delegated tokens to total supply + totalSupply = totalSupply.Add(b.Coins.Add(sdk.NewCoin(utils.BaseDenom, totalBondAmt))...) + } + + // add bonded amount to bonded pool module account + balances = append(balances, banktypes.Balance{ + Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), + Coins: sdk.Coins{sdk.NewCoin(utils.BaseDenom, totalBondAmt)}, + }) + + // update total supply + bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{}, []banktypes.SendEnabled{}) + genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis) + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + s.Require().NoError(err) + + header := evmosutil.NewHeader( + 2, + time.Now().UTC(), + cmn.DefaultChainID, + sdk.ConsAddress(validators[0].GetOperator()), + tmhash.Sum([]byte("app")), + tmhash.Sum([]byte("validators")), + ) + + // init chain will set the validator set and initialize the genesis accounts + app.InitChain( + abci.RequestInitChain{ + ChainId: cmn.DefaultChainID, + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: evmosapp.DefaultConsensusParams, + AppStateBytes: stateBytes, + }, + ) + + // create Context + s.ctx = app.BaseApp.NewContext(false, header) + + // commit genesis changes + app.Commit() + app.BeginBlock(abci.RequestBeginBlock{Header: header}) + + s.app = app +} + +func (s *PrecompileTestSuite) DoSetupTest() { + nValidators := 3 + signers := make(map[string]tmtypes.PrivValidator, nValidators) + validators := make([]*tmtypes.Validator, 0, nValidators) + + for i := 0; i < nValidators; i++ { + privVal := mock.NewPV() + pubKey, err := privVal.GetPubKey() + s.Require().NoError(err) + signers[pubKey.Address().String()] = privVal + validator := tmtypes.NewValidator(pubKey, 1) + validators = append(validators, validator) + } + + valSet := tmtypes.NewValidatorSet(validators) + + // generate genesis account + addr, priv := testutiltx.NewAddrKey() + s.privKey = priv + s.address = addr + s.signer = testutiltx.NewSigner(priv) + + baseAcc := authtypes.NewBaseAccount(priv.PubKey().Address().Bytes(), priv.PubKey(), 0, 0) + + acc := &evmostypes.EthAccount{ + BaseAccount: baseAcc, + CodeHash: common.BytesToHash(evmtypes.EmptyCodeHash).Hex(), + } + + amount := sdk.TokensFromConsensusPower(5, evmostypes.PowerReduction) + + balance := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, amount)), + } + + s.SetupWithGenesisValSet(valSet, []authtypes.GenesisAccount{acc}, balance) + + // Create StateDB + s.stateDB = statedb.New(s.ctx, s.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(s.ctx.HeaderHash().Bytes()))) + + // bond denom + stakingParams := s.app.StakingKeeper.GetParams(s.ctx) + stakingParams.BondDenom = utils.BaseDenom + s.bondDenom = stakingParams.BondDenom + err := s.app.StakingKeeper.SetParams(s.ctx, stakingParams) + s.Require().NoError(err) + + s.ethSigner = ethtypes.LatestSignerForChainID(s.app.EvmKeeper.ChainID()) + + precompile, err := staking.NewPrecompile(s.app.StakingKeeper, s.app.AuthzKeeper) + s.Require().NoError(err) + s.precompile = precompile + + coins := sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(5000000000000000000))) + distrCoins := sdk.NewCoins(sdk.NewCoin(utils.BaseDenom, math.NewInt(2000000000000000000))) + err = s.app.BankKeeper.MintCoins(s.ctx, inflationtypes.ModuleName, coins) + s.Require().NoError(err) + err = s.app.BankKeeper.SendCoinsFromModuleToModule(s.ctx, inflationtypes.ModuleName, authtypes.FeeCollectorName, distrCoins) + s.Require().NoError(err) + + queryHelperEvm := baseapp.NewQueryServerTestHelper(s.ctx, s.app.InterfaceRegistry()) + evmtypes.RegisterQueryServer(queryHelperEvm, s.app.EvmKeeper) + s.queryClientEVM = evmtypes.NewQueryClient(queryHelperEvm) +} + +// ApproveAndCheckAuthz is a helper function to approve a given authorization method and check if the authorization was created. +func (s *PrecompileTestSuite) ApproveAndCheckAuthz(method abi.Method, msgType string, amount *big.Int) { + approveArgs := []interface{}{ + s.address, + amount, + []string{msgType}, + } + resp, err := s.precompile.Approve(s.ctx, s.address, s.stateDB, &method, approveArgs) + s.Require().NoError(err) + s.Require().Equal(resp, cmn.TrueValue) + + auth, _ := s.CheckAuthorization(staking.DelegateAuthz, s.address, s.address) + s.Require().NotNil(auth) + s.Require().Equal(auth.AuthorizationType, staking.DelegateAuthz) + s.Require().Equal(auth.MaxTokens, &sdk.Coin{Denom: s.bondDenom, Amount: math.NewIntFromBigInt(amount)}) +} + +// CheckAuthorization is a helper function to check if the authorization is set and if it is the correct type. +func (s *PrecompileTestSuite) CheckAuthorization(authorizationType stakingtypes.AuthorizationType, grantee, granter common.Address) (*stakingtypes.StakeAuthorization, *time.Time) { + stakingAuthz := stakingtypes.StakeAuthorization{AuthorizationType: authorizationType} + auth, expirationTime := s.app.AuthzKeeper.GetAuthorization(s.ctx, grantee.Bytes(), granter.Bytes(), stakingAuthz.MsgTypeURL()) + + stakeAuthorization, ok := auth.(*stakingtypes.StakeAuthorization) + if !ok { + return nil, expirationTime + } + + return stakeAuthorization, expirationTime +} + +// CreateAuthorization is a helper function to create a new authorization of the given type for a spender address +// (=grantee). +// The authorization will be created to spend the given Coin. +// For testing purposes, this function will create a new authorization for all available validators, +// that are not jailed. +func (s *PrecompileTestSuite) CreateAuthorization(grantee common.Address, authzType stakingtypes.AuthorizationType, coin *sdk.Coin) error { + // Get all available validators and filter out jailed validators + validators := make([]sdk.ValAddress, 0) + s.app.StakingKeeper.IterateValidators( + s.ctx, func(_ int64, validator stakingtypes.ValidatorI) (stop bool) { + if validator.IsJailed() { + return + } + validators = append(validators, validator.GetOperator()) + return + }, + ) + + stakingAuthz, err := stakingtypes.NewStakeAuthorization(validators, nil, authzType, coin) + if err != nil { + return err + } + + expiration := time.Now().Add(cmn.DefaultExpirationDuration).UTC() + err = s.app.AuthzKeeper.SaveGrant(s.ctx, grantee.Bytes(), s.address.Bytes(), stakingAuthz, &expiration) + if err != nil { + return err + } + + return nil +} + +// SetupApproval sets up an approval, that authorizes the grantee to spend the given amount for the granter +// in transactions, that target the specified message types. +func (s *PrecompileTestSuite) SetupApproval( + granterPriv types.PrivKey, + grantee common.Address, + amount *big.Int, + msgTypes []string, +) { + approveArgs := contracts.CallArgs{ + ContractAddr: s.precompile.Address(), + ContractABI: s.precompile.ABI, + PrivKey: granterPriv, + MethodName: authorization.ApproveMethod, + Args: []interface{}{ + grantee, amount, msgTypes, + }, + } + + logCheckArgs := testutil.LogCheckArgs{ + ABIEvents: s.precompile.Events, + ExpEvents: []string{authorization.EventTypeApproval}, + ExpPass: true, + } + + res, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, approveArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while calling the contract to approve") + + s.NextBlock() + + // Check if the approval event is emitted + granterAddr := common.BytesToAddress(granterPriv.PubKey().Address().Bytes()) + testutil.CheckAuthorizationEvents( + s.precompile.Events[authorization.EventTypeApproval], + s.precompile.Address(), + granterAddr, + grantee, + res, + s.ctx.BlockHeight()-1, + msgTypes, + amount, + ) +} + +// SetupApprovalWithContractCalls is a helper function used to setup the allowance for the given spender. +func (s *PrecompileTestSuite) SetupApprovalWithContractCalls(approvalArgs contracts.CallArgs) { + msgTypes, ok := approvalArgs.Args[1].([]string) + Expect(ok).To(BeTrue(), "failed to convert msgTypes to []string") + expAmount, ok := approvalArgs.Args[2].(*big.Int) + Expect(ok).To(BeTrue(), "failed to convert amount to big.Int") + + logCheckArgs := testutil.LogCheckArgs{ + ABIEvents: s.precompile.Events, + ExpEvents: []string{authorization.EventTypeApproval}, + ExpPass: true, + } + + _, _, err := contracts.CallContractAndCheckLogs(s.ctx, s.app, approvalArgs, logCheckArgs) + Expect(err).To(BeNil(), "error while approving: %v", err) + + // iterate over args + var expectedAuthz stakingtypes.AuthorizationType + for _, msgType := range msgTypes { + switch msgType { + case staking.DelegateMsg: + expectedAuthz = staking.DelegateAuthz + case staking.UndelegateMsg: + expectedAuthz = staking.UndelegateAuthz + case staking.RedelegateMsg: + expectedAuthz = staking.RedelegateAuthz + case staking.CancelUnbondingDelegationMsg: + expectedAuthz = staking.CancelUnbondingDelegationAuthz + } + authz, expirationTime := s.CheckAuthorization(expectedAuthz, approvalArgs.ContractAddr, s.address) + Expect(authz).ToNot(BeNil(), "expected authorization to be set") + Expect(authz.MaxTokens.Amount).To(Equal(math.NewInt(expAmount.Int64())), "expected different allowance") + Expect(authz.MsgTypeURL()).To(Equal(msgType), "expected different message type") + Expect(expirationTime).ToNot(BeNil(), "expected expiration time to not be nil") + } +} + +// DeployContract deploys a contract that calls the staking precompile's methods for testing purposes. +func (s *PrecompileTestSuite) DeployContract(contract evmtypes.CompiledContract) (addr common.Address, err error) { + addr, err = evmosutil.DeployContract( + s.ctx, + s.app, + s.privKey, + s.queryClientEVM, + contract, + ) + return +} + +// NextBlock commits the current block and sets up the next block. +func (s *PrecompileTestSuite) NextBlock() { + var err error + s.ctx, err = evmosutil.CommitAndCreateNewCtx(s.ctx, s.app, time.Second, nil) + Expect(err).To(BeNil(), "failed to commit block") +} + +// CheckAllowanceChangeEvent is a helper function used to check the allowance change event arguments. +func (s *PrecompileTestSuite) CheckAllowanceChangeEvent(log *ethtypes.Log, methods []string, amounts []*big.Int) { + s.Require().Equal(log.Address, s.precompile.Address()) + // Check event signature matches the one emitted + event := s.precompile.ABI.Events[authorization.EventTypeAllowanceChange] + s.Require().Equal(event.ID, common.HexToHash(log.Topics[0].Hex())) + s.Require().Equal(log.BlockNumber, uint64(s.ctx.BlockHeight())) + + var approvalEvent authorization.EventAllowanceChange + err := cmn.UnpackLog(s.precompile.ABI, &approvalEvent, authorization.EventTypeAllowanceChange, *log) + s.Require().NoError(err) + s.Require().Equal(s.address, approvalEvent.Grantee) + s.Require().Equal(s.address, approvalEvent.Granter) + s.Require().Equal(len(methods), len(approvalEvent.Methods)) + + for i, method := range methods { + s.Require().Equal(method, approvalEvent.Methods[i]) + s.Require().Equal(amounts[i], approvalEvent.Values[i]) + } +} + +// ExpectAuthorization is a helper function for tests using the Ginkgo BDD style tests, to check that the +// authorization is correctly set. +func (s *PrecompileTestSuite) ExpectAuthorization(authorizationType stakingtypes.AuthorizationType, grantee, granter common.Address, maxTokens *sdk.Coin) { + authz, expirationTime := s.CheckAuthorization(authorizationType, grantee, granter) + Expect(authz).ToNot(BeNil(), "expected authorization to be set") + Expect(authz.AuthorizationType).To(Equal(authorizationType), "expected different authorization type") + Expect(authz.MaxTokens).To(Equal(maxTokens), "expected different max tokens") + Expect(expirationTime).ToNot(BeNil(), "expected expiration time to be not be nil") +} + +// assertValidatorsResponse asserts all the fields on the validators response +func (s *PrecompileTestSuite) assertValidatorsResponse(validators []staking.ValidatorInfo, expLen int) { + // returning order can change + valOrder := []int{0, 1} + if validators[0].OperatorAddress != s.validators[0].OperatorAddress { + valOrder = []int{1, 0} + } + for i := 0; i < expLen; i++ { + j := valOrder[i] + s.Require().Equal(s.validators[j].OperatorAddress, validators[i].OperatorAddress) + s.Require().Equal(uint8(s.validators[j].Status), validators[i].Status) + s.Require().Equal(s.validators[j].Tokens.Uint64(), validators[i].Tokens.Uint64()) + s.Require().Equal(s.validators[j].DelegatorShares.BigInt(), validators[i].DelegatorShares) + s.Require().Equal(s.validators[j].Jailed, validators[i].Jailed) + s.Require().Equal(s.validators[j].UnbondingHeight, validators[i].UnbondingHeight) + s.Require().Equal(int64(0), validators[i].UnbondingTime) + s.Require().Equal(int64(0), validators[i].Commission.Int64()) + s.Require().Equal(int64(0), validators[i].MinSelfDelegation.Int64()) + s.Require().Equal(validators[i].ConsensusPubkey, staking.FormatConsensusPubkey(s.validators[j].ConsensusPubkey)) + } +} + +// assertRedelegation asserts the redelegationOutput struct and its fields +func (s *PrecompileTestSuite) assertRedelegationsOutput(data []byte, redelTotalCount uint64, expAmt *big.Int, expCreationHeight int64, hasPagination bool) { + var redOut staking.RedelegationsOutput + err := s.precompile.UnpackIntoInterface(&redOut, staking.RedelegationsMethod, data) + s.Require().NoError(err, "failed to unpack output") + + s.Require().Len(redOut.Response, 1) + // check pagination - total count should be 2 + s.Require().Equal(redelTotalCount, redOut.PageResponse.Total) + if hasPagination { + s.Require().NotEmpty(redOut.PageResponse.NextKey) + } else { + s.Require().Empty(redOut.PageResponse.NextKey) + } + // check redelegation entry + // order may change, one redelegation has 2 entries + // and the other has one + if len(redOut.Response[0].Entries) == 2 { + s.assertRedelegation(redOut.Response[0], + 2, + s.validators[0].OperatorAddress, + s.validators[1].OperatorAddress, + expAmt, + expCreationHeight, + ) + } else { + s.assertRedelegation(redOut.Response[0], + 1, + s.validators[0].OperatorAddress, + sdk.ValAddress(s.address.Bytes()).String(), + expAmt, + expCreationHeight, + ) + } +} + +// assertRedelegation asserts all the fields on the redelegations response +// should specify the amount of entries expected and the expected amount for this +// the same amount is considered for all entries +func (s *PrecompileTestSuite) assertRedelegation(res staking.RedelegationResponse, entriesCount int, expValSrcAddr, expValDstAddr string, expAmt *big.Int, expCreationHeight int64) { + // check response + s.Require().Equal(res.Redelegation.DelegatorAddress, sdk.AccAddress(s.address.Bytes()).String()) + s.Require().Equal(res.Redelegation.ValidatorSrcAddress, expValSrcAddr) + s.Require().Equal(res.Redelegation.ValidatorDstAddress, expValDstAddr) + // check redelegation entries - should be empty + s.Require().Empty(res.Redelegation.Entries) + // check response entries, should be 2 + s.Require().Len(res.Entries, entriesCount) + // check redelegation entries + for _, e := range res.Entries { + s.Require().Equal(e.Balance, expAmt) + s.Require().True(e.RedelegationEntry.CompletionTime > 1600000000) + s.Require().Equal(expCreationHeight, e.RedelegationEntry.CreationHeight) + s.Require().Equal(e.RedelegationEntry.InitialBalance, expAmt) + } +} + +// setupRedelegations setups 2 entries for redelegation from validator[0] +// to validator[1], creates a validator using s.address +// and creates a redelegation from validator[0] to the new validator +func (s *PrecompileTestSuite) setupRedelegations(redelAmt *big.Int) error { + msg := stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: sdk.AccAddress(s.address.Bytes()).String(), + ValidatorSrcAddress: s.validators[0].OperatorAddress, + ValidatorDstAddress: s.validators[1].OperatorAddress, + Amount: sdk.NewCoin(s.bondDenom, math.NewIntFromBigInt(redelAmt)), + } + + msgSrv := stakingkeeper.NewMsgServerImpl(&s.app.StakingKeeper) + // create 2 entries for same redelegation + for i := 0; i < 2; i++ { + if _, err := msgSrv.BeginRedelegate(s.ctx, &msg); err != nil { + return err + } + } + + // create a validator with s.address and s.privKey + // then create a redelegation from validator[0] to this new validator + testutil.CreateValidator(s.ctx, s.T(), s.privKey.PubKey(), s.app.StakingKeeper, math.NewInt(100)) + msg.ValidatorDstAddress = sdk.ValAddress(s.address.Bytes()).String() + _, err := msgSrv.BeginRedelegate(s.ctx, &msg) + return err +} + +// CheckValidatorOutput checks that the given validator output +func (s *PrecompileTestSuite) CheckValidatorOutput(valOut staking.ValidatorInfo) { + validatorAddrs := make([]string, len(s.validators)) + for i, v := range s.validators { + validatorAddrs[i] = v.OperatorAddress + } + Expect(slices.Contains(validatorAddrs, valOut.OperatorAddress)).To(BeTrue(), "operator address not found in test suite validators") + Expect(valOut.DelegatorShares).To(Equal(big.NewInt(1e18)), "expected different delegator shares") +} diff --git a/precompiles/util/util.go b/precompiles/util/util.go new file mode 100644 index 00000000..142d0519 --- /dev/null +++ b/precompiles/util/util.go @@ -0,0 +1,38 @@ +package util + +import ( + "math/big" + + "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/types" + cmn "github.com/evmos/evmos/v16/precompiles/common" +) + +func EvmToAuraBigInt(amount *big.Int) *big.Int { + return new(big.Int).Div(amount, big.NewInt(1e12)) +} + +func AuraToEvmBigInt(amount *big.Int) *big.Int { + return new(big.Int).Mul(amount, big.NewInt(1e12)) +} + +func EvmToAuraInt(amount types.Int) types.Int { + return amount.Quo(types.NewInt(1e12)) +} + +func AuraToEvmInt(amount types.Int) types.Int { + return amount.Mul(types.NewInt(1e12)) +} + +func NewDecCoinsResponseEVM(amount types.DecCoins) []cmn.DecCoin { + // Create a new output for each coin and add it to the output array. + outputs := make([]cmn.DecCoin, len(amount)) + for i, coin := range amount { + outputs[i] = cmn.DecCoin{ + Denom: coin.Denom, + Amount: AuraToEvmBigInt(coin.Amount.TruncateInt().BigInt()), + Precision: math.LegacyPrecision, + } + } + return outputs +} diff --git a/tests/cosmjs/test/erc20/register_erc20.test.ts b/tests/cosmjs/test/erc20/register_erc20.test.ts index 2bf62209..9cfb9e62 100644 --- a/tests/cosmjs/test/erc20/register_erc20.test.ts +++ b/tests/cosmjs/test/erc20/register_erc20.test.ts @@ -1,7 +1,9 @@ import { SigningStargateClient } from '@cosmjs/stargate'; -import { Secp256k1HdWallet, StdFee } from '@cosmjs/amino'; +import { StdFee } from '@cosmjs/amino'; +import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; -import { http, WalletClient, createPublicClient, parseEther, getAddress, PublicClient } from 'viem' + +import { http, WalletClient, createPublicClient, parseEther, getAddress } from 'viem' import { localhost } from 'viem/chains' import { HDAccount } from 'viem/accounts' @@ -11,19 +13,22 @@ import hre from "hardhat"; import { assert } from 'chai'; import { convertBech32AddressToEthAddress } from '../util/convert_address'; -import { USERS, setupClients, localaura, auradev } from '../util/test_setup'; +import { USERS, setupClients, localaura } from '../util/test_setup'; -let cosmosAccounts: Secp256k1HdWallet[]; +let cosmosAccounts: DirectSecp256k1HdWallet[]; let cosmosClients: SigningStargateClient[]; let evmAccounts: HDAccount[]; let evmClients: WalletClient[]; -let publicClient: PublicClient; +let publicClient = createPublicClient({ + chain: localhost, + transport: http() +}); let erc20ContractAddr: `0x${string}`; describe('Should work with ERC20 tokens', () => { before(async () => { - const testClients = await setupClients('auradev'); + const testClients = await setupClients('localaura'); cosmosAccounts = testClients.cosmosAccounts; cosmosClients = testClients.cosmosClients; evmAccounts = testClients.evmAccounts; @@ -38,7 +43,7 @@ describe('Should work with ERC20 tokens', () => { account: evmAccounts[0], bytecode: TestErc20Code.bytecode as `0x${string}`, args: ['TestToken', 'TTT', parseEther('1000000')], - chain: auradev + chain: localaura }) const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); @@ -48,7 +53,6 @@ describe('Should work with ERC20 tokens', () => { throw new Error('Contract address not found'); } erc20ContractAddr = txReceipt.contractAddress; - console.log(erc20ContractAddr) // send some token to the a cosmos account const [cosmosAccount] = await cosmosAccounts[0].getAccounts(); @@ -61,7 +65,7 @@ describe('Should work with ERC20 tokens', () => { functionName: 'transfer', args: [receiver, sendAmt], account: evmAccounts[0], - chain: auradev + chain: localaura }) console.log(transferTx) @@ -100,15 +104,12 @@ describe('Should work with ERC20 tokens', () => { gas: '400000' } as StdFee - console.log(account.address) const submitTx = await cosmosClients[0].signAndBroadcast(account.address, [{ // typeUrl: cosmos.gov.v1.MsgExecLegacyContent.typeUrl, typeUrl: cosmos.gov.v1beta1.MsgSubmitProposal.typeUrl, value: proposalMsg }], fee, 'Register TestErc20') - console.log(submitTx) - const proposalId = submitTx?.events.find( (event: any) => event.type === 'submit_proposal' )?.attributes.find((attr: any) => attr.key === 'proposal_id')?.value; @@ -153,7 +154,7 @@ describe('Should work with ERC20 tokens', () => { // convert erc20 to coin const convertFee = { - amount: [{ amount: '500000', denom: 'utaura' }], + amount: [{ amount: '500000', denom: 'uaura' }], gas: '1000000' } as StdFee const sender = convertBech32AddressToEthAddress('aura', account.address) diff --git a/tests/evm/init-node.sh b/tests/evm/init-node.sh index 1c425553..6eefdc05 100755 --- a/tests/evm/init-node.sh +++ b/tests/evm/init-node.sh @@ -137,7 +137,7 @@ jq -r '.app_state.bank.supply[0].amount="100000000"' "$GENESIS" >"$TMP_GENESIS" jq -r --arg ibc_denom "$IBC_DENOM" '.app_state.bank.supply[0].denom=$ibc_denom' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" # set list of evm precompile contracts -jq '.app_state.evm.params.active_precompiles=[ "0x0000000000000000000000000000000000000400", "0x0000000000000000000000000000000000000800", "0x0000000000000000000000000000000000000801", "0x0000000000000000000000000000000000000802" ]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" +jq '.app_state.evm.params.active_precompiles=[ "0x0000000000000000000000000000000000000400", "0x0000000000000000000000000000000000000800", "0x0000000000000000000000000000000000000801", "0x0000000000000000000000000000000000000802" ]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" # set custom pruning settings if [ "$PRUNING" = "custom" ]; then diff --git a/tests/evm/solidity/suites/precompiles/test/staking.js b/tests/evm/solidity/suites/precompiles/test/staking.js index 1236ad61..8cc5aaf1 100644 --- a/tests/evm/solidity/suites/precompiles/test/staking.js +++ b/tests/evm/solidity/suites/precompiles/test/staking.js @@ -1,27 +1,90 @@ const { expect } = require('chai') const hre = require('hardhat') +// const E12 = hre.ethers.BigNumber.from('1000000000000') +const stakingAddr = '0x0000000000000000000000000000000000000800' +const valAddr = 'auravaloper1dd6psq88kntuzyap944et8fmh0mxmw2wqnrnpx' + describe('Staking', function () { - it('should stake EVMOS to a validator', async function () { - const valAddr = 'evmosvaloper10jmp6sgh4cc6zt3e8gw05wavvejgr5pwlawghe' - const stakeAmount = hre.ethers.parseEther('0.001') + it('should stake to a validator', async function () { + const stakeAmount = hre.ethers.parseEther('0.000111') + const [signer] = await hre.ethers.getSigners() const staking = await hre.ethers.getContractAt( 'StakingI', - '0x0000000000000000000000000000000000000800' + stakingAddr, + signer ) + console.log('Stake amount:', stakeAmount.toString()) + + + // get original balance + const originalBalance = await hre.ethers.provider.getBalance(signer) + console.log('Original balance:', originalBalance) + + // get current delegation + const currentDelegation = await staking.delegation(signer.address, valAddr) + console.log('Current delegation:', currentDelegation) - const [signer] = await hre.ethers.getSigners() const tx = await staking - .connect(signer) - .delegate(signer, valAddr, stakeAmount) - await tx.wait(1) + .delegate(signer.address, valAddr, stakeAmount) + const res = await tx.wait() // Query delegation - const delegation = await staking.delegation(signer, valAddr) + const delegation = await staking.delegation(signer.address, valAddr) + console.log('Delegation:', delegation) + expect(delegation.balance.amount).to.equal( - stakeAmount, + stakeAmount + currentDelegation.balance.amount, 'Stake amount does not match' ) + + // check balance + const newBalance = await hre.ethers.provider.getBalance(signer) + console.log('New balance:', newBalance); + // new balance should be less than original balance - stake amount (because of gas fee) + expect(newBalance).to.lessThan(originalBalance - stakeAmount, 'Available amount does not match') + }) + + it('should undelegate from a validator', async function () { + const [signer] = await hre.ethers.getSigners() + const staking = await hre.ethers.getContractAt( + 'StakingI', + stakingAddr, + signer + ) + + const prevDelegation = await staking.delegation(signer.address, valAddr) + console.log('Prev delegation:', prevDelegation) + + // undelegate half of the amount + const tmp = BigInt(prevDelegation.balance.amount) / 2n + const undelegateAmount = tmp - (tmp % (10n ** 12n)) + console.log('Undelegate amount:', undelegateAmount) + + const tx = await staking.undelegate( + signer.address, + valAddr, + undelegateAmount, + // TODO: gas estimation is not working, we have to set gas limit manually + { gasLimit: 200000 } + ) + const res = await tx.wait() + + const delegation = await staking.delegation(signer.address, valAddr) + console.log('Delegation:', delegation) + + expect(delegation.balance.amount).to.equal( + prevDelegation.balance.amount - undelegateAmount, + 'Undelegated amount does not match' + ) + + const undelegation = await staking.unbondingDelegation(signer.address, valAddr) + console.log('Unbonding delegation:', undelegation) + + expect(undelegation.balance.amount).to.equal( + undelegateAmount, + 'Unbonding amount does not match' + ) }) }) diff --git a/x/evmutil/client/cli/address.go b/x/evmutil/client/cli/address.go index ca106a6a..023f03eb 100644 --- a/x/evmutil/client/cli/address.go +++ b/x/evmutil/client/cli/address.go @@ -1,11 +1,9 @@ package cli import ( - "context" "fmt" "strings" - "github.com/aura-nw/aura/x/evmutil/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" ) @@ -31,43 +29,3 @@ func ParseAddrFromHexOrBech32(addrString string) (common.Address, error) { return common.BytesToAddress(accAddr), nil } - -// ParseOrQueryConversionPairAddress returns an EVM address of the provided -// ERC20 contract address string or denom. If an address string, just returns -// the parsed address. If a denom, fetches params, searches the enabled -// conversion pairs, and returns corresponding ERC20 contract address. -func ParseOrQueryConversionPairAddress( - queryClient types.QueryClient, - addrOrDenom string, -) (common.Address, error) { - if common.IsHexAddress(addrOrDenom) { - return common.HexToAddress(addrOrDenom), nil - } - - if err := sdk.ValidateDenom(addrOrDenom); err != nil { - return common.Address{}, fmt.Errorf( - "Kava ERC20 '%s' is not a valid hex address or denom", - addrOrDenom, - ) - } - - // Valid denom, try looking up as denom to get corresponding Kava ERC20 address - paramsRes, err := queryClient.Params( - context.Background(), - &types.QueryParamsRequest{}, - ) - if err != nil { - return common.Address{}, err - } - - for _, pair := range paramsRes.Params.EnabledConversionPairs { - if pair.Denom == addrOrDenom { - return pair.GetAddress().Address, nil - } - } - - return common.Address{}, fmt.Errorf( - "Kava ERC20 '%s' is not a valid hex address or denom (did not match any denoms in queried enabled conversion pairs)", - addrOrDenom, - ) -} diff --git a/x/evmutil/client/cli/query.go b/x/evmutil/client/cli/query.go index 161132c2..f3bbb6fa 100644 --- a/x/evmutil/client/cli/query.go +++ b/x/evmutil/client/cli/query.go @@ -6,7 +6,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/cosmos-sdk/version" "github.com/spf13/cobra" @@ -25,7 +24,6 @@ func GetQueryCmd() *cobra.Command { cmds := []*cobra.Command{ QueryParamsCmd(), - QueryDeployedCosmosCoinContractsCmd(), } for _, cmd := range cmds { @@ -63,43 +61,3 @@ func QueryParamsCmd() *cobra.Command { }, } } - -func QueryDeployedCosmosCoinContractsCmd() *cobra.Command { - var cosmosDenoms []string - cmdName := "deployed-cosmos-coin-contracts" - q := fmt.Sprintf("%[1]s q %[2]s %s", version.AppName, types.ModuleName, cmdName) - cmd := &cobra.Command{ - Use: fmt.Sprintf("%s [--denoms denom1,denom2] [flags]", cmdName), - Short: "Query for deployed ERC20 contract addresses representing cosmos coins in the EVM", - Example: fmt.Sprintf("Query all:\n %s\n\nQuery by denom:\n %s --denoms denom1,denom2", q, q), - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - page, err := client.ReadPageRequest(cmd.Flags()) - if err != nil { - return err - } - - queryClient := types.NewQueryClient(clientCtx) - request := types.QueryDeployedCosmosCoinContractsRequest{ - CosmosDenoms: cosmosDenoms, - Pagination: page, - } - res, err := queryClient.DeployedCosmosCoinContracts(context.Background(), &request) - if err != nil { - return err - } - - return clientCtx.PrintProto(res) - }, - } - - flags.AddPaginationFlagsToCmd(cmd, cmdName) - cmd.Flags().StringSliceVar(&cosmosDenoms, "denoms", []string{}, fmt.Sprintf("(optional) Cosmos denoms to get addresses for. If no contract is deployed, the result will be omitted. Limit %d per query.", query.DefaultLimit)) - - return cmd -} diff --git a/x/evmutil/client/cli/tx.go b/x/evmutil/client/cli/tx.go index 711596d4..afe891e1 100644 --- a/x/evmutil/client/cli/tx.go +++ b/x/evmutil/client/cli/tx.go @@ -3,16 +3,10 @@ package cli import ( "fmt" - "github.com/ethereum/go-ethereum/common" - "github.com/spf13/cobra" - sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/client/tx" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/version" "github.com/aura-nw/aura/x/evmutil/types" ) @@ -27,12 +21,7 @@ func GetTxCmd() *cobra.Command { RunE: client.ValidateCmd, } - cmds := []*cobra.Command{ - getCmdConvertEvmERC20FromCoin(), - getCmdConvertEvmERC20ToCoin(), - getCmdMsgConvertCosmosCoinToERC20(), - getCmdMsgConvertCosmosCoinFromERC20(), - } + cmds := []*cobra.Command{} for _, cmd := range cmds { flags.AddTxFlagsToCmd(cmd) @@ -42,160 +31,3 @@ func GetTxCmd() *cobra.Command { return txCmd } - -func getCmdConvertEvmERC20FromCoin() *cobra.Command { - return &cobra.Command{ - Use: "convert-evm-erc20-from-coin [Kava EVM address] [coin]", - Short: "EVM-native asset: converts a coin on Cosmos co-chain to an ERC20 on EVM co-chain", - Example: fmt.Sprintf( - `%s tx %s convert-evm-erc20-from-coin 0x7Bbf300890857b8c241b219C6a489431669b3aFA 500000000erc20/usdc --from --gas 2000000`, - version.AppName, types.ModuleName, - ), - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - receiver := args[0] - if !common.IsHexAddress(receiver) { - return fmt.Errorf("receiver '%s' is an invalid hex address", args[0]) - } - - coin, err := sdk.ParseCoinNormalized(args[1]) - if err != nil { - return err - } - - signer := clientCtx.GetFromAddress() - msg := types.NewMsgConvertCoinToERC20(signer.String(), receiver, coin) - if err := msg.ValidateBasic(); err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - } -} - -func getCmdConvertEvmERC20ToCoin() *cobra.Command { - return &cobra.Command{ - Use: "convert-evm-erc20-to-coin [Kava receiver address] [Kava ERC20 address] [amount]", - Short: "EVM-native asset: converts an ERC20 on EVM co-chain to a coin on Cosmos co-chain", - Example: fmt.Sprintf(` -%[1]s tx %[2]s convert-evm-erc20-to-coin kava10wlnqzyss4accfqmyxwx5jy5x9nfkwh6qm7n4t 0xeA7100edA2f805356291B0E55DaD448599a72C6d 1000000000000000 --from --gas 1000000 -`, version.AppName, types.ModuleName, - ), - Args: cobra.ExactArgs(3), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - receiver, err := sdk.AccAddressFromBech32(args[0]) - if err != nil { - return fmt.Errorf("receiver '%s' is not a bech32 address", args[0]) - } - - signer := clientCtx.GetFromAddress() - initiator, err := ParseAddrFromHexOrBech32(signer.String()) - if err != nil { - return err - } - - amount, ok := sdkmath.NewIntFromString(args[2]) - if !ok { - return fmt.Errorf("amount '%s' is invalid", args[2]) - } - - if !common.IsHexAddress(args[1]) { - return fmt.Errorf("contractAddr '%s' is not a hex address", args[1]) - } - contractAddr := types.NewInternalEVMAddress(common.HexToAddress(args[1])) - msg := types.NewMsgConvertERC20ToCoin(types.NewInternalEVMAddress(initiator), receiver, contractAddr, amount) - if err := msg.ValidateBasic(); err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - } -} - -func getCmdMsgConvertCosmosCoinToERC20() *cobra.Command { - return &cobra.Command{ - Use: "convert-cosmos-coin-to-erc20 [receiver_0x_address] [amount] [flags]", - Short: "Cosmos-native asset: converts a coin on Cosmos co-chain to an ERC20 on EVM co-chain", - Example: fmt.Sprintf( - `Convert 500 ATOM and send ERC20 to 0x03db6b11F47d074a532b9eb8a98aB7AdA5845087: - %s tx %s convert-cosmos-coin-to-erc20 0x03db6b11F47d074a532b9eb8a98aB7AdA5845087 500000000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --from --gas 2000000`, - version.AppName, types.ModuleName, - ), - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - receiver := args[0] - if !common.IsHexAddress(receiver) { - return fmt.Errorf("receiver '%s' is an invalid hex address", args[0]) - } - - amount, err := sdk.ParseCoinNormalized(args[1]) - if err != nil { - return err - } - - signer := clientCtx.GetFromAddress() - msg := types.NewMsgConvertCosmosCoinToERC20(signer.String(), receiver, amount) - if err := msg.ValidateBasic(); err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - } -} - -func getCmdMsgConvertCosmosCoinFromERC20() *cobra.Command { - return &cobra.Command{ - Use: "convert-cosmos-coin-from-erc20 [receiver_kava_address] [amount] [flags]", - Short: "Cosmos-native asset: converts an ERC20 on EVM co-chain back to a coin on Cosmos co-chain", - Example: fmt.Sprintf( - `Convert ERC20 representation of 500 ATOM back to a Cosmos coin, sending to kava1q0dkky0505r555etn6u2nz4h4kjcg5y8dg863a: - %s tx %s convert-cosmos-coin-from-erc20 kava1q0dkky0505r555etn6u2nz4h4kjcg5y8dg863a 500000000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --from --gas 2000000`, - version.AppName, types.ModuleName, - ), - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - receiver, err := sdk.AccAddressFromBech32(args[0]) - if err != nil { - return fmt.Errorf("receiver '%s' is an invalid kava address", args[0]) - } - - amount, err := sdk.ParseCoinNormalized(args[1]) - if err != nil { - return err - } - - signer := clientCtx.GetFromAddress() - initiator := common.BytesToAddress(signer.Bytes()) - - msg := types.NewMsgConvertCosmosCoinFromERC20(initiator.String(), receiver.String(), amount) - if err := msg.ValidateBasic(); err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - } -} diff --git a/x/evmutil/keeper/conversion_cosmos_native.go b/x/evmutil/keeper/conversion_cosmos_native.go deleted file mode 100644 index 76075887..00000000 --- a/x/evmutil/keeper/conversion_cosmos_native.go +++ /dev/null @@ -1,107 +0,0 @@ -package keeper - -import ( - "fmt" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "github.com/aura-nw/aura/x/evmutil/types" -) - -// ConvertCosmosCoinToERC20 locks the initiator's sdk.Coin in the module account -// and mints the receiver a corresponding amount of an ERC20 representing the Coin. -// If a conversion has never been made before and no contract exists, one will be deployed. -// Only denoms registered to the AllowedCosmosDenoms param may be converted. -func (k *Keeper) ConvertCosmosCoinToERC20( - ctx sdk.Context, - initiator sdk.AccAddress, - receiver types.InternalEVMAddress, - amount sdk.Coin, -) error { - // check that the conversion is allowed - tokenInfo, allowed := k.GetAllowedTokenMetadata(ctx, amount.Denom) - if !allowed { - return errorsmod.Wrapf(types.ErrSDKConversionNotEnabled, amount.Denom) - } - - // send coins from initiator to the module account - // do this before possible contract deploy to prevent unnecessary store interactions - err := k.bankKeeper.SendCoinsFromAccountToModule( - ctx, initiator, types.ModuleName, sdk.NewCoins(amount), - ) - if err != nil { - return err - } - - // find deployed contract if it exits - contractAddress, err := k.GetOrDeployCosmosCoinERC20Contract(ctx, tokenInfo) - if err != nil { - return err - } - - // mint erc20 tokens for the user - err = k.MintERC20(ctx, contractAddress, receiver, amount.Amount.BigInt()) - if err != nil { - return err - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeConvertCosmosCoinToERC20, - sdk.NewAttribute(types.AttributeKeyInitiator, initiator.String()), - sdk.NewAttribute(types.AttributeKeyReceiver, receiver.String()), - sdk.NewAttribute(types.AttributeKeyERC20Address, contractAddress.Hex()), - sdk.NewAttribute(types.AttributeKeyAmount, amount.String()), - )) - - return nil -} - -// ConvertCosmosCoinFromERC20 burns the ERC20 wrapper of the cosmos coin and -// sends the underlying sdk coin form the module account to the receiver. -func (k *Keeper) ConvertCosmosCoinFromERC20( - ctx sdk.Context, - initiator types.InternalEVMAddress, - receiver sdk.AccAddress, - coin sdk.Coin, -) error { - amount := coin.Amount.BigInt() - // get deployed contract - contractAddress, found := k.GetDeployedCosmosCoinContract(ctx, coin.Denom) - if !found { - // no contract deployed - return errorsmod.Wrapf(types.ErrInvalidCosmosDenom, fmt.Sprintf("no erc20 contract found for %s", coin.Denom)) - } - - // verify sufficient balance - balance, err := k.QueryERC20BalanceOf(ctx, contractAddress, initiator) - if err != nil { - return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance %s", err.Error()) - } - if balance.Cmp(amount) == -1 { - return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, "failed to convert to cosmos coins") - } - - // burn initiator's ERC20 tokens - err = k.BurnERC20(ctx, contractAddress, initiator, amount) - if err != nil { - return err - } - - // send sdk coins to receiver, unlocking them from the module account - err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, receiver, sdk.NewCoins(coin)) - if err != nil { - return err - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeConvertCosmosCoinFromERC20, - sdk.NewAttribute(types.AttributeKeyInitiator, initiator.String()), - sdk.NewAttribute(types.AttributeKeyReceiver, receiver.String()), - sdk.NewAttribute(types.AttributeKeyERC20Address, contractAddress.Hex()), - sdk.NewAttribute(types.AttributeKeyAmount, coin.String()), - )) - - return nil -} diff --git a/x/evmutil/keeper/conversion_cosmos_native_test.go.disabled b/x/evmutil/keeper/conversion_cosmos_native_test.go.disabled deleted file mode 100644 index 78cdd60e..00000000 --- a/x/evmutil/keeper/conversion_cosmos_native_test.go.disabled +++ /dev/null @@ -1,299 +0,0 @@ -// package keeper_test - -// import ( -// "fmt" -// "math/big" -// "testing" - -// "github.com/stretchr/testify/suite" - -// sdkmath "cosmossdk.io/math" -// sdk "github.com/cosmos/cosmos-sdk/types" - -// "github.com/aura-nw/aura/x/evmutil/testutil" -// "github.com/aura-nw/aura/x/evmutil/types" -// "github.com/kava-labs/kava/app" -// ) - -// type convertCosmosCoinToERC20Suite struct { -// testutil.Suite -// } - -// func TestConversionCosmosNativeToEvmSuite(t *testing.T) { -// suite.Run(t, new(convertCosmosCoinToERC20Suite)) -// } - -// // fail test if contract for denom not registered -// func (suite *convertCosmosCoinToERC20Suite) denomContractRegistered(denom string) types.InternalEVMAddress { -// contractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom) -// suite.True(found) -// return contractAddress -// } - -// // fail test if contract for denom IS registered -// func (suite *convertCosmosCoinToERC20Suite) denomContractNotRegistered(denom string) { -// _, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom) -// suite.False(found) -// } - -// // more tests of tests of this method are made to the msg handler, see ./msg_server_test.go -// func (suite *convertCosmosCoinToERC20Suite) TestConvertCosmosCoinToERC20() { -// allowedDenom := "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2" -// initialFunding := sdk.NewInt64Coin(allowedDenom, int64(1e10)) -// initiator := app.RandomAddress() - -// amount := sdk.NewInt64Coin(allowedDenom, 6e8) -// receiver1 := types.BytesToInternalEVMAddress(app.RandomAddress().Bytes()) -// receiver2 := types.BytesToInternalEVMAddress(app.RandomAddress().Bytes()) - -// var contractAddress types.InternalEVMAddress - -// caller, key := testutil.RandomEvmAccount() -// query := func(method string, args ...interface{}) ([]interface{}, error) { -// return suite.QueryContract( -// types.ERC20KavaWrappedCosmosCoinContract.ABI, -// caller, -// key, -// contractAddress, -// method, -// args..., -// ) -// } -// checkTotalSupply := func(expectedSupply sdkmath.Int) { -// res, err := query("totalSupply") -// suite.NoError(err) -// suite.Len(res, 1) -// suite.BigIntsEqual(expectedSupply.BigInt(), res[0].(*big.Int), "unexpected total supply") -// } -// checkBalanceOf := func(address types.InternalEVMAddress, expectedBalance sdkmath.Int) { -// res, err := query("balanceOf", address.Address) -// suite.NoError(err) -// suite.Len(res, 1) -// suite.BigIntsEqual(expectedBalance.BigInt(), res[0].(*big.Int), fmt.Sprintf("unexpected balanceOf for %s", address)) -// } - -// suite.SetupTest() - -// suite.Run("fails when denom not allowed", func() { -// suite.denomContractNotRegistered(allowedDenom) -// err := suite.Keeper.ConvertCosmosCoinToERC20( -// suite.Ctx, -// initiator, -// receiver1, -// sdk.NewCoin(allowedDenom, sdkmath.NewInt(6e8)), -// ) -// suite.ErrorContains(err, "sdk.Coin not enabled to convert to ERC20 token") -// suite.denomContractNotRegistered(allowedDenom) -// }) - -// suite.Run("allowed denoms have contract deploys on first conversion", func() { -// // make the denom allowed for conversion -// params := suite.Keeper.GetParams(suite.Ctx) -// params.AllowedCosmosDenoms = types.NewAllowedCosmosCoinERC20Tokens( -// types.NewAllowedCosmosCoinERC20Token(allowedDenom, "Kava EVM Atom", "ATOM", 6), -// ) -// suite.Keeper.SetParams(suite.Ctx, params) - -// // fund account -// err := suite.App.FundAccount(suite.Ctx, initiator, sdk.NewCoins(initialFunding)) -// suite.NoError(err, "failed to initially fund account") - -// // first conversion -// err = suite.Keeper.ConvertCosmosCoinToERC20( -// suite.Ctx, -// initiator, -// receiver1, -// sdk.NewCoin(allowedDenom, sdkmath.NewInt(6e8)), -// ) -// suite.NoError(err) - -// // contract should be deployed & registered -// contractAddress = suite.denomContractRegistered(allowedDenom) - -// // sdk coin deducted from initiator -// expectedBalance := initialFunding.Sub(amount) -// balance := suite.BankKeeper.GetBalance(suite.Ctx, initiator, allowedDenom) -// suite.Equal(expectedBalance, balance) - -// // erc20 minted to receiver -// checkBalanceOf(receiver1, amount.Amount) -// // total supply of erc20 should have increased -// checkTotalSupply(amount.Amount) - -// // event should be emitted -// suite.EventsContains(suite.GetEvents(), -// sdk.NewEvent( -// types.EventTypeConvertCosmosCoinToERC20, -// sdk.NewAttribute(types.AttributeKeyInitiator, initiator.String()), -// sdk.NewAttribute(types.AttributeKeyReceiver, receiver1.String()), -// sdk.NewAttribute(types.AttributeKeyERC20Address, contractAddress.Hex()), -// sdk.NewAttribute(types.AttributeKeyAmount, amount.String()), -// ), -// ) -// }) - -// suite.Run("2nd deploy uses same contract", func() { -// // expect no initial balance -// checkBalanceOf(receiver2, sdkmath.NewInt(0)) - -// // 2nd conversion -// err := suite.Keeper.ConvertCosmosCoinToERC20( -// suite.Ctx, -// initiator, -// receiver2, -// sdk.NewCoin(allowedDenom, sdkmath.NewInt(6e8)), -// ) -// suite.NoError(err) - -// // contract address should not change -// convertTwiceContractAddress := suite.denomContractRegistered(allowedDenom) -// suite.Equal(contractAddress, convertTwiceContractAddress) - -// // sdk coin deducted from initiator -// expectedBalance := initialFunding.Sub(amount).Sub(amount) -// balance := suite.BankKeeper.GetBalance(suite.Ctx, initiator, allowedDenom) -// suite.Equal(expectedBalance, balance) - -// // erc20 minted to receiver -// checkBalanceOf(receiver2, amount.Amount) -// // total supply of erc20 should have increased -// checkTotalSupply(amount.Amount.MulRaw(2)) - -// // event should be emitted -// suite.EventsContains(suite.GetEvents(), -// sdk.NewEvent( -// types.EventTypeConvertCosmosCoinToERC20, -// sdk.NewAttribute(types.AttributeKeyInitiator, initiator.String()), -// sdk.NewAttribute(types.AttributeKeyReceiver, receiver2.String()), -// sdk.NewAttribute(types.AttributeKeyERC20Address, contractAddress.Hex()), -// sdk.NewAttribute(types.AttributeKeyAmount, amount.String()), -// ), -// ) -// }) -// } - -// type convertCosmosCoinFromERC20Suite struct { -// testutil.Suite - -// denom string -// initiator types.InternalEVMAddress -// receiver sdk.AccAddress - -// contractAddress types.InternalEVMAddress -// initialPosition sdk.Coin - -// query func(method string, args ...interface{}) ([]interface{}, error) -// } - -// func (suite *convertCosmosCoinFromERC20Suite) SetupTest() { -// var err error -// suite.Suite.SetupTest() - -// suite.denom = "magic" -// suite.initiator = testutil.RandomInternalEVMAddress() -// suite.receiver = app.RandomAddress() - -// // manually create an initial position - sdk coin locked in module -// suite.initialPosition = sdk.NewInt64Coin(suite.denom, 1e12) -// err = suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(suite.initialPosition)) -// suite.NoError(err) - -// // deploy erc20 contract for the denom -// tokenInfo := types.AllowedCosmosCoinERC20Token{ -// CosmosDenom: suite.denom, -// Name: "Test Token", -// Symbol: "MAGIC", -// Decimals: 6, -// } -// suite.contractAddress, err = suite.Keeper.GetOrDeployCosmosCoinERC20Contract(suite.Ctx, tokenInfo) -// suite.NoError(err) - -// // manually create an initial position - minted tokens -// err = suite.Keeper.MintERC20(suite.Ctx, suite.contractAddress, suite.initiator, suite.initialPosition.Amount.BigInt()) -// suite.NoError(err) - -// caller, key := testutil.RandomEvmAccount() -// suite.query = func(method string, args ...interface{}) ([]interface{}, error) { -// return suite.QueryContract( -// types.ERC20KavaWrappedCosmosCoinContract.ABI, -// caller, -// key, -// suite.contractAddress, -// method, -// args..., -// ) -// } -// } - -// func (suite *convertCosmosCoinFromERC20Suite) checkTotalSupply(expectedSupply sdkmath.Int) { -// res, err := suite.query("totalSupply") -// suite.NoError(err) -// suite.Len(res, 1) -// suite.BigIntsEqual(expectedSupply.BigInt(), res[0].(*big.Int), "unexpected total supply") -// } - -// func (suite *convertCosmosCoinFromERC20Suite) checkBalanceOf(address types.InternalEVMAddress, expectedBalance sdkmath.Int) { -// res, err := suite.query("balanceOf", address.Address) -// suite.NoError(err) -// suite.Len(res, 1) -// suite.BigIntsEqual(expectedBalance.BigInt(), res[0].(*big.Int), fmt.Sprintf("unexpected balanceOf for %s", address)) -// } - -// func TestConversionCosmosNativeFromEVMSuite(t *testing.T) { -// suite.Run(t, new(convertCosmosCoinFromERC20Suite)) -// } - -// func (suite *convertCosmosCoinFromERC20Suite) TestConvertCosmosCoinFromERC20_NoContractDeployed() { -// err := suite.Keeper.ConvertCosmosCoinFromERC20( -// suite.Ctx, -// suite.initiator, -// suite.receiver, -// sdk.NewInt64Coin("unsupported-denom", 1e6), -// ) -// suite.ErrorContains(err, "no erc20 contract found for unsupported-denom") -// } - -// func (suite *convertCosmosCoinFromERC20Suite) TestConvertCosmosCoinFromERC20() { -// // half the initial position -// amount := suite.initialPosition.SubAmount(suite.initialPosition.Amount.QuoRaw(2)) - -// suite.Run("partial withdraw", func() { -// err := suite.Keeper.ConvertCosmosCoinFromERC20( -// suite.Ctx, -// suite.initiator, -// suite.receiver, -// amount, -// ) -// suite.NoError(err) - -// suite.checkTotalSupply(amount.Amount) -// suite.checkBalanceOf(suite.initiator, amount.Amount) -// suite.App.CheckBalance(suite.T(), suite.Ctx, suite.receiver, sdk.NewCoins(amount)) -// }) - -// suite.Run("full withdraw", func() { -// err := suite.Keeper.ConvertCosmosCoinFromERC20( -// suite.Ctx, -// suite.initiator, -// suite.receiver, -// amount, -// ) -// suite.NoError(err) - -// // expect no remaining erc20 balance -// suite.checkTotalSupply(sdkmath.ZeroInt()) -// suite.checkBalanceOf(suite.initiator, sdkmath.ZeroInt()) -// // expect full amount withdrawn to receiver -// suite.App.CheckBalance(suite.T(), suite.Ctx, suite.receiver, sdk.NewCoins(suite.initialPosition)) -// }) - -// suite.Run("insufficient balance", func() { -// err := suite.Keeper.ConvertCosmosCoinFromERC20( -// suite.Ctx, -// suite.initiator, -// suite.receiver, -// amount, -// ) -// suite.ErrorContains(err, "failed to convert to cosmos coins: insufficient funds") -// }) -// } \ No newline at end of file diff --git a/x/evmutil/keeper/conversion_evm_native.go b/x/evmutil/keeper/conversion_evm_native.go deleted file mode 100644 index 979a112b..00000000 --- a/x/evmutil/keeper/conversion_evm_native.go +++ /dev/null @@ -1,224 +0,0 @@ -package keeper - -import ( - "math/big" - - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/aura-nw/aura/x/evmutil/types" -) - -// MintConversionPairCoin mints the given amount of a ConversionPair denom and -// sends it to the provided address. -func (k Keeper) MintConversionPairCoin( - ctx sdk.Context, - pair types.ConversionPair, - amount *big.Int, - recipient sdk.AccAddress, -) (sdk.Coin, error) { - coin := sdk.NewCoin(pair.Denom, sdkmath.NewIntFromBigInt(amount)) - coins := sdk.NewCoins(coin) - - if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil { - return sdk.Coin{}, err - } - - if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, recipient, coins); err != nil { - return sdk.Coin{}, err - } - - return coin, nil -} - -// BurnConversionPairCoin transfers the provided amount to the module account -// then burns it. -func (k Keeper) BurnConversionPairCoin( - ctx sdk.Context, - pair types.ConversionPair, - coin sdk.Coin, - account sdk.AccAddress, -) error { - coins := sdk.NewCoins(coin) - - if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, account, types.ModuleName, coins); err != nil { - return err - } - - if err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, coins); err != nil { - return err - } - - return nil -} - -// ConvertCoinToERC20 converts an sdk.Coin from the originating account to an -// ERC20 to the receiver account. -func (k Keeper) ConvertCoinToERC20( - ctx sdk.Context, - initiatorAccount sdk.AccAddress, - receiverAccount types.InternalEVMAddress, - coin sdk.Coin, -) error { - pair, err := k.GetEnabledConversionPairFromDenom(ctx, coin.Denom) - if err != nil { - // Coin not in enabled conversion pair list - return err - } - - if err := k.BurnConversionPairCoin(ctx, pair, coin, initiatorAccount); err != nil { - return err - } - - if err := k.UnlockERC20Tokens(ctx, pair, coin.Amount.BigInt(), receiverAccount); err != nil { - return err - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeConvertCoinToERC20, - sdk.NewAttribute(types.AttributeKeyInitiator, initiatorAccount.String()), - sdk.NewAttribute(types.AttributeKeyReceiver, receiverAccount.String()), - sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()), - sdk.NewAttribute(types.AttributeKeyAmount, coin.String()), - )) - - return nil -} - -// ConvertERC20ToCoin converts an ERC20 coin from the originating account to an -// sdk.Coin to the receiver account. -func (k Keeper) ConvertERC20ToCoin( - ctx sdk.Context, - initiator types.InternalEVMAddress, - receiver sdk.AccAddress, - contractAddr types.InternalEVMAddress, - amount sdkmath.Int, -) error { - // Check that the contract is enabled to convert to coin - pair, err := k.GetEnabledConversionPairFromERC20Address(ctx, contractAddr) - if err != nil { - // contract not in enabled conversion pair list - return err - } - - // lock erc20 tokens - if err := k.LockERC20Tokens(ctx, pair, amount.BigInt(), initiator); err != nil { - return err - } - - // mint conversion pair coin - coin, err := k.MintConversionPairCoin(ctx, pair, amount.BigInt(), receiver) - if err != nil { - return err - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeConvertERC20ToCoin, - sdk.NewAttribute(types.AttributeKeyERC20Address, contractAddr.String()), - sdk.NewAttribute(types.AttributeKeyInitiator, initiator.String()), - sdk.NewAttribute(types.AttributeKeyReceiver, receiver.String()), - sdk.NewAttribute(types.AttributeKeyAmount, coin.String()), - )) - - return nil -} - -// UnlockERC20Tokens transfers the given amount of a conversion pair ERC20 token -// to the provided account. -func (k Keeper) UnlockERC20Tokens( - ctx sdk.Context, - pair types.ConversionPair, - amount *big.Int, - receiver types.InternalEVMAddress, -) error { - contractAddr := pair.GetAddress() - startBal, err := k.QueryERC20BalanceOf(ctx, contractAddr, receiver) - if err != nil { - return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance %s", err.Error()) - } - res, err := k.CallEVM( - ctx, - types.ERC20MintableBurnableContract.ABI, // abi - types.ModuleEVMAddress, // from addr - pair.GetAddress(), // contract addr - "transfer", // method - // Transfer ERC20 args - receiver.Address, - amount, - ) - if err != nil { - return err - } - - // validate end bal - endBal, err := k.QueryERC20BalanceOf(ctx, contractAddr, receiver) - if err != nil { - return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance %s", err.Error()) - } - expectedEndBal := big.NewInt(0).Add(startBal, amount) - if expectedEndBal.Cmp(endBal) != 0 { - return errorsmod.Wrapf( - types.ErrBalanceInvariance, - "invalid token balance - expected: %v, actual: %v", - expectedEndBal, endBal, - ) - } - - // Check for unexpected `Approval` event in logs - if err := k.monitorApprovalEvent(res); err != nil { - return err - } - - return err -} - -// LockERC20Tokens transfers the given amount of a conversion pair ERC20 token -// from the initiator account to the module account. -func (k Keeper) LockERC20Tokens( - ctx sdk.Context, - pair types.ConversionPair, - amount *big.Int, - initiator types.InternalEVMAddress, -) error { - contractAddr := pair.GetAddress() - initiatorStartBal, err := k.QueryERC20BalanceOf(ctx, contractAddr, initiator) - if err != nil { - return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance: %s", err.Error()) - } - - res, err := k.CallEVM( - ctx, - types.ERC20MintableBurnableContract.ABI, // abi - initiator.Address, // from addr - contractAddr, // contract addr - "transfer", // method - // Transfer ERC20 args - types.ModuleEVMAddress, - amount, - ) - if err != nil { - return err - } - - // validate end bal - initiatorEndBal, err := k.QueryERC20BalanceOf(ctx, contractAddr, initiator) - if err != nil { - return errorsmod.Wrapf(types.ErrEVMCall, "failed to retrieve balance %s", err.Error()) - } - expectedEndBal := big.NewInt(0).Sub(initiatorStartBal, amount) - if expectedEndBal.Cmp(initiatorEndBal) != 0 { - return errorsmod.Wrapf( - types.ErrBalanceInvariance, - "invalid token balance - expected: %v, actual: %v", - expectedEndBal, initiatorEndBal, - ) - } - - // Check for unexpected `Approval` event in logs - if err := k.monitorApprovalEvent(res); err != nil { - return err - } - - return err -} diff --git a/x/evmutil/keeper/conversion_evm_native_test.go.disabled b/x/evmutil/keeper/conversion_evm_native_test.go.disabled deleted file mode 100644 index ffc9d158..00000000 --- a/x/evmutil/keeper/conversion_evm_native_test.go.disabled +++ /dev/null @@ -1,353 +0,0 @@ -package keeper_test - -import ( - "math/big" - "testing" - - sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/suite" - - "github.com/aura-nw/aura/x/evmutil/testutil" - "github.com/aura-nw/aura/x/evmutil/types" -) - -type ConversionTestSuite struct { - testutil.Suite -} - -func TestConversionTestSuite(t *testing.T) { - suite.Run(t, new(ConversionTestSuite)) -} - -func (suite *ConversionTestSuite) TestMint() { - pair := types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000A"), - "erc20/usdc", - ) - - amount := big.NewInt(100) - recipient := suite.Key1.PubKey().Address().Bytes() - - coin, err := suite.Keeper.MintConversionPairCoin(suite.Ctx, pair, amount, recipient) - suite.Require().NoError(err) - suite.Require().Equal(sdk.NewCoin(pair.Denom, sdkmath.NewIntFromBigInt(amount)), coin) - - bal := suite.App.GetBankKeeper().GetBalance(suite.Ctx, recipient, pair.Denom) - suite.Require().Equal(amount, bal.Amount.BigInt(), "minted amount should increase balance") -} - -func (suite *ConversionTestSuite) TestBurn_InsufficientBalance() { - pair := types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000A"), - "erc20/usdc", - ) - - amount := sdkmath.NewInt(100) - recipient := suite.Key1.PubKey().Address().Bytes() - - err := suite.Keeper.BurnConversionPairCoin(suite.Ctx, pair, sdk.NewCoin(pair.Denom, amount), recipient) - suite.Require().Error(err) - suite.Require().Equal("spendable balance is smaller than 100erc20/usdc: insufficient funds", err.Error()) -} - -func (suite *ConversionTestSuite) TestBurn() { - pair := types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000A"), - "erc20/usdc", - ) - - amount := sdkmath.NewInt(100) - recipient := suite.Key1.PubKey().Address().Bytes() - - coin, err := suite.Keeper.MintConversionPairCoin(suite.Ctx, pair, amount.BigInt(), recipient) - suite.Require().NoError(err) - suite.Require().Equal(sdk.NewCoin(pair.Denom, amount), coin) - - bal := suite.App.GetBankKeeper().GetBalance(suite.Ctx, recipient, pair.Denom) - suite.Require().Equal(amount, bal.Amount, "minted amount should increase balance") - - err = suite.Keeper.BurnConversionPairCoin(suite.Ctx, pair, sdk.NewCoin(pair.Denom, amount), recipient) - suite.Require().NoError(err) - - bal = suite.App.GetBankKeeper().GetBalance(suite.Ctx, recipient, pair.Denom) - suite.Require().Equal(sdk.ZeroInt(), bal.Amount, "balance should be zero after burn") -} - -func (suite *ConversionTestSuite) TestUnlockERC20Tokens() { - contractAddr := suite.DeployERC20() - - pair := types.NewConversionPair( - contractAddr, - "erc20/usdc", - ) - - amount := big.NewInt(100) - recipient := types.NewInternalEVMAddress(common.BytesToAddress(suite.Key1.PubKey().Address())) - moduleAddr := types.NewInternalEVMAddress(types.ModuleEVMAddress) - - // Mint some initial balance for module account to transfer - err := suite.Keeper.MintERC20( - suite.Ctx, - pair.GetAddress(), // contractAddr - moduleAddr, //receiver - amount, - ) - suite.Require().NoError(err) - - err = suite.Keeper.UnlockERC20Tokens(suite.Ctx, pair, amount, recipient) - suite.Require().NoError(err) - - // Check balance of recipient - bal := suite.GetERC20BalanceOf( - types.ERC20MintableBurnableContract.ABI, - pair.GetAddress(), - recipient, - ) - suite.Require().Equal(amount, bal, "balance should increase by unlock amount") - - // Check balance of module account - bal = suite.GetERC20BalanceOf( - types.ERC20MintableBurnableContract.ABI, - pair.GetAddress(), - moduleAddr, - ) - suite.Require().Equal( - // String() due to non-equal struct values for 0 - big.NewInt(0).String(), - bal.String(), - "balance should decrease module account by unlock amount", - ) -} - -func (suite *ConversionTestSuite) TestUnlockERC20Tokens_Insufficient() { - contractAddr := suite.DeployERC20() - - pair := types.NewConversionPair( - contractAddr, - "erc20/usdc", - ) - - amount := big.NewInt(100) - recipient := types.NewInternalEVMAddress(common.BytesToAddress(suite.Key1.PubKey().Address())) - - // Module account has 0 balance, cannot unlock - err := suite.Keeper.UnlockERC20Tokens(suite.Ctx, pair, amount, recipient) - suite.Require().Error(err) - suite.Require().Contains(err.Error(), "execution reverted: ERC20: transfer amount exceeds balance") -} - -func (suite *ConversionTestSuite) TestConvertCoinToERC20() { - contractAddr := suite.DeployERC20() - - pair := types.NewConversionPair( - contractAddr, - "erc20/usdc", - ) - - amount := big.NewInt(100) - originAcc := sdk.AccAddress(suite.Key1.PubKey().Address().Bytes()) - recipientAcc := types.NewInternalEVMAddress(common.BytesToAddress(suite.Key2.PubKey().Address())) - moduleAddr := types.NewInternalEVMAddress(types.ModuleEVMAddress) - - // Starting balance of origin account - coin, err := suite.Keeper.MintConversionPairCoin(suite.Ctx, pair, amount, originAcc) - suite.Require().NoError(err) - suite.Require().Equal(sdk.NewCoin(pair.Denom, sdkmath.NewIntFromBigInt(amount)), coin) - - // Mint same initial balance for module account as backing erc20 supply - err = suite.Keeper.MintERC20( - suite.Ctx, - pair.GetAddress(), // contractAddr - moduleAddr, //receiver - amount, - ) - suite.Require().NoError(err) - - // convert coin to erc20 - ctx := suite.Ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - err = suite.Keeper.ConvertCoinToERC20( - ctx, - originAcc, - recipientAcc, - sdk.NewCoin(pair.Denom, sdkmath.NewIntFromBigInt(amount)), - ) - suite.Require().NoError(err) - suite.Require().LessOrEqual(ctx.GasMeter().GasConsumed(), uint64(500000)) - suite.Require().GreaterOrEqual(ctx.GasMeter().GasConsumed(), uint64(50000)) - - // Source should decrease - bal := suite.App.GetBankKeeper().GetBalance(suite.Ctx, originAcc, pair.Denom) - suite.Require().Equal(sdk.ZeroInt(), bal.Amount, "conversion should decrease source balance") - - // Module bal should also decrease - moduleBal := suite.GetERC20BalanceOf( - types.ERC20MintableBurnableContract.ABI, - pair.GetAddress(), - moduleAddr, - ) - suite.Require().Equal( - // String() due to non-equal struct values for 0 - big.NewInt(0).String(), - moduleBal.String(), - "balance should decrease module account by unlock amount", - ) - - // Recipient balance should increase by same amount - recipientBal := suite.GetERC20BalanceOf( - types.ERC20MintableBurnableContract.ABI, - pair.GetAddress(), - recipientAcc, - ) - suite.Require().Equal( - // String() due to non-equal struct values for 0 - amount, - recipientBal, - "recipient balance should increase", - ) - - suite.EventsContains(suite.GetEvents(), - sdk.NewEvent( - types.EventTypeConvertCoinToERC20, - sdk.NewAttribute(types.AttributeKeyInitiator, originAcc.String()), - sdk.NewAttribute(types.AttributeKeyReceiver, recipientAcc.String()), - sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()), - sdk.NewAttribute(types.AttributeKeyAmount, coin.String()), - )) -} - -func (suite *ConversionTestSuite) TestConvertCoinToERC20_InsufficientBalance() { - contractAddr := suite.DeployERC20() - - pair := types.NewConversionPair( - contractAddr, - "erc20/usdc", - ) - - amount := big.NewInt(100) - originAcc := sdk.AccAddress(suite.Key1.PubKey().Address().Bytes()) - recipientAcc := types.NewInternalEVMAddress(common.BytesToAddress(suite.Key2.PubKey().Address())) - - err := suite.Keeper.ConvertCoinToERC20( - suite.Ctx, - originAcc, - recipientAcc, - sdk.NewCoin(pair.Denom, sdkmath.NewIntFromBigInt(amount)), - ) - - suite.Require().Error(err) - suite.Require().Equal("spendable balance is smaller than 100erc20/usdc: insufficient funds", err.Error()) -} - -func (suite *ConversionTestSuite) TestConvertCoinToERC20_NotEnabled() { - contractAddr := suite.DeployERC20() - - pair := types.NewConversionPair( - contractAddr, - "erc20/notenabled", - ) - - amount := big.NewInt(100) - originAcc := sdk.AccAddress(suite.Key1.PubKey().Address().Bytes()) - recipientAcc := types.NewInternalEVMAddress(common.BytesToAddress(suite.Key2.PubKey().Address())) - - err := suite.Keeper.ConvertCoinToERC20( - suite.Ctx, - originAcc, - recipientAcc, - sdk.NewCoin(pair.Denom, sdkmath.NewIntFromBigInt(amount)), - ) - - suite.Require().Error(err) - suite.Require().Equal("erc20/notenabled: ERC20 token not enabled to convert to sdk.Coin", err.Error()) -} - -func (suite *ConversionTestSuite) TestConvertERC20ToCoin() { - contractAddr := suite.DeployERC20() - - pair := types.NewConversionPair( - contractAddr, - "erc20/usdc", - ) - - totalAmt := big.NewInt(100) - userAddr := sdk.AccAddress(suite.Key1.PubKey().Address().Bytes()) - userEvmAddr := types.NewInternalEVMAddress(common.BytesToAddress(suite.Key1.PubKey().Address())) - - // Mint same initial balance for user account - err := suite.Keeper.MintERC20( - suite.Ctx, - pair.GetAddress(), // contractAddr - userEvmAddr, //receiver - totalAmt, - ) - suite.Require().NoError(err) - - ctx := suite.Ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - convertAmt := sdkmath.NewInt(50) - err = suite.Keeper.ConvertERC20ToCoin( - ctx, - userEvmAddr, - userAddr, - pair.GetAddress(), - convertAmt, - ) - suite.Require().NoError(err) - suite.Require().LessOrEqual(ctx.GasMeter().GasConsumed(), uint64(500000)) - suite.Require().GreaterOrEqual(ctx.GasMeter().GasConsumed(), uint64(50000)) - - // bank balance should decrease - bal := suite.App.GetBankKeeper().GetBalance(suite.Ctx, userAddr, pair.Denom) - suite.Require().Equal(convertAmt, bal.Amount, "conversion should decrease source balance") - - // Module bal should also decrease - userBal := suite.GetERC20BalanceOf( - types.ERC20MintableBurnableContract.ABI, - pair.GetAddress(), - userEvmAddr, - ) - suite.Require().Equal( - // String() due to non-equal struct values for 0 - big.NewInt(50).String(), - userBal.String(), - "balance should decrease module account by unlock amount", - ) - - suite.EventsContains(suite.GetEvents(), - sdk.NewEvent( - types.EventTypeConvertERC20ToCoin, - sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()), - sdk.NewAttribute(types.AttributeKeyInitiator, userEvmAddr.String()), - sdk.NewAttribute(types.AttributeKeyReceiver, userAddr.String()), - sdk.NewAttribute(types.AttributeKeyAmount, sdk.NewCoin(pair.Denom, convertAmt).String()), - ), - ) -} - -func (suite *ConversionTestSuite) TestConvertERC20ToCoin_EmptyContract() { - contractAddr := testutil.MustNewInternalEVMAddressFromString("0x15932E26f5BD4923d46a2b205191C4b5d5f43FE3") - pair := types.NewConversionPair( - contractAddr, - "erc20/usdc", - ) - - userAddr := sdk.AccAddress(suite.Key1.PubKey().Address().Bytes()) - userEvmAddr := types.NewInternalEVMAddress(common.BytesToAddress(suite.Key1.PubKey().Address())) - convertAmt := sdkmath.NewInt(100) - - // Trying to convert erc20 from an empty contract should fail - err := suite.Keeper.ConvertERC20ToCoin( - suite.Ctx, - userEvmAddr, - userAddr, - pair.GetAddress(), - convertAmt, - ) - suite.Require().Error(err) - suite.Require().ErrorContains(err, "failed to retrieve balance: failed to unpack method balanceOf") - - // bank balance should not change - bal := suite.App.GetBankKeeper().GetBalance(suite.Ctx, userAddr, pair.Denom) - suite.Require().Equal(sdk.ZeroInt(), bal.Amount) -} diff --git a/x/evmutil/keeper/erc20.go b/x/evmutil/keeper/erc20.go deleted file mode 100644 index 3cb9c587..00000000 --- a/x/evmutil/keeper/erc20.go +++ /dev/null @@ -1,271 +0,0 @@ -package keeper - -import ( - "encoding/hex" - "fmt" - "math/big" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - evmtypes "github.com/evmos/evmos/v16/x/evm/types" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/aura-nw/aura/x/evmutil/types" -) - -const ( - erc20BalanceOfMethod = "balanceOf" - erc20BurnMethod = "burn" - erc20MintMethod = "mint" - erc20TotalSupplyMethod = "totalSupply" -) - -// DeployTestMintableERC20Contract deploys an ERC20 contract on the EVM as the -// module account and returns the address of the contract. This contract has -// minting permissions for the module account. -// Derived from tharsis/evmos -// https://github.com/tharsis/evmos/blob/ee54f496551df937915ff6f74a94732a35abc505/x/erc20/keeper/evm.go -func (k Keeper) DeployTestMintableERC20Contract( - ctx sdk.Context, - name string, - symbol string, - decimals uint8, -) (types.InternalEVMAddress, error) { - ctorArgs, err := types.ERC20MintableBurnableContract.ABI.Pack( - "", // Empty string for contract constructor - name, - symbol, - decimals, - ) - if err != nil { - return types.InternalEVMAddress{}, errorsmod.Wrapf(err, "token %v is invalid", name) - } - - data := make([]byte, len(types.ERC20MintableBurnableContract.Bin)+len(ctorArgs)) - copy( - data[:len(types.ERC20MintableBurnableContract.Bin)], - types.ERC20MintableBurnableContract.Bin, - ) - copy( - data[len(types.ERC20MintableBurnableContract.Bin):], - ctorArgs, - ) - - nonce, err := k.accountKeeper.GetSequence(ctx, types.ModuleEVMAddress.Bytes()) - if err != nil { - return types.InternalEVMAddress{}, err - } - - contractAddr := crypto.CreateAddress(types.ModuleEVMAddress, nonce) - _, err = k.CallEVMWithData(ctx, types.ModuleEVMAddress, nil, data) - if err != nil { - return types.InternalEVMAddress{}, fmt.Errorf("failed to deploy ERC20 for %s: %w", name, err) - } - - return types.NewInternalEVMAddress(contractAddr), nil -} - -// DeployKavaWrappedCosmosCoinERC20Contract validates token details and then deploys an ERC20 -// contract with the token metadata. -// This method does NOT check if a token for the provided SdkDenom has already been deployed. -func (k Keeper) DeployKavaWrappedCosmosCoinERC20Contract( - ctx sdk.Context, - token types.AllowedCosmosCoinERC20Token, -) (types.InternalEVMAddress, error) { - if err := token.Validate(); err != nil { - return types.InternalEVMAddress{}, errorsmod.Wrapf(err, "failed to deploy erc20 for sdk denom %s", token.CosmosDenom) - } - - packedAbi, err := types.ERC20KavaWrappedCosmosCoinContract.ABI.Pack( - "", // Empty string for contract constructor - token.Name, - token.Symbol, - uint8(token.Decimals), // cast to uint8 is safe because of Validate() - ) - if err != nil { - return types.InternalEVMAddress{}, errorsmod.Wrapf(err, "failed to pack token with details %+v", token) - } - - data := make([]byte, len(types.ERC20KavaWrappedCosmosCoinContract.Bin)+len(packedAbi)) - copy( - data[:len(types.ERC20KavaWrappedCosmosCoinContract.Bin)], - types.ERC20KavaWrappedCosmosCoinContract.Bin, - ) - copy( - data[len(types.ERC20KavaWrappedCosmosCoinContract.Bin):], - packedAbi, - ) - - nonce, err := k.accountKeeper.GetSequence(ctx, types.ModuleEVMAddress.Bytes()) - if err != nil { - return types.InternalEVMAddress{}, err - } - - contractAddr := crypto.CreateAddress(types.ModuleEVMAddress, nonce) - _, err = k.CallEVMWithData(ctx, types.ModuleEVMAddress, nil, data) - if err != nil { - return types.InternalEVMAddress{}, fmt.Errorf("failed to deploy ERC20 %s (nonce=%d, data=%s): %s", token.Name, nonce, hex.EncodeToString(data), err) - } - - return types.NewInternalEVMAddress(contractAddr), nil -} - -// GetOrDeployCosmosCoinERC20Contract checks the module store for a deployed contract for the given -// token info and returns it if preset. Otherwise, it deploys and registers the contract. -func (k *Keeper) GetOrDeployCosmosCoinERC20Contract( - ctx sdk.Context, - tokenInfo types.AllowedCosmosCoinERC20Token, -) (types.InternalEVMAddress, error) { - contractAddress, found := k.GetDeployedCosmosCoinContract(ctx, tokenInfo.CosmosDenom) - if found { - // contract has already been deployed - return contractAddress, nil - } - - // deploy a new contract - contractAddress, err := k.DeployKavaWrappedCosmosCoinERC20Contract(ctx, tokenInfo) - if err != nil { - return contractAddress, err - } - - // register the contract to the module store - err = k.SetDeployedCosmosCoinContract(ctx, tokenInfo.CosmosDenom, contractAddress) - - // TODO: emit event that contract was deployed - - return contractAddress, err -} - -// MintERC20 mints the given amount of an ERC20 token to an address. This is -// unchecked and should only be called after permission and enabled ERC20 checks. -func (k Keeper) MintERC20( - ctx sdk.Context, - contractAddr types.InternalEVMAddress, - receiver types.InternalEVMAddress, - amount *big.Int, -) error { - _, err := k.CallEVM( - ctx, - types.ERC20MintableBurnableContract.ABI, - types.ModuleEVMAddress, - contractAddr, - erc20MintMethod, - // Mint ERC20 args - receiver.Address, - amount, - ) - - return err -} - -// BurnERC20 burns the token amount from the initiator's balance. -func (k Keeper) BurnERC20( - ctx sdk.Context, - contractAddr types.InternalEVMAddress, - initiator types.InternalEVMAddress, - amount *big.Int, -) error { - _, err := k.CallEVM( - ctx, - types.ERC20KavaWrappedCosmosCoinContract.ABI, - types.ModuleEVMAddress, - contractAddr, - erc20BurnMethod, - // Burn ERC20 args - initiator.Address, - amount, - ) - - return err -} - -// QueryERC20BalanceOf makes a contract call to the balanceOf method of the ERC20 contract to get -// the ERC20 balance of the given account. -func (k Keeper) QueryERC20BalanceOf( - ctx sdk.Context, - contractAddr types.InternalEVMAddress, - account types.InternalEVMAddress, -) (*big.Int, error) { - res, err := k.CallEVM( - ctx, - types.ERC20MintableBurnableContract.ABI, - types.ModuleEVMAddress, - contractAddr, - erc20BalanceOfMethod, - // balanceOf ERC20 args - account.Address, - ) - if err != nil { - return nil, err - } - - return unpackERC20ResToBigInt(res, erc20BalanceOfMethod) -} - -// QueryERC20TotalSupply makes a contract call to the totalSupply method of the ERC20 contract to -// get the total supply of the token. -func (k Keeper) QueryERC20TotalSupply( - ctx sdk.Context, - contractAddr types.InternalEVMAddress, -) (*big.Int, error) { - res, err := k.CallEVM( - ctx, - types.ERC20KavaWrappedCosmosCoinContract.ABI, - types.ModuleEVMAddress, - contractAddr, - erc20TotalSupplyMethod, - // totalSupply takes no args - ) - if err != nil { - return nil, err - } - - return unpackERC20ResToBigInt(res, erc20TotalSupplyMethod) -} - -func unpackERC20ResToBigInt(res *evmtypes.MsgEthereumTxResponse, methodName string) (*big.Int, error) { - if res.Failed() { - if res.VmError == vm.ErrExecutionReverted.Error() { - // Unpacks revert - return nil, evmtypes.NewExecErrorWithReason(res.Ret) - } - - return nil, status.Error(codes.Internal, res.VmError) - } - - if len(res.Ret) == 0 { - return nil, fmt.Errorf("failed to unpack method %s: expected response to be big.Int but found nil", methodName) - } - - anyOutput, err := types.ERC20MintableBurnableContract.ABI.Unpack(methodName, res.Ret) - if err != nil { - return nil, fmt.Errorf( - "failed to unpack method %v response: %w", - methodName, - err, - ) - } - - if len(anyOutput) != 1 { - return nil, fmt.Errorf( - "invalid ERC20 %v call return outputs %v, expected %v", - methodName, - len(anyOutput), - 1, - ) - } - - bal, ok := anyOutput[0].(*big.Int) - if !ok { - return nil, fmt.Errorf( - "invalid ERC20 return type %T, expected %T", - anyOutput[0], - &big.Int{}, - ) - } - - return bal, nil -} diff --git a/x/evmutil/keeper/erc20_test.go.disabled b/x/evmutil/keeper/erc20_test.go.disabled deleted file mode 100644 index 87529619..00000000 --- a/x/evmutil/keeper/erc20_test.go.disabled +++ /dev/null @@ -1,232 +0,0 @@ -// package keeper_test - -// import ( -// "math/big" -// "testing" - -// "github.com/ethereum/go-ethereum/common" -// "github.com/stretchr/testify/suite" - -// "github.com/aura-nw/aura/x/evmutil/testutil" -// "github.com/aura-nw/aura/x/evmutil/types" -// "github.com/kava-labs/kava/app" -// ) - -// type ERC20TestSuite struct { -// testutil.Suite - -// contractAddr types.InternalEVMAddress -// } - -// func TestERC20TestSuite(t *testing.T) { -// suite.Run(t, new(ERC20TestSuite)) -// } - -// func (suite *ERC20TestSuite) SetupTest() { -// suite.Suite.SetupTest() -// suite.contractAddr = suite.DeployERC20() -// } - -// func (suite *ERC20TestSuite) TestERC20QueryBalanceOf_Empty() { -// bal, err := suite.Keeper.QueryERC20BalanceOf( -// suite.Ctx, -// suite.contractAddr, -// suite.Key1Addr, -// ) -// suite.Require().NoError(err) -// suite.Require().True(bal.Cmp(big.NewInt(0)) == 0, "balance should be 0") -// } - -// func (suite *ERC20TestSuite) TestERC20QueryBalanceOf_NonEmpty() { -// // Mint some tokens for the address -// err := suite.Keeper.MintERC20( -// suite.Ctx, -// suite.contractAddr, -// suite.Key1Addr, -// big.NewInt(10), -// ) -// suite.Require().NoError(err) - -// bal, err := suite.Keeper.QueryERC20BalanceOf( -// suite.Ctx, -// suite.contractAddr, -// suite.Key1Addr, -// ) -// suite.Require().NoError(err) -// suite.Require().Equal(big.NewInt(10), bal, "balance should be 10") -// } - -// func (suite *ERC20TestSuite) TestERC20Mint() { -// contractAddr := suite.DeployERC20() - -// // We can't test mint by module account like the Unauthorized test as we -// // cannot sign as the module account. Instead, we test the keeper method for -// // minting. - -// receiver := common.BytesToAddress(suite.Key2.PubKey().Address()) -// amount := big.NewInt(1234) -// err := suite.App.GetEvmutilKeeper().MintERC20(suite.Ctx, contractAddr, types.NewInternalEVMAddress(receiver), amount) -// suite.Require().NoError(err) - -// // Query ERC20.balanceOf() -// addr := common.BytesToAddress(suite.Key1.PubKey().Address()) -// res, err := suite.QueryContract( -// types.ERC20MintableBurnableContract.ABI, -// addr, -// suite.Key1, -// contractAddr, -// "balanceOf", -// receiver, -// ) -// suite.Require().NoError(err) -// suite.Require().Len(res, 1) - -// balance, ok := res[0].(*big.Int) -// suite.Require().True(ok, "balanceOf should respond with *big.Int") -// suite.Require().Equal(big.NewInt(1234), balance) -// } - -// func (suite *ERC20TestSuite) TestQueryERC20TotalSupply() { -// suite.Run("with no balance", func() { -// bal, err := suite.Keeper.QueryERC20TotalSupply(suite.Ctx, suite.contractAddr) -// suite.NoError(err) -// suite.BigIntsEqual(big.NewInt(0), bal, "expected total supply of 0") -// }) - -// suite.Run("with balance", func() { -// amount := big.NewInt(1e10) -// expectedTotal := big.NewInt(3e10) -// // mint 1e10 to three random accounts -// suite.NoError(suite.Keeper.MintERC20(suite.Ctx, suite.contractAddr, testutil.RandomInternalEVMAddress(), amount)) -// suite.NoError(suite.Keeper.MintERC20(suite.Ctx, suite.contractAddr, testutil.RandomInternalEVMAddress(), amount)) -// suite.NoError(suite.Keeper.MintERC20(suite.Ctx, suite.contractAddr, testutil.RandomInternalEVMAddress(), amount)) - -// bal, err := suite.Keeper.QueryERC20TotalSupply(suite.Ctx, suite.contractAddr) -// suite.NoError(err) -// suite.BigIntsEqual(expectedTotal, bal, "unexpected total supply after minting") -// }) -// } - -// func (suite *ERC20TestSuite) TestDeployKavaWrappedCosmosCoinERC20Contract() { -// suite.Run("fails to deploy invalid contract", func() { -// // empty other fields means this token is invalid. -// invalidToken := types.AllowedCosmosCoinERC20Token{CosmosDenom: "nope"} -// _, err := suite.Keeper.DeployKavaWrappedCosmosCoinERC20Contract(suite.Ctx, invalidToken) -// suite.ErrorContains(err, "token's name cannot be empty") -// }) - -// suite.Run("deploys contract with expected metadata & permissions", func() { -// caller, privKey := testutil.RandomEvmAccount() - -// token := types.NewAllowedCosmosCoinERC20Token("hard", "EVM HARD", "HARD", 6) -// addr, err := suite.Keeper.DeployKavaWrappedCosmosCoinERC20Contract(suite.Ctx, token) -// suite.NoError(err) -// suite.NotNil(addr) - -// callContract := func(method string, args ...interface{}) ([]interface{}, error) { -// return suite.QueryContract( -// types.ERC20KavaWrappedCosmosCoinContract.ABI, -// caller, -// privKey, -// addr, -// method, -// args..., -// ) -// } - -// // owner must be the evmutil module account -// data, err := callContract("owner") -// suite.NoError(err) -// suite.Len(data, 1) -// suite.Equal(types.ModuleEVMAddress, data[0].(common.Address)) - -// // get name -// data, err = callContract("name") -// suite.NoError(err) -// suite.Len(data, 1) -// suite.Equal(token.Name, data[0].(string)) - -// // get symbol -// data, err = callContract("symbol") -// suite.NoError(err) -// suite.Len(data, 1) -// suite.Equal(token.Symbol, data[0].(string)) - -// // get decimals -// data, err = callContract("decimals") -// suite.NoError(err) -// suite.Len(data, 1) -// suite.Equal(token.Decimals, uint32(data[0].(uint8))) - -// // should not be able to call mint -// _, err = callContract("mint", caller, big.NewInt(1)) -// suite.ErrorContains(err, "Ownable: caller is not the owner") - -// // should not be able to call burn -// _, err = callContract("burn", caller, big.NewInt(1)) -// suite.ErrorContains(err, "Ownable: caller is not the owner") -// }) -// } - -// func (suite *ERC20TestSuite) TestGetOrDeployCosmosCoinERC20Contract() { -// suite.Run("finds existing contract address", func() { -// suite.SetupTest() -// denom := "magic" -// addr := types.BytesToInternalEVMAddress(app.RandomAddress().Bytes()) -// // pretend like we've registered a contract in a previous life -// err := suite.Keeper.SetDeployedCosmosCoinContract(suite.Ctx, denom, addr) -// suite.NoError(err) - -// // expect it to find the registered address -// tokenInfo := types.AllowedCosmosCoinERC20Token{CosmosDenom: denom} -// contractAddress, err := suite.Keeper.GetOrDeployCosmosCoinERC20Contract(suite.Ctx, tokenInfo) -// suite.NoError(err) -// suite.Equal(addr, contractAddress) - -// // expect it to still be registered -// contractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom) -// suite.True(found) -// suite.Equal(addr, contractAddress) -// }) - -// suite.Run("deploys & registers contract when one does not exist", func() { -// suite.SetupTest() -// denom := "magic" -// tokenInfo := types.NewAllowedCosmosCoinERC20Token(denom, "Magic Coin", "MAGIC", 6) - -// // expect it to not be registered -// _, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom) -// suite.False(found) - -// // deploy the contract -// contractAddress, err := suite.Keeper.GetOrDeployCosmosCoinERC20Contract(suite.Ctx, tokenInfo) -// suite.NoError(err) - -// // expect it to be registered now -// registeredAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom) -// suite.True(found) -// suite.False(registeredAddress.IsNil()) -// suite.Equal(contractAddress, registeredAddress) -// }) - -// // this can only happen if governance passes a bad allowed token -// suite.Run("fails when token can't be deployed", func() { -// suite.SetupTest() -// denom := "nope" -// // empty other fields means this token is invalid. -// invalidToken := types.AllowedCosmosCoinERC20Token{CosmosDenom: denom} - -// // expect it to not be registered -// _, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom) -// suite.False(found) - -// // attempt to deploy the contract -// contractAddress, err := suite.Keeper.GetOrDeployCosmosCoinERC20Contract(suite.Ctx, invalidToken) -// suite.ErrorContains(err, "failed to deploy erc20") -// suite.True(contractAddress.IsNil()) - -// // still expect it to not be registered -// _, found = suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom) -// suite.False(found) -// }) -// } diff --git a/x/evmutil/keeper/grpc_query.go b/x/evmutil/keeper/grpc_query.go index 3cbfb326..08de9b00 100644 --- a/x/evmutil/keeper/grpc_query.go +++ b/x/evmutil/keeper/grpc_query.go @@ -6,9 +6,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/query" "github.com/aura-nw/aura/x/evmutil/types" ) @@ -17,7 +15,7 @@ type queryServer struct { keeper Keeper } -// NewQueryServerImpl creates a new server for handling gRPC queries. +// NewQueryServerImpl creates a new server for handling gRPC queries.1 func NewQueryServerImpl(k Keeper) types.QueryServer { return &queryServer{keeper: k} } @@ -35,79 +33,3 @@ func (s queryServer) Params(stdCtx context.Context, req *types.QueryParamsReques return &types.QueryParamsResponse{Params: params}, nil } - -// DeployedCosmosCoinContracts gets contract addresses for deployed erc20 contracts -// representing cosmos-sdk coins -func (s queryServer) DeployedCosmosCoinContracts( - goCtx context.Context, - req *types.QueryDeployedCosmosCoinContractsRequest, -) (res *types.QueryDeployedCosmosCoinContractsResponse, err error) { - if req == nil { - return nil, status.Errorf(codes.InvalidArgument, "empty request") - } - - ctx := sdk.UnwrapSDKContext(goCtx) - if len(req.CosmosDenoms) > 0 { - res, err = getDeployedCosmosCoinContractsByDenoms(&s.keeper, ctx, req.CosmosDenoms) - } else { - // requesting no sdk denoms is a request for all denoms - res, err = getAllDeployedCosmosCoinContractsPage(&s.keeper, ctx, req.Pagination) - } - - return res, err -} - -// getAllDeployedCosmosCoinContractsPage gets a page of deployed contracts (no filtering) -func getAllDeployedCosmosCoinContractsPage( - k *Keeper, ctx sdk.Context, pagination *query.PageRequest, -) (*types.QueryDeployedCosmosCoinContractsResponse, error) { - contracts := make([]types.DeployedCosmosCoinContract, 0) - contractStore := prefix.NewStore( - ctx.KVStore(k.storeKey), - types.DeployedCosmosCoinContractKeyPrefix, - ) - - pageRes, err := query.FilteredPaginate(contractStore, pagination, - func(key []byte, value []byte, accumulate bool) (bool, error) { - if !accumulate { - return true, nil - } - address := types.BytesToInternalEVMAddress(value) - contract := types.DeployedCosmosCoinContract{ - CosmosDenom: string(key), - Address: &address, - } - contracts = append(contracts, contract) - return true, nil - }) - if err != nil { - return &types.QueryDeployedCosmosCoinContractsResponse{}, err - } - - return &types.QueryDeployedCosmosCoinContractsResponse{ - DeployedCosmosCoinContracts: contracts, - Pagination: pageRes, - }, nil -} - -func getDeployedCosmosCoinContractsByDenoms( - k *Keeper, ctx sdk.Context, denoms []string, -) (*types.QueryDeployedCosmosCoinContractsResponse, error) { - if len(denoms) > query.DefaultLimit { - // forego dealing with pagination by rejecting reqs for >100 denoms - return nil, status.Errorf(codes.InvalidArgument, "maximum of %d denoms allowed per request", query.DefaultLimit) - } - - contracts := make([]types.DeployedCosmosCoinContract, 0, len(denoms)) - for _, denom := range denoms { - address, found := k.GetDeployedCosmosCoinContract(ctx, denom) - if !found { - continue - } - contracts = append(contracts, types.NewDeployedCosmosCoinContract(denom, address)) - } - - return &types.QueryDeployedCosmosCoinContractsResponse{ - DeployedCosmosCoinContracts: contracts, - }, nil -} diff --git a/x/evmutil/keeper/invariants.go b/x/evmutil/keeper/invariants.go index 55abd67f..f785f90c 100644 --- a/x/evmutil/keeper/invariants.go +++ b/x/evmutil/keeper/invariants.go @@ -1,8 +1,6 @@ package keeper import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -13,9 +11,6 @@ import ( func RegisterInvariants(ir sdk.InvariantRegistry, bankK types.BankKeeper, k Keeper) { ir.RegisterRoute(types.ModuleName, "fully-backed", FullyBackedInvariant(bankK, k)) ir.RegisterRoute(types.ModuleName, "small-balances", SmallBalancesInvariant(bankK, k)) - ir.RegisterRoute(types.ModuleName, "cosmos-coins-fully-backed", CosmosCoinsFullyBackedInvariant(bankK, k)) - // Disable this invariant due to some issues with it requiring some staking params to be set in genesis. - // ir.RegisterRoute(types.ModuleName, "backed-conversion-coins", BackedCoinsInvariant(bankK, k)) } // AllInvariants runs all invariants of the swap module @@ -24,12 +19,6 @@ func AllInvariants(bankK types.BankKeeper, k Keeper) sdk.Invariant { if res, stop := FullyBackedInvariant(bankK, k)(ctx); stop { return res, stop } - if res, stop := BackedCoinsInvariant(bankK, k)(ctx); stop { - return res, stop - } - if res, stop := CosmosCoinsFullyBackedInvariant(bankK, k)(ctx); stop { - return res, stop - } return SmallBalancesInvariant(bankK, k)(ctx) } } @@ -74,75 +63,3 @@ func SmallBalancesInvariant(_ types.BankKeeper, k Keeper) sdk.Invariant { return message, broken } } - -// BackedCoinsInvariant iterates all conversion pairs and asserts that the -// sdk.Coin balances are less than the module ERC20 balance. -// **Note:** This compares <= and not == as anyone can send tokens to the -// ERC20 contract address and break the invariant if a strict equal check. -func BackedCoinsInvariant(_ types.BankKeeper, k Keeper) sdk.Invariant { - broken := false - message := sdk.FormatInvariant( - types.ModuleName, - "backed coins broken", - "coin supply is greater than module account ERC20 tokens", - ) - - return func(ctx sdk.Context) (string, bool) { - params := k.GetParams(ctx) - for _, pair := range params.EnabledConversionPairs { - erc20Balance, err := k.QueryERC20BalanceOf( - ctx, - pair.GetAddress(), - types.NewInternalEVMAddress(types.ModuleEVMAddress), - ) - if err != nil { - panic(err) - } - - supply := k.bankKeeper.GetSupply(ctx, pair.Denom) - - // Must be true: sdk.Coin supply < ERC20 balanceOf(module account) - if supply.Amount.BigInt().Cmp(erc20Balance) > 0 { - broken = true - break - } - } - - return message, broken - } -} - -// CosmosCoinsFullyBackedInvariant ensures the total supply of ERC20 representations of sdk.Coins -// match the balances in the module account. -// -// This invariant depends on the fact that coins can only become part of the balance through -// conversion to ERC20s. -// If in the future sdk.Coins can be sent directly to the module account, -// or the module account balance can be increased in any other way, -// this invariant should be changed from checking that the balance equals the total supply, -// to check that the balance is greater than or equal to the total supply. -func CosmosCoinsFullyBackedInvariant(bankK types.BankKeeper, k Keeper) sdk.Invariant { - broken := false - message := sdk.FormatInvariant( - types.ModuleName, - "cosmos coins fully-backed broken", - "ERC20 total supply is not equal to module account balance", - ) - maccAddress := authtypes.NewModuleAddress(types.ModuleName) - - return func(ctx sdk.Context) (string, bool) { - k.IterateAllDeployedCosmosCoinContracts(ctx, func(c types.DeployedCosmosCoinContract) bool { - moduleBalance := bankK.GetBalance(ctx, maccAddress, c.CosmosDenom).Amount - totalSupply, err := k.QueryERC20TotalSupply(ctx, *c.Address) - if err != nil { - panic(fmt.Sprintf("failed to query total supply for %+v", c)) - } - // expect total supply to equal balance in the module - if totalSupply.Cmp(moduleBalance.BigInt()) != 0 { - broken = true - } - return broken - }) - return message, broken - } -} diff --git a/x/evmutil/keeper/keeper.go b/x/evmutil/keeper/keeper.go index 3563eeff..e800104a 100644 --- a/x/evmutil/keeper/keeper.go +++ b/x/evmutil/keeper/keeper.go @@ -183,50 +183,3 @@ func (k Keeper) RemoveBalance(ctx sdk.Context, addr sdk.AccAddress, amt sdkmath. } return k.SetBalance(ctx, addr, finalBal) } - -// SetDeployedCosmosCoinContract stores a single deployed ERC20KavaWrappedCosmosCoin contract address -func (k *Keeper) SetDeployedCosmosCoinContract(ctx sdk.Context, cosmosDenom string, contractAddress types.InternalEVMAddress) error { - if err := sdk.ValidateDenom(cosmosDenom); err != nil { - return errorsmod.Wrap(types.ErrInvalidCosmosDenom, cosmosDenom) - } - if contractAddress.IsNil() { - return errorsmod.Wrapf( - sdkerrors.ErrInvalidAddress, - "attempting to register empty contract address for denom '%s'", - cosmosDenom, - ) - } - store := ctx.KVStore(k.storeKey) - storeKey := types.DeployedCosmosCoinContractKey(cosmosDenom) - - store.Set(storeKey, contractAddress.Bytes()) - return nil -} - -// SetDeployedCosmosCoinContract gets a deployed ERC20KavaWrappedCosmosCoin contract address by cosmos denom -// Returns the stored address and a bool indicating if it was found or not -func (k *Keeper) GetDeployedCosmosCoinContract(ctx sdk.Context, cosmosDenom string) (types.InternalEVMAddress, bool) { - store := ctx.KVStore(k.storeKey) - storeKey := types.DeployedCosmosCoinContractKey(cosmosDenom) - bz := store.Get(storeKey) - found := len(bz) != 0 - return types.BytesToInternalEVMAddress(bz), found -} - -// IterateAllDeployedCosmosCoinContracts iterates through all the deployed ERC20 contracts representing -// cosmos-sdk coins. If true is returned from the callback, iteration is halted. -func (k Keeper) IterateAllDeployedCosmosCoinContracts(ctx sdk.Context, cb func(types.DeployedCosmosCoinContract) bool) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, types.DeployedCosmosCoinContractKeyPrefix) - - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - contract := types.NewDeployedCosmosCoinContract( - types.DenomFromDeployedCosmosCoinContractKey(iterator.Key()), - types.BytesToInternalEVMAddress(iterator.Value()), - ) - if cb(contract) { - break - } - } -} diff --git a/x/evmutil/keeper/migrations.go b/x/evmutil/keeper/migrations.go index 72ab1598..3be1ada6 100644 --- a/x/evmutil/keeper/migrations.go +++ b/x/evmutil/keeper/migrations.go @@ -1,10 +1,5 @@ package keeper -import ( - v2 "github.com/aura-nw/aura/x/evmutil/migrations/v2" - sdk "github.com/cosmos/cosmos-sdk/types" -) - // Migrator is a struct for handling in-place store migrations. type Migrator struct { keeper Keeper @@ -16,8 +11,3 @@ func NewMigrator(keeper Keeper) Migrator { keeper: keeper, } } - -// Migrate1to2 migrates from version 1 to 2. -func (m Migrator) Migrate1to2(ctx sdk.Context) error { - return v2.MigrateStore(ctx, m.keeper.paramSubspace) -} diff --git a/x/evmutil/keeper/msg_server.go b/x/evmutil/keeper/msg_server.go deleted file mode 100644 index 6f01848e..00000000 --- a/x/evmutil/keeper/msg_server.go +++ /dev/null @@ -1,189 +0,0 @@ -package keeper - -import ( - "context" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/aura-nw/aura/x/evmutil/types" -) - -type msgServer struct { - keeper Keeper -} - -// NewMsgServerImpl returns an implementation of the evmutil MsgServer interface -// for the provided Keeper. -func NewMsgServerImpl(keeper Keeper) types.MsgServer { - return &msgServer{keeper: keeper} -} - -var _ types.MsgServer = msgServer{} - -//////////////////////////// -// EVM-native assets -> Cosmos SDK -//////////////////////////// - -// ConvertCoinToERC20 handles a MsgConvertCoinToERC20 message to convert -// sdk.Coin to Kava EVM tokens. -func (s msgServer) ConvertCoinToERC20( - goCtx context.Context, - msg *types.MsgConvertCoinToERC20, -) (*types.MsgConvertCoinToERC20Response, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - initiator, err := sdk.AccAddressFromBech32(msg.Initiator) - if err != nil { - return nil, fmt.Errorf("invalid Initiator address: %w", err) - } - - receiver, err := types.NewInternalEVMAddressFromString(msg.Receiver) - if err != nil { - return nil, fmt.Errorf("invalid Receiver address: %w", err) - } - - if err := s.keeper.ConvertCoinToERC20( - ctx, - initiator, - receiver, - *msg.Amount, - ); err != nil { - return nil, err - } - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(sdk.AttributeKeySender, msg.Initiator), - ), - ) - - return &types.MsgConvertCoinToERC20Response{}, nil -} - -// ConvertERC20ToCoin handles a MsgConvertERC20ToCoin message to convert -// sdk.Coin to Kava EVM tokens. -func (s msgServer) ConvertERC20ToCoin( - goCtx context.Context, - msg *types.MsgConvertERC20ToCoin, -) (*types.MsgConvertERC20ToCoinResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - initiator, err := types.NewInternalEVMAddressFromString(msg.Initiator) - if err != nil { - return nil, fmt.Errorf("invalid initiator address: %w", err) - } - - receiver, err := sdk.AccAddressFromBech32(msg.Receiver) - if err != nil { - return nil, fmt.Errorf("invalid receiver address: %w", err) - } - - contractAddr, err := types.NewInternalEVMAddressFromString(msg.KavaERC20Address) - if err != nil { - return nil, fmt.Errorf("invalid contract address: %w", err) - } - - if err := s.keeper.ConvertERC20ToCoin( - ctx, - initiator, - receiver, - contractAddr, - msg.Amount, - ); err != nil { - return nil, err - } - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(sdk.AttributeKeySender, msg.Initiator), - ), - ) - - return &types.MsgConvertERC20ToCoinResponse{}, nil -} - -//////////////////////////// -// Cosmos SDK-native assets -> EVM -//////////////////////////// - -// ConvertCosmosCoinToERC20 converts a native sdk.Coin to an ERC20. -// If no ERC20 contract has been deployed for the given denom, a new -// contract will be deployed and registered to the module. -func (s msgServer) ConvertCosmosCoinToERC20( - goCtx context.Context, - msg *types.MsgConvertCosmosCoinToERC20, -) (*types.MsgConvertCosmosCoinToERC20Response, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - initiator, err := sdk.AccAddressFromBech32(msg.Initiator) - if err != nil { - return nil, fmt.Errorf("invalid initiator address: %w", err) - } - - receiver, err := types.NewInternalEVMAddressFromString(msg.Receiver) - if err != nil { - return nil, fmt.Errorf("invalid receiver address: %w", err) - } - - if err := s.keeper.ConvertCosmosCoinToERC20( - ctx, - initiator, - receiver, - *msg.Amount, - ); err != nil { - return nil, err - } - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(sdk.AttributeKeySender, msg.Initiator), - ), - ) - - return &types.MsgConvertCosmosCoinToERC20Response{}, nil -} - -// ConvertCosmosCoinFromERC20 converts an ERC20 representation of a cosmos-native asset -// back into an sdk.Coin. -func (s msgServer) ConvertCosmosCoinFromERC20( - goCtx context.Context, - msg *types.MsgConvertCosmosCoinFromERC20, -) (*types.MsgConvertCosmosCoinFromERC20Response, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - initiator, err := types.NewInternalEVMAddressFromString(msg.Initiator) - if err != nil { - return nil, fmt.Errorf("invalid initiator address: %w", err) - } - - receiver, err := sdk.AccAddressFromBech32(msg.Receiver) - if err != nil { - return nil, fmt.Errorf("invalid receiver address: %w", err) - } - - if err := s.keeper.ConvertCosmosCoinFromERC20( - ctx, - initiator, - receiver, - *msg.Amount, - ); err != nil { - return nil, err - } - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(sdk.AttributeKeySender, msg.Initiator), - ), - ) - - return &types.MsgConvertCosmosCoinFromERC20Response{}, nil -} diff --git a/x/evmutil/keeper/msg_server_test.go.disabled b/x/evmutil/keeper/msg_server_test.go.disabled deleted file mode 100644 index bcae8518..00000000 --- a/x/evmutil/keeper/msg_server_test.go.disabled +++ /dev/null @@ -1,730 +0,0 @@ -// package keeper_test - -// import ( -// "math/big" -// "testing" - -// "github.com/stretchr/testify/suite" - -// sdkmath "cosmossdk.io/math" -// sdk "github.com/cosmos/cosmos-sdk/types" - -// "github.com/ethereum/go-ethereum/common" -// "github.com/ethereum/go-ethereum/common/math" - -// "github.com/aura-nw/aura/x/evmutil/keeper" -// "github.com/aura-nw/aura/x/evmutil/testutil" -// "github.com/aura-nw/aura/x/evmutil/types" -// "github.com/kava-labs/kava/app" -// ) - -// type MsgServerSuite struct { -// testutil.Suite - -// msgServer types.MsgServer -// } - -// func (suite *MsgServerSuite) SetupTest() { -// suite.Suite.SetupTest() -// suite.msgServer = keeper.NewMsgServerImpl(suite.App.GetEvmutilKeeper()) -// } - -// func TestMsgServerSuite(t *testing.T) { -// suite.Run(t, new(MsgServerSuite)) -// } - -// func (suite *MsgServerSuite) TestConvertCoinToERC20() { -// invoker, err := sdk.AccAddressFromBech32("kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz") -// suite.Require().NoError(err) - -// err = suite.App.FundAccount(suite.Ctx, invoker, sdk.NewCoins(sdk.NewCoin("erc20/usdc", sdkmath.NewInt(10000)))) -// suite.Require().NoError(err) - -// contractAddr := suite.DeployERC20() - -// pair := types.NewConversionPair( -// contractAddr, -// "erc20/usdc", -// ) - -// // Module account should have starting balance -// pairStartingBal := big.NewInt(10000) -// err = suite.Keeper.MintERC20( -// suite.Ctx, -// pair.GetAddress(), // contractAddr -// types.NewInternalEVMAddress(types.ModuleEVMAddress), //receiver -// pairStartingBal, -// ) -// suite.Require().NoError(err) - -// type errArgs struct { -// expectPass bool -// contains string -// } - -// tests := []struct { -// name string -// msg types.MsgConvertCoinToERC20 -// errArgs errArgs -// }{ -// { -// "valid", -// types.NewMsgConvertCoinToERC20( -// invoker.String(), -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", -// sdk.NewCoin("erc20/usdc", sdkmath.NewInt(1234)), -// ), -// errArgs{ -// expectPass: true, -// }, -// }, -// { -// "invalid - odd length hex address", -// types.NewMsgConvertCoinToERC20( -// invoker.String(), -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc", -// sdk.NewCoin("erc20/usdc", sdkmath.NewInt(1234)), -// ), -// errArgs{ -// expectPass: false, -// contains: "invalid Receiver address: string is not a hex address", -// }, -// }, -// // Amount coin is not validated by msg_server, but on msg itself -// } - -// for _, tc := range tests { -// suite.Run(tc.name, func() { -// _, err := suite.msgServer.ConvertCoinToERC20(sdk.WrapSDKContext(suite.Ctx), &tc.msg) - -// if tc.errArgs.expectPass { -// suite.Require().NoError(err) - -// bal := suite.GetERC20BalanceOf( -// types.ERC20MintableBurnableContract.ABI, -// pair.GetAddress(), -// testutil.MustNewInternalEVMAddressFromString(tc.msg.Receiver), -// ) - -// suite.Require().Equal(tc.msg.Amount.Amount.BigInt(), bal, "balance should match converted amount") - -// // msg server event -// suite.EventsContains(suite.GetEvents(), -// sdk.NewEvent( -// sdk.EventTypeMessage, -// sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), -// sdk.NewAttribute(sdk.AttributeKeySender, tc.msg.Initiator), -// )) - -// // keeper event -// suite.EventsContains(suite.GetEvents(), -// sdk.NewEvent( -// types.EventTypeConvertCoinToERC20, -// sdk.NewAttribute(types.AttributeKeyInitiator, tc.msg.Initiator), -// sdk.NewAttribute(types.AttributeKeyReceiver, tc.msg.Receiver), -// sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()), -// sdk.NewAttribute(types.AttributeKeyAmount, tc.msg.Amount.String()), -// )) -// } else { -// suite.Require().Error(err) -// suite.Require().Contains(err.Error(), tc.errArgs.contains) -// } -// }) -// } -// } - -// func (suite *MsgServerSuite) TestConvertERC20ToCoin() { -// contractAddr := suite.DeployERC20() -// pair := types.NewConversionPair( -// contractAddr, -// "erc20/usdc", -// ) - -// // give invoker account some erc20 usdc to begin with -// invoker := testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") -// pairStartingBal := big.NewInt(10_000_000) -// err := suite.Keeper.MintERC20( -// suite.Ctx, -// pair.GetAddress(), // contractAddr -// invoker, //receiver -// pairStartingBal, -// ) -// suite.Require().NoError(err) - -// invokerCosmosAddr, err := sdk.AccAddressFromHexUnsafe(invoker.String()[2:]) -// suite.Require().NoError(err) - -// // create user account, otherwise `CallEVMWithData` will fail due to failing to get user account when finding its sequence. -// err = suite.App.FundAccount(suite.Ctx, invokerCosmosAddr, sdk.NewCoins(sdk.NewCoin(pair.Denom, sdk.ZeroInt()))) -// suite.Require().NoError(err) - -// type errArgs struct { -// expectPass bool -// contains string -// } - -// tests := []struct { -// name string -// msg types.MsgConvertERC20ToCoin -// approvalAmount *big.Int -// errArgs errArgs -// }{ -// { -// "valid", -// types.NewMsgConvertERC20ToCoin( -// invoker, -// invokerCosmosAddr, -// contractAddr, -// sdkmath.NewInt(10_000), -// ), -// math.MaxBig256, -// errArgs{ -// expectPass: true, -// }, -// }, -// { -// "invalid - invalid hex address", -// types.MsgConvertERC20ToCoin{ -// Initiator: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc", -// Receiver: invokerCosmosAddr.String(), -// KavaERC20Address: contractAddr.String(), -// Amount: sdkmath.NewInt(10_000), -// }, -// math.MaxBig256, -// errArgs{ -// expectPass: false, -// contains: "invalid initiator address: string is not a hex address", -// }, -// }, -// { -// "invalid - insufficient coins", -// types.NewMsgConvertERC20ToCoin( -// invoker, -// invokerCosmosAddr, -// contractAddr, -// sdkmath.NewIntFromBigInt(pairStartingBal).Add(sdk.OneInt()), -// ), -// math.MaxBig256, -// errArgs{ -// expectPass: false, -// contains: "transfer amount exceeds balance", -// }, -// }, -// { -// "invalid - contract address", -// types.NewMsgConvertERC20ToCoin( -// invoker, -// invokerCosmosAddr, -// testutil.MustNewInternalEVMAddressFromString("0x7Bbf300890857b8c241b219C6a489431669b3aFA"), -// sdkmath.NewInt(10_000), -// ), -// math.MaxBig256, -// errArgs{ -// expectPass: false, -// contains: "ERC20 token not enabled to convert to sdk.Coin", -// }, -// }, -// } - -// for _, tc := range tests { -// suite.Run(tc.name, func() { -// _, err := suite.msgServer.ConvertERC20ToCoin(sdk.WrapSDKContext(suite.Ctx), &tc.msg) - -// if tc.errArgs.expectPass { -// suite.Require().NoError(err) - -// // validate user balance after conversion -// bal := suite.GetERC20BalanceOf( -// types.ERC20MintableBurnableContract.ABI, -// pair.GetAddress(), -// testutil.MustNewInternalEVMAddressFromString(tc.msg.Initiator), -// ) -// expectedBal := sdkmath.NewIntFromBigInt(pairStartingBal).Sub(tc.msg.Amount) -// suite.Require().Equal(expectedBal.BigInt(), bal, "user erc20 balance is invalid") - -// // validate user coin balance -// coinBal := suite.App.GetBankKeeper().GetBalance(suite.Ctx, invokerCosmosAddr, pair.Denom) -// suite.Require().Equal(tc.msg.Amount, coinBal.Amount, "user coin balance is invalid") - -// // msg server event -// suite.EventsContains(suite.GetEvents(), -// sdk.NewEvent( -// sdk.EventTypeMessage, -// sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), -// sdk.NewAttribute(sdk.AttributeKeySender, tc.msg.Initiator), -// )) - -// // keeper event -// suite.EventsContains(suite.GetEvents(), -// sdk.NewEvent( -// types.EventTypeConvertERC20ToCoin, -// sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()), -// sdk.NewAttribute(types.AttributeKeyInitiator, tc.msg.Initiator), -// sdk.NewAttribute(types.AttributeKeyReceiver, tc.msg.Receiver), -// sdk.NewAttribute(types.AttributeKeyAmount, sdk.NewCoin(pair.Denom, tc.msg.Amount).String()), -// )) -// } else { -// suite.Require().Error(err) -// suite.Require().Contains(err.Error(), tc.errArgs.contains) -// } -// }) -// } -// } - -// func (suite *MsgServerSuite) TestConvertCosmosCoinToERC20_InitialContractDeploy() { -// allowedDenom := "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2" -// initialFunding := int64(1e10) -// fundedAccount := app.RandomAddress() - -// setup := func() { -// suite.SetupTest() - -// // make the denom allowed for conversion -// params := suite.Keeper.GetParams(suite.Ctx) -// params.AllowedCosmosDenoms = types.NewAllowedCosmosCoinERC20Tokens( -// types.NewAllowedCosmosCoinERC20Token(allowedDenom, "Kava EVM Atom", "ATOM", 6), -// ) -// suite.Keeper.SetParams(suite.Ctx, params) - -// // fund account -// err := suite.App.FundAccount(suite.Ctx, fundedAccount, sdk.NewCoins( -// sdk.NewInt64Coin(allowedDenom, initialFunding), -// )) -// suite.NoError(err, "failed to initially fund account") -// } - -// testCases := []struct { -// name string -// msg types.MsgConvertCosmosCoinToERC20 -// amountConverted sdkmath.Int -// expectedErr string -// }{ -// { -// name: "valid - first conversion deploys contract, send to self", -// msg: types.NewMsgConvertCosmosCoinToERC20( -// fundedAccount.String(), -// common.BytesToAddress(fundedAccount.Bytes()).Hex(), // it's me! -// sdk.NewInt64Coin(allowedDenom, 5e7), -// ), -// amountConverted: sdkmath.NewInt(5e7), -// expectedErr: "", -// }, -// { -// name: "valid - first conversion deploys contract, send to other", -// msg: types.NewMsgConvertCosmosCoinToERC20( -// fundedAccount.String(), -// testutil.RandomEvmAddress().Hex(), // someone else! -// sdk.NewInt64Coin(allowedDenom, 9993317), -// ), -// amountConverted: sdkmath.NewInt(9993317), -// expectedErr: "", -// }, -// { -// name: "invalid - un-allowed denom", -// msg: types.NewMsgConvertCosmosCoinToERC20( -// app.RandomAddress().String(), -// testutil.RandomEvmAddress().Hex(), -// sdk.NewInt64Coin("not-allowed-denom", 1e4), -// ), -// expectedErr: "sdk.Coin not enabled to convert to ERC20 token", -// }, -// { -// name: "invalid - bad initiator", -// msg: types.NewMsgConvertCosmosCoinToERC20( -// "invalid-kava-address", -// testutil.RandomEvmAddress().Hex(), -// sdk.NewInt64Coin(allowedDenom, 1e4), -// ), -// expectedErr: "invalid initiator address", -// }, -// { -// name: "invalid - bad receiver", -// msg: types.NewMsgConvertCosmosCoinToERC20( -// app.RandomAddress().String(), -// "invalid-0x-address", -// sdk.NewInt64Coin(allowedDenom, 1e4), -// ), -// expectedErr: "invalid receiver address", -// }, -// { -// name: "invalid - bad receiver", -// msg: types.NewMsgConvertCosmosCoinToERC20( -// app.RandomAddress().String(), -// "invalid-0x-address", -// sdk.NewInt64Coin(allowedDenom, 1e4), -// ), -// expectedErr: "invalid receiver address", -// }, -// { -// name: "invalid - insufficient balance", -// msg: types.NewMsgConvertCosmosCoinToERC20( -// fundedAccount.String(), -// testutil.RandomEvmAddress().Hex(), -// sdk.NewInt64Coin(allowedDenom, initialFunding+1), -// ), -// expectedErr: "insufficient funds", -// }, -// // NOTE: a zero amount tx passes in this scope but will fail to pass ValidateBasic() -// } - -// for _, tc := range testCases { -// suite.Run(tc.name, func() { -// // initial setup -// setup() - -// moduleBalanceBefore := suite.ModuleBalance(allowedDenom) - -// // submit message -// _, err := suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &tc.msg) - -// // verify error, if expected -// if tc.expectedErr != "" { -// suite.ErrorContains(err, tc.expectedErr) -// // the contract wasn't previously deployed, so still shouldn't be -// _, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, allowedDenom) -// suite.False(found) -// return -// } - -// // verify success -// suite.NoError(err) - -// initiator := sdk.MustAccAddressFromBech32(tc.msg.Initiator) -// receiver := testutil.MustNewInternalEVMAddressFromString(tc.msg.Receiver) - -// // initiator no longer has sdk coins -// cosmosBalanceAfter := suite.BankKeeper.GetBalance(suite.Ctx, initiator, allowedDenom) -// suite.Equal( -// sdkmath.NewInt(initialFunding).Sub(tc.amountConverted), -// cosmosBalanceAfter.Amount, -// "unexpected sdk.Coin balance of initiator", -// ) - -// // sdk coins are locked into module -// moduleBalanceAfter := suite.ModuleBalance(allowedDenom) -// suite.Equal( -// moduleBalanceBefore.Add(tc.amountConverted), -// moduleBalanceAfter, -// "unexpected module balance", -// ) - -// // deployed contract address is registered in module store -// contractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, allowedDenom) -// suite.True(found, "expected deployed contract address to be registered, found none") - -// // receiver has been minted correct number of tokens -// erc20Balance, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, receiver) -// suite.NoError(err) -// suite.Equal(tc.amountConverted.BigInt(), erc20Balance, "unexpected erc20 balance for receiver") - -// // msg server event -// suite.EventsContains(suite.GetEvents(), -// sdk.NewEvent( -// sdk.EventTypeMessage, -// sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), -// sdk.NewAttribute(sdk.AttributeKeySender, initiator.String()), -// )) - -// // keeper event -// suite.EventsContains(suite.GetEvents(), -// sdk.NewEvent( -// types.EventTypeConvertCosmosCoinToERC20, -// sdk.NewAttribute(types.AttributeKeyInitiator, initiator.String()), -// sdk.NewAttribute(types.AttributeKeyReceiver, receiver.String()), -// sdk.NewAttribute(types.AttributeKeyERC20Address, contractAddress.Hex()), -// sdk.NewAttribute(types.AttributeKeyAmount, tc.msg.Amount.String()), -// )) -// }) -// } -// } - -// func (suite *MsgServerSuite) TestConvertCosmosCoinToERC20_AlreadyDeployedContract() { -// allowedDenom := "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2" -// initialFunding := int64(1e10) -// fundedAccount := app.RandomAddress() - -// amount := sdkmath.NewInt(6e8) -// receiver1 := types.BytesToInternalEVMAddress(app.RandomAddress().Bytes()) -// receiver2 := types.BytesToInternalEVMAddress(app.RandomAddress().Bytes()) - -// suite.SetupTest() - -// // make the denom allowed for conversion -// params := suite.Keeper.GetParams(suite.Ctx) -// params.AllowedCosmosDenoms = types.NewAllowedCosmosCoinERC20Tokens( -// types.NewAllowedCosmosCoinERC20Token(allowedDenom, "Kava EVM Atom", "ATOM", 6), -// ) -// suite.Keeper.SetParams(suite.Ctx, params) - -// // fund account -// err := suite.App.FundAccount(suite.Ctx, fundedAccount, sdk.NewCoins( -// sdk.NewInt64Coin(allowedDenom, initialFunding), -// )) -// suite.NoError(err, "failed to initially fund account") - -// // verify contract is not deployed -// _, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, allowedDenom) -// suite.False(found) - -// // initial convert deploys contract -// msg := types.NewMsgConvertCosmosCoinToERC20( -// fundedAccount.String(), -// receiver1.Hex(), -// sdk.NewCoin(allowedDenom, amount), -// ) -// _, err = suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg) -// suite.NoError(err) - -// contractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, allowedDenom) -// suite.True(found) - -// // second convert uses same contract -// msg.Receiver = receiver2.Hex() -// _, err = suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg) -// suite.NoError(err) - -// after2ndUseAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, allowedDenom) -// suite.True(found) -// suite.Equal(contractAddress, after2ndUseAddress, "contract address should remain the same") - -// // check balances -// bal1, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, receiver1) -// suite.NoError(err) -// suite.Equal(amount.BigInt(), bal1) - -// bal2, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, receiver2) -// suite.NoError(err) -// suite.Equal(amount.BigInt(), bal2) - -// // check total supply -// caller, key := testutil.RandomEvmAccount() -// totalSupply, err := suite.QueryContract( -// types.ERC20KavaWrappedCosmosCoinContract.ABI, -// caller, -// key, -// contractAddress, -// "totalSupply", -// ) -// suite.NoError(err) -// suite.Len(totalSupply, 1) -// suite.Equal(amount.MulRaw(2).BigInt(), totalSupply[0].(*big.Int)) -// } - -// func (suite *MsgServerSuite) TestConvertCosmosCoinFromERC20() { -// denom := "magic" -// tokenInfo := types.NewAllowedCosmosCoinERC20Token(denom, "Cosmos Coin", "MAGIC", 6) -// initialPosition := sdk.NewInt64Coin(denom, 1e10) -// initiator := testutil.RandomInternalEVMAddress() - -// var contractAddress types.InternalEVMAddress -// setup := func() { -// suite.SetupTest() - -// // allow conversion to the denom -// params := suite.Keeper.GetParams(suite.Ctx) -// params.AllowedCosmosDenoms = append(params.AllowedCosmosDenoms, tokenInfo) -// suite.Keeper.SetParams(suite.Ctx, params) - -// // setup initial position -// addr := app.RandomAddress() -// err := suite.App.FundAccount(suite.Ctx, addr, sdk.NewCoins(initialPosition)) -// suite.NoError(err) -// err = suite.Keeper.ConvertCosmosCoinToERC20(suite.Ctx, addr, initiator, initialPosition) -// suite.NoError(err) - -// contractAddress, _ = suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom) -// } - -// testCases := []struct { -// name string -// msg types.MsgConvertCosmosCoinFromERC20 -// amountConverted sdkmath.Int -// expectedErr string -// }{ -// { -// name: "valid - full convert", -// msg: types.NewMsgConvertCosmosCoinFromERC20( -// initiator.Hex(), -// app.RandomAddress().String(), -// initialPosition, -// ), -// amountConverted: initialPosition.Amount, -// expectedErr: "", -// }, -// { -// name: "valid - partial convert", -// msg: types.NewMsgConvertCosmosCoinFromERC20( -// initiator.Hex(), -// app.RandomAddress().String(), -// sdk.NewInt64Coin(denom, 123456), -// ), -// amountConverted: sdkmath.NewInt(123456), -// expectedErr: "", -// }, -// { -// name: "invalid - bad initiator", -// msg: types.NewMsgConvertCosmosCoinFromERC20( -// "invalid-address", -// app.RandomAddress().String(), -// sdk.NewInt64Coin(denom, 123456), -// ), -// amountConverted: sdkmath.ZeroInt(), -// expectedErr: "invalid initiator address", -// }, -// { -// name: "invalid - bad receiver", -// msg: types.NewMsgConvertCosmosCoinFromERC20( -// testutil.RandomEvmAddress().Hex(), -// "invalid-address", -// sdk.NewInt64Coin(denom, 123456), -// ), -// amountConverted: sdkmath.ZeroInt(), -// expectedErr: "invalid receiver address", -// }, -// { -// name: "invalid - unsupported asset", -// msg: types.NewMsgConvertCosmosCoinFromERC20( -// initiator.Hex(), -// app.RandomAddress().String(), -// sdk.NewInt64Coin("not-supported", 123456), -// ), -// amountConverted: sdkmath.ZeroInt(), -// expectedErr: "no erc20 contract found", -// }, -// { -// name: "invalid - insufficient funds", -// msg: types.NewMsgConvertCosmosCoinFromERC20( -// initiator.Hex(), -// app.RandomAddress().String(), -// initialPosition.AddAmount(sdkmath.OneInt()), -// ), -// amountConverted: sdkmath.ZeroInt(), -// expectedErr: "failed to convert to cosmos coins: insufficient funds", -// }, -// } - -// for _, tc := range testCases { -// suite.Run(tc.name, func() { -// setup() - -// _, err := suite.msgServer.ConvertCosmosCoinFromERC20(suite.Ctx, &tc.msg) - -// if tc.expectedErr != "" { -// suite.ErrorContains(err, tc.expectedErr) -// // expect no change in erc20 balance -// balance, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, initiator) -// suite.NoError(err) -// suite.BigIntsEqual(initialPosition.Amount.BigInt(), balance, "expected no change in initiator's erc20 balance") -// // expect no change in module balance -// suite.Equal(initialPosition.Amount, suite.ModuleBalance(denom), "expected no change in module balance") -// return -// } - -// suite.NoError(err) - -// receiver := sdk.MustAccAddressFromBech32(tc.msg.Receiver) -// // expect receiver to have the sdk coins -// sdkBalance := suite.BankKeeper.GetBalance(suite.Ctx, receiver, denom) -// suite.Equal(tc.amountConverted, sdkBalance.Amount) - -// newEvmBalance := initialPosition.SubAmount(tc.amountConverted) -// // expect initiator to have the balance deducted -// evmBalance, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, initiator) -// suite.NoError(err) -// suite.BigIntsEqual(newEvmBalance.Amount.BigInt(), evmBalance, "unexpected initiator final erc20 balance") - -// // expect tokens to be deducted from module account -// suite.True(newEvmBalance.Amount.Equal(suite.ModuleBalance(denom)), "unexpected module balance") - -// // expect erc20 total supply to reflect new value -// caller, key := testutil.RandomEvmAccount() -// totalSupply, err := suite.QueryContract( -// types.ERC20KavaWrappedCosmosCoinContract.ABI, -// caller, -// key, -// contractAddress, -// "totalSupply", -// ) -// suite.NoError(err) -// suite.BigIntsEqual(newEvmBalance.Amount.BigInt(), totalSupply[0].(*big.Int), "unexpected total supply") -// }) -// } -// } - -// // the test verifies the behavior for when a denom is removed from the params list -// // after conversions have been made: -// // - it should prevent more conversions from sdk -> evm for that denom -// // - existing erc20s should be allowed to get converted back to a sdk.Coins -// // - allowing the denom again should use existing contract -// func (suite *MsgServerSuite) TestConvertCosmosCoinForRemovedDenom() { -// denom := "magic" -// tokenInfo := types.NewAllowedCosmosCoinERC20Token(denom, "MAGIC COIN", "MAGIC", 6) -// account := app.RandomAddress() -// evmAddr := types.BytesToInternalEVMAddress(account.Bytes()) -// coin := func(amt int64) sdk.Coin { return sdk.NewInt64Coin(denom, amt) } - -// // fund account -// suite.NoError(suite.App.FundAccount(suite.Ctx, account, sdk.NewCoins(coin(1e10)))) - -// // setup the token as allowed -// params := suite.Keeper.GetParams(suite.Ctx) -// params.AllowedCosmosDenoms = append(params.AllowedCosmosDenoms, tokenInfo) -// suite.Keeper.SetParams(suite.Ctx, params) - -// // convert some coins while its allowed -// msg := types.NewMsgConvertCosmosCoinToERC20(account.String(), evmAddr.Hex(), coin(5e9)) -// _, err := suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg) -// suite.NoError(err) - -// // expect contract registered -// contractAddress, isRegistered := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom) -// suite.True(isRegistered) -// suite.False(contractAddress.IsNil()) - -// // unregister contract -// params.AllowedCosmosDenoms = []types.AllowedCosmosCoinERC20Token{} -// suite.Keeper.SetParams(suite.Ctx, params) - -// suite.Run("disallows sdk -> evm when removed", func() { -// msg := types.NewMsgConvertCosmosCoinToERC20(account.String(), evmAddr.Hex(), coin(5e9)) -// _, err := suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg) -// suite.ErrorContains(err, "sdk.Coin not enabled to convert to ERC20 token") -// }) - -// suite.Run("allows conversion of existing ERC20s", func() { -// msg := types.NewMsgConvertCosmosCoinFromERC20(evmAddr.Hex(), account.String(), coin(5e9)) -// _, err := suite.msgServer.ConvertCosmosCoinFromERC20(suite.Ctx, &msg) -// suite.NoError(err) - -// // should be fully withdrawn -// erc20Bal, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, evmAddr) -// suite.NoError(err) -// suite.BigIntsEqual(big.NewInt(0), erc20Bal, "cosmos coins were not converted back") -// sdkBal := suite.BankKeeper.GetBalance(suite.Ctx, account, denom) -// suite.Equal(coin(1e10), sdkBal) -// }) - -// suite.Run("contract stays registered", func() { -// postDisableContractAddress, found := suite.Keeper.GetDeployedCosmosCoinContract(suite.Ctx, denom) -// suite.True(found) -// suite.Equal(contractAddress, postDisableContractAddress) -// }) - -// suite.Run("re-enable uses original contract", func() { -// // re-enable contract -// params.AllowedCosmosDenoms = append(params.AllowedCosmosDenoms, tokenInfo) -// suite.Keeper.SetParams(suite.Ctx, params) - -// // attempt conversion -// msg := types.NewMsgConvertCosmosCoinToERC20(account.String(), evmAddr.Hex(), coin(1e10)) -// _, err := suite.msgServer.ConvertCosmosCoinToERC20(suite.Ctx, &msg) -// suite.NoError(err) - -// // should have balance on original ERC20 contract -// erc20Bal, err := suite.Keeper.QueryERC20BalanceOf(suite.Ctx, contractAddress, evmAddr) -// suite.NoError(err) -// suite.BigIntsEqual(big.NewInt(1e10), erc20Bal, "cosmos coins were not converted") -// sdkBal := suite.BankKeeper.GetBalance(suite.Ctx, account, denom) -// suite.True(sdkBal.IsZero()) -// }) -// } diff --git a/x/evmutil/keeper/params.go b/x/evmutil/keeper/params.go index be897033..ab779417 100644 --- a/x/evmutil/keeper/params.go +++ b/x/evmutil/keeper/params.go @@ -1,9 +1,6 @@ package keeper import ( - "bytes" - - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/aura-nw/aura/x/evmutil/types" @@ -19,45 +16,3 @@ func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { k.paramSubspace.SetParamSet(ctx, ¶ms) } - -// GetAllowedTokenMetadata gets the token metadata for the given cosmosDenom if it is allowed. -// Returns the metadata if allowed, and a bool indicating if the denom was in the allow list or not. -func (k Keeper) GetAllowedTokenMetadata(ctx sdk.Context, cosmosDenom string) (types.AllowedCosmosCoinERC20Token, bool) { - params := k.GetParams(ctx) - for _, token := range params.AllowedCosmosDenoms { - if token.CosmosDenom == cosmosDenom { - return token, true - } - } - return types.AllowedCosmosCoinERC20Token{}, false -} - -// GetEnabledConversionPairFromERC20Address returns an ConversionPair from the internal contract address. -func (k Keeper) GetEnabledConversionPairFromERC20Address( - ctx sdk.Context, - address types.InternalEVMAddress, -) (types.ConversionPair, error) { - params := k.GetParams(ctx) - for _, pair := range params.EnabledConversionPairs { - if bytes.Equal(pair.KavaERC20Address, address.Bytes()) { - return pair, nil - } - } - - return types.ConversionPair{}, errorsmod.Wrap(types.ErrEVMConversionNotEnabled, address.String()) -} - -// GetEnabledConversionPairFromDenom returns an ConversionPair from the sdk.Coin denom. -func (k Keeper) GetEnabledConversionPairFromDenom( - ctx sdk.Context, - denom string, -) (types.ConversionPair, error) { - params := k.GetParams(ctx) - for _, pair := range params.EnabledConversionPairs { - if pair.Denom == denom { - return pair, nil - } - } - - return types.ConversionPair{}, errorsmod.Wrap(types.ErrEVMConversionNotEnabled, denom) -} diff --git a/x/evmutil/keeper/params_test.go.disabled b/x/evmutil/keeper/params_test.go.disabled deleted file mode 100644 index 900a0cc7..00000000 --- a/x/evmutil/keeper/params_test.go.disabled +++ /dev/null @@ -1,89 +0,0 @@ -package keeper_test - -import ( - "testing" - - "github.com/stretchr/testify/suite" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/aura-nw/aura/x/evmutil/keeper" - "github.com/aura-nw/aura/x/evmutil/testutil" - "github.com/aura-nw/aura/x/evmutil/types" -) - -type ParamsTestSuite struct { - testutil.Suite -} - -func TestParamsSuite(t *testing.T) { - suite.Run(t, new(ParamsTestSuite)) -} - -func (suite *ParamsTestSuite) TestEnabledConversionPair() { - pairAddr := testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") - expPair := types.ConversionPair{ - KavaERC20Address: pairAddr.Bytes(), - Denom: "weth", - } - params := types.DefaultParams() - params.EnabledConversionPairs = []types.ConversionPair{expPair} - suite.Keeper.SetParams(suite.Ctx, params) - - actualPair, err := suite.Keeper.GetEnabledConversionPairFromERC20Address( - suite.Ctx, - pairAddr, - ) - suite.Require().NoError(err) - suite.Require().Equal(expPair, actualPair) -} - -func (suite *ParamsTestSuite) TestHistoricParamsQuery() { - // setup a params store that lacks allowed_cosmos_denoms param (as was the case in v1) - oldParamStore := suite.App.GetParamsKeeper().Subspace("test_subspace_for_evmutil") - oldParamStore.WithKeyTable(types.ParamKeyTable()) - oldParamStore.Set(suite.Ctx, types.KeyEnabledConversionPairs, types.ConversionPairs{}) - - suite.True(oldParamStore.Has(suite.Ctx, types.KeyEnabledConversionPairs)) - suite.False(oldParamStore.Has(suite.Ctx, types.KeyAllowedCosmosDenoms)) - - oldStateKeeper := keeper.NewKeeper( - suite.App.AppCodec(), - sdk.NewKVStoreKey(types.StoreKey), - oldParamStore, - suite.App.GetBankKeeper(), - suite.App.GetAccountKeeper(), - ) - - // prior to making GetParams() use GetParamSetIfExists, this would panic. - suite.NotPanics(func() { - _ = oldStateKeeper.GetParams(suite.Ctx) - }) -} - -func (suite *keeperTestSuite) TestGetAllowedTokenMetadata() { - suite.SetupTest() - - atom := types.NewAllowedCosmosCoinERC20Token( - "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", - "Kava EVM ATOM", "ATOM", 6, - ) - hard := types.NewAllowedCosmosCoinERC20Token("hard", "Kava EVM Hard", "HARD", 6) - - // init state with some allowed tokens - params := suite.Keeper.GetParams(suite.Ctx) - params.AllowedCosmosDenoms = types.NewAllowedCosmosCoinERC20Tokens(atom, hard) - suite.Keeper.SetParams(suite.Ctx, params) - - // finds allowed tokens by denom - storedAtom, allowed := suite.Keeper.GetAllowedTokenMetadata(suite.Ctx, atom.CosmosDenom) - suite.True(allowed) - suite.Equal(atom, storedAtom) - storedHard, allowed := suite.Keeper.GetAllowedTokenMetadata(suite.Ctx, hard.CosmosDenom) - suite.True(allowed) - suite.Equal(hard, storedHard) - - // returns not-allowed when token not allowed - _, allowed = suite.Keeper.GetAllowedTokenMetadata(suite.Ctx, "not-in-list") - suite.False(allowed) -} diff --git a/x/evmutil/migrations/v2/store.go b/x/evmutil/migrations/v2/store.go deleted file mode 100644 index d092c9dd..00000000 --- a/x/evmutil/migrations/v2/store.go +++ /dev/null @@ -1,23 +0,0 @@ -package v2 - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" - - "github.com/aura-nw/aura/x/evmutil/types" -) - -// MigrateStore performs in-place store migrations for consensus version 2 -// V2 adds the allowed_cosmos_denoms param to parameters. -func MigrateStore(ctx sdk.Context, paramstore paramtypes.Subspace) error { - migrateParamsStore(ctx, paramstore) - return nil -} - -// migrateParamsStore ensures the param key table exists and has the allowed_cosmos_denoms property -func migrateParamsStore(ctx sdk.Context, paramstore paramtypes.Subspace) { - if !paramstore.HasKeyTable() { - paramstore.WithKeyTable(types.ParamKeyTable()) - } - paramstore.Set(ctx, types.KeyAllowedCosmosDenoms, types.DefaultAllowedCosmosDenoms) -} diff --git a/x/evmutil/migrations/v2/store_test.go b/x/evmutil/migrations/v2/store_test.go deleted file mode 100644 index cb150a00..00000000 --- a/x/evmutil/migrations/v2/store_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package v2_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/cosmos/cosmos-sdk/testutil" - sdk "github.com/cosmos/cosmos-sdk/types" - moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" - - v2evmutil "github.com/aura-nw/aura/x/evmutil/migrations/v2" - "github.com/aura-nw/aura/x/evmutil/types" -) - -func TestStoreMigrationAddsKeyTableIncludingNewParam(t *testing.T) { - encCfg := moduletestutil.MakeTestEncodingConfig() - evmutilKey := sdk.NewKVStoreKey(types.ModuleName) - tEvmutilKey := sdk.NewTransientStoreKey("transient_test") - ctx := testutil.DefaultContext(evmutilKey, tEvmutilKey) - paramstore := paramtypes.NewSubspace(encCfg.Codec, encCfg.Amino, evmutilKey, tEvmutilKey, types.ModuleName) - - // Check param doesn't exist before - require.False(t, paramstore.Has(ctx, types.KeyAllowedCosmosDenoms)) - - // Run migrations. - err := v2evmutil.MigrateStore(ctx, paramstore) - require.NoError(t, err) - - // Make sure the new params are set. - require.True(t, paramstore.Has(ctx, types.KeyAllowedCosmosDenoms)) -} - -func TestStoreMigrationSetsNewParamOnExistingKeyTable(t *testing.T) { - encCfg := moduletestutil.MakeTestEncodingConfig() - evmutilKey := sdk.NewKVStoreKey(types.ModuleName) - tEvmutilKey := sdk.NewTransientStoreKey("transient_test") - ctx := testutil.DefaultContext(evmutilKey, tEvmutilKey) - paramstore := paramtypes.NewSubspace(encCfg.Codec, encCfg.Amino, evmutilKey, tEvmutilKey, types.ModuleName) - paramstore.WithKeyTable(types.ParamKeyTable()) - - // expect it to have key table - require.True(t, paramstore.HasKeyTable()) - // expect it to not have new param - require.False(t, paramstore.Has(ctx, types.KeyAllowedCosmosDenoms)) - - // Run migrations. - err := v2evmutil.MigrateStore(ctx, paramstore) - require.NoError(t, err) - - // Make sure the new params are set. - require.True(t, paramstore.Has(ctx, types.KeyAllowedCosmosDenoms)) -} diff --git a/x/evmutil/module.go b/x/evmutil/module.go index 5a30c651..540d0027 100644 --- a/x/evmutil/module.go +++ b/x/evmutil/module.go @@ -3,7 +3,6 @@ package evmutil import ( "context" "encoding/json" - "fmt" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -47,12 +46,10 @@ func (AppModuleBasic) Name() string { // Registers legacy amino codec func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { - types.RegisterLegacyAminoCodec(cdc) } // RegisterInterfaces registers the module's interface types func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { - types.RegisterInterfaces(reg) } // DefaultGenesis default genesis state @@ -119,13 +116,6 @@ func (am AppModule) Name() string { // RegisterServices registers a GRPC query service to respond to the // module-specific GRPC queries. func (am AppModule) RegisterServices(cfg module.Configurator) { - types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) - types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServerImpl(am.keeper)) - - m := keeper.NewMigrator(am.keeper) - if err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2); err != nil { - panic(fmt.Sprintf("failed to migrate x/evmutil from version 1 to 2: %v", err)) - } } // RegisterInvariants registers evmutil module's invariants. diff --git a/x/evmutil/types/codec.go b/x/evmutil/types/codec.go deleted file mode 100644 index fa9c5fe6..00000000 --- a/x/evmutil/types/codec.go +++ /dev/null @@ -1,46 +0,0 @@ -package types - -import ( - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/codec/legacy" - cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/msgservice" - authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec" -) - -// RegisterLegacyAminoCodec registers the necessary evmutil interfaces and concrete types -// on the provided LegacyAmino codec. These types are used for Amino JSON serialization. -func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { - legacy.RegisterAminoMsg(cdc, &MsgConvertCoinToERC20{}, "evmutil/MsgConvertCoinToERC20") - legacy.RegisterAminoMsg(cdc, &MsgConvertERC20ToCoin{}, "evmutil/MsgConvertERC20ToCoin") - legacy.RegisterAminoMsg(cdc, &MsgConvertCosmosCoinToERC20{}, "evmutil/MsgConvertCosmosCoinToERC20") - legacy.RegisterAminoMsg(cdc, &MsgConvertCosmosCoinFromERC20{}, "evmutil/MsgConvertCosmosCoinFromERC20") -} - -func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { - registry.RegisterImplementations((*sdk.Msg)(nil), - &MsgConvertCoinToERC20{}, - &MsgConvertERC20ToCoin{}, - &MsgConvertCosmosCoinToERC20{}, - &MsgConvertCosmosCoinFromERC20{}, - ) - - msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) -} - -var ( - amino = codec.NewLegacyAmino() - ModuleCdc = codec.NewAminoCodec(amino) -) - -func init() { - RegisterLegacyAminoCodec(amino) - cryptocodec.RegisterCrypto(amino) - amino.Seal() - - // Register all Amino interfaces and concrete types on the authz Amino codec so that this can later be - // used to properly serialize MsgGrant and MsgExec instances - RegisterLegacyAminoCodec(authzcodec.Amino) -} diff --git a/x/evmutil/types/conversion_pair.go b/x/evmutil/types/conversion_pair.go deleted file mode 100644 index eab8b318..00000000 --- a/x/evmutil/types/conversion_pair.go +++ /dev/null @@ -1,187 +0,0 @@ -package types - -import ( - bytes "bytes" - "encoding/hex" - "errors" - "fmt" - "math" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" -) - -/////////////// -// EVM -> Cosmos SDK -/////////////// - -// NewConversionPair returns a new ConversionPair. -func NewConversionPair(address InternalEVMAddress, denom string) ConversionPair { - return ConversionPair{ - KavaERC20Address: address.Address.Bytes(), - Denom: denom, - } -} - -// GetAddress returns the InternalEVMAddress of the Kava ERC20 address. -func (pair ConversionPair) GetAddress() InternalEVMAddress { - return NewInternalEVMAddress(common.BytesToAddress(pair.KavaERC20Address)) -} - -// Validate returns an error if the ConversionPair is invalid. -func (pair ConversionPair) Validate() error { - if err := sdk.ValidateDenom(pair.Denom); err != nil { - return fmt.Errorf("conversion pair denom invalid: %v", err) - } - - if len(pair.KavaERC20Address) != common.AddressLength { - return fmt.Errorf("address length is %v but expected %v", len(pair.KavaERC20Address), common.AddressLength) - } - - if bytes.Equal(pair.KavaERC20Address, common.Address{}.Bytes()) { - return fmt.Errorf("address cannot be zero value %v", hex.EncodeToString(pair.KavaERC20Address)) - } - - return nil -} - -// ConversionPairs defines a slice of ConversionPair. -type ConversionPairs []ConversionPair - -// NewConversionPairs returns ConversionPairs from the provided values. -func NewConversionPairs(pairs ...ConversionPair) ConversionPairs { - return ConversionPairs(pairs) -} - -func (pairs ConversionPairs) Validate() error { - // Check for duplicates for both addrs and denoms - addrs := map[string]bool{} - denoms := map[string]bool{} - - for _, pair := range pairs { - if addrs[hex.EncodeToString(pair.KavaERC20Address)] { - return fmt.Errorf( - "found duplicate enabled conversion pair internal ERC20 address %s", - hex.EncodeToString(pair.KavaERC20Address), - ) - } - - if denoms[pair.Denom] { - return fmt.Errorf( - "found duplicate enabled conversion pair denom %s", - pair.Denom, - ) - } - - if err := pair.Validate(); err != nil { - return err - } - - addrs[hex.EncodeToString(pair.KavaERC20Address)] = true - denoms[pair.Denom] = true - } - - return nil -} - -// validateConversionPairs validates an interface as ConversionPairs -func validateConversionPairs(i interface{}) error { - pairs, ok := i.(ConversionPairs) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) - } - - return pairs.Validate() -} - -/////////////// -// Cosmos SDK -> EVM -/////////////// - -// NewDeployedCosmosCoinContract returns a new DeployedCosmosCoinContract -func NewDeployedCosmosCoinContract(denom string, address InternalEVMAddress) DeployedCosmosCoinContract { - return DeployedCosmosCoinContract{ - CosmosDenom: denom, - Address: &address, - } -} - -// NewAllowedCosmosCoinERC20Token returns an AllowedCosmosCoinERC20Token -func NewAllowedCosmosCoinERC20Token( - cosmosDenom, name, symbol string, - decimal uint32, -) AllowedCosmosCoinERC20Token { - return AllowedCosmosCoinERC20Token{ - CosmosDenom: cosmosDenom, - Name: name, - Symbol: symbol, - Decimals: decimal, - } -} - -// Validate validates the fields of a single AllowedCosmosCoinERC20Token -func (token AllowedCosmosCoinERC20Token) Validate() error { - // disallow empty string fields - if err := sdk.ValidateDenom(token.CosmosDenom); err != nil { - return fmt.Errorf("allowed cosmos coin erc20 token's sdk denom is invalid: %v", err) - } - - if token.Name == "" { - return errors.New("allowed cosmos coin erc20 token's name cannot be empty") - } - - if token.Symbol == "" { - return errors.New("allowed cosmos coin erc20 token's symbol cannot be empty") - } - - // ensure decimals will properly cast to uint8 of erc20 spec - if token.Decimals > math.MaxUint8 { - return fmt.Errorf("allowed cosmos coin erc20 token's decimals must be less than 256, found %d", token.Decimals) - } - - return nil -} - -// AllowedCosmosCoinERC20Tokens defines a slice of AllowedCosmosCoinERC20Token -type AllowedCosmosCoinERC20Tokens []AllowedCosmosCoinERC20Token - -// NewAllowedCosmosCoinERC20Tokens returns AllowedCosmosCoinERC20Tokens from the provided values. -func NewAllowedCosmosCoinERC20Tokens(pairs ...AllowedCosmosCoinERC20Token) AllowedCosmosCoinERC20Tokens { - return AllowedCosmosCoinERC20Tokens(pairs) -} - -// Validate checks that all containing tokens are valid and that there are -// no duplicate denoms or symbols. -func (tokens AllowedCosmosCoinERC20Tokens) Validate() error { - // Disallow multiple instances of a single sdk_denom or evm symbol - denoms := make(map[string]struct{}, len(tokens)) - symbols := make(map[string]struct{}, len(tokens)) - - for i, t := range tokens { - if err := t.Validate(); err != nil { - return fmt.Errorf("invalid token at index %d: %s", i, err) - } - - if _, found := denoms[t.CosmosDenom]; found { - return fmt.Errorf("found duplicate token with sdk denom %s", t.CosmosDenom) - } - if _, found := symbols[t.Symbol]; found { - return fmt.Errorf("found duplicate token with symbol %s", t.Symbol) - } - - denoms[t.CosmosDenom] = struct{}{} - symbols[t.Symbol] = struct{}{} - } - - return nil -} - -// validateAllowedCosmosCoinERC20Tokens validates an interface as AllowedCosmosCoinERC20Tokens -func validateAllowedCosmosCoinERC20Tokens(i interface{}) error { - pairs, ok := i.(AllowedCosmosCoinERC20Tokens) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) - } - - return pairs.Validate() -} diff --git a/x/evmutil/types/conversion_pair.pb.go b/x/evmutil/types/conversion_pair.pb.go deleted file mode 100644 index 637dd783..00000000 --- a/x/evmutil/types/conversion_pair.pb.go +++ /dev/null @@ -1,793 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: kava/evmutil/v1beta1/conversion_pair.proto - -package types - -import ( - bytes "bytes" - fmt "fmt" - _ "github.com/cosmos/gogoproto/gogoproto" - proto "github.com/cosmos/gogoproto/proto" - io "io" - math "math" - math_bits "math/bits" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -// ConversionPair defines a Kava ERC20 address and corresponding denom that is -// allowed to be converted between ERC20 and sdk.Coin -type ConversionPair struct { - // ERC20 address of the token on the Kava EVM - KavaERC20Address HexBytes `protobuf:"bytes,1,opt,name=kava_erc20_address,json=kavaErc20Address,proto3,casttype=HexBytes" json:"kava_erc20_address,omitempty"` - // Denom of the corresponding sdk.Coin - Denom string `protobuf:"bytes,2,opt,name=denom,proto3" json:"denom,omitempty"` -} - -func (m *ConversionPair) Reset() { *m = ConversionPair{} } -func (m *ConversionPair) String() string { return proto.CompactTextString(m) } -func (*ConversionPair) ProtoMessage() {} -func (*ConversionPair) Descriptor() ([]byte, []int) { - return fileDescriptor_e1396d08199817d0, []int{0} -} -func (m *ConversionPair) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *ConversionPair) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_ConversionPair.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *ConversionPair) XXX_Merge(src proto.Message) { - xxx_messageInfo_ConversionPair.Merge(m, src) -} -func (m *ConversionPair) XXX_Size() int { - return m.Size() -} -func (m *ConversionPair) XXX_DiscardUnknown() { - xxx_messageInfo_ConversionPair.DiscardUnknown(m) -} - -var xxx_messageInfo_ConversionPair proto.InternalMessageInfo - -// AllowedCosmosCoinERC20Token defines allowed cosmos-sdk denom & metadata -// for evm token representations of sdk assets. -// NOTE: once evm token contracts are deployed, changes to metadata for a given -// cosmos_denom will not change metadata of deployed contract. -type AllowedCosmosCoinERC20Token struct { - // Denom of the sdk.Coin - CosmosDenom string `protobuf:"bytes,1,opt,name=cosmos_denom,json=cosmosDenom,proto3" json:"cosmos_denom,omitempty"` - // Name of ERC20 contract - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - // Symbol of ERC20 contract - Symbol string `protobuf:"bytes,3,opt,name=symbol,proto3" json:"symbol,omitempty"` - // Number of decimals ERC20 contract is deployed with. - Decimals uint32 `protobuf:"varint,4,opt,name=decimals,proto3" json:"decimals,omitempty"` -} - -func (m *AllowedCosmosCoinERC20Token) Reset() { *m = AllowedCosmosCoinERC20Token{} } -func (m *AllowedCosmosCoinERC20Token) String() string { return proto.CompactTextString(m) } -func (*AllowedCosmosCoinERC20Token) ProtoMessage() {} -func (*AllowedCosmosCoinERC20Token) Descriptor() ([]byte, []int) { - return fileDescriptor_e1396d08199817d0, []int{1} -} -func (m *AllowedCosmosCoinERC20Token) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *AllowedCosmosCoinERC20Token) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_AllowedCosmosCoinERC20Token.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *AllowedCosmosCoinERC20Token) XXX_Merge(src proto.Message) { - xxx_messageInfo_AllowedCosmosCoinERC20Token.Merge(m, src) -} -func (m *AllowedCosmosCoinERC20Token) XXX_Size() int { - return m.Size() -} -func (m *AllowedCosmosCoinERC20Token) XXX_DiscardUnknown() { - xxx_messageInfo_AllowedCosmosCoinERC20Token.DiscardUnknown(m) -} - -var xxx_messageInfo_AllowedCosmosCoinERC20Token proto.InternalMessageInfo - -func init() { - proto.RegisterType((*ConversionPair)(nil), "kava.evmutil.v1beta1.ConversionPair") - proto.RegisterType((*AllowedCosmosCoinERC20Token)(nil), "kava.evmutil.v1beta1.AllowedCosmosCoinERC20Token") -} - -func init() { - proto.RegisterFile("kava/evmutil/v1beta1/conversion_pair.proto", fileDescriptor_e1396d08199817d0) -} - -var fileDescriptor_e1396d08199817d0 = []byte{ - // 356 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x3c, 0x91, 0xcf, 0x4a, 0xeb, 0x40, - 0x18, 0xc5, 0x33, 0xf7, 0xf6, 0x96, 0xde, 0xb1, 0x4a, 0x19, 0x8a, 0x84, 0x0a, 0xd3, 0xd8, 0x55, - 0x15, 0x4c, 0xda, 0xba, 0x73, 0xd7, 0xc6, 0x82, 0x50, 0x10, 0x09, 0xae, 0xdc, 0x84, 0x49, 0x32, - 0xd4, 0xd0, 0x24, 0x5f, 0xc9, 0xa4, 0xb1, 0x05, 0x1f, 0xc0, 0x95, 0xf8, 0x08, 0x2e, 0x7d, 0x14, - 0x97, 0x5d, 0xba, 0x2a, 0x35, 0x7d, 0x0b, 0x57, 0x92, 0x49, 0xe8, 0xee, 0x3b, 0xe7, 0x3b, 0xe7, - 0xc7, 0xfc, 0xc1, 0xe7, 0x33, 0x96, 0x32, 0x83, 0xa7, 0xe1, 0x22, 0xf1, 0x03, 0x23, 0xed, 0x3b, - 0x3c, 0x61, 0x7d, 0xc3, 0x85, 0x28, 0xe5, 0xb1, 0xf0, 0x21, 0xb2, 0xe7, 0xcc, 0x8f, 0xf5, 0x79, - 0x0c, 0x09, 0x90, 0x66, 0x9e, 0xd5, 0xcb, 0xac, 0x5e, 0x66, 0x5b, 0xcd, 0x29, 0x4c, 0x41, 0x06, - 0x8c, 0x7c, 0x2a, 0xb2, 0x9d, 0x67, 0x7c, 0x64, 0xee, 0x21, 0x77, 0xcc, 0x8f, 0xc9, 0x2d, 0x26, - 0x79, 0xdf, 0xe6, 0xb1, 0x3b, 0xe8, 0xd9, 0xcc, 0xf3, 0x62, 0x2e, 0x84, 0x8a, 0x34, 0xd4, 0xad, - 0x8f, 0xb4, 0x6c, 0xd3, 0x6e, 0x4c, 0x58, 0xca, 0xc6, 0x96, 0x39, 0xe8, 0x0d, 0x8b, 0xdd, 0xcf, - 0xa6, 0x5d, 0xbb, 0xe1, 0xcb, 0xd1, 0x2a, 0xe1, 0xc2, 0x6a, 0xe4, 0xdd, 0x71, 0x5e, 0x2d, 0xb7, - 0xa4, 0x89, 0xff, 0x79, 0x3c, 0x82, 0x50, 0xfd, 0xa3, 0xa1, 0xee, 0x7f, 0xab, 0x10, 0x57, 0x95, - 0x97, 0xf7, 0xb6, 0xd2, 0x79, 0x45, 0xf8, 0x64, 0x18, 0x04, 0xf0, 0xc4, 0x3d, 0x13, 0x44, 0x08, - 0xc2, 0x04, 0x3f, 0x92, 0xec, 0x7b, 0x98, 0xf1, 0x88, 0x9c, 0xe2, 0xba, 0x2b, 0x7d, 0xbb, 0x40, - 0x20, 0x89, 0x38, 0x28, 0xbc, 0xeb, 0xdc, 0x22, 0x04, 0x57, 0x22, 0x16, 0xf2, 0x92, 0x2e, 0x67, - 0x72, 0x8c, 0xab, 0x62, 0x15, 0x3a, 0x10, 0xa8, 0x7f, 0xa5, 0x5b, 0x2a, 0xd2, 0xc2, 0x35, 0x8f, - 0xbb, 0x7e, 0xc8, 0x02, 0xa1, 0x56, 0x34, 0xd4, 0x3d, 0xb4, 0xf6, 0xba, 0x38, 0xd0, 0x68, 0xb2, - 0xfd, 0xa6, 0xe8, 0x23, 0xa3, 0xe8, 0x33, 0xa3, 0x68, 0x9d, 0x51, 0xb4, 0xcd, 0x28, 0x7a, 0xdb, - 0x51, 0x65, 0xbd, 0xa3, 0xca, 0xd7, 0x8e, 0x2a, 0x0f, 0x67, 0x53, 0x3f, 0x79, 0x5c, 0x38, 0xba, - 0x0b, 0xa1, 0x91, 0xdf, 0xf5, 0x22, 0x60, 0x8e, 0x90, 0x93, 0xb1, 0xdc, 0xff, 0x4f, 0xb2, 0x9a, - 0x73, 0xe1, 0x54, 0xe5, 0x13, 0x5f, 0xfe, 0x06, 0x00, 0x00, 0xff, 0xff, 0x95, 0x40, 0xc5, 0x63, - 0xbc, 0x01, 0x00, 0x00, -} - -func (this *ConversionPair) VerboseEqual(that interface{}) error { - if that == nil { - if this == nil { - return nil - } - return fmt.Errorf("that == nil && this != nil") - } - - that1, ok := that.(*ConversionPair) - if !ok { - that2, ok := that.(ConversionPair) - if ok { - that1 = &that2 - } else { - return fmt.Errorf("that is not of type *ConversionPair") - } - } - if that1 == nil { - if this == nil { - return nil - } - return fmt.Errorf("that is type *ConversionPair but is nil && this != nil") - } else if this == nil { - return fmt.Errorf("that is type *ConversionPair but is not nil && this == nil") - } - if !bytes.Equal(this.KavaERC20Address, that1.KavaERC20Address) { - return fmt.Errorf("KavaERC20Address this(%v) Not Equal that(%v)", this.KavaERC20Address, that1.KavaERC20Address) - } - if this.Denom != that1.Denom { - return fmt.Errorf("Denom this(%v) Not Equal that(%v)", this.Denom, that1.Denom) - } - return nil -} -func (this *ConversionPair) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*ConversionPair) - if !ok { - that2, ok := that.(ConversionPair) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - if !bytes.Equal(this.KavaERC20Address, that1.KavaERC20Address) { - return false - } - if this.Denom != that1.Denom { - return false - } - return true -} -func (this *AllowedCosmosCoinERC20Token) VerboseEqual(that interface{}) error { - if that == nil { - if this == nil { - return nil - } - return fmt.Errorf("that == nil && this != nil") - } - - that1, ok := that.(*AllowedCosmosCoinERC20Token) - if !ok { - that2, ok := that.(AllowedCosmosCoinERC20Token) - if ok { - that1 = &that2 - } else { - return fmt.Errorf("that is not of type *AllowedCosmosCoinERC20Token") - } - } - if that1 == nil { - if this == nil { - return nil - } - return fmt.Errorf("that is type *AllowedCosmosCoinERC20Token but is nil && this != nil") - } else if this == nil { - return fmt.Errorf("that is type *AllowedCosmosCoinERC20Token but is not nil && this == nil") - } - if this.CosmosDenom != that1.CosmosDenom { - return fmt.Errorf("CosmosDenom this(%v) Not Equal that(%v)", this.CosmosDenom, that1.CosmosDenom) - } - if this.Name != that1.Name { - return fmt.Errorf("Name this(%v) Not Equal that(%v)", this.Name, that1.Name) - } - if this.Symbol != that1.Symbol { - return fmt.Errorf("Symbol this(%v) Not Equal that(%v)", this.Symbol, that1.Symbol) - } - if this.Decimals != that1.Decimals { - return fmt.Errorf("Decimals this(%v) Not Equal that(%v)", this.Decimals, that1.Decimals) - } - return nil -} -func (this *AllowedCosmosCoinERC20Token) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*AllowedCosmosCoinERC20Token) - if !ok { - that2, ok := that.(AllowedCosmosCoinERC20Token) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - if this.CosmosDenom != that1.CosmosDenom { - return false - } - if this.Name != that1.Name { - return false - } - if this.Symbol != that1.Symbol { - return false - } - if this.Decimals != that1.Decimals { - return false - } - return true -} -func (m *ConversionPair) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ConversionPair) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ConversionPair) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Denom) > 0 { - i -= len(m.Denom) - copy(dAtA[i:], m.Denom) - i = encodeVarintConversionPair(dAtA, i, uint64(len(m.Denom))) - i-- - dAtA[i] = 0x12 - } - if len(m.KavaERC20Address) > 0 { - i -= len(m.KavaERC20Address) - copy(dAtA[i:], m.KavaERC20Address) - i = encodeVarintConversionPair(dAtA, i, uint64(len(m.KavaERC20Address))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *AllowedCosmosCoinERC20Token) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *AllowedCosmosCoinERC20Token) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *AllowedCosmosCoinERC20Token) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Decimals != 0 { - i = encodeVarintConversionPair(dAtA, i, uint64(m.Decimals)) - i-- - dAtA[i] = 0x20 - } - if len(m.Symbol) > 0 { - i -= len(m.Symbol) - copy(dAtA[i:], m.Symbol) - i = encodeVarintConversionPair(dAtA, i, uint64(len(m.Symbol))) - i-- - dAtA[i] = 0x1a - } - if len(m.Name) > 0 { - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintConversionPair(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0x12 - } - if len(m.CosmosDenom) > 0 { - i -= len(m.CosmosDenom) - copy(dAtA[i:], m.CosmosDenom) - i = encodeVarintConversionPair(dAtA, i, uint64(len(m.CosmosDenom))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func encodeVarintConversionPair(dAtA []byte, offset int, v uint64) int { - offset -= sovConversionPair(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *ConversionPair) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.KavaERC20Address) - if l > 0 { - n += 1 + l + sovConversionPair(uint64(l)) - } - l = len(m.Denom) - if l > 0 { - n += 1 + l + sovConversionPair(uint64(l)) - } - return n -} - -func (m *AllowedCosmosCoinERC20Token) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.CosmosDenom) - if l > 0 { - n += 1 + l + sovConversionPair(uint64(l)) - } - l = len(m.Name) - if l > 0 { - n += 1 + l + sovConversionPair(uint64(l)) - } - l = len(m.Symbol) - if l > 0 { - n += 1 + l + sovConversionPair(uint64(l)) - } - if m.Decimals != 0 { - n += 1 + sovConversionPair(uint64(m.Decimals)) - } - return n -} - -func sovConversionPair(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozConversionPair(x uint64) (n int) { - return sovConversionPair(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *ConversionPair) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowConversionPair - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ConversionPair: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ConversionPair: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field KavaERC20Address", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowConversionPair - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthConversionPair - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthConversionPair - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.KavaERC20Address = append(m.KavaERC20Address[:0], dAtA[iNdEx:postIndex]...) - if m.KavaERC20Address == nil { - m.KavaERC20Address = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowConversionPair - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthConversionPair - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthConversionPair - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Denom = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipConversionPair(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthConversionPair - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AllowedCosmosCoinERC20Token) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowConversionPair - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: AllowedCosmosCoinERC20Token: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AllowedCosmosCoinERC20Token: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CosmosDenom", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowConversionPair - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthConversionPair - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthConversionPair - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.CosmosDenom = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowConversionPair - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthConversionPair - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthConversionPair - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Symbol", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowConversionPair - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthConversionPair - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthConversionPair - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Symbol = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Decimals", wireType) - } - m.Decimals = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowConversionPair - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Decimals |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipConversionPair(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthConversionPair - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipConversionPair(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowConversionPair - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowConversionPair - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowConversionPair - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthConversionPair - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupConversionPair - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthConversionPair - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthConversionPair = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowConversionPair = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupConversionPair = fmt.Errorf("proto: unexpected end of group") -) diff --git a/x/evmutil/types/conversion_pairs_test.go.disabled b/x/evmutil/types/conversion_pairs_test.go.disabled deleted file mode 100644 index 90355a09..00000000 --- a/x/evmutil/types/conversion_pairs_test.go.disabled +++ /dev/null @@ -1,355 +0,0 @@ -package types_test - -import ( - "testing" - - "github.com/aura-nw/aura/x/evmutil/testutil" - "github.com/aura-nw/aura/x/evmutil/types" - "github.com/stretchr/testify/require" -) - -func TestConversionPairValidate(t *testing.T) { - type errArgs struct { - expectPass bool - contains string - } - tests := []struct { - name string - giveAddress types.InternalEVMAddress - giveDenom string - errArgs errArgs - }{ - { - "valid", - testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), - "weth", - errArgs{ - expectPass: true, - }, - }, - { - "invalid - empty denom", - testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), - "", - errArgs{ - expectPass: false, - contains: "conversion pair denom invalid: invalid denom", - }, - }, - { - "invalid - zero address", - testutil.MustNewInternalEVMAddressFromString("0x0000000000000000000000000000000000000000"), - "weth", - errArgs{ - expectPass: false, - contains: "address cannot be zero value", - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - pair := types.NewConversionPair(tc.giveAddress, tc.giveDenom) - - err := pair.Validate() - - if tc.errArgs.expectPass { - require.NoError(t, err) - } else { - require.Error(t, err) - require.Contains(t, err.Error(), tc.errArgs.contains) - } - }) - } -} - -func TestConversionPairValidate_Direct(t *testing.T) { - type errArgs struct { - expectPass bool - contains string - } - tests := []struct { - name string - givePair types.ConversionPair - errArgs errArgs - }{ - { - "valid", - types.ConversionPair{ - KavaERC20Address: testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").Bytes(), - Denom: "weth", - }, - errArgs{ - expectPass: true, - }, - }, - - { - "invalid - length", - types.ConversionPair{ - KavaERC20Address: []byte{1}, - Denom: "weth", - }, - errArgs{ - expectPass: false, - contains: "address length is 1 but expected 20", - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - err := tc.givePair.Validate() - - if tc.errArgs.expectPass { - require.NoError(t, err) - } else { - require.Error(t, err) - require.Contains(t, err.Error(), tc.errArgs.contains) - } - }) - } -} - -func TestConversionPair_GetAddress(t *testing.T) { - addr := testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") - - pair := types.NewConversionPair( - addr, - "weth", - ) - - require.Equal(t, types.HexBytes(addr.Bytes()), pair.KavaERC20Address, "struct address should match input bytes") - require.Equal(t, addr, pair.GetAddress(), "get internal address should match input bytes") -} - -func TestConversionPairs_Validate(t *testing.T) { - type errArgs struct { - expectPass bool - contains string - } - tests := []struct { - name string - givePairs types.ConversionPairs - errArgs errArgs - }{ - { - "valid", - types.NewConversionPairs( - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), - "weth", - ), - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000A"), - "kava", - ), - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000B"), - "usdc", - ), - ), - errArgs{ - expectPass: true, - }, - }, - { - "invalid - duplicate address", - types.NewConversionPairs( - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), - "weth", - ), - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), - "kava", - ), - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000B"), - "usdc", - ), - ), - errArgs{ - expectPass: false, - contains: "found duplicate enabled conversion pair internal ERC20 address", - }, - }, - { - "invalid - duplicate denom", - types.NewConversionPairs( - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), - "weth", - ), - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000A"), - "kava", - ), - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000B"), - "kava", - ), - ), - errArgs{ - expectPass: false, - contains: "found duplicate enabled conversion pair denom kava", - }, - }, - { - "invalid - invalid pair", - types.NewConversionPairs( - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), - "weth", - ), - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0x0000000000000000000000000000000000000000"), - "usdc", - ), - types.NewConversionPair( - testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000B"), - "kava", - ), - ), - errArgs{ - expectPass: false, - contains: "address cannot be zero value", - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - err := tc.givePairs.Validate() - - if tc.errArgs.expectPass { - require.NoError(t, err) - } else { - require.Error(t, err) - require.Contains(t, err.Error(), tc.errArgs.contains) - } - }) - } -} - -func TestAllowedCosmosCoinERC20Token_Validate(t *testing.T) { - testCases := []struct { - name string - token types.AllowedCosmosCoinERC20Token - expErr string - }{ - { - name: "valid token", - token: types.NewAllowedCosmosCoinERC20Token("uatom", "Kava-wrapped ATOM", "kATOM", 6), - expErr: "", - }, - { - name: "valid - highest allowed decimals", - token: types.NewAllowedCosmosCoinERC20Token("uatom", "Kava-wrapped ATOM", "kATOM", 255), - expErr: "", - }, - { - name: "invalid - Empty SdkDenom", - token: types.AllowedCosmosCoinERC20Token{ - CosmosDenom: "", - Name: "Example Token", - Symbol: "ETK", - Decimals: 0, - }, - expErr: "sdk denom is invalid", - }, - { - name: "invalid - Empty Name", - token: types.AllowedCosmosCoinERC20Token{ - CosmosDenom: "example_denom", - Name: "", - Symbol: "ETK", - Decimals: 6, - }, - expErr: "name cannot be empty", - }, - { - name: "invalid - Empty Symbol", - token: types.AllowedCosmosCoinERC20Token{ - CosmosDenom: "example_denom", - Name: "Example Token", - Symbol: "", - Decimals: 6, - }, - expErr: "symbol cannot be empty", - }, - { - name: "invalid - decimals higher than uint8", - token: types.NewAllowedCosmosCoinERC20Token("uatom", "Kava-wrapped ATOM", "kATOM", 256), - expErr: "decimals must be less than 256", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := tc.token.Validate() - if tc.expErr != "" { - require.ErrorContains(t, err, tc.expErr, "Expected validation error") - } else { - require.NoError(t, err, "Expected no validation error") - } - }) - } -} - -func TestAllowedCosmosCoinERC20Tokens_Validate(t *testing.T) { - token1 := types.NewAllowedCosmosCoinERC20Token("denom1", "Token 1", "TK1", 6) - token2 := types.NewAllowedCosmosCoinERC20Token("denom2", "Token 2", "TK2", 0) - invalidToken := types.NewAllowedCosmosCoinERC20Token("", "No SDK Denom Token", "TK3", 18) - - testCases := []struct { - name string - tokens types.AllowedCosmosCoinERC20Tokens - expErr string - }{ - { - name: "valid - no tokens", - tokens: types.NewAllowedCosmosCoinERC20Tokens(), - expErr: "", - }, - { - name: "valid - one token", - tokens: types.NewAllowedCosmosCoinERC20Tokens(token1), - expErr: "", - }, - { - name: "valid - multiple tokens", - tokens: types.NewAllowedCosmosCoinERC20Tokens(token1, token2), - expErr: "", - }, - { - name: "invalid - contains invalid token", - tokens: types.NewAllowedCosmosCoinERC20Tokens(token1, token2, invalidToken), - expErr: "invalid token at index 2", - }, - { - name: "invalid - duplicate denoms", - tokens: types.NewAllowedCosmosCoinERC20Tokens(token1, token2, token1), - expErr: "found duplicate token with sdk denom denom1", - }, - { - name: "invalid - duplicate symbol", - tokens: types.NewAllowedCosmosCoinERC20Tokens( - token1, - types.NewAllowedCosmosCoinERC20Token("diff", "Diff Denom, Same Symbol", "TK1", 6), - ), - expErr: "found duplicate token with symbol TK1", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := tc.tokens.Validate() - if tc.expErr != "" { - require.ErrorContains(t, err, tc.expErr, "Expected validation error") - } else { - require.NoError(t, err, "Expected no validation error") - } - }) - } -} diff --git a/x/evmutil/types/genesis.pb.go b/x/evmutil/types/genesis.pb.go index 93aa0b97..e2c5d01b 100644 --- a/x/evmutil/types/genesis.pb.go +++ b/x/evmutil/types/genesis.pb.go @@ -108,12 +108,6 @@ var xxx_messageInfo_Account proto.InternalMessageInfo // Params defines the evmutil module params type Params struct { - // enabled_conversion_pairs defines the list of conversion pairs allowed to be - // converted between Kava ERC20 and sdk.Coin - EnabledConversionPairs ConversionPairs `protobuf:"bytes,4,rep,name=enabled_conversion_pairs,json=enabledConversionPairs,proto3,castrepeated=ConversionPairs" json:"enabled_conversion_pairs"` - // allowed_cosmos_denoms is a list of denom & erc20 token metadata pairs. - // if a denom is in the list, it is allowed to be converted to an erc20 in the evm. - AllowedCosmosDenoms AllowedCosmosCoinERC20Tokens `protobuf:"bytes,1,rep,name=allowed_cosmos_denoms,json=allowedCosmosDenoms,proto3,castrepeated=AllowedCosmosCoinERC20Tokens" json:"allowed_cosmos_denoms"` } func (m *Params) Reset() { *m = Params{} } @@ -149,20 +143,6 @@ func (m *Params) XXX_DiscardUnknown() { var xxx_messageInfo_Params proto.InternalMessageInfo -func (m *Params) GetEnabledConversionPairs() ConversionPairs { - if m != nil { - return m.EnabledConversionPairs - } - return nil -} - -func (m *Params) GetAllowedCosmosDenoms() AllowedCosmosCoinERC20Tokens { - if m != nil { - return m.AllowedCosmosDenoms - } - return nil -} - func init() { proto.RegisterType((*GenesisState)(nil), "kava.evmutil.v1beta1.GenesisState") proto.RegisterType((*Account)(nil), "kava.evmutil.v1beta1.Account") @@ -174,38 +154,30 @@ func init() { } var fileDescriptor_d916ab97b8e628c2 = []byte{ - // 489 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0x41, 0x6b, 0x13, 0x41, - 0x14, 0xde, 0xa9, 0x21, 0xd1, 0x69, 0x41, 0xd8, 0x56, 0x8d, 0xa5, 0xce, 0x96, 0x50, 0x24, 0x0a, - 0xbb, 0x6b, 0xe2, 0xad, 0x08, 0xd2, 0x8d, 0xa2, 0xc5, 0x4b, 0x59, 0xc5, 0x83, 0x97, 0xf0, 0x76, - 0x77, 0x88, 0x4b, 0x76, 0x67, 0xc2, 0xce, 0x24, 0xb5, 0xff, 0x40, 0xf0, 0xa0, 0xfe, 0x03, 0x8f, - 0xe2, 0xb9, 0x3f, 0xa2, 0xe0, 0xa5, 0xf4, 0x24, 0x1e, 0x62, 0x4d, 0xfe, 0x85, 0x27, 0xd9, 0x99, - 0x49, 0xa8, 0x21, 0x8a, 0xa7, 0x9d, 0x7d, 0xf3, 0x7d, 0xef, 0xfb, 0xe6, 0x7b, 0x0f, 0x37, 0xfa, - 0x30, 0x02, 0x9f, 0x8e, 0xf2, 0xa1, 0x4c, 0x33, 0x7f, 0xd4, 0x8a, 0xa8, 0x84, 0x96, 0xdf, 0xa3, - 0x8c, 0x8a, 0x54, 0x78, 0x83, 0x82, 0x4b, 0x6e, 0x6f, 0x94, 0x18, 0xcf, 0x60, 0x3c, 0x83, 0xd9, - 0xbc, 0x19, 0x73, 0x91, 0x73, 0xd1, 0x55, 0x18, 0x5f, 0xff, 0x68, 0xc2, 0xe6, 0x46, 0x8f, 0xf7, - 0xb8, 0xae, 0x97, 0x27, 0x53, 0xbd, 0xbb, 0x54, 0x2a, 0xe6, 0x6c, 0x44, 0x0b, 0x91, 0x72, 0xd6, - 0x1d, 0x40, 0x5a, 0x68, 0x6c, 0xe3, 0x23, 0xc2, 0x6b, 0x4f, 0xb4, 0x89, 0xe7, 0x12, 0x24, 0xb5, - 0x1f, 0xe2, 0xcb, 0x10, 0xc7, 0x7c, 0xc8, 0xa4, 0xa8, 0xa3, 0xed, 0x4b, 0xcd, 0xd5, 0xf6, 0x2d, - 0x6f, 0x99, 0x2d, 0x6f, 0x4f, 0xa3, 0x82, 0xca, 0xc9, 0xd8, 0xb1, 0xc2, 0x39, 0xc9, 0xde, 0xc5, - 0xd5, 0x01, 0x14, 0x90, 0x8b, 0xfa, 0xca, 0x36, 0x6a, 0xae, 0xb6, 0xb7, 0x96, 0xd3, 0x0f, 0x14, - 0xc6, 0xb0, 0x0d, 0x63, 0xb7, 0xf2, 0xf6, 0x93, 0x63, 0x35, 0xbe, 0x22, 0x5c, 0x33, 0xdd, 0xed, - 0x08, 0xd7, 0x20, 0x49, 0x0a, 0x2a, 0x4a, 0x37, 0xa8, 0xb9, 0x16, 0x3c, 0xfd, 0x35, 0x76, 0xdc, - 0x5e, 0x2a, 0x5f, 0x0f, 0x23, 0x2f, 0xe6, 0xb9, 0xc9, 0xc3, 0x7c, 0x5c, 0x91, 0xf4, 0x7d, 0x79, - 0x34, 0xa0, 0xa2, 0xb4, 0xb7, 0xa7, 0x89, 0x67, 0xc7, 0xee, 0xba, 0x49, 0xcd, 0x54, 0x82, 0x23, - 0x49, 0x45, 0x38, 0x6b, 0x6c, 0xbf, 0xc4, 0xb5, 0x08, 0x32, 0x60, 0x31, 0x55, 0x96, 0xaf, 0x04, - 0x0f, 0x4a, 0x53, 0xdf, 0xc7, 0xce, 0xed, 0xff, 0xd0, 0xd9, 0x67, 0xf2, 0xec, 0xd8, 0xc5, 0x46, - 0x60, 0x9f, 0xc9, 0x70, 0xd6, 0xcc, 0xbc, 0xe6, 0xfd, 0x0a, 0xae, 0xea, 0xc7, 0xda, 0x87, 0xb8, - 0x4e, 0x19, 0x44, 0x19, 0x4d, 0xba, 0x0b, 0xd3, 0x10, 0xf5, 0x8a, 0xca, 0x7a, 0x67, 0x79, 0x58, - 0x9d, 0x39, 0xfa, 0x00, 0xd2, 0x22, 0xb8, 0x51, 0xfa, 0xfb, 0xf2, 0xc3, 0xb9, 0xfa, 0x67, 0x5d, - 0x84, 0xd7, 0x4d, 0xfb, 0x85, 0xba, 0xfd, 0x0e, 0xe1, 0x6b, 0x90, 0x65, 0xfc, 0x50, 0x29, 0xab, - 0x6d, 0x4a, 0x28, 0xe3, 0xf9, 0x6c, 0xc4, 0xad, 0xbf, 0x8c, 0x58, 0x53, 0x3a, 0x8a, 0xd1, 0xe1, - 0x29, 0x7b, 0x1c, 0x76, 0xda, 0xf7, 0x5e, 0xf0, 0x3e, 0x65, 0xc1, 0x8e, 0xf1, 0xb0, 0xf5, 0x0f, - 0x90, 0x08, 0xd7, 0xe1, 0xe2, 0xed, 0x23, 0xa5, 0x19, 0x3c, 0x3b, 0xff, 0x49, 0xd0, 0xe7, 0x09, - 0x41, 0x27, 0x13, 0x82, 0x4e, 0x27, 0x04, 0x9d, 0x4f, 0x08, 0xfa, 0x30, 0x25, 0xd6, 0xe9, 0x94, - 0x58, 0xdf, 0xa6, 0xc4, 0x7a, 0x75, 0xe7, 0x42, 0xf0, 0xa5, 0x33, 0x37, 0x83, 0x48, 0xa8, 0x93, - 0xff, 0x66, 0xbe, 0xd8, 0x2a, 0xff, 0xa8, 0xaa, 0xf6, 0xf8, 0xfe, 0xef, 0x00, 0x00, 0x00, 0xff, - 0xff, 0xa1, 0xec, 0x74, 0x78, 0x60, 0x03, 0x00, 0x00, + // 364 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xca, 0x4e, 0x2c, 0x4b, + 0xd4, 0x4f, 0x2d, 0xcb, 0x2d, 0x2d, 0xc9, 0xcc, 0xd1, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, + 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, + 0x01, 0xa9, 0xd1, 0x83, 0xaa, 0xd1, 0x83, 0xaa, 0x91, 0x92, 0x4c, 0xce, 0x2f, 0xce, 0xcd, 0x2f, + 0x8e, 0x07, 0xab, 0xd1, 0x87, 0x70, 0x20, 0x1a, 0xa4, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0x21, 0xe2, + 0x20, 0x16, 0x44, 0x54, 0x69, 0x22, 0x23, 0x17, 0x8f, 0x3b, 0xc4, 0xe0, 0xe0, 0x92, 0xc4, 0x92, + 0x54, 0x21, 0x7b, 0x2e, 0x8e, 0xc4, 0xe4, 0xe4, 0xfc, 0xd2, 0xbc, 0x92, 0x62, 0x09, 0x46, 0x05, + 0x66, 0x0d, 0x6e, 0x23, 0x59, 0x3d, 0x6c, 0x56, 0xe9, 0x39, 0x42, 0x54, 0x39, 0xb1, 0x9c, 0xb8, + 0x27, 0xcf, 0x10, 0x04, 0xd7, 0x24, 0x64, 0xc5, 0xc5, 0x56, 0x90, 0x58, 0x94, 0x98, 0x5b, 0x2c, + 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x6d, 0x24, 0x83, 0x5d, 0x7b, 0x00, 0x58, 0x0d, 0x54, 0x37, 0x54, + 0x87, 0x15, 0x4b, 0xc7, 0x02, 0x79, 0x06, 0xa5, 0xd3, 0x8c, 0x5c, 0xec, 0x50, 0xd3, 0x85, 0x92, + 0xb8, 0xd8, 0x13, 0x53, 0x52, 0x8a, 0x52, 0x8b, 0x41, 0xae, 0x61, 0xd4, 0xe0, 0x71, 0xf2, 0xf8, + 0x75, 0x4f, 0x5e, 0x37, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x17, 0xea, 0x47, + 0x28, 0xa5, 0x5b, 0x9c, 0x92, 0xad, 0x5f, 0x52, 0x59, 0x90, 0x5a, 0x0c, 0x72, 0x9e, 0x23, 0x44, + 0xe3, 0xa5, 0x2d, 0xba, 0xc2, 0xd0, 0x90, 0x80, 0x8a, 0x38, 0x55, 0x96, 0xa4, 0x16, 0x07, 0xc1, + 0x0c, 0x16, 0x0a, 0xe3, 0x62, 0x4f, 0x4a, 0xcc, 0x49, 0xcc, 0x4b, 0x4e, 0x05, 0x3b, 0x99, 0xd3, + 0xc9, 0x06, 0xe4, 0xa8, 0x5b, 0xf7, 0xe4, 0xd5, 0x88, 0xb0, 0xc7, 0x33, 0xaf, 0xe4, 0xd2, 0x16, + 0x5d, 0x2e, 0xa8, 0x05, 0x9e, 0x79, 0x25, 0x41, 0x30, 0xc3, 0xa0, 0xbe, 0xe1, 0xe0, 0x62, 0x83, + 0xfa, 0xd5, 0xfb, 0xc1, 0x43, 0x39, 0xc6, 0x15, 0x8f, 0xe4, 0x18, 0x4f, 0x3c, 0x92, 0x63, 0xbc, + 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, + 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x4a, 0x13, 0xc9, 0x42, 0x50, 0xa8, 0xe9, 0xe6, 0x24, 0x26, 0x15, + 0x83, 0x59, 0xfa, 0x15, 0xf0, 0xf4, 0x00, 0xb6, 0x37, 0x89, 0x0d, 0x1c, 0x7f, 0xc6, 0x80, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xae, 0x2e, 0x4a, 0x40, 0x2c, 0x02, 0x00, 0x00, } func (this *GenesisState) VerboseEqual(that interface{}) error { @@ -363,22 +335,6 @@ func (this *Params) VerboseEqual(that interface{}) error { } else if this == nil { return fmt.Errorf("that is type *Params but is not nil && this == nil") } - if len(this.EnabledConversionPairs) != len(that1.EnabledConversionPairs) { - return fmt.Errorf("EnabledConversionPairs this(%v) Not Equal that(%v)", len(this.EnabledConversionPairs), len(that1.EnabledConversionPairs)) - } - for i := range this.EnabledConversionPairs { - if !this.EnabledConversionPairs[i].Equal(&that1.EnabledConversionPairs[i]) { - return fmt.Errorf("EnabledConversionPairs this[%v](%v) Not Equal that[%v](%v)", i, this.EnabledConversionPairs[i], i, that1.EnabledConversionPairs[i]) - } - } - if len(this.AllowedCosmosDenoms) != len(that1.AllowedCosmosDenoms) { - return fmt.Errorf("AllowedCosmosDenoms this(%v) Not Equal that(%v)", len(this.AllowedCosmosDenoms), len(that1.AllowedCosmosDenoms)) - } - for i := range this.AllowedCosmosDenoms { - if !this.AllowedCosmosDenoms[i].Equal(&that1.AllowedCosmosDenoms[i]) { - return fmt.Errorf("AllowedCosmosDenoms this[%v](%v) Not Equal that[%v](%v)", i, this.AllowedCosmosDenoms[i], i, that1.AllowedCosmosDenoms[i]) - } - } return nil } func (this *Params) Equal(that interface{}) bool { @@ -400,22 +356,6 @@ func (this *Params) Equal(that interface{}) bool { } else if this == nil { return false } - if len(this.EnabledConversionPairs) != len(that1.EnabledConversionPairs) { - return false - } - for i := range this.EnabledConversionPairs { - if !this.EnabledConversionPairs[i].Equal(&that1.EnabledConversionPairs[i]) { - return false - } - } - if len(this.AllowedCosmosDenoms) != len(that1.AllowedCosmosDenoms) { - return false - } - for i := range this.AllowedCosmosDenoms { - if !this.AllowedCosmosDenoms[i].Equal(&that1.AllowedCosmosDenoms[i]) { - return false - } - } return true } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -525,34 +465,6 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.EnabledConversionPairs) > 0 { - for iNdEx := len(m.EnabledConversionPairs) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.EnabledConversionPairs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenesis(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - } - } - if len(m.AllowedCosmosDenoms) > 0 { - for iNdEx := len(m.AllowedCosmosDenoms) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.AllowedCosmosDenoms[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenesis(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - } return len(dAtA) - i, nil } @@ -605,18 +517,6 @@ func (m *Params) Size() (n int) { } var l int _ = l - if len(m.AllowedCosmosDenoms) > 0 { - for _, e := range m.AllowedCosmosDenoms { - l = e.Size() - n += 1 + l + sovGenesis(uint64(l)) - } - } - if len(m.EnabledConversionPairs) > 0 { - for _, e := range m.EnabledConversionPairs { - l = e.Size() - n += 1 + l + sovGenesis(uint64(l)) - } - } return n } @@ -890,74 +790,6 @@ func (m *Params) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AllowedCosmosDenoms", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenesis - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenesis - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenesis - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AllowedCosmosDenoms = append(m.AllowedCosmosDenoms, AllowedCosmosCoinERC20Token{}) - if err := m.AllowedCosmosDenoms[len(m.AllowedCosmosDenoms)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field EnabledConversionPairs", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenesis - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenesis - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenesis - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.EnabledConversionPairs = append(m.EnabledConversionPairs, ConversionPair{}) - if err := m.EnabledConversionPairs[len(m.EnabledConversionPairs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/evmutil/types/msg.go b/x/evmutil/types/msg.go deleted file mode 100644 index 8c9b5650..00000000 --- a/x/evmutil/types/msg.go +++ /dev/null @@ -1,266 +0,0 @@ -package types - -import ( - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" - "github.com/ethereum/go-ethereum/common" -) - -// ensure Msg interface compliance at compile time -var ( - _ sdk.Msg = &MsgConvertCoinToERC20{} - _ legacytx.LegacyMsg = &MsgConvertCoinToERC20{} - _ sdk.Msg = &MsgConvertERC20ToCoin{} - _ legacytx.LegacyMsg = &MsgConvertERC20ToCoin{} - - _ sdk.Msg = &MsgConvertCosmosCoinToERC20{} - _ legacytx.LegacyMsg = &MsgConvertCosmosCoinToERC20{} - _ sdk.Msg = &MsgConvertCosmosCoinFromERC20{} - _ legacytx.LegacyMsg = &MsgConvertCosmosCoinFromERC20{} -) - -// legacy message types -const ( - TypeMsgConvertCoinToERC20 = "evmutil_convert_coin_to_erc20" - TypeMsgConvertERC20ToCoin = "evmutil_convert_erc20_to_coin" - - TypeMsgConvertCosmosCoinToERC20 = "evmutil_convert_cosmos_coin_to_erc20" - TypeMsgConvertCosmosCoinFromERC20 = "evmutil_convert_cosmos_coin_from_erc20" -) - -//////////////////////////// -// EVM-native assets -> Cosmos SDK -//////////////////////////// - -// NewMsgConvertCoinToERC20 returns a new MsgConvertCoinToERC20 -func NewMsgConvertCoinToERC20( - initiator string, - receiver string, - amount sdk.Coin, -) MsgConvertCoinToERC20 { - return MsgConvertCoinToERC20{ - Initiator: initiator, - Receiver: receiver, - Amount: &amount, - } -} - -// GetSigners returns the addresses of signers that must sign. -func (msg MsgConvertCoinToERC20) GetSigners() []sdk.AccAddress { - sender, err := sdk.AccAddressFromBech32(msg.Initiator) - if err != nil { - panic(err) - } - return []sdk.AccAddress{sender} -} - -// ValidateBasic does a simple validation check that doesn't require access to any other information. -func (msg MsgConvertCoinToERC20) ValidateBasic() error { - _, err := sdk.AccAddressFromBech32(msg.Initiator) - if err != nil { - return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) - } - - if !common.IsHexAddress(msg.Receiver) { - return errorsmod.Wrap( - sdkerrors.ErrInvalidAddress, - "Receiver is not a valid hex address", - ) - } - - if msg.Amount.IsZero() { - return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "amount cannot be zero") - } - - // Checks for negative - return msg.Amount.Validate() -} - -// GetSignBytes implements the LegacyMsg.GetSignBytes method. -func (msg MsgConvertCoinToERC20) GetSignBytes() []byte { - return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) -} - -// Route implements the LegacyMsg.Route method. -func (msg MsgConvertCoinToERC20) Route() string { - return RouterKey -} - -// Type implements the LegacyMsg.Type method. -func (msg MsgConvertCoinToERC20) Type() string { - return TypeMsgConvertCoinToERC20 -} - -// NewMsgConvertERC20ToCoin returns a new MsgConvertERC20ToCoin -func NewMsgConvertERC20ToCoin( - initiator InternalEVMAddress, - receiver sdk.AccAddress, - contractAddr InternalEVMAddress, - amount sdkmath.Int, -) MsgConvertERC20ToCoin { - return MsgConvertERC20ToCoin{ - Initiator: initiator.String(), - Receiver: receiver.String(), - KavaERC20Address: contractAddr.String(), - Amount: amount, - } -} - -// GetSigners returns the addresses of signers that must sign. -func (msg MsgConvertERC20ToCoin) GetSigners() []sdk.AccAddress { - addr := common.HexToAddress(msg.Initiator) - sender := sdk.AccAddress(addr.Bytes()) - return []sdk.AccAddress{sender} -} - -// ValidateBasic does a simple validation check that doesn't require access to any other information. -func (msg MsgConvertERC20ToCoin) ValidateBasic() error { - if !common.IsHexAddress(msg.Initiator) { - return errorsmod.Wrap( - sdkerrors.ErrInvalidAddress, - "initiator is not a valid hex address", - ) - } - - if !common.IsHexAddress(msg.KavaERC20Address) { - return errorsmod.Wrap( - sdkerrors.ErrInvalidAddress, - "erc20 contract address is not a valid hex address", - ) - } - - _, err := sdk.AccAddressFromBech32(msg.Receiver) - if err != nil { - return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "receiver is not a valid bech32 address") - } - - if msg.Amount.IsNil() || msg.Amount.LTE(sdk.ZeroInt()) { - return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "amount cannot be zero or less") - } - - return nil -} - -// GetSignBytes implements the LegacyMsg.GetSignBytes method. -func (msg MsgConvertERC20ToCoin) GetSignBytes() []byte { - return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) -} - -// Route implements the LegacyMsg.Route method. -func (msg MsgConvertERC20ToCoin) Route() string { - return RouterKey -} - -// Type implements the LegacyMsg.Type method. -func (msg MsgConvertERC20ToCoin) Type() string { - return TypeMsgConvertERC20ToCoin -} - -//////////////////////////// -// Cosmos SDK-native assets -> EVM -//////////////////////////// - -// NewMsgConvertCosmosCoinToERC20 returns a new MsgConvertCosmosCoinToERC20 -func NewMsgConvertCosmosCoinToERC20( - initiator string, - receiver string, - amount sdk.Coin, -) MsgConvertCosmosCoinToERC20 { - return MsgConvertCosmosCoinToERC20{ - Initiator: initiator, - Receiver: receiver, - Amount: &amount, - } -} - -// GetSigners implements types.Msg -func (msg MsgConvertCosmosCoinToERC20) GetSigners() []sdk.AccAddress { - sender, err := sdk.AccAddressFromBech32(msg.Initiator) - if err != nil { - panic(err) - } - return []sdk.AccAddress{sender} -} - -// ValidateBasic implements types.Msg -func (msg MsgConvertCosmosCoinToERC20) ValidateBasic() error { - _, err := sdk.AccAddressFromBech32(msg.Initiator) - if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid initiator address (%s): %s", msg.Initiator, err.Error()) - } - - if !common.IsHexAddress(msg.Receiver) { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "receiver is not a valid hex address (%s)", msg.Receiver) - } - - if msg.Amount.IsNil() || !msg.Amount.IsValid() || msg.Amount.IsZero() { - return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "'%s'", msg.Amount) - } - - return nil -} - -// GetSignBytes implements legacytx.LegacyMsg -func (msg MsgConvertCosmosCoinToERC20) GetSignBytes() []byte { - return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) -} - -// Route implements legacytx.LegacyMsg -func (MsgConvertCosmosCoinToERC20) Route() string { return RouterKey } - -// Type implements legacytx.LegacyMsg -func (MsgConvertCosmosCoinToERC20) Type() string { return TypeMsgConvertCosmosCoinToERC20 } - -// NewMsgConvertCosmosCoinToERC20 returns a new MsgConvertCosmosCoinToERC20 -func NewMsgConvertCosmosCoinFromERC20( - initiator string, - receiver string, - amount sdk.Coin, -) MsgConvertCosmosCoinFromERC20 { - return MsgConvertCosmosCoinFromERC20{ - Initiator: initiator, - Receiver: receiver, - Amount: &amount, - } -} - -// GetSigners implements types.Msg -func (msg MsgConvertCosmosCoinFromERC20) GetSigners() []sdk.AccAddress { - sender0x, err := NewInternalEVMAddressFromString(msg.Initiator) - if err != nil { - panic(err) - } - return []sdk.AccAddress{sender0x.Bytes()} -} - -// ValidateBasic implements types.Msg -func (msg MsgConvertCosmosCoinFromERC20) ValidateBasic() error { - if !common.IsHexAddress(msg.Initiator) { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "initiator is not a valid hex address (%s)", msg.Initiator) - } - - _, err := sdk.AccAddressFromBech32(msg.Receiver) - if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid receiver address (%s): %s", msg.Receiver, err.Error()) - } - - if msg.Amount.IsNil() || !msg.Amount.IsValid() || msg.Amount.IsZero() { - return errorsmod.Wrapf(sdkerrors.ErrInvalidCoins, "'%s'", msg.Amount) - } - - return nil -} - -// GetSignBytes implements legacytx.LegacyMsg -func (msg MsgConvertCosmosCoinFromERC20) GetSignBytes() []byte { - return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) -} - -// Route implements legacytx.LegacyMsg -func (MsgConvertCosmosCoinFromERC20) Route() string { return RouterKey } - -// Type implements legacytx.LegacyMsg -func (MsgConvertCosmosCoinFromERC20) Type() string { return TypeMsgConvertCosmosCoinFromERC20 } diff --git a/x/evmutil/types/msg_test.go.disabled b/x/evmutil/types/msg_test.go.disabled deleted file mode 100644 index 7a74a95b..00000000 --- a/x/evmutil/types/msg_test.go.disabled +++ /dev/null @@ -1,424 +0,0 @@ -// package types_test - -// import ( -// "testing" - -// "github.com/aura-nw/aura/x/evmutil/testutil" -// "github.com/aura-nw/aura/x/evmutil/types" -// "github.com/kava-labs/kava/app" -// "github.com/stretchr/testify/require" - -// sdkmath "cosmossdk.io/math" -// sdk "github.com/cosmos/cosmos-sdk/types" -// ) - -// func TestMsgConvertCoinToERC20(t *testing.T) { -// app.SetSDKConfig() - -// type errArgs struct { -// expectPass bool -// contains string -// } - -// tests := []struct { -// name string -// giveInitiator string -// giveReceiver string -// giveAmount sdk.Coin -// errArgs errArgs -// }{ -// { -// "valid", -// "kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz", -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", -// sdk.NewCoin("erc20/weth", sdkmath.NewInt(1234)), -// errArgs{ -// expectPass: true, -// }, -// }, -// { -// "invalid - odd length hex address", -// "kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz", -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc", -// sdk.NewCoin("erc20/weth", sdkmath.NewInt(1234)), -// errArgs{ -// expectPass: false, -// contains: "Receiver is not a valid hex address: invalid address", -// }, -// }, -// { -// "invalid - zero amount", -// "kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz", -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", -// sdk.NewCoin("erc20/weth", sdkmath.NewInt(0)), -// errArgs{ -// expectPass: false, -// contains: "amount cannot be zero", -// }, -// }, -// { -// "invalid - negative amount", -// "kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz", -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", -// // Create manually so there is no validation -// sdk.Coin{Denom: "erc20/weth", Amount: sdkmath.NewInt(-1234)}, -// errArgs{ -// expectPass: false, -// contains: "negative coin amount", -// }, -// }, -// { -// "invalid - empty denom", -// "kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz", -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", -// sdk.Coin{Denom: "", Amount: sdkmath.NewInt(-1234)}, -// errArgs{ -// expectPass: false, -// contains: "invalid denom", -// }, -// }, -// { -// "invalid - invalid denom", -// "kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz", -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", -// sdk.Coin{Denom: "h", Amount: sdkmath.NewInt(-1234)}, -// errArgs{ -// expectPass: false, -// contains: "invalid denom", -// }, -// }, -// } - -// for _, tc := range tests { -// t.Run(tc.name, func(t *testing.T) { -// msg := types.NewMsgConvertCoinToERC20( -// tc.giveInitiator, -// tc.giveReceiver, -// tc.giveAmount, -// ) -// err := msg.ValidateBasic() - -// if tc.errArgs.expectPass { -// require.NoError(t, err) -// } else { -// require.Error(t, err) -// require.Contains(t, err.Error(), tc.errArgs.contains) -// } -// }) -// } -// } - -// func TestMsgConvertERC20ToCoin(t *testing.T) { -// app.SetSDKConfig() - -// type errArgs struct { -// expectPass bool -// contains string -// } - -// tests := []struct { -// name string -// receiver string -// initiator string -// contractAddr string -// amount sdkmath.Int -// errArgs errArgs -// }{ -// { -// "valid", -// "kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz", -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", -// "0x404F9466d758eA33eA84CeBE9E444b06533b369e", -// sdkmath.NewInt(1234), -// errArgs{ -// expectPass: true, -// }, -// }, -// { -// "invalid - odd length hex address", -// "kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz", -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc", -// "0x404F9466d758eA33eA84CeBE9E444b06533b369e", -// sdkmath.NewInt(1234), -// errArgs{ -// expectPass: false, -// contains: "initiator is not a valid hex address", -// }, -// }, -// { -// "invalid - zero amount", -// "kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz", -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", -// "0x404F9466d758eA33eA84CeBE9E444b06533b369e", -// sdkmath.NewInt(0), -// errArgs{ -// expectPass: false, -// contains: "amount cannot be zero", -// }, -// }, -// { -// "invalid - negative amount", -// "kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz", -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", -// "0x404F9466d758eA33eA84CeBE9E444b06533b369e", -// sdkmath.NewInt(-1234), -// errArgs{ -// expectPass: false, -// contains: "amount cannot be zero or less", -// }, -// }, -// { -// "invalid - invalid contract address", -// "kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz", -// "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", -// "0x404F9466d758eA33eA84CeBE9E444b06533b369", -// sdkmath.NewInt(1234), -// errArgs{ -// expectPass: false, -// contains: "erc20 contract address is not a valid hex address", -// }, -// }, -// } - -// for _, tc := range tests { -// t.Run(tc.name, func(t *testing.T) { -// msg := types.MsgConvertERC20ToCoin{ -// Initiator: tc.initiator, -// Receiver: tc.receiver, -// KavaERC20Address: tc.contractAddr, -// Amount: tc.amount, -// } -// err := msg.ValidateBasic() - -// if tc.errArgs.expectPass { -// require.NoError(t, err) -// } else { -// require.Error(t, err) -// require.Contains(t, err.Error(), tc.errArgs.contains) -// } -// }) -// } -// } - -// func TestConvertCosmosCoinToERC20_ValidateBasic(t *testing.T) { -// validKavaAddr := app.RandomAddress() -// validHexAddr, _ := testutil.RandomEvmAccount() -// invalidAddr := "not-an-address" -// validAmount := sdk.NewInt64Coin("hard", 5e3) - -// testCases := []struct { -// name string -// initiator string -// receiver string -// amount sdk.Coin -// expectedErr string -// }{ -// { -// name: "valid", -// initiator: validKavaAddr.String(), -// receiver: validHexAddr.String(), -// amount: validAmount, -// expectedErr: "", -// }, -// { -// name: "invalid - sending to kava addr", -// initiator: validKavaAddr.String(), -// receiver: app.RandomAddress().String(), -// amount: validAmount, -// expectedErr: "receiver is not a valid hex address", -// }, -// { -// name: "invalid - invalid initiator", -// initiator: invalidAddr, -// receiver: app.RandomAddress().String(), -// amount: validAmount, -// expectedErr: "invalid initiator address", -// }, -// { -// name: "invalid - invalid receiver", -// initiator: validKavaAddr.String(), -// receiver: invalidAddr, -// amount: validAmount, -// expectedErr: "receiver is not a valid hex address", -// }, -// { -// name: "invalid - invalid amount - nil", -// initiator: validKavaAddr.String(), -// receiver: validHexAddr.String(), -// amount: sdk.Coin{}, -// expectedErr: "invalid coins", -// }, -// { -// name: "invalid - invalid amount - zero", -// initiator: validKavaAddr.String(), -// receiver: validHexAddr.String(), -// amount: sdk.NewInt64Coin("magic", 0), -// expectedErr: "invalid coins", -// }, -// { -// name: "invalid - invalid amount - negative", -// initiator: validKavaAddr.String(), -// receiver: validHexAddr.String(), -// amount: sdk.Coin{Denom: "magic", Amount: sdkmath.NewInt(-42)}, -// expectedErr: "invalid coins", -// }, -// { -// name: "invalid - invalid amount - invalid denom", -// initiator: validKavaAddr.String(), -// receiver: validHexAddr.String(), -// amount: sdk.Coin{Denom: "", Amount: sdkmath.NewInt(42)}, -// expectedErr: "invalid coins", -// }, -// } - -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// msg := types.NewMsgConvertCosmosCoinToERC20( -// tc.initiator, -// tc.receiver, -// tc.amount, -// ) -// err := msg.ValidateBasic() - -// if tc.expectedErr != "" { -// require.ErrorContains(t, err, tc.expectedErr) -// } else { -// require.NoError(t, err) -// require.Equal(t, "evmutil", msg.Route()) -// require.Equal(t, "evmutil_convert_cosmos_coin_to_erc20", msg.Type()) -// require.NotPanics(t, func() { _ = msg.GetSignBytes() }) -// } -// }) -// } -// } - -// func TestConvertCosmosCoinToERC20_GetSigners(t *testing.T) { -// t.Run("valid", func(t *testing.T) { -// initiator := app.RandomAddress() -// signers := types.MsgConvertCosmosCoinToERC20{ -// Initiator: initiator.String(), -// }.GetSigners() -// require.Len(t, signers, 1) -// require.Equal(t, initiator, signers[0]) -// }) - -// t.Run("panics when depositor is invalid", func(t *testing.T) { -// require.Panics(t, func() { -// types.MsgConvertCosmosCoinToERC20{ -// Initiator: "not-an-address", -// }.GetSigners() -// }) -// }) -// } - -// func TestConvertCosmosCoinFromERC20_ValidateBasic(t *testing.T) { -// validHexAddr := testutil.RandomEvmAddress() -// validKavaAddr := app.RandomAddress() -// invalidAddr := "not-an-address" -// validAmount := sdk.NewInt64Coin("hard", 5e3) - -// testCases := []struct { -// name string -// initiator string -// receiver string -// amount sdk.Coin -// expectedErr string -// }{ -// { -// name: "valid", -// initiator: validHexAddr.String(), -// receiver: validKavaAddr.String(), -// amount: validAmount, -// expectedErr: "", -// }, -// { -// name: "invalid - sending to 0x addr", -// initiator: validHexAddr.String(), -// receiver: testutil.RandomEvmAddress().Hex(), -// amount: validAmount, -// expectedErr: "invalid receiver address", -// }, -// { -// name: "invalid - invalid initiator", -// initiator: invalidAddr, -// receiver: app.RandomAddress().String(), -// amount: validAmount, -// expectedErr: "initiator is not a valid hex address", -// }, -// { -// name: "invalid - invalid receiver", -// initiator: validHexAddr.String(), -// receiver: invalidAddr, -// amount: validAmount, -// expectedErr: "invalid receiver address", -// }, -// { -// name: "invalid - invalid amount - nil", -// initiator: validHexAddr.String(), -// receiver: validKavaAddr.String(), -// amount: sdk.Coin{}, -// expectedErr: "invalid coins", -// }, -// { -// name: "invalid - invalid amount - zero", -// initiator: validHexAddr.String(), -// receiver: validKavaAddr.String(), -// amount: sdk.NewInt64Coin("magic", 0), -// expectedErr: "invalid coins", -// }, -// { -// name: "invalid - invalid amount - negative", -// initiator: validHexAddr.String(), -// receiver: validKavaAddr.String(), -// amount: sdk.Coin{Denom: "magic", Amount: sdkmath.NewInt(-42)}, -// expectedErr: "invalid coins", -// }, -// { -// name: "invalid - invalid amount - invalid denom", -// initiator: validHexAddr.String(), -// receiver: validKavaAddr.String(), -// amount: sdk.Coin{Denom: "", Amount: sdkmath.NewInt(42)}, -// expectedErr: "invalid coins", -// }, -// } - -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// msg := types.NewMsgConvertCosmosCoinFromERC20( -// tc.initiator, -// tc.receiver, -// tc.amount, -// ) -// err := msg.ValidateBasic() - -// if tc.expectedErr != "" { -// require.ErrorContains(t, err, tc.expectedErr) -// } else { -// require.NoError(t, err) -// require.Equal(t, "evmutil", msg.Route()) -// require.Equal(t, "evmutil_convert_cosmos_coin_from_erc20", msg.Type()) -// require.NotPanics(t, func() { _ = msg.GetSignBytes() }) -// } -// }) -// } -// } - -// func TestConvertCosmosCoinFromERC20_GetSigners(t *testing.T) { -// t.Run("valid", func(t *testing.T) { -// initiator0x := testutil.RandomEvmAddress() -// initiator := sdk.AccAddress(initiator0x.Bytes()) -// signers := types.MsgConvertCosmosCoinFromERC20{ -// Initiator: initiator0x.Hex(), -// }.GetSigners() -// require.Len(t, signers, 1) -// require.Equal(t, initiator, signers[0]) -// }) - -// t.Run("panics when depositor is invalid", func(t *testing.T) { -// require.Panics(t, func() { -// types.MsgConvertCosmosCoinFromERC20{ -// Initiator: "not-an-address", -// }.GetSigners() -// }) -// }) -// } diff --git a/x/evmutil/types/params.go b/x/evmutil/types/params.go index b2940631..509afa57 100644 --- a/x/evmutil/types/params.go +++ b/x/evmutil/types/params.go @@ -4,14 +4,6 @@ import ( paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ) -// Parameter keys and default values -var ( - KeyEnabledConversionPairs = []byte("EnabledConversionPairs") - DefaultConversionPairs = ConversionPairs{} - KeyAllowedCosmosDenoms = []byte("AllowedCosmosDenoms") - DefaultAllowedCosmosDenoms = AllowedCosmosCoinERC20Tokens{} -) - // ParamKeyTable for evmutil module. func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) @@ -20,38 +12,20 @@ func ParamKeyTable() paramtypes.KeyTable { // ParamSetPairs implements the ParamSet interface and returns all the key/value // pairs pairs of the evmutil module's parameters. func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { - return paramtypes.ParamSetPairs{ - paramtypes.NewParamSetPair(KeyEnabledConversionPairs, &p.EnabledConversionPairs, validateConversionPairs), - paramtypes.NewParamSetPair(KeyAllowedCosmosDenoms, &p.AllowedCosmosDenoms, validateAllowedCosmosCoinERC20Tokens), - } + return paramtypes.ParamSetPairs{} } // NewParams returns new evmutil module Params. -func NewParams( - conversionPairs ConversionPairs, - allowedCosmosDenoms AllowedCosmosCoinERC20Tokens, -) Params { - return Params{ - EnabledConversionPairs: conversionPairs, - AllowedCosmosDenoms: allowedCosmosDenoms, - } +func NewParams() Params { + return Params{} } // DefaultParams returns the default parameters for evmutil. func DefaultParams() Params { - return NewParams( - DefaultConversionPairs, - DefaultAllowedCosmosDenoms, - ) + return NewParams() } // Validate returns an error if the Params is invalid. func (p *Params) Validate() error { - if err := p.EnabledConversionPairs.Validate(); err != nil { - return err - } - if err := p.AllowedCosmosDenoms.Validate(); err != nil { - return err - } return nil } diff --git a/x/evmutil/types/params_test.go.disabled b/x/evmutil/types/params_test.go.disabled deleted file mode 100644 index 2e006546..00000000 --- a/x/evmutil/types/params_test.go.disabled +++ /dev/null @@ -1,165 +0,0 @@ -// package types_test - -// import ( -// bytes "bytes" -// "testing" - -// "github.com/stretchr/testify/suite" -// "sigs.k8s.io/yaml" - -// paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" - -// "github.com/aura-nw/aura/x/evmutil/testutil" -// "github.com/aura-nw/aura/x/evmutil/types" -// "github.com/kava-labs/kava/app" -// ) - -// type ParamsTestSuite struct { -// suite.Suite -// } - -// func (suite *ParamsTestSuite) SetupTest() { -// app.SetSDKConfig() -// } - -// func (suite *ParamsTestSuite) TestDefault() { -// defaultParams := types.DefaultParams() -// suite.Require().NoError(defaultParams.Validate()) -// } - -// func (suite *ParamsTestSuite) TestMarshalYAML() { -// conversionPairs := types.NewConversionPairs( -// types.NewConversionPair( -// testutil.MustNewInternalEVMAddressFromString("0x0000000000000000000000000000000000000001"), -// "usdc", -// ), -// ) -// allowedCosmosDenoms := types.NewAllowedCosmosCoinERC20Tokens( -// types.NewAllowedCosmosCoinERC20Token("denom", "Sdk Denom!", "DENOM", 6), -// ) - -// p := types.NewParams( -// conversionPairs, -// allowedCosmosDenoms, -// ) - -// data, err := yaml.Marshal(p) -// suite.Require().NoError(err) - -// var params map[string]interface{} -// err = yaml.Unmarshal(data, ¶ms) -// suite.Require().NoError(err) -// _, ok := params["enabled_conversion_pairs"] -// suite.Require().True(ok, "enabled_conversion_pairs should exist in yaml") -// _, ok = params["allowed_cosmos_denoms"] -// suite.Require().True(ok, "allowed_cosmos_denoms should exist in yaml") -// } - -// func (suite *ParamsTestSuite) TestParamSetPairs_EnabledConversionPairs() { -// suite.Require().Equal([]byte("EnabledConversionPairs"), types.KeyEnabledConversionPairs) -// defaultParams := types.DefaultParams() - -// var paramSetPair *paramstypes.ParamSetPair -// for _, pair := range defaultParams.ParamSetPairs() { -// if bytes.Equal(pair.Key, types.KeyEnabledConversionPairs) { -// paramSetPair = &pair -// break -// } -// } -// suite.Require().NotNil(paramSetPair) - -// pairs, ok := paramSetPair.Value.(*types.ConversionPairs) -// suite.Require().True(ok) -// suite.Require().Equal(pairs, &defaultParams.EnabledConversionPairs) - -// suite.Require().Nil(paramSetPair.ValidatorFn(*pairs)) -// suite.Require().EqualError(paramSetPair.ValidatorFn(struct{}{}), "invalid parameter type: struct {}") -// } - -// func (suite *ParamsTestSuite) TestParamSetPairs_AllowedCosmosDenoms() { -// suite.Require().Equal([]byte("AllowedCosmosDenoms"), types.KeyAllowedCosmosDenoms) -// defaultParams := types.DefaultParams() - -// var paramSetPair *paramstypes.ParamSetPair -// for _, pair := range defaultParams.ParamSetPairs() { -// if bytes.Equal(pair.Key, types.KeyAllowedCosmosDenoms) { -// paramSetPair = &pair -// break -// } -// } -// suite.Require().NotNil(paramSetPair) - -// pairs, ok := paramSetPair.Value.(*types.AllowedCosmosCoinERC20Tokens) -// suite.Require().True(ok) -// suite.Require().Equal(pairs, &defaultParams.AllowedCosmosDenoms) - -// suite.Require().Nil(paramSetPair.ValidatorFn(*pairs)) -// suite.Require().EqualError(paramSetPair.ValidatorFn(struct{}{}), "invalid parameter type: struct {}") -// } - -// func (suite *ParamsTestSuite) TestParams_Validate() { -// validConversionPairs := types.NewConversionPairs( -// types.NewConversionPair( -// testutil.MustNewInternalEVMAddressFromString("0x0000000000000000000000000000000000000001"), -// "usdc", -// ), -// ) -// invalidConversionPairs := types.NewConversionPairs( -// types.NewConversionPair( -// testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000A"), -// "kava", -// ), -// types.NewConversionPair( -// testutil.MustNewInternalEVMAddressFromString("0x000000000000000000000000000000000000000B"), -// "kava", // duplicate denom! -// ), -// ) -// validAllowedCosmosDenoms := types.NewAllowedCosmosCoinERC20Tokens( -// types.NewAllowedCosmosCoinERC20Token("hard", "EVM Hard", "HARD", 6), -// ) -// invalidAllowedCosmosDenoms := types.NewAllowedCosmosCoinERC20Tokens( -// types.NewAllowedCosmosCoinERC20Token("", "Invalid Token", "NOPE", 0), // empty sdk denom -// ) - -// testCases := []struct { -// name string -// params types.Params -// expErr string -// }{ -// { -// name: "valid - empty", -// params: types.NewParams(types.NewConversionPairs(), types.NewAllowedCosmosCoinERC20Tokens()), -// expErr: "", -// }, -// { -// name: "valid - with data", -// params: types.NewParams(validConversionPairs, validAllowedCosmosDenoms), -// expErr: "", -// }, -// { -// name: "invalid - invalid conversion pair", -// params: types.NewParams(invalidConversionPairs, validAllowedCosmosDenoms), -// expErr: "found duplicate", -// }, -// { -// name: "invalid - invalid allowed cosmos denoms", -// params: types.NewParams(validConversionPairs, invalidAllowedCosmosDenoms), -// expErr: "invalid token", -// }, -// } - -// for _, tc := range testCases { -// suite.Run(tc.name, func() { -// err := tc.params.Validate() -// if tc.expErr != "" { -// suite.ErrorContains(err, tc.expErr, "Expected validation error") -// } else { -// suite.NoError(err, "Expected no validation error") -// } -// }) -// } -// } - -// func TestParamsTestSuite(t *testing.T) { -// suite.Run(t, new(ParamsTestSuite)) -// } diff --git a/x/evmutil/types/query.pb.go b/x/evmutil/types/query.pb.go index 9336d95d..8495a451 100644 --- a/x/evmutil/types/query.pb.go +++ b/x/evmutil/types/query.pb.go @@ -6,7 +6,7 @@ package types import ( context "context" fmt "fmt" - query "github.com/cosmos/cosmos-sdk/types/query" + _ "github.com/cosmos/cosmos-sdk/types/query" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" @@ -112,200 +112,34 @@ func (m *QueryParamsResponse) GetParams() Params { return Params{} } -// QueryDeployedCosmosCoinContractsRequest defines the request type for Query/DeployedCosmosCoinContracts method. -type QueryDeployedCosmosCoinContractsRequest struct { - // optional query param to only return specific denoms in the list - // denoms that do not have deployed contracts will be omitted from the result - // must request fewer than 100 denoms at a time. - CosmosDenoms []string `protobuf:"bytes,1,rep,name=cosmos_denoms,json=cosmosDenoms,proto3" json:"cosmos_denoms,omitempty"` - // pagination defines an optional pagination for the request. - Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` -} - -func (m *QueryDeployedCosmosCoinContractsRequest) Reset() { - *m = QueryDeployedCosmosCoinContractsRequest{} -} -func (m *QueryDeployedCosmosCoinContractsRequest) String() string { return proto.CompactTextString(m) } -func (*QueryDeployedCosmosCoinContractsRequest) ProtoMessage() {} -func (*QueryDeployedCosmosCoinContractsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_4a8d0512331709e7, []int{2} -} -func (m *QueryDeployedCosmosCoinContractsRequest) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *QueryDeployedCosmosCoinContractsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryDeployedCosmosCoinContractsRequest.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *QueryDeployedCosmosCoinContractsRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryDeployedCosmosCoinContractsRequest.Merge(m, src) -} -func (m *QueryDeployedCosmosCoinContractsRequest) XXX_Size() int { - return m.Size() -} -func (m *QueryDeployedCosmosCoinContractsRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryDeployedCosmosCoinContractsRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryDeployedCosmosCoinContractsRequest proto.InternalMessageInfo - -// QueryDeployedCosmosCoinContractsResponse defines the response type for the Query/DeployedCosmosCoinContracts method. -type QueryDeployedCosmosCoinContractsResponse struct { - // deployed_cosmos_coin_contracts is a list of cosmos-sdk coin denom and its deployed contract address - DeployedCosmosCoinContracts []DeployedCosmosCoinContract `protobuf:"bytes,1,rep,name=deployed_cosmos_coin_contracts,json=deployedCosmosCoinContracts,proto3" json:"deployed_cosmos_coin_contracts"` - // pagination defines the pagination in the response. - Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` -} - -func (m *QueryDeployedCosmosCoinContractsResponse) Reset() { - *m = QueryDeployedCosmosCoinContractsResponse{} -} -func (m *QueryDeployedCosmosCoinContractsResponse) String() string { return proto.CompactTextString(m) } -func (*QueryDeployedCosmosCoinContractsResponse) ProtoMessage() {} -func (*QueryDeployedCosmosCoinContractsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_4a8d0512331709e7, []int{3} -} -func (m *QueryDeployedCosmosCoinContractsResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *QueryDeployedCosmosCoinContractsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryDeployedCosmosCoinContractsResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *QueryDeployedCosmosCoinContractsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryDeployedCosmosCoinContractsResponse.Merge(m, src) -} -func (m *QueryDeployedCosmosCoinContractsResponse) XXX_Size() int { - return m.Size() -} -func (m *QueryDeployedCosmosCoinContractsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryDeployedCosmosCoinContractsResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryDeployedCosmosCoinContractsResponse proto.InternalMessageInfo - -func (m *QueryDeployedCosmosCoinContractsResponse) GetDeployedCosmosCoinContracts() []DeployedCosmosCoinContract { - if m != nil { - return m.DeployedCosmosCoinContracts - } - return nil -} - -func (m *QueryDeployedCosmosCoinContractsResponse) GetPagination() *query.PageResponse { - if m != nil { - return m.Pagination - } - return nil -} - -// DeployedCosmosCoinContract defines a deployed token contract to the evm representing a native cosmos-sdk coin -type DeployedCosmosCoinContract struct { - CosmosDenom string `protobuf:"bytes,1,opt,name=cosmos_denom,json=cosmosDenom,proto3" json:"cosmos_denom,omitempty"` - Address *InternalEVMAddress `protobuf:"bytes,2,opt,name=address,proto3,customtype=InternalEVMAddress" json:"address,omitempty"` -} - -func (m *DeployedCosmosCoinContract) Reset() { *m = DeployedCosmosCoinContract{} } -func (m *DeployedCosmosCoinContract) String() string { return proto.CompactTextString(m) } -func (*DeployedCosmosCoinContract) ProtoMessage() {} -func (*DeployedCosmosCoinContract) Descriptor() ([]byte, []int) { - return fileDescriptor_4a8d0512331709e7, []int{4} -} -func (m *DeployedCosmosCoinContract) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *DeployedCosmosCoinContract) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_DeployedCosmosCoinContract.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *DeployedCosmosCoinContract) XXX_Merge(src proto.Message) { - xxx_messageInfo_DeployedCosmosCoinContract.Merge(m, src) -} -func (m *DeployedCosmosCoinContract) XXX_Size() int { - return m.Size() -} -func (m *DeployedCosmosCoinContract) XXX_DiscardUnknown() { - xxx_messageInfo_DeployedCosmosCoinContract.DiscardUnknown(m) -} - -var xxx_messageInfo_DeployedCosmosCoinContract proto.InternalMessageInfo - -func (m *DeployedCosmosCoinContract) GetCosmosDenom() string { - if m != nil { - return m.CosmosDenom - } - return "" -} - func init() { proto.RegisterType((*QueryParamsRequest)(nil), "kava.evmutil.v1beta1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "kava.evmutil.v1beta1.QueryParamsResponse") - proto.RegisterType((*QueryDeployedCosmosCoinContractsRequest)(nil), "kava.evmutil.v1beta1.QueryDeployedCosmosCoinContractsRequest") - proto.RegisterType((*QueryDeployedCosmosCoinContractsResponse)(nil), "kava.evmutil.v1beta1.QueryDeployedCosmosCoinContractsResponse") - proto.RegisterType((*DeployedCosmosCoinContract)(nil), "kava.evmutil.v1beta1.DeployedCosmosCoinContract") } func init() { proto.RegisterFile("kava/evmutil/v1beta1/query.proto", fileDescriptor_4a8d0512331709e7) } var fileDescriptor_4a8d0512331709e7 = []byte{ - // 542 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0x4f, 0x6f, 0xd3, 0x30, - 0x14, 0x8f, 0x0b, 0x14, 0xea, 0x8e, 0x8b, 0xa9, 0xd0, 0xd4, 0x55, 0xe9, 0x08, 0x88, 0x75, 0x48, - 0x38, 0x5b, 0x41, 0x1c, 0x26, 0x40, 0xa2, 0x1d, 0x20, 0x0e, 0x48, 0x2c, 0x07, 0x0e, 0x5c, 0x2a, - 0x27, 0xb1, 0x42, 0x44, 0x6a, 0xa7, 0xb1, 0x5b, 0x51, 0x71, 0x83, 0x0b, 0x47, 0x24, 0xbe, 0x40, - 0x3f, 0xce, 0x8e, 0x93, 0xb8, 0xa0, 0x1d, 0x26, 0xd4, 0x72, 0x40, 0x9c, 0xf8, 0x08, 0xa8, 0xb6, - 0xbb, 0x15, 0x91, 0xb6, 0x88, 0x9b, 0xf5, 0xfc, 0x7b, 0xfe, 0xfd, 0x79, 0x2f, 0x81, 0x9b, 0x6f, - 0xc8, 0x80, 0xb8, 0x74, 0xd0, 0xed, 0xcb, 0x38, 0x71, 0x07, 0xbb, 0x3e, 0x95, 0x64, 0xd7, 0xed, - 0xf5, 0x69, 0x36, 0xc4, 0x69, 0xc6, 0x25, 0x47, 0x95, 0x29, 0x02, 0x1b, 0x04, 0x36, 0x88, 0xea, - 0xad, 0x80, 0x8b, 0x2e, 0x17, 0xae, 0x4f, 0x04, 0xd5, 0xf0, 0xd3, 0xe6, 0x94, 0x44, 0x31, 0x23, - 0x32, 0xe6, 0x4c, 0xbf, 0x50, 0xad, 0x44, 0x3c, 0xe2, 0xea, 0xe8, 0x4e, 0x4f, 0xa6, 0x5a, 0x8b, - 0x38, 0x8f, 0x12, 0xea, 0x92, 0x34, 0x76, 0x09, 0x63, 0x5c, 0xaa, 0x16, 0x61, 0x6e, 0x9d, 0x5c, - 0x5d, 0x11, 0x65, 0x54, 0xc4, 0x06, 0xe3, 0x54, 0x20, 0x3a, 0x98, 0x32, 0xbf, 0x20, 0x19, 0xe9, - 0x0a, 0x8f, 0xf6, 0xfa, 0x54, 0x48, 0xe7, 0x00, 0x5e, 0xf9, 0xa3, 0x2a, 0x52, 0xce, 0x04, 0x45, - 0x7b, 0xb0, 0x98, 0xaa, 0xca, 0x3a, 0xd8, 0x04, 0x8d, 0x72, 0xb3, 0x86, 0xf3, 0x7c, 0x61, 0xdd, - 0xd5, 0x3a, 0x7f, 0x78, 0x52, 0xb7, 0x3c, 0xd3, 0xe1, 0x8c, 0x00, 0xdc, 0x52, 0x6f, 0xee, 0xd3, - 0x34, 0xe1, 0x43, 0x1a, 0xb6, 0x95, 0xf9, 0x36, 0x8f, 0x59, 0x9b, 0x33, 0x99, 0x91, 0x40, 0xce, - 0xe8, 0xd1, 0x75, 0x78, 0x59, 0x47, 0xd3, 0x09, 0x29, 0xe3, 0x8a, 0xee, 0x5c, 0xa3, 0xe4, 0xad, - 0xe9, 0xe2, 0xbe, 0xaa, 0xa1, 0x27, 0x10, 0x9e, 0xa5, 0xb4, 0x5e, 0x50, 0x82, 0x6e, 0x62, 0x0d, - 0xc1, 0xd3, 0x48, 0xb1, 0x9e, 0xc0, 0x99, 0xaa, 0x88, 0x1a, 0x02, 0x6f, 0xae, 0x73, 0xef, 0xd2, - 0xc7, 0x51, 0xdd, 0xfa, 0x31, 0xaa, 0x5b, 0xce, 0x2f, 0x00, 0x1b, 0xab, 0x25, 0x9a, 0x2c, 0xde, - 0x41, 0x3b, 0x34, 0xb0, 0x8e, 0x11, 0x1b, 0xf0, 0x98, 0x75, 0x82, 0x19, 0x52, 0x89, 0x2e, 0x37, - 0x77, 0xf2, 0x33, 0x5a, 0x4c, 0x61, 0x72, 0xdb, 0x08, 0x17, 0x8b, 0x40, 0x4f, 0x73, 0xbc, 0x6f, - 0xad, 0xf4, 0xae, 0x95, 0xcf, 0x9b, 0x77, 0x7a, 0xb0, 0xba, 0x58, 0x09, 0xba, 0x06, 0xd7, 0xe6, - 0xe7, 0xa0, 0xa6, 0x5e, 0xf2, 0xca, 0x73, 0x63, 0x40, 0x3b, 0xf0, 0x22, 0x09, 0xc3, 0x8c, 0x0a, - 0xa1, 0x64, 0x94, 0x5a, 0x57, 0x8f, 0x4f, 0xea, 0xe8, 0x19, 0x93, 0x34, 0x63, 0x24, 0x79, 0xfc, - 0xf2, 0xf9, 0x23, 0x7d, 0xeb, 0xcd, 0x60, 0xcd, 0x9f, 0x05, 0x78, 0x41, 0xa5, 0x8c, 0x3e, 0x00, - 0x58, 0xd4, 0xbb, 0x82, 0x1a, 0xf9, 0x29, 0xfd, 0xbd, 0x9a, 0xd5, 0xed, 0x7f, 0x40, 0x6a, 0xa3, - 0xce, 0x8d, 0xf7, 0x5f, 0xbe, 0x7f, 0x2e, 0xd8, 0xa8, 0xe6, 0xe6, 0x7e, 0x08, 0x7a, 0x31, 0xd1, - 0x31, 0x80, 0x1b, 0x4b, 0x06, 0x8e, 0x1e, 0x2c, 0x21, 0x5c, 0xbd, 0xcb, 0xd5, 0x87, 0xff, 0xdb, - 0x6e, 0x4c, 0xdc, 0x57, 0x26, 0xee, 0xa1, 0xbb, 0xf9, 0x26, 0x96, 0xef, 0x60, 0xab, 0x7d, 0x38, - 0xb6, 0xc1, 0xd1, 0xd8, 0x06, 0xdf, 0xc6, 0x36, 0xf8, 0x34, 0xb1, 0xad, 0xa3, 0x89, 0x6d, 0x7d, - 0x9d, 0xd8, 0xd6, 0xab, 0xed, 0x28, 0x96, 0xaf, 0xfb, 0x3e, 0x0e, 0x78, 0x57, 0xbd, 0x7c, 0x3b, - 0x21, 0xbe, 0xd0, 0x1c, 0x6f, 0x4f, 0x59, 0xe4, 0x30, 0xa5, 0xc2, 0x2f, 0xaa, 0x5f, 0xc5, 0x9d, - 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x1f, 0xfa, 0x86, 0x41, 0xe8, 0x04, 0x00, 0x00, + // 304 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x90, 0x31, 0x4b, 0x03, 0x31, + 0x14, 0xc7, 0x2f, 0xa2, 0x1d, 0xe2, 0x16, 0x3b, 0x48, 0x29, 0xb1, 0x1c, 0x0e, 0xad, 0x60, 0x42, + 0xeb, 0xe6, 0x58, 0xbf, 0x80, 0xed, 0xe8, 0x96, 0x2b, 0x21, 0x06, 0x7b, 0x79, 0x69, 0x93, 0x2b, + 0x76, 0xd5, 0x59, 0x10, 0xfc, 0x52, 0x1d, 0x0b, 0x2e, 0x4e, 0x22, 0xad, 0x1f, 0x44, 0x9a, 0x84, + 0x82, 0x78, 0x83, 0xdb, 0xe3, 0xdd, 0xef, 0xff, 0xbb, 0xff, 0x0b, 0xee, 0x3c, 0x88, 0x85, 0xe0, + 0x72, 0x51, 0x56, 0x5e, 0x4f, 0xf9, 0xa2, 0x5f, 0x48, 0x2f, 0xfa, 0x7c, 0x56, 0xc9, 0xf9, 0x92, + 0xd9, 0x39, 0x78, 0x20, 0xcd, 0x1d, 0xc1, 0x12, 0xc1, 0x12, 0xd1, 0xba, 0x98, 0x80, 0x2b, 0xc1, + 0xf1, 0x42, 0x38, 0x19, 0xf1, 0x7d, 0xd8, 0x0a, 0xa5, 0x8d, 0xf0, 0x1a, 0x4c, 0x34, 0xb4, 0x9a, + 0x0a, 0x14, 0x84, 0x91, 0xef, 0xa6, 0xb4, 0x6d, 0x2b, 0x00, 0x35, 0x95, 0x5c, 0x58, 0xcd, 0x85, + 0x31, 0xe0, 0x43, 0xc4, 0xa5, 0xaf, 0x79, 0x6d, 0x2f, 0x25, 0x8d, 0x74, 0x3a, 0x31, 0x79, 0x13, + 0x93, 0xd1, 0xee, 0xcf, 0xb7, 0x62, 0x2e, 0x4a, 0x37, 0x96, 0xb3, 0x4a, 0x3a, 0x9f, 0x8f, 0xf0, + 0xc9, 0xaf, 0xad, 0xb3, 0x60, 0x9c, 0x24, 0xd7, 0xb8, 0x61, 0xc3, 0xe6, 0x14, 0x75, 0x50, 0xf7, + 0x78, 0xd0, 0x66, 0x75, 0x77, 0xb1, 0x98, 0x1a, 0x1e, 0xae, 0x3e, 0xcf, 0xb2, 0x71, 0x4a, 0x0c, + 0x5e, 0x10, 0x3e, 0x0a, 0x4e, 0xf2, 0x8c, 0x70, 0x23, 0x22, 0xa4, 0x5b, 0x2f, 0xf8, 0xdb, 0xa8, + 0xd5, 0xfb, 0x07, 0x19, 0x5b, 0xe6, 0xe7, 0x4f, 0xef, 0xdf, 0x6f, 0x07, 0x94, 0xb4, 0x79, 0xed, + 0xfd, 0xb1, 0xcf, 0xf0, 0x66, 0xb5, 0xa1, 0x68, 0xbd, 0xa1, 0xe8, 0x6b, 0x43, 0xd1, 0xeb, 0x96, + 0x66, 0xeb, 0x2d, 0xcd, 0x3e, 0xb6, 0x34, 0xbb, 0xeb, 0x29, 0xed, 0xef, 0xab, 0x82, 0x4d, 0xa0, + 0x0c, 0x86, 0xcb, 0xa9, 0x28, 0x5c, 0x74, 0x3d, 0xee, 0x6d, 0x7e, 0x69, 0xa5, 0x2b, 0x1a, 0xe1, + 0x11, 0xaf, 0x7e, 0x02, 0x00, 0x00, 0xff, 0xff, 0xed, 0x5d, 0x65, 0xb1, 0x02, 0x02, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -322,8 +156,6 @@ const _ = grpc.SupportPackageIsVersion4 type QueryClient interface { // Params queries all parameters of the evmutil module. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) - // DeployedCosmosCoinContracts queries a list cosmos coin denom and their deployed erc20 address - DeployedCosmosCoinContracts(ctx context.Context, in *QueryDeployedCosmosCoinContractsRequest, opts ...grpc.CallOption) (*QueryDeployedCosmosCoinContractsResponse, error) } type queryClient struct { @@ -343,21 +175,10 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . return out, nil } -func (c *queryClient) DeployedCosmosCoinContracts(ctx context.Context, in *QueryDeployedCosmosCoinContractsRequest, opts ...grpc.CallOption) (*QueryDeployedCosmosCoinContractsResponse, error) { - out := new(QueryDeployedCosmosCoinContractsResponse) - err := c.cc.Invoke(ctx, "/kava.evmutil.v1beta1.Query/DeployedCosmosCoinContracts", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - // QueryServer is the server API for Query service. type QueryServer interface { // Params queries all parameters of the evmutil module. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) - // DeployedCosmosCoinContracts queries a list cosmos coin denom and their deployed erc20 address - DeployedCosmosCoinContracts(context.Context, *QueryDeployedCosmosCoinContractsRequest) (*QueryDeployedCosmosCoinContractsResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -367,9 +188,6 @@ type UnimplementedQueryServer struct { func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") } -func (*UnimplementedQueryServer) DeployedCosmosCoinContracts(ctx context.Context, req *QueryDeployedCosmosCoinContractsRequest) (*QueryDeployedCosmosCoinContractsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeployedCosmosCoinContracts not implemented") -} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -393,24 +211,6 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf return interceptor(ctx, in, info, handler) } -func _Query_DeployedCosmosCoinContracts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryDeployedCosmosCoinContractsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServer).DeployedCosmosCoinContracts(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/kava.evmutil.v1beta1.Query/DeployedCosmosCoinContracts", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).DeployedCosmosCoinContracts(ctx, req.(*QueryDeployedCosmosCoinContractsRequest)) - } - return interceptor(ctx, in, info, handler) -} - var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "kava.evmutil.v1beta1.Query", HandlerType: (*QueryServer)(nil), @@ -419,10 +219,6 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "Params", Handler: _Query_Params_Handler, }, - { - MethodName: "DeployedCosmosCoinContracts", - Handler: _Query_DeployedCosmosCoinContracts_Handler, - }, }, Streams: []grpc.StreamDesc{}, Metadata: "kava/evmutil/v1beta1/query.proto", @@ -484,141 +280,6 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *QueryDeployedCosmosCoinContractsRequest) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryDeployedCosmosCoinContractsRequest) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryDeployedCosmosCoinContractsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Pagination != nil { - { - size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if len(m.CosmosDenoms) > 0 { - for iNdEx := len(m.CosmosDenoms) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.CosmosDenoms[iNdEx]) - copy(dAtA[i:], m.CosmosDenoms[iNdEx]) - i = encodeVarintQuery(dAtA, i, uint64(len(m.CosmosDenoms[iNdEx]))) - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *QueryDeployedCosmosCoinContractsResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryDeployedCosmosCoinContractsResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryDeployedCosmosCoinContractsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Pagination != nil { - { - size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if len(m.DeployedCosmosCoinContracts) > 0 { - for iNdEx := len(m.DeployedCosmosCoinContracts) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.DeployedCosmosCoinContracts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *DeployedCosmosCoinContract) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *DeployedCosmosCoinContract) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *DeployedCosmosCoinContract) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Address != nil { - { - size := m.Address.Size() - i -= size - if _, err := m.Address.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintQuery(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - if len(m.CosmosDenom) > 0 { - i -= len(m.CosmosDenom) - copy(dAtA[i:], m.CosmosDenom) - i = encodeVarintQuery(dAtA, i, uint64(len(m.CosmosDenom))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -650,61 +311,6 @@ func (m *QueryParamsResponse) Size() (n int) { return n } -func (m *QueryDeployedCosmosCoinContractsRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.CosmosDenoms) > 0 { - for _, s := range m.CosmosDenoms { - l = len(s) - n += 1 + l + sovQuery(uint64(l)) - } - } - if m.Pagination != nil { - l = m.Pagination.Size() - n += 1 + l + sovQuery(uint64(l)) - } - return n -} - -func (m *QueryDeployedCosmosCoinContractsResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.DeployedCosmosCoinContracts) > 0 { - for _, e := range m.DeployedCosmosCoinContracts { - l = e.Size() - n += 1 + l + sovQuery(uint64(l)) - } - } - if m.Pagination != nil { - l = m.Pagination.Size() - n += 1 + l + sovQuery(uint64(l)) - } - return n -} - -func (m *DeployedCosmosCoinContract) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.CosmosDenom) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } - if m.Address != nil { - l = m.Address.Size() - n += 1 + l + sovQuery(uint64(l)) - } - return n -} - func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -844,362 +450,6 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryDeployedCosmosCoinContractsRequest) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryDeployedCosmosCoinContractsRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryDeployedCosmosCoinContractsRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CosmosDenoms", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.CosmosDenoms = append(m.CosmosDenoms, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Pagination == nil { - m.Pagination = &query.PageRequest{} - } - if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *QueryDeployedCosmosCoinContractsResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryDeployedCosmosCoinContractsResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryDeployedCosmosCoinContractsResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DeployedCosmosCoinContracts", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DeployedCosmosCoinContracts = append(m.DeployedCosmosCoinContracts, DeployedCosmosCoinContract{}) - if err := m.DeployedCosmosCoinContracts[len(m.DeployedCosmosCoinContracts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Pagination == nil { - m.Pagination = &query.PageResponse{} - } - if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DeployedCosmosCoinContract) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DeployedCosmosCoinContract: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DeployedCosmosCoinContract: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CosmosDenom", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.CosmosDenom = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v InternalEVMAddress - m.Address = &v - if err := m.Address.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/evmutil/types/query.pb.gw.go b/x/evmutil/types/query.pb.gw.go index e9c9b1df..03ae0664 100644 --- a/x/evmutil/types/query.pb.gw.go +++ b/x/evmutil/types/query.pb.gw.go @@ -51,42 +51,6 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } -var ( - filter_Query_DeployedCosmosCoinContracts_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} -) - -func request_Query_DeployedCosmosCoinContracts_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryDeployedCosmosCoinContractsRequest - var metadata runtime.ServerMetadata - - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_DeployedCosmosCoinContracts_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := client.DeployedCosmosCoinContracts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_Query_DeployedCosmosCoinContracts_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryDeployedCosmosCoinContractsRequest - var metadata runtime.ServerMetadata - - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_DeployedCosmosCoinContracts_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.DeployedCosmosCoinContracts(ctx, &protoReq) - return msg, metadata, err - -} - // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -116,29 +80,6 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_DeployedCosmosCoinContracts_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_Query_DeployedCosmosCoinContracts_0(rctx, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_DeployedCosmosCoinContracts_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - return nil } @@ -200,37 +141,13 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_DeployedCosmosCoinContracts_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_Query_DeployedCosmosCoinContracts_0(rctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_DeployedCosmosCoinContracts_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - return nil } var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "evmutil", "v1beta1", "params"}, "", runtime.AssumeColonVerbOpt(false))) - - pattern_Query_DeployedCosmosCoinContracts_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "evmutil", "v1beta1", "deployed_cosmos_coin_contracts"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( forward_Query_Params_0 = runtime.ForwardResponseMessage - - forward_Query_DeployedCosmosCoinContracts_0 = runtime.ForwardResponseMessage ) diff --git a/x/evmutil/types/tx.pb.go b/x/evmutil/types/tx.pb.go deleted file mode 100644 index d4aa63b4..00000000 --- a/x/evmutil/types/tx.pb.go +++ /dev/null @@ -1,2497 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: kava/evmutil/v1beta1/tx.proto - -package types - -import ( - context "context" - fmt "fmt" - _ "github.com/cosmos/cosmos-proto" - github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" - types "github.com/cosmos/cosmos-sdk/types" - _ "github.com/cosmos/gogoproto/gogoproto" - grpc1 "github.com/cosmos/gogoproto/grpc" - proto "github.com/cosmos/gogoproto/proto" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - io "io" - math "math" - math_bits "math/bits" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -// MsgConvertCoinToERC20 defines a conversion from sdk.Coin to Kava ERC20 for EVM-native assets. -type MsgConvertCoinToERC20 struct { - // Kava bech32 address initiating the conversion. - Initiator string `protobuf:"bytes,1,opt,name=initiator,proto3" json:"initiator,omitempty"` - // EVM 0x hex address that will receive the converted Kava ERC20 tokens. - Receiver string `protobuf:"bytes,2,opt,name=receiver,proto3" json:"receiver,omitempty"` - // Amount is the sdk.Coin amount to convert. - Amount *types.Coin `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` -} - -func (m *MsgConvertCoinToERC20) Reset() { *m = MsgConvertCoinToERC20{} } -func (m *MsgConvertCoinToERC20) String() string { return proto.CompactTextString(m) } -func (*MsgConvertCoinToERC20) ProtoMessage() {} -func (*MsgConvertCoinToERC20) Descriptor() ([]byte, []int) { - return fileDescriptor_6e82783c6c58f89c, []int{0} -} -func (m *MsgConvertCoinToERC20) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgConvertCoinToERC20) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgConvertCoinToERC20.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgConvertCoinToERC20) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgConvertCoinToERC20.Merge(m, src) -} -func (m *MsgConvertCoinToERC20) XXX_Size() int { - return m.Size() -} -func (m *MsgConvertCoinToERC20) XXX_DiscardUnknown() { - xxx_messageInfo_MsgConvertCoinToERC20.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgConvertCoinToERC20 proto.InternalMessageInfo - -func (m *MsgConvertCoinToERC20) GetInitiator() string { - if m != nil { - return m.Initiator - } - return "" -} - -func (m *MsgConvertCoinToERC20) GetReceiver() string { - if m != nil { - return m.Receiver - } - return "" -} - -func (m *MsgConvertCoinToERC20) GetAmount() *types.Coin { - if m != nil { - return m.Amount - } - return nil -} - -// MsgConvertCoinToERC20Response defines the response value from Msg/ConvertCoinToERC20. -type MsgConvertCoinToERC20Response struct { -} - -func (m *MsgConvertCoinToERC20Response) Reset() { *m = MsgConvertCoinToERC20Response{} } -func (m *MsgConvertCoinToERC20Response) String() string { return proto.CompactTextString(m) } -func (*MsgConvertCoinToERC20Response) ProtoMessage() {} -func (*MsgConvertCoinToERC20Response) Descriptor() ([]byte, []int) { - return fileDescriptor_6e82783c6c58f89c, []int{1} -} -func (m *MsgConvertCoinToERC20Response) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgConvertCoinToERC20Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgConvertCoinToERC20Response.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgConvertCoinToERC20Response) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgConvertCoinToERC20Response.Merge(m, src) -} -func (m *MsgConvertCoinToERC20Response) XXX_Size() int { - return m.Size() -} -func (m *MsgConvertCoinToERC20Response) XXX_DiscardUnknown() { - xxx_messageInfo_MsgConvertCoinToERC20Response.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgConvertCoinToERC20Response proto.InternalMessageInfo - -// MsgConvertERC20ToCoin defines a conversion from Kava ERC20 to sdk.Coin for EVM-native assets. -type MsgConvertERC20ToCoin struct { - // EVM 0x hex address initiating the conversion. - Initiator string `protobuf:"bytes,1,opt,name=initiator,proto3" json:"initiator,omitempty"` - // Kava bech32 address that will receive the converted sdk.Coin. - Receiver string `protobuf:"bytes,2,opt,name=receiver,proto3" json:"receiver,omitempty"` - // EVM 0x hex address of the ERC20 contract. - KavaERC20Address string `protobuf:"bytes,3,opt,name=kava_erc20_address,json=kavaErc20Address,proto3" json:"kava_erc20_address,omitempty"` - // ERC20 token amount to convert. - Amount github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"amount"` -} - -func (m *MsgConvertERC20ToCoin) Reset() { *m = MsgConvertERC20ToCoin{} } -func (m *MsgConvertERC20ToCoin) String() string { return proto.CompactTextString(m) } -func (*MsgConvertERC20ToCoin) ProtoMessage() {} -func (*MsgConvertERC20ToCoin) Descriptor() ([]byte, []int) { - return fileDescriptor_6e82783c6c58f89c, []int{2} -} -func (m *MsgConvertERC20ToCoin) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgConvertERC20ToCoin) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgConvertERC20ToCoin.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgConvertERC20ToCoin) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgConvertERC20ToCoin.Merge(m, src) -} -func (m *MsgConvertERC20ToCoin) XXX_Size() int { - return m.Size() -} -func (m *MsgConvertERC20ToCoin) XXX_DiscardUnknown() { - xxx_messageInfo_MsgConvertERC20ToCoin.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgConvertERC20ToCoin proto.InternalMessageInfo - -func (m *MsgConvertERC20ToCoin) GetInitiator() string { - if m != nil { - return m.Initiator - } - return "" -} - -func (m *MsgConvertERC20ToCoin) GetReceiver() string { - if m != nil { - return m.Receiver - } - return "" -} - -func (m *MsgConvertERC20ToCoin) GetKavaERC20Address() string { - if m != nil { - return m.KavaERC20Address - } - return "" -} - -// MsgConvertERC20ToCoinResponse defines the response value from -// Msg/MsgConvertERC20ToCoin. -type MsgConvertERC20ToCoinResponse struct { -} - -func (m *MsgConvertERC20ToCoinResponse) Reset() { *m = MsgConvertERC20ToCoinResponse{} } -func (m *MsgConvertERC20ToCoinResponse) String() string { return proto.CompactTextString(m) } -func (*MsgConvertERC20ToCoinResponse) ProtoMessage() {} -func (*MsgConvertERC20ToCoinResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_6e82783c6c58f89c, []int{3} -} -func (m *MsgConvertERC20ToCoinResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgConvertERC20ToCoinResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgConvertERC20ToCoinResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgConvertERC20ToCoinResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgConvertERC20ToCoinResponse.Merge(m, src) -} -func (m *MsgConvertERC20ToCoinResponse) XXX_Size() int { - return m.Size() -} -func (m *MsgConvertERC20ToCoinResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgConvertERC20ToCoinResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgConvertERC20ToCoinResponse proto.InternalMessageInfo - -// MsgConvertCosmosCoinToERC20 defines a conversion from cosmos sdk.Coin to ERC20 for cosmos-native assets. -type MsgConvertCosmosCoinToERC20 struct { - // Kava bech32 address initiating the conversion. - Initiator string `protobuf:"bytes,1,opt,name=initiator,proto3" json:"initiator,omitempty"` - // EVM hex address that will receive the ERC20 tokens. - Receiver string `protobuf:"bytes,2,opt,name=receiver,proto3" json:"receiver,omitempty"` - // Amount is the sdk.Coin amount to convert. - Amount *types.Coin `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` -} - -func (m *MsgConvertCosmosCoinToERC20) Reset() { *m = MsgConvertCosmosCoinToERC20{} } -func (m *MsgConvertCosmosCoinToERC20) String() string { return proto.CompactTextString(m) } -func (*MsgConvertCosmosCoinToERC20) ProtoMessage() {} -func (*MsgConvertCosmosCoinToERC20) Descriptor() ([]byte, []int) { - return fileDescriptor_6e82783c6c58f89c, []int{4} -} -func (m *MsgConvertCosmosCoinToERC20) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgConvertCosmosCoinToERC20) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgConvertCosmosCoinToERC20.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgConvertCosmosCoinToERC20) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgConvertCosmosCoinToERC20.Merge(m, src) -} -func (m *MsgConvertCosmosCoinToERC20) XXX_Size() int { - return m.Size() -} -func (m *MsgConvertCosmosCoinToERC20) XXX_DiscardUnknown() { - xxx_messageInfo_MsgConvertCosmosCoinToERC20.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgConvertCosmosCoinToERC20 proto.InternalMessageInfo - -func (m *MsgConvertCosmosCoinToERC20) GetInitiator() string { - if m != nil { - return m.Initiator - } - return "" -} - -func (m *MsgConvertCosmosCoinToERC20) GetReceiver() string { - if m != nil { - return m.Receiver - } - return "" -} - -func (m *MsgConvertCosmosCoinToERC20) GetAmount() *types.Coin { - if m != nil { - return m.Amount - } - return nil -} - -// MsgConvertCosmosCoinToERC20Response defines the response value from Msg/MsgConvertCosmosCoinToERC20. -type MsgConvertCosmosCoinToERC20Response struct { -} - -func (m *MsgConvertCosmosCoinToERC20Response) Reset() { *m = MsgConvertCosmosCoinToERC20Response{} } -func (m *MsgConvertCosmosCoinToERC20Response) String() string { return proto.CompactTextString(m) } -func (*MsgConvertCosmosCoinToERC20Response) ProtoMessage() {} -func (*MsgConvertCosmosCoinToERC20Response) Descriptor() ([]byte, []int) { - return fileDescriptor_6e82783c6c58f89c, []int{5} -} -func (m *MsgConvertCosmosCoinToERC20Response) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgConvertCosmosCoinToERC20Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgConvertCosmosCoinToERC20Response.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgConvertCosmosCoinToERC20Response) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgConvertCosmosCoinToERC20Response.Merge(m, src) -} -func (m *MsgConvertCosmosCoinToERC20Response) XXX_Size() int { - return m.Size() -} -func (m *MsgConvertCosmosCoinToERC20Response) XXX_DiscardUnknown() { - xxx_messageInfo_MsgConvertCosmosCoinToERC20Response.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgConvertCosmosCoinToERC20Response proto.InternalMessageInfo - -// MsgConvertCosmosCoinFromERC20 defines a conversion from ERC20 to cosmos coins for cosmos-native assets. -type MsgConvertCosmosCoinFromERC20 struct { - // EVM hex address initiating the conversion. - Initiator string `protobuf:"bytes,1,opt,name=initiator,proto3" json:"initiator,omitempty"` - // Kava bech32 address that will receive the cosmos coins. - Receiver string `protobuf:"bytes,2,opt,name=receiver,proto3" json:"receiver,omitempty"` - // Amount is the amount to convert, expressed as a Cosmos coin. - Amount *types.Coin `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` -} - -func (m *MsgConvertCosmosCoinFromERC20) Reset() { *m = MsgConvertCosmosCoinFromERC20{} } -func (m *MsgConvertCosmosCoinFromERC20) String() string { return proto.CompactTextString(m) } -func (*MsgConvertCosmosCoinFromERC20) ProtoMessage() {} -func (*MsgConvertCosmosCoinFromERC20) Descriptor() ([]byte, []int) { - return fileDescriptor_6e82783c6c58f89c, []int{6} -} -func (m *MsgConvertCosmosCoinFromERC20) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgConvertCosmosCoinFromERC20) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgConvertCosmosCoinFromERC20.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgConvertCosmosCoinFromERC20) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgConvertCosmosCoinFromERC20.Merge(m, src) -} -func (m *MsgConvertCosmosCoinFromERC20) XXX_Size() int { - return m.Size() -} -func (m *MsgConvertCosmosCoinFromERC20) XXX_DiscardUnknown() { - xxx_messageInfo_MsgConvertCosmosCoinFromERC20.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgConvertCosmosCoinFromERC20 proto.InternalMessageInfo - -func (m *MsgConvertCosmosCoinFromERC20) GetInitiator() string { - if m != nil { - return m.Initiator - } - return "" -} - -func (m *MsgConvertCosmosCoinFromERC20) GetReceiver() string { - if m != nil { - return m.Receiver - } - return "" -} - -func (m *MsgConvertCosmosCoinFromERC20) GetAmount() *types.Coin { - if m != nil { - return m.Amount - } - return nil -} - -// MsgConvertCosmosCoinFromERC20Response defines the response value from Msg/MsgConvertCosmosCoinFromERC20. -type MsgConvertCosmosCoinFromERC20Response struct { -} - -func (m *MsgConvertCosmosCoinFromERC20Response) Reset() { *m = MsgConvertCosmosCoinFromERC20Response{} } -func (m *MsgConvertCosmosCoinFromERC20Response) String() string { return proto.CompactTextString(m) } -func (*MsgConvertCosmosCoinFromERC20Response) ProtoMessage() {} -func (*MsgConvertCosmosCoinFromERC20Response) Descriptor() ([]byte, []int) { - return fileDescriptor_6e82783c6c58f89c, []int{7} -} -func (m *MsgConvertCosmosCoinFromERC20Response) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MsgConvertCosmosCoinFromERC20Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgConvertCosmosCoinFromERC20Response.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MsgConvertCosmosCoinFromERC20Response) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgConvertCosmosCoinFromERC20Response.Merge(m, src) -} -func (m *MsgConvertCosmosCoinFromERC20Response) XXX_Size() int { - return m.Size() -} -func (m *MsgConvertCosmosCoinFromERC20Response) XXX_DiscardUnknown() { - xxx_messageInfo_MsgConvertCosmosCoinFromERC20Response.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgConvertCosmosCoinFromERC20Response proto.InternalMessageInfo - -func init() { - proto.RegisterType((*MsgConvertCoinToERC20)(nil), "kava.evmutil.v1beta1.MsgConvertCoinToERC20") - proto.RegisterType((*MsgConvertCoinToERC20Response)(nil), "kava.evmutil.v1beta1.MsgConvertCoinToERC20Response") - proto.RegisterType((*MsgConvertERC20ToCoin)(nil), "kava.evmutil.v1beta1.MsgConvertERC20ToCoin") - proto.RegisterType((*MsgConvertERC20ToCoinResponse)(nil), "kava.evmutil.v1beta1.MsgConvertERC20ToCoinResponse") - proto.RegisterType((*MsgConvertCosmosCoinToERC20)(nil), "kava.evmutil.v1beta1.MsgConvertCosmosCoinToERC20") - proto.RegisterType((*MsgConvertCosmosCoinToERC20Response)(nil), "kava.evmutil.v1beta1.MsgConvertCosmosCoinToERC20Response") - proto.RegisterType((*MsgConvertCosmosCoinFromERC20)(nil), "kava.evmutil.v1beta1.MsgConvertCosmosCoinFromERC20") - proto.RegisterType((*MsgConvertCosmosCoinFromERC20Response)(nil), "kava.evmutil.v1beta1.MsgConvertCosmosCoinFromERC20Response") -} - -func init() { proto.RegisterFile("kava/evmutil/v1beta1/tx.proto", fileDescriptor_6e82783c6c58f89c) } - -var fileDescriptor_6e82783c6c58f89c = []byte{ - // 559 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x55, 0x41, 0x6b, 0xd4, 0x4c, - 0x18, 0xde, 0x69, 0x4b, 0xf9, 0x76, 0xbe, 0x4b, 0x19, 0x56, 0x48, 0xa3, 0xcd, 0x96, 0x95, 0x6a, - 0x45, 0x36, 0xe9, 0xee, 0x8a, 0x20, 0x7a, 0x71, 0x97, 0x0a, 0xa5, 0xf4, 0x12, 0xf7, 0xe4, 0x65, - 0x99, 0x64, 0x87, 0x18, 0xda, 0x64, 0x96, 0xcc, 0x6c, 0xa8, 0x3f, 0x40, 0x10, 0x11, 0xd1, 0x3f, - 0xe0, 0xd9, 0x1f, 0xd0, 0x1f, 0xd1, 0x63, 0xe9, 0x49, 0x3c, 0x2c, 0x35, 0xfb, 0x47, 0x64, 0x92, - 0x49, 0x3a, 0xd6, 0x98, 0xb5, 0x22, 0x78, 0xda, 0xcc, 0xbc, 0xcf, 0xf3, 0xbe, 0xcf, 0xf3, 0xbe, - 0x33, 0xb3, 0x70, 0xe3, 0x10, 0xc7, 0xd8, 0x22, 0x71, 0x30, 0xe5, 0xfe, 0x91, 0x15, 0x77, 0x1c, - 0xc2, 0x71, 0xc7, 0xe2, 0xc7, 0xe6, 0x24, 0xa2, 0x9c, 0xa2, 0x86, 0x08, 0x9b, 0x32, 0x6c, 0xca, - 0xb0, 0x6e, 0xb8, 0x94, 0x05, 0x94, 0x59, 0x0e, 0x66, 0xa4, 0xe0, 0xb8, 0xd4, 0x0f, 0x33, 0x96, - 0xbe, 0x9e, 0xc5, 0x47, 0xe9, 0xca, 0xca, 0x16, 0x32, 0xd4, 0xf0, 0xa8, 0x47, 0xb3, 0x7d, 0xf1, - 0x95, 0xed, 0xb6, 0x3e, 0x01, 0x78, 0xe3, 0x80, 0x79, 0x03, 0x1a, 0xc6, 0x24, 0xe2, 0x03, 0xea, - 0x87, 0x43, 0xba, 0x6b, 0x0f, 0xba, 0x3b, 0xe8, 0x21, 0xac, 0xfb, 0xa1, 0xcf, 0x7d, 0xcc, 0x69, - 0xa4, 0x81, 0x4d, 0xb0, 0x5d, 0xef, 0x6b, 0xe7, 0x27, 0xed, 0x86, 0x4c, 0xfa, 0x74, 0x3c, 0x8e, - 0x08, 0x63, 0xcf, 0x79, 0xe4, 0x87, 0x9e, 0x7d, 0x09, 0x45, 0x3a, 0xfc, 0x2f, 0x22, 0x2e, 0xf1, - 0x63, 0x12, 0x69, 0x4b, 0x82, 0x66, 0x17, 0x6b, 0xd4, 0x81, 0xab, 0x38, 0xa0, 0xd3, 0x90, 0x6b, - 0xcb, 0x9b, 0x60, 0xfb, 0xff, 0xee, 0xba, 0x29, 0xb3, 0x09, 0x3f, 0xb9, 0x49, 0x53, 0xa8, 0xb0, - 0x25, 0xb0, 0xd5, 0x84, 0x1b, 0xa5, 0xfa, 0x6c, 0xc2, 0x26, 0x34, 0x64, 0xa4, 0xf5, 0x7a, 0x49, - 0x75, 0x90, 0xc6, 0x86, 0x54, 0x00, 0xd1, 0xad, 0x9f, 0x1c, 0xa8, 0x3a, 0x1f, 0x5c, 0xd5, 0x59, - 0x61, 0xef, 0xd2, 0x41, 0x1f, 0x22, 0x31, 0x98, 0x11, 0x89, 0xdc, 0xee, 0xce, 0x08, 0x67, 0xa8, - 0xd4, 0x4d, 0xbd, 0xdf, 0x48, 0x66, 0xcd, 0xb5, 0x7d, 0x1c, 0xe3, 0x54, 0x84, 0xcc, 0x60, 0xaf, - 0x09, 0xfc, 0xae, 0x80, 0xcb, 0x1d, 0x34, 0x2c, 0xba, 0xb0, 0x92, 0xf2, 0x9e, 0x9c, 0xce, 0x9a, - 0xb5, 0xaf, 0xb3, 0xe6, 0x1d, 0xcf, 0xe7, 0x2f, 0xa7, 0x8e, 0xe9, 0xd2, 0x40, 0x8e, 0x4e, 0xfe, - 0xb4, 0xd9, 0xf8, 0xd0, 0xe2, 0xaf, 0x26, 0x84, 0x99, 0x7b, 0x21, 0x3f, 0x3f, 0x69, 0x43, 0xa9, - 0x72, 0x2f, 0xe4, 0xe5, 0x8d, 0x52, 0xda, 0x50, 0x34, 0xea, 0x2d, 0x80, 0x37, 0xd5, 0x56, 0x8a, - 0x0c, 0xea, 0xc0, 0xab, 0xdb, 0xf5, 0x97, 0xc7, 0xba, 0x05, 0x6f, 0x57, 0x68, 0x29, 0x34, 0xbf, - 0x03, 0x3f, 0x8e, 0x3f, 0xc7, 0x3d, 0x8b, 0x68, 0xf0, 0x0f, 0x54, 0xdf, 0x85, 0x5b, 0x95, 0x6a, - 0x72, 0xdd, 0xdd, 0x8f, 0x2b, 0x70, 0xf9, 0x80, 0x79, 0x28, 0x86, 0xa8, 0xe4, 0x6a, 0xdd, 0x37, - 0xcb, 0x2e, 0xb7, 0x59, 0x7a, 0xce, 0xf5, 0xde, 0x35, 0xc0, 0x79, 0x7d, 0xa5, 0xae, 0x7a, 0x21, - 0x16, 0xd6, 0x55, 0xc0, 0x8b, 0xeb, 0x96, 0x9c, 0x31, 0xf4, 0x06, 0x40, 0xed, 0x97, 0x07, 0xac, - 0xb3, 0xd8, 0xc9, 0x15, 0x8a, 0xfe, 0xe8, 0xda, 0x94, 0x42, 0xca, 0x7b, 0x00, 0xf5, 0x8a, 0x73, - 0xd3, 0xfb, 0xfd, 0xcc, 0x05, 0x49, 0x7f, 0xfc, 0x07, 0xa4, 0x5c, 0x50, 0x7f, 0xff, 0xe2, 0x9b, - 0x01, 0x3e, 0x27, 0x06, 0x38, 0x4d, 0x0c, 0x70, 0x96, 0x18, 0xe0, 0x22, 0x31, 0xc0, 0x87, 0xb9, - 0x51, 0x3b, 0x9b, 0x1b, 0xb5, 0x2f, 0x73, 0xa3, 0xf6, 0xe2, 0x9e, 0xf2, 0x00, 0x88, 0x42, 0xed, - 0x23, 0xec, 0xb0, 0xf4, 0xcb, 0x3a, 0x2e, 0xfe, 0x29, 0xd2, 0x77, 0xc0, 0x59, 0x4d, 0x9f, 0xef, - 0xde, 0xf7, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa2, 0x5a, 0x1f, 0x90, 0x46, 0x06, 0x00, 0x00, -} - -func (this *MsgConvertCoinToERC20) VerboseEqual(that interface{}) error { - if that == nil { - if this == nil { - return nil - } - return fmt.Errorf("that == nil && this != nil") - } - - that1, ok := that.(*MsgConvertCoinToERC20) - if !ok { - that2, ok := that.(MsgConvertCoinToERC20) - if ok { - that1 = &that2 - } else { - return fmt.Errorf("that is not of type *MsgConvertCoinToERC20") - } - } - if that1 == nil { - if this == nil { - return nil - } - return fmt.Errorf("that is type *MsgConvertCoinToERC20 but is nil && this != nil") - } else if this == nil { - return fmt.Errorf("that is type *MsgConvertCoinToERC20 but is not nil && this == nil") - } - if this.Initiator != that1.Initiator { - return fmt.Errorf("Initiator this(%v) Not Equal that(%v)", this.Initiator, that1.Initiator) - } - if this.Receiver != that1.Receiver { - return fmt.Errorf("Receiver this(%v) Not Equal that(%v)", this.Receiver, that1.Receiver) - } - if !this.Amount.Equal(that1.Amount) { - return fmt.Errorf("Amount this(%v) Not Equal that(%v)", this.Amount, that1.Amount) - } - return nil -} -func (this *MsgConvertCoinToERC20) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*MsgConvertCoinToERC20) - if !ok { - that2, ok := that.(MsgConvertCoinToERC20) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - if this.Initiator != that1.Initiator { - return false - } - if this.Receiver != that1.Receiver { - return false - } - if !this.Amount.Equal(that1.Amount) { - return false - } - return true -} -func (this *MsgConvertCoinToERC20Response) VerboseEqual(that interface{}) error { - if that == nil { - if this == nil { - return nil - } - return fmt.Errorf("that == nil && this != nil") - } - - that1, ok := that.(*MsgConvertCoinToERC20Response) - if !ok { - that2, ok := that.(MsgConvertCoinToERC20Response) - if ok { - that1 = &that2 - } else { - return fmt.Errorf("that is not of type *MsgConvertCoinToERC20Response") - } - } - if that1 == nil { - if this == nil { - return nil - } - return fmt.Errorf("that is type *MsgConvertCoinToERC20Response but is nil && this != nil") - } else if this == nil { - return fmt.Errorf("that is type *MsgConvertCoinToERC20Response but is not nil && this == nil") - } - return nil -} -func (this *MsgConvertCoinToERC20Response) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*MsgConvertCoinToERC20Response) - if !ok { - that2, ok := that.(MsgConvertCoinToERC20Response) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - return true -} -func (this *MsgConvertERC20ToCoin) VerboseEqual(that interface{}) error { - if that == nil { - if this == nil { - return nil - } - return fmt.Errorf("that == nil && this != nil") - } - - that1, ok := that.(*MsgConvertERC20ToCoin) - if !ok { - that2, ok := that.(MsgConvertERC20ToCoin) - if ok { - that1 = &that2 - } else { - return fmt.Errorf("that is not of type *MsgConvertERC20ToCoin") - } - } - if that1 == nil { - if this == nil { - return nil - } - return fmt.Errorf("that is type *MsgConvertERC20ToCoin but is nil && this != nil") - } else if this == nil { - return fmt.Errorf("that is type *MsgConvertERC20ToCoin but is not nil && this == nil") - } - if this.Initiator != that1.Initiator { - return fmt.Errorf("Initiator this(%v) Not Equal that(%v)", this.Initiator, that1.Initiator) - } - if this.Receiver != that1.Receiver { - return fmt.Errorf("Receiver this(%v) Not Equal that(%v)", this.Receiver, that1.Receiver) - } - if this.KavaERC20Address != that1.KavaERC20Address { - return fmt.Errorf("KavaERC20Address this(%v) Not Equal that(%v)", this.KavaERC20Address, that1.KavaERC20Address) - } - if !this.Amount.Equal(that1.Amount) { - return fmt.Errorf("Amount this(%v) Not Equal that(%v)", this.Amount, that1.Amount) - } - return nil -} -func (this *MsgConvertERC20ToCoin) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*MsgConvertERC20ToCoin) - if !ok { - that2, ok := that.(MsgConvertERC20ToCoin) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - if this.Initiator != that1.Initiator { - return false - } - if this.Receiver != that1.Receiver { - return false - } - if this.KavaERC20Address != that1.KavaERC20Address { - return false - } - if !this.Amount.Equal(that1.Amount) { - return false - } - return true -} -func (this *MsgConvertERC20ToCoinResponse) VerboseEqual(that interface{}) error { - if that == nil { - if this == nil { - return nil - } - return fmt.Errorf("that == nil && this != nil") - } - - that1, ok := that.(*MsgConvertERC20ToCoinResponse) - if !ok { - that2, ok := that.(MsgConvertERC20ToCoinResponse) - if ok { - that1 = &that2 - } else { - return fmt.Errorf("that is not of type *MsgConvertERC20ToCoinResponse") - } - } - if that1 == nil { - if this == nil { - return nil - } - return fmt.Errorf("that is type *MsgConvertERC20ToCoinResponse but is nil && this != nil") - } else if this == nil { - return fmt.Errorf("that is type *MsgConvertERC20ToCoinResponse but is not nil && this == nil") - } - return nil -} -func (this *MsgConvertERC20ToCoinResponse) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*MsgConvertERC20ToCoinResponse) - if !ok { - that2, ok := that.(MsgConvertERC20ToCoinResponse) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - return true -} -func (this *MsgConvertCosmosCoinToERC20) VerboseEqual(that interface{}) error { - if that == nil { - if this == nil { - return nil - } - return fmt.Errorf("that == nil && this != nil") - } - - that1, ok := that.(*MsgConvertCosmosCoinToERC20) - if !ok { - that2, ok := that.(MsgConvertCosmosCoinToERC20) - if ok { - that1 = &that2 - } else { - return fmt.Errorf("that is not of type *MsgConvertCosmosCoinToERC20") - } - } - if that1 == nil { - if this == nil { - return nil - } - return fmt.Errorf("that is type *MsgConvertCosmosCoinToERC20 but is nil && this != nil") - } else if this == nil { - return fmt.Errorf("that is type *MsgConvertCosmosCoinToERC20 but is not nil && this == nil") - } - if this.Initiator != that1.Initiator { - return fmt.Errorf("Initiator this(%v) Not Equal that(%v)", this.Initiator, that1.Initiator) - } - if this.Receiver != that1.Receiver { - return fmt.Errorf("Receiver this(%v) Not Equal that(%v)", this.Receiver, that1.Receiver) - } - if !this.Amount.Equal(that1.Amount) { - return fmt.Errorf("Amount this(%v) Not Equal that(%v)", this.Amount, that1.Amount) - } - return nil -} -func (this *MsgConvertCosmosCoinToERC20) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*MsgConvertCosmosCoinToERC20) - if !ok { - that2, ok := that.(MsgConvertCosmosCoinToERC20) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - if this.Initiator != that1.Initiator { - return false - } - if this.Receiver != that1.Receiver { - return false - } - if !this.Amount.Equal(that1.Amount) { - return false - } - return true -} -func (this *MsgConvertCosmosCoinToERC20Response) VerboseEqual(that interface{}) error { - if that == nil { - if this == nil { - return nil - } - return fmt.Errorf("that == nil && this != nil") - } - - that1, ok := that.(*MsgConvertCosmosCoinToERC20Response) - if !ok { - that2, ok := that.(MsgConvertCosmosCoinToERC20Response) - if ok { - that1 = &that2 - } else { - return fmt.Errorf("that is not of type *MsgConvertCosmosCoinToERC20Response") - } - } - if that1 == nil { - if this == nil { - return nil - } - return fmt.Errorf("that is type *MsgConvertCosmosCoinToERC20Response but is nil && this != nil") - } else if this == nil { - return fmt.Errorf("that is type *MsgConvertCosmosCoinToERC20Response but is not nil && this == nil") - } - return nil -} -func (this *MsgConvertCosmosCoinToERC20Response) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*MsgConvertCosmosCoinToERC20Response) - if !ok { - that2, ok := that.(MsgConvertCosmosCoinToERC20Response) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - return true -} -func (this *MsgConvertCosmosCoinFromERC20) VerboseEqual(that interface{}) error { - if that == nil { - if this == nil { - return nil - } - return fmt.Errorf("that == nil && this != nil") - } - - that1, ok := that.(*MsgConvertCosmosCoinFromERC20) - if !ok { - that2, ok := that.(MsgConvertCosmosCoinFromERC20) - if ok { - that1 = &that2 - } else { - return fmt.Errorf("that is not of type *MsgConvertCosmosCoinFromERC20") - } - } - if that1 == nil { - if this == nil { - return nil - } - return fmt.Errorf("that is type *MsgConvertCosmosCoinFromERC20 but is nil && this != nil") - } else if this == nil { - return fmt.Errorf("that is type *MsgConvertCosmosCoinFromERC20 but is not nil && this == nil") - } - if this.Initiator != that1.Initiator { - return fmt.Errorf("Initiator this(%v) Not Equal that(%v)", this.Initiator, that1.Initiator) - } - if this.Receiver != that1.Receiver { - return fmt.Errorf("Receiver this(%v) Not Equal that(%v)", this.Receiver, that1.Receiver) - } - if !this.Amount.Equal(that1.Amount) { - return fmt.Errorf("Amount this(%v) Not Equal that(%v)", this.Amount, that1.Amount) - } - return nil -} -func (this *MsgConvertCosmosCoinFromERC20) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*MsgConvertCosmosCoinFromERC20) - if !ok { - that2, ok := that.(MsgConvertCosmosCoinFromERC20) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - if this.Initiator != that1.Initiator { - return false - } - if this.Receiver != that1.Receiver { - return false - } - if !this.Amount.Equal(that1.Amount) { - return false - } - return true -} -func (this *MsgConvertCosmosCoinFromERC20Response) VerboseEqual(that interface{}) error { - if that == nil { - if this == nil { - return nil - } - return fmt.Errorf("that == nil && this != nil") - } - - that1, ok := that.(*MsgConvertCosmosCoinFromERC20Response) - if !ok { - that2, ok := that.(MsgConvertCosmosCoinFromERC20Response) - if ok { - that1 = &that2 - } else { - return fmt.Errorf("that is not of type *MsgConvertCosmosCoinFromERC20Response") - } - } - if that1 == nil { - if this == nil { - return nil - } - return fmt.Errorf("that is type *MsgConvertCosmosCoinFromERC20Response but is nil && this != nil") - } else if this == nil { - return fmt.Errorf("that is type *MsgConvertCosmosCoinFromERC20Response but is not nil && this == nil") - } - return nil -} -func (this *MsgConvertCosmosCoinFromERC20Response) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*MsgConvertCosmosCoinFromERC20Response) - if !ok { - that2, ok := that.(MsgConvertCosmosCoinFromERC20Response) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - return true -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// MsgClient is the client API for Msg service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type MsgClient interface { - // ConvertCoinToERC20 defines a method for converting sdk.Coin to Kava ERC20. - ConvertCoinToERC20(ctx context.Context, in *MsgConvertCoinToERC20, opts ...grpc.CallOption) (*MsgConvertCoinToERC20Response, error) - // ConvertERC20ToCoin defines a method for converting Kava ERC20 to sdk.Coin. - ConvertERC20ToCoin(ctx context.Context, in *MsgConvertERC20ToCoin, opts ...grpc.CallOption) (*MsgConvertERC20ToCoinResponse, error) - // ConvertCosmosCoinToERC20 defines a method for converting a cosmos sdk.Coin to an ERC20. - ConvertCosmosCoinToERC20(ctx context.Context, in *MsgConvertCosmosCoinToERC20, opts ...grpc.CallOption) (*MsgConvertCosmosCoinToERC20Response, error) - // ConvertCosmosCoinFromERC20 defines a method for converting a cosmos sdk.Coin to an ERC20. - ConvertCosmosCoinFromERC20(ctx context.Context, in *MsgConvertCosmosCoinFromERC20, opts ...grpc.CallOption) (*MsgConvertCosmosCoinFromERC20Response, error) -} - -type msgClient struct { - cc grpc1.ClientConn -} - -func NewMsgClient(cc grpc1.ClientConn) MsgClient { - return &msgClient{cc} -} - -func (c *msgClient) ConvertCoinToERC20(ctx context.Context, in *MsgConvertCoinToERC20, opts ...grpc.CallOption) (*MsgConvertCoinToERC20Response, error) { - out := new(MsgConvertCoinToERC20Response) - err := c.cc.Invoke(ctx, "/kava.evmutil.v1beta1.Msg/ConvertCoinToERC20", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *msgClient) ConvertERC20ToCoin(ctx context.Context, in *MsgConvertERC20ToCoin, opts ...grpc.CallOption) (*MsgConvertERC20ToCoinResponse, error) { - out := new(MsgConvertERC20ToCoinResponse) - err := c.cc.Invoke(ctx, "/kava.evmutil.v1beta1.Msg/ConvertERC20ToCoin", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *msgClient) ConvertCosmosCoinToERC20(ctx context.Context, in *MsgConvertCosmosCoinToERC20, opts ...grpc.CallOption) (*MsgConvertCosmosCoinToERC20Response, error) { - out := new(MsgConvertCosmosCoinToERC20Response) - err := c.cc.Invoke(ctx, "/kava.evmutil.v1beta1.Msg/ConvertCosmosCoinToERC20", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *msgClient) ConvertCosmosCoinFromERC20(ctx context.Context, in *MsgConvertCosmosCoinFromERC20, opts ...grpc.CallOption) (*MsgConvertCosmosCoinFromERC20Response, error) { - out := new(MsgConvertCosmosCoinFromERC20Response) - err := c.cc.Invoke(ctx, "/kava.evmutil.v1beta1.Msg/ConvertCosmosCoinFromERC20", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// MsgServer is the server API for Msg service. -type MsgServer interface { - // ConvertCoinToERC20 defines a method for converting sdk.Coin to Kava ERC20. - ConvertCoinToERC20(context.Context, *MsgConvertCoinToERC20) (*MsgConvertCoinToERC20Response, error) - // ConvertERC20ToCoin defines a method for converting Kava ERC20 to sdk.Coin. - ConvertERC20ToCoin(context.Context, *MsgConvertERC20ToCoin) (*MsgConvertERC20ToCoinResponse, error) - // ConvertCosmosCoinToERC20 defines a method for converting a cosmos sdk.Coin to an ERC20. - ConvertCosmosCoinToERC20(context.Context, *MsgConvertCosmosCoinToERC20) (*MsgConvertCosmosCoinToERC20Response, error) - // ConvertCosmosCoinFromERC20 defines a method for converting a cosmos sdk.Coin to an ERC20. - ConvertCosmosCoinFromERC20(context.Context, *MsgConvertCosmosCoinFromERC20) (*MsgConvertCosmosCoinFromERC20Response, error) -} - -// UnimplementedMsgServer can be embedded to have forward compatible implementations. -type UnimplementedMsgServer struct { -} - -func (*UnimplementedMsgServer) ConvertCoinToERC20(ctx context.Context, req *MsgConvertCoinToERC20) (*MsgConvertCoinToERC20Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method ConvertCoinToERC20 not implemented") -} -func (*UnimplementedMsgServer) ConvertERC20ToCoin(ctx context.Context, req *MsgConvertERC20ToCoin) (*MsgConvertERC20ToCoinResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ConvertERC20ToCoin not implemented") -} -func (*UnimplementedMsgServer) ConvertCosmosCoinToERC20(ctx context.Context, req *MsgConvertCosmosCoinToERC20) (*MsgConvertCosmosCoinToERC20Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method ConvertCosmosCoinToERC20 not implemented") -} -func (*UnimplementedMsgServer) ConvertCosmosCoinFromERC20(ctx context.Context, req *MsgConvertCosmosCoinFromERC20) (*MsgConvertCosmosCoinFromERC20Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method ConvertCosmosCoinFromERC20 not implemented") -} - -func RegisterMsgServer(s grpc1.Server, srv MsgServer) { - s.RegisterService(&_Msg_serviceDesc, srv) -} - -func _Msg_ConvertCoinToERC20_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgConvertCoinToERC20) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).ConvertCoinToERC20(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/kava.evmutil.v1beta1.Msg/ConvertCoinToERC20", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).ConvertCoinToERC20(ctx, req.(*MsgConvertCoinToERC20)) - } - return interceptor(ctx, in, info, handler) -} - -func _Msg_ConvertERC20ToCoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgConvertERC20ToCoin) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).ConvertERC20ToCoin(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/kava.evmutil.v1beta1.Msg/ConvertERC20ToCoin", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).ConvertERC20ToCoin(ctx, req.(*MsgConvertERC20ToCoin)) - } - return interceptor(ctx, in, info, handler) -} - -func _Msg_ConvertCosmosCoinToERC20_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgConvertCosmosCoinToERC20) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).ConvertCosmosCoinToERC20(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/kava.evmutil.v1beta1.Msg/ConvertCosmosCoinToERC20", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).ConvertCosmosCoinToERC20(ctx, req.(*MsgConvertCosmosCoinToERC20)) - } - return interceptor(ctx, in, info, handler) -} - -func _Msg_ConvertCosmosCoinFromERC20_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgConvertCosmosCoinFromERC20) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).ConvertCosmosCoinFromERC20(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/kava.evmutil.v1beta1.Msg/ConvertCosmosCoinFromERC20", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).ConvertCosmosCoinFromERC20(ctx, req.(*MsgConvertCosmosCoinFromERC20)) - } - return interceptor(ctx, in, info, handler) -} - -var _Msg_serviceDesc = grpc.ServiceDesc{ - ServiceName: "kava.evmutil.v1beta1.Msg", - HandlerType: (*MsgServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "ConvertCoinToERC20", - Handler: _Msg_ConvertCoinToERC20_Handler, - }, - { - MethodName: "ConvertERC20ToCoin", - Handler: _Msg_ConvertERC20ToCoin_Handler, - }, - { - MethodName: "ConvertCosmosCoinToERC20", - Handler: _Msg_ConvertCosmosCoinToERC20_Handler, - }, - { - MethodName: "ConvertCosmosCoinFromERC20", - Handler: _Msg_ConvertCosmosCoinFromERC20_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "kava/evmutil/v1beta1/tx.proto", -} - -func (m *MsgConvertCoinToERC20) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgConvertCoinToERC20) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgConvertCoinToERC20) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Amount != nil { - { - size, err := m.Amount.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if len(m.Receiver) > 0 { - i -= len(m.Receiver) - copy(dAtA[i:], m.Receiver) - i = encodeVarintTx(dAtA, i, uint64(len(m.Receiver))) - i-- - dAtA[i] = 0x12 - } - if len(m.Initiator) > 0 { - i -= len(m.Initiator) - copy(dAtA[i:], m.Initiator) - i = encodeVarintTx(dAtA, i, uint64(len(m.Initiator))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgConvertCoinToERC20Response) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgConvertCoinToERC20Response) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgConvertCoinToERC20Response) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func (m *MsgConvertERC20ToCoin) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgConvertERC20ToCoin) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgConvertERC20ToCoin) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size := m.Amount.Size() - i -= size - if _, err := m.Amount.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x22 - if len(m.KavaERC20Address) > 0 { - i -= len(m.KavaERC20Address) - copy(dAtA[i:], m.KavaERC20Address) - i = encodeVarintTx(dAtA, i, uint64(len(m.KavaERC20Address))) - i-- - dAtA[i] = 0x1a - } - if len(m.Receiver) > 0 { - i -= len(m.Receiver) - copy(dAtA[i:], m.Receiver) - i = encodeVarintTx(dAtA, i, uint64(len(m.Receiver))) - i-- - dAtA[i] = 0x12 - } - if len(m.Initiator) > 0 { - i -= len(m.Initiator) - copy(dAtA[i:], m.Initiator) - i = encodeVarintTx(dAtA, i, uint64(len(m.Initiator))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgConvertERC20ToCoinResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgConvertERC20ToCoinResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgConvertERC20ToCoinResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func (m *MsgConvertCosmosCoinToERC20) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgConvertCosmosCoinToERC20) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgConvertCosmosCoinToERC20) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Amount != nil { - { - size, err := m.Amount.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if len(m.Receiver) > 0 { - i -= len(m.Receiver) - copy(dAtA[i:], m.Receiver) - i = encodeVarintTx(dAtA, i, uint64(len(m.Receiver))) - i-- - dAtA[i] = 0x12 - } - if len(m.Initiator) > 0 { - i -= len(m.Initiator) - copy(dAtA[i:], m.Initiator) - i = encodeVarintTx(dAtA, i, uint64(len(m.Initiator))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgConvertCosmosCoinToERC20Response) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgConvertCosmosCoinToERC20Response) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgConvertCosmosCoinToERC20Response) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func (m *MsgConvertCosmosCoinFromERC20) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgConvertCosmosCoinFromERC20) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgConvertCosmosCoinFromERC20) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Amount != nil { - { - size, err := m.Amount.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if len(m.Receiver) > 0 { - i -= len(m.Receiver) - copy(dAtA[i:], m.Receiver) - i = encodeVarintTx(dAtA, i, uint64(len(m.Receiver))) - i-- - dAtA[i] = 0x12 - } - if len(m.Initiator) > 0 { - i -= len(m.Initiator) - copy(dAtA[i:], m.Initiator) - i = encodeVarintTx(dAtA, i, uint64(len(m.Initiator))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgConvertCosmosCoinFromERC20Response) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgConvertCosmosCoinFromERC20Response) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgConvertCosmosCoinFromERC20Response) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func encodeVarintTx(dAtA []byte, offset int, v uint64) int { - offset -= sovTx(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *MsgConvertCoinToERC20) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Initiator) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.Receiver) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.Amount != nil { - l = m.Amount.Size() - n += 1 + l + sovTx(uint64(l)) - } - return n -} - -func (m *MsgConvertCoinToERC20Response) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func (m *MsgConvertERC20ToCoin) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Initiator) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.Receiver) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.KavaERC20Address) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = m.Amount.Size() - n += 1 + l + sovTx(uint64(l)) - return n -} - -func (m *MsgConvertERC20ToCoinResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func (m *MsgConvertCosmosCoinToERC20) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Initiator) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.Receiver) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.Amount != nil { - l = m.Amount.Size() - n += 1 + l + sovTx(uint64(l)) - } - return n -} - -func (m *MsgConvertCosmosCoinToERC20Response) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func (m *MsgConvertCosmosCoinFromERC20) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Initiator) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.Receiver) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.Amount != nil { - l = m.Amount.Size() - n += 1 + l + sovTx(uint64(l)) - } - return n -} - -func (m *MsgConvertCosmosCoinFromERC20Response) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func sovTx(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozTx(x uint64) (n int) { - return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *MsgConvertCoinToERC20) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgConvertCoinToERC20: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgConvertCoinToERC20: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Initiator", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Initiator = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Receiver", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Receiver = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Amount == nil { - m.Amount = &types.Coin{} - } - if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgConvertCoinToERC20Response) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgConvertCoinToERC20Response: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgConvertCoinToERC20Response: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgConvertERC20ToCoin) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgConvertERC20ToCoin: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgConvertERC20ToCoin: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Initiator", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Initiator = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Receiver", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Receiver = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field KavaERC20Address", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.KavaERC20Address = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgConvertERC20ToCoinResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgConvertERC20ToCoinResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgConvertERC20ToCoinResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgConvertCosmosCoinToERC20) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgConvertCosmosCoinToERC20: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgConvertCosmosCoinToERC20: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Initiator", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Initiator = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Receiver", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Receiver = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Amount == nil { - m.Amount = &types.Coin{} - } - if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgConvertCosmosCoinToERC20Response) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgConvertCosmosCoinToERC20Response: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgConvertCosmosCoinToERC20Response: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgConvertCosmosCoinFromERC20) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgConvertCosmosCoinFromERC20: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgConvertCosmosCoinFromERC20: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Initiator", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Initiator = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Receiver", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Receiver = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Amount == nil { - m.Amount = &types.Coin{} - } - if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MsgConvertCosmosCoinFromERC20Response) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgConvertCosmosCoinFromERC20Response: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgConvertCosmosCoinFromERC20Response: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipTx(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowTx - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowTx - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowTx - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthTx - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupTx - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthTx - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") -)