diff --git a/changelog.md b/changelog.md index 6992b9a706..b470d8a82f 100644 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,7 @@ * [3105](https://github.com/zeta-chain/node/pull/3105) - split Bitcoin E2E tests into two runners for deposit and withdraw * [3154](https://github.com/zeta-chain/node/pull/3154) - configure Solana gateway program id for E2E tests * [3188](https://github.com/zeta-chain/node/pull/3188) - add e2e test for v2 deposit and call with swap +* [3151](https://github.com/zeta-chain/node/pull/3151) - add withdraw emissions to e2e tests ### Refactor diff --git a/cmd/zetae2e/config/local.yml b/cmd/zetae2e/config/local.yml index aed44fc155..15de10ceda 100644 --- a/cmd/zetae2e/config/local.yml +++ b/cmd/zetae2e/config/local.yml @@ -65,6 +65,10 @@ additional_accounts: bech32_address: "zeta1nry9yeg6njhjrp2ctppa8558vqxal9fxk69zxg" evm_address: "0x98c852651A9CAF2185585843d3D287600Ddf9526" private_key: "bf9456c679bb5a952a9a137fcfc920e0413efdb97c36de1e57455763084230cb" + user_emissions_withdraw: + bech32_address: "zeta1n9zhyn4unvaee3ey40k7x7f5nmj7zet6qr5kl7" + evm_address: "0x9945724EBc9B3B9cc724abedE379349EE5E1657a" + private_key: "9d524fe318c0eb5f80d8b246993a9f15f924db24d4b8b873839b13bc30040d03" policy_accounts: emergency_policy_account: bech32_address: "zeta16m2cnrdwtgweq4njc6t470vl325gw4kp6s7tap" diff --git a/cmd/zetae2e/config/localnet.yml b/cmd/zetae2e/config/localnet.yml index 15f1e332f6..6e1c2b392c 100644 --- a/cmd/zetae2e/config/localnet.yml +++ b/cmd/zetae2e/config/localnet.yml @@ -65,6 +65,10 @@ additional_accounts: bech32_address: "zeta1nry9yeg6njhjrp2ctppa8558vqxal9fxk69zxg" evm_address: "0x98c852651A9CAF2185585843d3D287600Ddf9526" private_key: "bf9456c679bb5a952a9a137fcfc920e0413efdb97c36de1e57455763084230cb" + user_emissions_withdraw: + bech32_address: "zeta1n9zhyn4unvaee3ey40k7x7f5nmj7zet6qr5kl7" + evm_address: "0x9945724EBc9B3B9cc724abedE379349EE5E1657a" + private_key: "9d524fe318c0eb5f80d8b246993a9f15f924db24d4b8b873839b13bc30040d03" policy_accounts: emergency_policy_account: bech32_address: "zeta16m2cnrdwtgweq4njc6t470vl325gw4kp6s7tap" diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index a752c0e388..1feebca291 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -160,11 +160,17 @@ func localE2ETest(cmd *cobra.Command, _ []string) { zetaTxServer, err := txserver.NewZetaTxServer( conf.RPCs.ZetaCoreRPC, - []string{utils.EmergencyPolicyName, utils.OperationalPolicyName, utils.AdminPolicyName}, + []string{ + utils.EmergencyPolicyName, + utils.OperationalPolicyName, + utils.AdminPolicyName, + utils.UserEmissionsWithdrawName, + }, []string{ conf.PolicyAccounts.EmergencyPolicyAccount.RawPrivateKey.String(), conf.PolicyAccounts.OperationalPolicyAccount.RawPrivateKey.String(), conf.PolicyAccounts.AdminPolicyAccount.RawPrivateKey.String(), + conf.AdditionalAccounts.UserEmissionsWithdraw.RawPrivateKey.String(), }, conf.ZetaChainID, ) @@ -488,6 +494,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { logger.Print("❌ e2e tests failed after %s", time.Since(testStartTime).String()) os.Exit(1) } + noError(deployerRunner.WithdrawEmissions()) // if all tests pass, cancel txs priority monitoring and check if tx priority is not correct in some blocks logger.Print("⏳ e2e tests passed, checking tx priority") diff --git a/contrib/localnet/orchestrator/start-zetae2e.sh b/contrib/localnet/orchestrator/start-zetae2e.sh index 5d8c9c5586..a4156b07c3 100644 --- a/contrib/localnet/orchestrator/start-zetae2e.sh +++ b/contrib/localnet/orchestrator/start-zetae2e.sh @@ -134,6 +134,9 @@ fund_eth_from_config '.additional_accounts.user_v2_ether_revert.evm_address' 100 # unlock v2 erc20 revert tests accounts fund_eth_from_config '.additional_accounts.user_v2_erc20_revert.evm_address' 10000 "V2 ERC20 revert tester" +# unlock emissions withdraw tests accounts +fund_eth_from_config '.additional_accounts.user_emissions_withdraw.evm_address' 10000 "emissions withdraw tester" + # unlock local solana relayer accounts if host solana > /dev/null; then solana_url=$(config_str '.rpcs.solana') diff --git a/contrib/localnet/scripts/start-zetacored.sh b/contrib/localnet/scripts/start-zetacored.sh index 38047a6b46..56e30358c1 100755 --- a/contrib/localnet/scripts/start-zetacored.sh +++ b/contrib/localnet/scripts/start-zetacored.sh @@ -67,6 +67,55 @@ add_v17_message_authorizations() { ' $json_file > temp.json && mv temp.json $json_file } + +add_emissions_withdraw_authorizations() { + + config_file="/root/config.yml" + json_file="/root/.zetacored/config/genesis.json" + + # Check if config file exists + if [[ ! -f "$config_file" ]]; then + echo "Error: Config file not found at $config_file" + return 1 + fi + # Address to add emissions withdraw authorizations + address=$(yq -r '.additional_accounts.user_emissions_withdraw.bech32_address' "$config_file") + + # Check if genesis file exists + if [[ ! -f "$json_file" ]]; then + echo "Error: Genesis file not found at $json_file" + return 1 + fi + + echo "Adding emissions withdraw authorizations for address: $address" + + + # Using jq to parse JSON, create new entries, and append them to the authorization array + if ! jq --arg address "$address" ' + # Store the nodeAccountList array + .app_state.observer.nodeAccountList as $list | + # Iterate over the stored list to construct new objects and append to the authorization array + .app_state.authz.authorization += [ + $list[] | + { + "granter": .operator, + "grantee": $address, + "authorization": { + "@type": "/cosmos.authz.v1beta1.GenericAuthorization", + "msg": "/zetachain.zetacore.emissions.MsgWithdrawEmission" + }, + "expiration": null + } + ] + ' "$json_file" > temp.json; then + echo "Error: Failed to update genesis file" + return 1 + fi + mv temp.json "$json_file" +} + + + # create keys CHAINID="athens_101-1" KEYRING="test" @@ -191,6 +240,12 @@ then zetacored collect-observer-info zetacored add-observer-list --keygen-block 25 + # Add emissions withdraw authorizations + if ! add_emissions_withdraw_authorizations; then + echo "Error: Failed to add emissions withdraw authorizations" + exit 1 + fi + # Check for the existence of "AddToOutTxTracker" string in the genesis file # If this message is found in the genesis, it means add-observer-list has been run with the v16 binary for upgrade tests # In this case, we need to add authorizations for the new v17 messages to the genesis file @@ -272,6 +327,9 @@ then # v2 erc20 revert tester address=$(yq -r '.additional_accounts.user_v2_erc20_revert.bech32_address' /root/config.yml) zetacored add-genesis-account "$address" 100000000000000000000000000azeta +# emissions withdraw tester + address=$(yq -r '.additional_accounts.user_emissions_withdraw.bech32_address' /root/config.yml) + zetacored add-genesis-account "$address" 100000000000000000000000000azeta # 3. Copy the genesis.json to all the nodes .And use it to create a gentx for every node zetacored gentx operator 1000000000000000000000azeta --chain-id=$CHAINID --keyring-backend=$KEYRING --gas-prices 20000000000azeta diff --git a/e2e/config/config.go b/e2e/config/config.go index 22382c1fa1..1ae7d4109a 100644 --- a/e2e/config/config.go +++ b/e2e/config/config.go @@ -61,21 +61,22 @@ type Account struct { // AdditionalAccounts are extra accounts required to run specific tests type AdditionalAccounts struct { - UserERC20 Account `yaml:"user_erc20"` - UserZetaTest Account `yaml:"user_zeta_test"` - UserZEVMMPTest Account `yaml:"user_zevm_mp_test"` - UserBitcoinDeposit Account `yaml:"user_bitcoin_deposit"` - UserBitcoinWithdraw Account `yaml:"user_bitcoin_withdraw"` - UserSolana Account `yaml:"user_solana"` - UserEther Account `yaml:"user_ether"` - UserMisc Account `yaml:"user_misc"` - UserAdmin Account `yaml:"user_admin"` - UserMigration Account `yaml:"user_migration"` // used for TSS migration, TODO: rename (https://github.com/zeta-chain/node/issues/2780) - UserPrecompile Account `yaml:"user_precompile"` - UserV2Ether Account `yaml:"user_v2_ether"` - UserV2ERC20 Account `yaml:"user_v2_erc20"` - UserV2EtherRevert Account `yaml:"user_v2_ether_revert"` - UserV2ERC20Revert Account `yaml:"user_v2_erc20_revert"` + UserERC20 Account `yaml:"user_erc20"` + UserZetaTest Account `yaml:"user_zeta_test"` + UserZEVMMPTest Account `yaml:"user_zevm_mp_test"` + UserBitcoinDeposit Account `yaml:"user_bitcoin_deposit"` + UserBitcoinWithdraw Account `yaml:"user_bitcoin_withdraw"` + UserSolana Account `yaml:"user_solana"` + UserEther Account `yaml:"user_ether"` + UserMisc Account `yaml:"user_misc"` + UserAdmin Account `yaml:"user_admin"` + UserMigration Account `yaml:"user_migration"` // used for TSS migration, TODO: rename (https://github.com/zeta-chain/node/issues/2780) + UserPrecompile Account `yaml:"user_precompile"` + UserV2Ether Account `yaml:"user_v2_ether"` + UserV2ERC20 Account `yaml:"user_v2_erc20"` + UserV2EtherRevert Account `yaml:"user_v2_ether_revert"` + UserV2ERC20Revert Account `yaml:"user_v2_erc20_revert"` + UserEmissionsWithdraw Account `yaml:"user_emissions_withdraw"` } type PolicyAccounts struct { @@ -248,6 +249,7 @@ func (a AdditionalAccounts) AsSlice() []Account { a.UserV2ERC20, a.UserV2EtherRevert, a.UserV2ERC20Revert, + a.UserEmissionsWithdraw, } } @@ -364,6 +366,10 @@ func (c *Config) GenerateKeys() error { if err != nil { return err } + c.AdditionalAccounts.UserEmissionsWithdraw, err = generateAccount() + if err != nil { + return err + } c.PolicyAccounts.EmergencyPolicyAccount, err = generateAccount() if err != nil { diff --git a/e2e/runner/emissions.go b/e2e/runner/emissions.go new file mode 100644 index 0000000000..c950cf854c --- /dev/null +++ b/e2e/runner/emissions.go @@ -0,0 +1,89 @@ +package runner + +import ( + "fmt" + + "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/zeta-chain/node/cmd/zetacored/config" + "github.com/zeta-chain/node/e2e/txserver" + e2eutils "github.com/zeta-chain/node/e2e/utils" + emissionstypes "github.com/zeta-chain/node/x/emissions/types" + observertypes "github.com/zeta-chain/node/x/observer/types" +) + +// FundEmissionsPool funds the emissions pool on ZetaChain with the same value as used originally on mainnet (20M ZETA) +func (r *E2ERunner) FundEmissionsPool() error { + r.Logger.Print("⚙️ funding the emissions pool on ZetaChain with 20M ZETA (%s)", txserver.EmissionsPoolAddress) + + return r.ZetaTxServer.FundEmissionsPool(e2eutils.OperationalPolicyName, EmissionsPoolFunding) +} + +// WithdrawEmissions withdraws emissions from the emission pool on ZetaChain for all observers +// This functions uses the UserEmissionsWithdrawName to create the withdraw tx. +// UserEmissionsWithdraw can sign the authz transactions because the necessary permissions are granted in the genesis file +func (r *E2ERunner) WithdrawEmissions() error { + observerSet, err := r.ObserverClient.ObserverSet(r.Ctx, &observertypes.QueryObserverSet{}) + if err != nil { + return err + } + + for _, observer := range observerSet.Observers { + r.Logger.Print("🏃 Withdrawing emissions for observer %s", observer) + var ( + baseDenom = config.BaseDenom + queryObserverBalance = &banktypes.QueryBalanceRequest{ + Address: observer, + Denom: baseDenom, + } + ) + + balanceBefore, err := r.BankClient.Balance(r.Ctx, queryObserverBalance) + if err != nil { + return errors.Wrapf(err, "failed to get balance for observer before withdrawing emissions %s", observer) + } + + availableAmount, err := r.EmissionsClient.ShowAvailableEmissions( + r.Ctx, + &emissionstypes.QueryShowAvailableEmissionsRequest{ + Address: observer, + }, + ) + if err != nil { + return fmt.Errorf("failed to get available emissions for observer %s: %w", observer, err) + } + + availableCoin, err := sdk.ParseCoinNormalized(availableAmount.Amount) + if err != nil { + return fmt.Errorf("failed to parse coin amount: %w", err) + } + + if availableCoin.Amount.IsZero() { + r.Logger.Print("no emissions to withdraw for observer %s", observer) + continue + } + + if err := r.ZetaTxServer.WithdrawAllEmissions(availableCoin.Amount, e2eutils.UserEmissionsWithdrawName, observer); err != nil { + return err + } + + balanceAfter, err := r.BankClient.Balance(r.Ctx, queryObserverBalance) + if err != nil { + return errors.Wrapf(err, "failed to get balance for observer after withdrawing emissions %s", observer) + } + + changeInBalance := balanceAfter.Balance.Sub(*balanceBefore.Balance).Amount + if !changeInBalance.Equal(availableCoin.Amount) { + return fmt.Errorf( + "invalid balance change for observer %s, expected %s, got %s", + observer, + availableCoin.Amount, + changeInBalance, + ) + } + } + + return nil +} diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index f117758c28..9e75c6fcd7 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -45,6 +45,7 @@ import ( toncontracts "github.com/zeta-chain/node/pkg/contracts/ton" authoritytypes "github.com/zeta-chain/node/x/authority/types" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + emissionstypes "github.com/zeta-chain/node/x/emissions/types" fungibletypes "github.com/zeta-chain/node/x/fungible/types" lightclienttypes "github.com/zeta-chain/node/x/lightclient/types" observertypes "github.com/zeta-chain/node/x/observer/types" @@ -99,6 +100,7 @@ type E2ERunner struct { ObserverClient observertypes.QueryClient LightclientClient lightclienttypes.QueryClient DistributionClient distributiontypes.QueryClient + EmissionsClient emissionstypes.QueryClient // optional zeta (cosmos) client // typically only in test runners that need it @@ -210,6 +212,7 @@ func NewE2ERunner( ObserverClient: clients.Zetacore.Observer, LightclientClient: clients.Zetacore.Lightclient, DistributionClient: clients.Zetacore.Distribution, + EmissionsClient: clients.Zetacore.Emissions, EVMAuth: clients.EvmAuth, ZEVMAuth: clients.ZevmAuth, diff --git a/e2e/runner/setup_zeta.go b/e2e/runner/setup_zeta.go index 072957346c..56c803a376 100644 --- a/e2e/runner/setup_zeta.go +++ b/e2e/runner/setup_zeta.go @@ -276,10 +276,3 @@ func (r *E2ERunner) EnableHeaderVerification(chainIDList []int64) error { return r.ZetaTxServer.EnableHeaderVerification(e2eutils.AdminPolicyName, chainIDList) } - -// FundEmissionsPool funds the emissions pool on ZetaChain with the same value as used originally on mainnet (20M ZETA) -func (r *E2ERunner) FundEmissionsPool() error { - r.Logger.Print("⚙️ funding the emissions pool on ZetaChain with 20M ZETA (%s)", txserver.EmissionsPoolAddress) - - return r.ZetaTxServer.FundEmissionsPool(e2eutils.OperationalPolicyName, EmissionsPoolFunding) -} diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go index 146dda4cf2..e83d6a0d71 100644 --- a/e2e/txserver/zeta_tx_server.go +++ b/e2e/txserver/zeta_tx_server.go @@ -10,6 +10,7 @@ import ( "strings" "time" + sdkmath "cosmossdk.io/math" abci "github.com/cometbft/cometbft/abci/types" rpchttp "github.com/cometbft/cometbft/rpc/client/http" coretypes "github.com/cometbft/cometbft/rpc/core/types" @@ -585,6 +586,28 @@ func (zts ZetaTxServer) FundEmissionsPool(account string, amount *big.Int) error return err } +func (zts ZetaTxServer) WithdrawAllEmissions(withdrawAmount sdkmath.Int, account, observer string) error { + // retrieve account + acc, err := zts.clientCtx.Keyring.Key(account) + if err != nil { + return fmt.Errorf("failed to get withdrawer account: %w", err) + } + withdrawerAddress, err := acc.GetAddress() + if err != nil { + return fmt.Errorf("failed to get withdrawer account address: %w", err) + } + + msg := emissionstypes.MsgWithdrawEmission{ + Creator: observer, + Amount: withdrawAmount, + } + + authzMessage := authz.NewMsgExec(withdrawerAddress, []sdktypes.Msg{&msg}) + + _, err = zts.BroadcastTx(account, &authzMessage) + return err +} + // UpdateKeygen sets a new keygen height . The new height is the current height + 30 func (zts ZetaTxServer) UpdateKeygen(height int64) error { keygenHeight := height + 30 diff --git a/e2e/utils/zetacore.go b/e2e/utils/zetacore.go index 34e53e61b0..bf01d79ec8 100644 --- a/e2e/utils/zetacore.go +++ b/e2e/utils/zetacore.go @@ -16,9 +16,11 @@ import ( type CCTXClient = crosschaintypes.QueryClient const ( - EmergencyPolicyName = "emergency" - AdminPolicyName = "admin" - OperationalPolicyName = "operational" + EmergencyPolicyName = "emergency" + AdminPolicyName = "admin" + OperationalPolicyName = "operational" + UserEmissionsWithdrawName = "emissions_withdraw" + // The timeout was increased from 4 to 6 , which allows for a higher success in test runs // However this needs to be researched as to why the increase in timeout was needed. // https://github.com/zeta-chain/node/issues/2690 diff --git a/pkg/rpc/clients.go b/pkg/rpc/clients.go index 4d94b317e4..8ae9813b53 100644 --- a/pkg/rpc/clients.go +++ b/pkg/rpc/clients.go @@ -17,6 +17,7 @@ import ( etherminttypes "github.com/zeta-chain/node/rpc/types" authoritytypes "github.com/zeta-chain/node/x/authority/types" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + emissionstypes "github.com/zeta-chain/node/x/emissions/types" fungibletypes "github.com/zeta-chain/node/x/fungible/types" lightclienttypes "github.com/zeta-chain/node/x/lightclient/types" observertypes "github.com/zeta-chain/node/x/observer/types" @@ -51,6 +52,8 @@ type Clients struct { Observer observertypes.QueryClient // Lightclient is a github.com/zeta-chain/zetacore/x/lightclient/types QueryClient Lightclient lightclienttypes.QueryClient + // EmissionsClient is a github.com/zeta-chain/zetacore/x/emissions/types QueryClient + Emissions emissionstypes.QueryClient // Ethermint specific clients @@ -79,6 +82,7 @@ func newClients(ctx client.Context) (Clients, error) { Fungible: fungibletypes.NewQueryClient(ctx), Observer: observertypes.NewQueryClient(ctx), Lightclient: lightclienttypes.NewQueryClient(ctx), + Emissions: emissionstypes.NewQueryClient(ctx), // Ethermint specific clients Ethermint: etherminttypes.NewQueryClient(ctx), EthermintFeeMarket: feemarkettypes.NewQueryClient(ctx),