diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 62e0182ecf..c061f4479b 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -301,6 +301,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { e2etests.TestUpdateBytecodeZRC20Name, e2etests.TestUpdateBytecodeConnectorName, e2etests.TestDepositEtherLiquidityCapName, + e2etests.TestRateLimiterName, // TestMigrateChainSupportName tests EVM chain migration. Currently this test doesn't work with Anvil because pre-EIP1559 txs are not supported // See issue below for details diff --git a/contrib/localnet/orchestrator/start-zetae2e.sh b/contrib/localnet/orchestrator/start-zetae2e.sh index a893c689ee..228ee297ff 100644 --- a/contrib/localnet/orchestrator/start-zetae2e.sh +++ b/contrib/localnet/orchestrator/start-zetae2e.sh @@ -14,36 +14,36 @@ sleep 2 ### Create the accounts and fund them with Ether on local Ethereum network # unlock the deployer account -echo "funding deployer address 0xE5C5367B8224807Ac2207d350E60e1b6F27a7ecC with 100 Ether" -geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0xE5C5367B8224807Ac2207d350E60e1b6F27a7ecC", value: web3.toWei(100,"ether")})' attach http://eth:8545 +echo "funding deployer address 0xE5C5367B8224807Ac2207d350E60e1b6F27a7ecC with 10000 Ether" +geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0xE5C5367B8224807Ac2207d350E60e1b6F27a7ecC", value: web3.toWei(10000,"ether")})' attach http://eth:8545 # unlock erc20 tester accounts -echo "funding deployer address 0x6F57D5E7c6DBb75e59F1524a3dE38Fc389ec5Fd6 with 100 Ether" -geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0x6F57D5E7c6DBb75e59F1524a3dE38Fc389ec5Fd6", value: web3.toWei(100,"ether")})' attach http://eth:8545 +echo "funding deployer address 0x6F57D5E7c6DBb75e59F1524a3dE38Fc389ec5Fd6 with 10000 Ether" +geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0x6F57D5E7c6DBb75e59F1524a3dE38Fc389ec5Fd6", value: web3.toWei(10000,"ether")})' attach http://eth:8545 # unlock zeta tester accounts -echo "funding deployer address 0x5cC2fBb200A929B372e3016F1925DcF988E081fd with 100 Ether" -geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0x5cC2fBb200A929B372e3016F1925DcF988E081fd", value: web3.toWei(100,"ether")})' attach http://eth:8545 +echo "funding deployer address 0x5cC2fBb200A929B372e3016F1925DcF988E081fd with 10000 Ether" +geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0x5cC2fBb200A929B372e3016F1925DcF988E081fd", value: web3.toWei(10000,"ether")})' attach http://eth:8545 # unlock bitcoin tester accounts -echo "funding deployer address 0x283d810090EdF4043E75247eAeBcE848806237fD with 100 Ether" -geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0x283d810090EdF4043E75247eAeBcE848806237fD", value: web3.toWei(100,"ether")})' attach http://eth:8545 +echo "funding deployer address 0x283d810090EdF4043E75247eAeBcE848806237fD with 10000 Ether" +geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0x283d810090EdF4043E75247eAeBcE848806237fD", value: web3.toWei(10000,"ether")})' attach http://eth:8545 # unlock ethers tester accounts -echo "funding deployer address 0x8D47Db7390AC4D3D449Cc20D799ce4748F97619A with 100 Ether" -geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0x8D47Db7390AC4D3D449Cc20D799ce4748F97619A", value: web3.toWei(100,"ether")})' attach http://eth:8545 +echo "funding deployer address 0x8D47Db7390AC4D3D449Cc20D799ce4748F97619A with 10000 Ether" +geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0x8D47Db7390AC4D3D449Cc20D799ce4748F97619A", value: web3.toWei(10000,"ether")})' attach http://eth:8545 # unlock miscellaneous tests accounts -echo "funding deployer address 0x90126d02E41c9eB2a10cfc43aAb3BD3460523Cdf with 100 Ether" -geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0x90126d02E41c9eB2a10cfc43aAb3BD3460523Cdf", value: web3.toWei(100,"ether")})' attach http://eth:8545 +echo "funding deployer address 0x90126d02E41c9eB2a10cfc43aAb3BD3460523Cdf with 10000 Ether" +geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0x90126d02E41c9eB2a10cfc43aAb3BD3460523Cdf", value: web3.toWei(10000,"ether")})' attach http://eth:8545 # unlock admin erc20 tests accounts -echo "funding deployer address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 with 100 Ether" -geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", value: web3.toWei(100,"ether")})' attach http://eth:8545 +echo "funding deployer address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 with 10000 Ether" +geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", value: web3.toWei(10000,"ether")})' attach http://eth:8545 # unlock the TSS account -echo "funding TSS address 0xF421292cb0d3c97b90EEEADfcD660B893592c6A2 with 100 Ether" -geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0xF421292cb0d3c97b90EEEADfcD660B893592c6A2", value: web3.toWei(100,"ether")})' attach http://eth:8545 +echo "funding TSS address 0xF421292cb0d3c97b90EEEADfcD660B893592c6A2 with 10000 Ether" +geth --exec 'eth.sendTransaction({from: eth.coinbase, to: "0xF421292cb0d3c97b90EEEADfcD660B893592c6A2", value: web3.toWei(10000,"ether")})' attach http://eth:8545 ### Run zetae2e command depending on the option passed diff --git a/docs/cli/zetacored/zetacored_query_crosschain.md b/docs/cli/zetacored/zetacored_query_crosschain.md index 48345c136d..467cce1f25 100644 --- a/docs/cli/zetacored/zetacored_query_crosschain.md +++ b/docs/cli/zetacored/zetacored_query_crosschain.md @@ -35,6 +35,7 @@ zetacored query crosschain [flags] * [zetacored query crosschain list-in-tx-tracker](zetacored_query_crosschain_list-in-tx-tracker.md) - shows a list of in tx tracker by chainId * [zetacored query crosschain list-out-tx-tracker](zetacored_query_crosschain_list-out-tx-tracker.md) - list all OutTxTracker * [zetacored query crosschain list-pending-cctx](zetacored_query_crosschain_list-pending-cctx.md) - shows pending CCTX +* [zetacored query crosschain list_pending_cctx_within_rate_limit](zetacored_query_crosschain_list_pending_cctx_within_rate_limit.md) - list all pending CCTX within rate limit * [zetacored query crosschain show-cctx](zetacored_query_crosschain_show-cctx.md) - shows a CCTX * [zetacored query crosschain show-gas-price](zetacored_query_crosschain_show-gas-price.md) - shows a gasPrice * [zetacored query crosschain show-in-tx-hash-to-cctx](zetacored_query_crosschain_show-in-tx-hash-to-cctx.md) - shows a inTxHashToCctx diff --git a/docs/cli/zetacored/zetacored_query_crosschain_list_pending_cctx_within_rate_limit.md b/docs/cli/zetacored/zetacored_query_crosschain_list_pending_cctx_within_rate_limit.md new file mode 100644 index 0000000000..aeb3fa12f1 --- /dev/null +++ b/docs/cli/zetacored/zetacored_query_crosschain_list_pending_cctx_within_rate_limit.md @@ -0,0 +1,33 @@ +# query crosschain list_pending_cctx_within_rate_limit + +list all pending CCTX within rate limit + +``` +zetacored query crosschain list_pending_cctx_within_rate_limit [flags] +``` + +### Options + +``` + --grpc-addr string the gRPC endpoint to use for this chain + --grpc-insecure allow gRPC over insecure channels, if not TLS the server must use TLS + --height int Use a specific height to query state at (this can error if the node is pruning state) + -h, --help help for list_pending_cctx_within_rate_limit + --node string [host]:[port] to Tendermint RPC interface for this chain + -o, --output string Output format (text|json) +``` + +### Options inherited from parent commands + +``` + --chain-id string The network chain ID + --home string directory for config and data + --log_format string The logging format (json|plain) + --log_level string The logging level (trace|debug|info|warn|error|fatal|panic) + --trace print out full stack trace on errors +``` + +### SEE ALSO + +* [zetacored query crosschain](zetacored_query_crosschain.md) - Querying commands for the crosschain module + diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 583606ec50..e822d9c74a 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -59,6 +59,7 @@ const ( TestPauseZRC20Name = "pause_zrc20" TestUpdateBytecodeZRC20Name = "update_bytecode_zrc20" TestUpdateBytecodeConnectorName = "update_bytecode_connector" + TestRateLimiterName = "rate_limiter" ) // AllE2ETests is an ordered list of all e2e tests @@ -386,6 +387,12 @@ var AllE2ETests = []runner.E2ETest{ []runner.ArgDefinition{}, TestUpdateBytecodeConnector, ), + runner.NewE2ETest( + TestRateLimiterName, + "test sending cctxs with rate limiter enabled and show logs when processing cctxs", + []runner.ArgDefinition{}, + TestRateLimiter, + ), runner.NewE2ETest( TestMessagePassingZEVMToEVMName, "zevm -> evm message passing contract call", diff --git a/e2e/e2etests/test_eth_deposit.go b/e2e/e2etests/test_eth_deposit.go index 65a79f9c15..b4382b7740 100644 --- a/e2e/e2etests/test_eth_deposit.go +++ b/e2e/e2etests/test_eth_deposit.go @@ -268,8 +268,8 @@ func TestDepositEtherLiquidityCap(r *runner.E2ERunner, args []string) { } liquidityCap := math.NewUintFromBigInt(supply).Add(liquidityCapArg) - amountLessThanCap := liquidityCap.BigInt().Div(liquidityCap.BigInt(), big.NewInt(10)) // 1/10 of the cap - amountMoreThanCap := liquidityCap.BigInt().Mul(liquidityCap.BigInt(), big.NewInt(10)) // 10 times the cap + amountLessThanCap := liquidityCapArg.BigInt().Div(liquidityCapArg.BigInt(), big.NewInt(10)) // 1/10 of the cap + amountMoreThanCap := liquidityCapArg.BigInt().Mul(liquidityCapArg.BigInt(), big.NewInt(10)) // 10 times the cap msg := fungibletypes.NewMsgUpdateZRC20LiquidityCap( r.ZetaTxServer.GetAccountAddress(0), r.ETHZRC20Addr.Hex(), diff --git a/e2e/e2etests/test_migrate_chain_support.go b/e2e/e2etests/test_migrate_chain_support.go index 75bdb29d0e..0bbb754771 100644 --- a/e2e/e2etests/test_migrate_chain_support.go +++ b/e2e/e2etests/test_migrate_chain_support.go @@ -157,7 +157,20 @@ func TestMigrateChainSupport(r *runner.E2ERunner, _ []string) { newRunner.WaitForMinedCCTX(txEtherDeposit) // perform withdrawals on the new chain - TestZetaWithdraw(newRunner, []string{"10000000000000000000"}) + amount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) + newRunner.DepositAndApproveWZeta(amount) + tx := newRunner.WithdrawZeta(amount, true) + cctx := utils.WaitCctxMinedByInTxHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "zeta withdraw") + if cctx.CctxStatus.Status != crosschaintypes.CctxStatus_OutboundMined { + panic(fmt.Errorf( + "expected cctx status to be %s; got %s, message %s", + crosschaintypes.CctxStatus_OutboundMined, + cctx.CctxStatus.Status.String(), + cctx.CctxStatus.StatusMessage, + )) + } + TestEtherWithdraw(newRunner, []string{"50000000000000000"}) // finally try to deposit Zeta back diff --git a/e2e/e2etests/test_rate_limiter.go b/e2e/e2etests/test_rate_limiter.go new file mode 100644 index 0000000000..e28c627574 --- /dev/null +++ b/e2e/e2etests/test_rate_limiter.go @@ -0,0 +1,203 @@ +package e2etests + +import ( + "context" + "fmt" + "math/big" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + "golang.org/x/sync/errgroup" +) + +const RateLimiterWithdrawNumber = 5 + +// rateLimiterFlags are the rate limiter flags for the test +var rateLimiterFlags = crosschaintypes.RateLimiterFlags{ + Enabled: true, + Rate: sdk.NewUint(1e17).MulUint64(5), // this value is used so rate is reached + Window: 10, +} + +func TestRateLimiter(r *runner.E2ERunner, _ []string) { + r.Logger.Info("TestRateLimiter") + + // deposit and approve 50 WZETA for the tests + r.DepositAndApproveWZeta(big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(50))) + + // add liquidity in the pool to prevent high slippage in WZETA/gas pair + if err := addZetaGasLiquidity(r); err != nil { + panic(err) + } + + // Set the rate limiter to 0.5ZETA per 10 blocks + // These rate limiter flags will only allow to process 1 withdraw per 10 blocks + r.Logger.Info("setting up rate limiter flags") + if err := setupRateLimiterFlags(r, rateLimiterFlags); err != nil { + panic(err) + } + + // Test with rate limiter + // TODO: define proper assertion to check the rate limiter is working + // https://github.com/zeta-chain/node/issues/2090 + r.Logger.Print("rate limiter enabled") + if err := createAndWaitWithdraws(r); err != nil { + panic(err) + } + + // Disable rate limiter + r.Logger.Info("disabling rate limiter") + if err := setupRateLimiterFlags(r, crosschaintypes.RateLimiterFlags{Enabled: false}); err != nil { + panic(err) + } + + // Test without rate limiter again + r.Logger.Print("rate limiter disabled") + if err := createAndWaitWithdraws(r); err != nil { + panic(err) + } +} + +// setupRateLimiterFlags sets up the rate limiter flags with flags defined in the test +func setupRateLimiterFlags(r *runner.E2ERunner, flags crosschaintypes.RateLimiterFlags) error { + adminAddr, err := r.ZetaTxServer.GetAccountAddressFromName(utils.FungibleAdminName) + if err != nil { + return err + } + _, err = r.ZetaTxServer.BroadcastTx(utils.FungibleAdminName, crosschaintypes.NewMsgUpdateRateLimiterFlags( + adminAddr, + flags, + )) + if err != nil { + return err + } + + return nil +} + +// createAndWaitWithdraws performs RateLimiterWithdrawNumber withdraws +func createAndWaitWithdraws(r *runner.E2ERunner) error { + startTime := time.Now() + + r.Logger.Print("starting %d withdraws", RateLimiterWithdrawNumber) + + // Perform RateLimiterWithdrawNumber withdraws to log time for completion + txs := make([]*ethtypes.Transaction, RateLimiterWithdrawNumber) + for i := 0; i < RateLimiterWithdrawNumber; i++ { + amount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(3)) + txs[i] = r.WithdrawZeta(amount, true) + } + + // start a error group to wait for all the withdraws to be mined + g, ctx := errgroup.WithContext(r.Ctx) + for i, tx := range txs { + // capture the loop variables + tx, i := tx, i + + // start a goroutine to wait for the withdraw to be mined + g.Go(func() error { + return waitForZetaWithdrawMined(ctx, r, tx, i, startTime) + }) + } + + // wait for all the withdraws to be mined + if err := g.Wait(); err != nil { + return err + } + + duration := time.Now().Sub(startTime).Seconds() + block, err := r.ZEVMClient.BlockNumber(r.Ctx) + if err != nil { + return fmt.Errorf("error getting block number: %w", err) + } + r.Logger.Print("all withdraws completed in %vs at block %d", duration, block) + + return nil +} + +// waitForZetaWithdrawMined waits for a zeta withdraw to be mined +// we first wait to get the receipt +// NOTE: this could be a more general function but we define it here for this test because we emit in the function logs specific to this test +func waitForZetaWithdrawMined(ctx context.Context, r *runner.E2ERunner, tx *ethtypes.Transaction, index int, startTime time.Time) error { + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInTxHash(ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "zeta withdraw") + if cctx.CctxStatus.Status != crosschaintypes.CctxStatus_OutboundMined { + return fmt.Errorf( + "expected cctx status to be %s; got %s, message %s", + crosschaintypes.CctxStatus_OutboundMined, + cctx.CctxStatus.Status.String(), + cctx.CctxStatus.StatusMessage, + ) + } + + // record the time for completion + duration := time.Now().Sub(startTime).Seconds() + block, err := r.ZEVMClient.BlockNumber(ctx) + if err != nil { + return err + } + r.Logger.Print("cctx %d mined in %vs at block %d", index, duration, block) + + return nil +} + +// addZetaGasLiquidity adds liquidity to the ZETA/gas pool +func addZetaGasLiquidity(r *runner.E2ERunner) error { + // use 10 ZETA and 10 ETH for the liquidity + // this will be sufficient for the tests + amount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) + approveAmount := big.NewInt(0).Mul(amount, big.NewInt(10)) + + // approve uniswap router to spend gas + txETHZRC20Approve, err := r.ETHZRC20.Approve(r.ZEVMAuth, r.UniswapV2RouterAddr, approveAmount) + if err != nil { + return fmt.Errorf("error approving ZETA: %w", err) + } + + // wait for the tx to be mined + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, txETHZRC20Approve, r.Logger, r.ReceiptTimeout) + if receipt.Status != 1 { + return fmt.Errorf("approve failed") + } + + // approve uniswap router to spend ZETA + txZETAApprove, err := r.WZeta.Approve(r.ZEVMAuth, r.UniswapV2RouterAddr, approveAmount) + if err != nil { + return fmt.Errorf("error approving ZETA: %w", err) + } + + // wait for the tx to be mined + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, txZETAApprove, r.Logger, r.ReceiptTimeout) + if receipt.Status != 1 { + return fmt.Errorf("approve failed") + } + + // add liquidity in the pool to prevent high slippage in WZETA/gas pair + r.ZEVMAuth.Value = amount + txAddLiquidity, err := r.UniswapV2Router.AddLiquidityETH( + r.ZEVMAuth, + r.ETHZRC20Addr, + amount, + big.NewInt(1e18), + big.NewInt(1e18), + r.DeployerAddress, + big.NewInt(time.Now().Add(10*time.Minute).Unix()), + ) + if err != nil { + return fmt.Errorf("error adding liquidity: %w", err) + } + r.ZEVMAuth.Value = big.NewInt(0) + + // wait for the tx to be mined + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, txAddLiquidity, r.Logger, r.ReceiptTimeout) + if receipt.Status != 1 { + return fmt.Errorf("add liquidity failed") + } + + return nil +} diff --git a/e2e/e2etests/test_update_bytecode_connector.go b/e2e/e2etests/test_update_bytecode_connector.go index 0770e80dc4..e961c1c074 100644 --- a/e2e/e2etests/test_update_bytecode_connector.go +++ b/e2e/e2etests/test_update_bytecode_connector.go @@ -1,17 +1,33 @@ package e2etests import ( + "fmt" + "math/big" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/zeta-chain/zetacore/e2e/contracts/testconnectorzevm" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" ) // TestUpdateBytecodeConnector tests updating the bytecode of a connector and interact with it func TestUpdateBytecodeConnector(r *runner.E2ERunner, _ []string) { - // Can withdraw 0.1ZETA - TestZetaWithdraw(r, []string{"10000000000000000000"}) + // Can withdraw 10ZETA + amount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) + r.DepositAndApproveWZeta(amount) + tx := r.WithdrawZeta(amount, true) + cctx := utils.WaitCctxMinedByInTxHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "zeta withdraw") + if cctx.CctxStatus.Status != crosschaintypes.CctxStatus_OutboundMined { + panic(fmt.Errorf( + "expected cctx status to be %s; got %s, message %s", + crosschaintypes.CctxStatus_OutboundMined, + cctx.CctxStatus.Status.String(), + cctx.CctxStatus.StatusMessage, + )) + } // Deploy the test contract newTestConnectorAddr, tx, _, err := testconnectorzevm.DeployTestZetaConnectorZEVM( @@ -65,6 +81,17 @@ func TestUpdateBytecodeConnector(r *runner.E2ERunner, _ []string) { panic("unexpected response") } - // Can continue to interact with the connector: withdraw 0.1ZETA - TestZetaWithdraw(r, []string{"10000000000000000000"}) + // Can continue to interact with the connector: withdraw 10ZETA + r.DepositAndApproveWZeta(amount) + tx = r.WithdrawZeta(amount, true) + cctx = utils.WaitCctxMinedByInTxHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "zeta withdraw") + if cctx.CctxStatus.Status != crosschaintypes.CctxStatus_OutboundMined { + panic(fmt.Errorf( + "expected cctx status to be %s; got %s, message %s", + crosschaintypes.CctxStatus_OutboundMined, + cctx.CctxStatus.Status.String(), + cctx.CctxStatus.StatusMessage, + )) + } } diff --git a/e2e/e2etests/test_zeta_withdraw.go b/e2e/e2etests/test_zeta_withdraw.go index 6dfc664813..a51da4c6f5 100644 --- a/e2e/e2etests/test_zeta_withdraw.go +++ b/e2e/e2etests/test_zeta_withdraw.go @@ -5,12 +5,11 @@ import ( "math/big" - ethcommon "github.com/ethereum/go-ethereum/common" connectorzevm "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zetaconnectorzevm.sol" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" "github.com/zeta-chain/zetacore/pkg/chains" - cctxtypes "github.com/zeta-chain/zetacore/x/crosschain/types" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) func TestZetaWithdraw(r *runner.E2ERunner, args []string) { @@ -20,76 +19,18 @@ func TestZetaWithdraw(r *runner.E2ERunner, args []string) { amount, ok := big.NewInt(0).SetString(args[0], 10) if !ok { - panic("Invalid amount specified for TestZetaWithdraw.") + panic("invalid amount specified") } - r.ZEVMAuth.Value = amount - tx, err := r.WZeta.Deposit(r.ZEVMAuth) - if err != nil { - panic(err) - } - r.ZEVMAuth.Value = big.NewInt(0) - r.Logger.Info("wzeta deposit tx hash: %s", tx.Hash().Hex()) - - receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - r.Logger.EVMReceipt(*receipt, "wzeta deposit") - if receipt.Status == 0 { - panic("deposit failed") - } - - chainID, err := r.EVMClient.ChainID(r.Ctx) - if err != nil { - panic(err) - } - - tx, err = r.WZeta.Approve(r.ZEVMAuth, r.ConnectorZEVMAddr, amount) - if err != nil { - panic(err) - } - r.Logger.Info("wzeta approve tx hash: %s", tx.Hash().Hex()) - - receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - r.Logger.EVMReceipt(*receipt, "wzeta approve") - if receipt.Status == 0 { - panic(fmt.Sprintf("approve failed, logs: %+v", receipt.Logs)) - } - - tx, err = r.ConnectorZEVM.Send(r.ZEVMAuth, connectorzevm.ZetaInterfacesSendInput{ - DestinationChainId: chainID, - DestinationAddress: r.DeployerAddress.Bytes(), - DestinationGasLimit: big.NewInt(400_000), - Message: nil, - ZetaValueAndGas: amount, - ZetaParams: nil, - }) - if err != nil { - panic(err) - } - r.Logger.Info("send tx hash: %s", tx.Hash().Hex()) - receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - r.Logger.EVMReceipt(*receipt, "send") - if receipt.Status == 0 { - panic(fmt.Sprintf("send failed, logs: %+v", receipt.Logs)) - } - - r.Logger.Info(" Logs:") - for _, log := range receipt.Logs { - sentLog, err := r.ConnectorZEVM.ParseZetaSent(*log) - if err == nil { - r.Logger.Info(" Dest Addr: %s", ethcommon.BytesToAddress(sentLog.DestinationAddress).Hex()) - r.Logger.Info(" Dest Chain: %d", sentLog.DestinationChainId) - r.Logger.Info(" Dest Gas: %d", sentLog.DestinationGasLimit) - r.Logger.Info(" Zeta Value: %d", sentLog.ZetaValueAndGas) - } - } - r.Logger.Info("waiting for cctx status to change to final...") + r.DepositAndApproveWZeta(amount) + tx := r.WithdrawZeta(amount, true) cctx := utils.WaitCctxMinedByInTxHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "zeta withdraw") - if cctx.CctxStatus.Status != cctxtypes.CctxStatus_OutboundMined { + if cctx.CctxStatus.Status != crosschaintypes.CctxStatus_OutboundMined { panic(fmt.Errorf( "expected cctx status to be %s; got %s, message %s", - cctxtypes.CctxStatus_OutboundMined, + crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status.String(), cctx.CctxStatus.StatusMessage, )) diff --git a/e2e/runner/evm.go b/e2e/runner/evm.go index 0bc6d77070..e878de97e7 100644 --- a/e2e/runner/evm.go +++ b/e2e/runner/evm.go @@ -127,7 +127,8 @@ func (runner *E2ERunner) DepositERC20WithAmountAndMessage(to ethcommon.Address, // DepositEther sends Ethers into ZEVM func (runner *E2ERunner) DepositEther(testHeader bool) ethcommon.Hash { - return runner.DepositEtherWithAmount(testHeader, big.NewInt(1000000000000000000)) // in wei (1 eth) + amount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(100)) // 100 eth + return runner.DepositEtherWithAmount(testHeader, amount) } // DepositEtherWithAmount sends Ethers into ZEVM diff --git a/e2e/runner/zeta.go b/e2e/runner/zeta.go index d7fdbdcdf4..58a4513923 100644 --- a/e2e/runner/zeta.go +++ b/e2e/runner/zeta.go @@ -7,6 +7,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" zetaconnectoreth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.eth.sol" + connectorzevm "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zetaconnectorzevm.sol" "github.com/zeta-chain/zetacore/e2e/utils" "github.com/zeta-chain/zetacore/x/crosschain/types" ) @@ -138,3 +139,76 @@ func (runner *E2ERunner) DepositZetaWithAmount(to ethcommon.Address, amount *big return tx.Hash() } + +// DepositAndApproveWZeta deposits and approves WZETA on ZetaChain from the ZETA smart contract on ZEVM +func (runner *E2ERunner) DepositAndApproveWZeta(amount *big.Int) { + runner.ZEVMAuth.Value = amount + tx, err := runner.WZeta.Deposit(runner.ZEVMAuth) + if err != nil { + panic(err) + } + runner.ZEVMAuth.Value = big.NewInt(0) + runner.Logger.Info("wzeta deposit tx hash: %s", tx.Hash().Hex()) + + receipt := utils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, tx, runner.Logger, runner.ReceiptTimeout) + runner.Logger.EVMReceipt(*receipt, "wzeta deposit") + if receipt.Status == 0 { + panic("deposit failed") + } + + tx, err = runner.WZeta.Approve(runner.ZEVMAuth, runner.ConnectorZEVMAddr, amount) + if err != nil { + panic(err) + } + runner.Logger.Info("wzeta approve tx hash: %s", tx.Hash().Hex()) + + receipt = utils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, tx, runner.Logger, runner.ReceiptTimeout) + runner.Logger.EVMReceipt(*receipt, "wzeta approve") + if receipt.Status == 0 { + panic(fmt.Sprintf("approve failed, logs: %+v", receipt.Logs)) + } +} + +// WithdrawZeta withdraws ZETA from ZetaChain to the ZETA smart contract on EVM +// waitReceipt specifies whether to wait for the tx receipt and check if the tx was successful +func (runner *E2ERunner) WithdrawZeta(amount *big.Int, waitReceipt bool) *ethtypes.Transaction { + chainID, err := runner.EVMClient.ChainID(runner.Ctx) + if err != nil { + panic(err) + } + + tx, err := runner.ConnectorZEVM.Send(runner.ZEVMAuth, connectorzevm.ZetaInterfacesSendInput{ + DestinationChainId: chainID, + DestinationAddress: runner.DeployerAddress.Bytes(), + DestinationGasLimit: big.NewInt(400_000), + Message: nil, + ZetaValueAndGas: amount, + ZetaParams: nil, + }) + if err != nil { + panic(err) + } + runner.Logger.Info("send tx hash: %s", tx.Hash().Hex()) + + if waitReceipt { + receipt := utils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, tx, runner.Logger, runner.ReceiptTimeout) + runner.Logger.EVMReceipt(*receipt, "send") + if receipt.Status == 0 { + panic(fmt.Sprintf("send failed, logs: %+v", receipt.Logs)) + + } + + runner.Logger.Info(" Logs:") + for _, log := range receipt.Logs { + sentLog, err := runner.ConnectorZEVM.ParseZetaSent(*log) + if err == nil { + runner.Logger.Info(" Dest Addr: %s", ethcommon.BytesToAddress(sentLog.DestinationAddress).Hex()) + runner.Logger.Info(" Dest Chain: %d", sentLog.DestinationChainId) + runner.Logger.Info(" Dest Gas: %d", sentLog.DestinationGasLimit) + runner.Logger.Info(" Zeta Value: %d", sentLog.ZetaValueAndGas) + } + } + } + + return tx +} diff --git a/e2e/utils/evm.go b/e2e/utils/evm.go index d3f34f818c..fc6792d617 100644 --- a/e2e/utils/evm.go +++ b/e2e/utils/evm.go @@ -55,7 +55,7 @@ func MustWaitForTxReceipt( receipt, err := client.TransactionReceipt(ctx, tx.Hash()) if err != nil { if !errors.Is(err, ethereum.NotFound) && i%10 == 0 { - logger.Info("fetching tx receipt error: ", err.Error()) + logger.Info("fetching tx %s receipt error: %s ", tx.Hash().Hex(), err.Error()) } time.Sleep(1 * time.Second) continue diff --git a/x/crosschain/client/cli/query.go b/x/crosschain/client/cli/query.go index 9f9ae29752..22085bfb68 100644 --- a/x/crosschain/client/cli/query.go +++ b/x/crosschain/client/cli/query.go @@ -36,6 +36,7 @@ func GetQueryCmd(_ string) *cobra.Command { CmdListInTxTrackerByChain(), CmdListInTxTrackers(), CmdGetZetaAccounting(), + CmdListPendingCCTXWithinRateLimit(), CmdShowUpdateRateLimiterFlags(), ) diff --git a/x/crosschain/client/cli/query_cctx_rate_limit.go b/x/crosschain/client/cli/query_cctx_rate_limit.go new file mode 100644 index 0000000000..251a3897de --- /dev/null +++ b/x/crosschain/client/cli/query_cctx_rate_limit.go @@ -0,0 +1,35 @@ +package cli + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func CmdListPendingCCTXWithinRateLimit() *cobra.Command { + cmd := &cobra.Command{ + Use: "list_pending_cctx_within_rate_limit", + Short: "list all pending CCTX within rate limit", + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.ListPendingCctxWithinRateLimit( + context.Background(), &types.QueryListPendingCctxWithinRateLimitRequest{}, + ) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/crosschain/keeper/grpc_query_cctx_rate_limit.go b/x/crosschain/keeper/grpc_query_cctx_rate_limit.go index 57ce375048..176a3dcee4 100644 --- a/x/crosschain/keeper/grpc_query_cctx_rate_limit.go +++ b/x/crosschain/keeper/grpc_query_cctx_rate_limit.go @@ -17,7 +17,7 @@ import ( // ListPendingCctxWithinRateLimit returns a list of pending cctxs that do not exceed the outbound rate limit // a limit for the number of cctxs to return can be specified or the default is MaxPendingCctxs -func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.QueryListPendingCctxWithinRateLimitRequest) (*types.QueryListPendingCctxWithinRateLimitResponse, error) { +func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.QueryListPendingCctxWithinRateLimitRequest) (res *types.QueryListPendingCctxWithinRateLimitResponse, err error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } @@ -108,16 +108,16 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que // query pending nonces for each foreign chain and get the lowest height of the pending cctxs // Note: The pending nonces could change during the RPC call, so query them beforehand lowestPendingCctxHeight := int64(0) - pendingNoncesMap := make(map[int64]*observertypes.PendingNonces) + pendingNoncesMap := make(map[int64]observertypes.PendingNonces) for _, chain := range chains { pendingNonces, found := k.GetObserverKeeper().GetPendingNonces(ctx, tss.TssPubkey, chain.ChainId) if !found { return nil, status.Error(codes.Internal, "pending nonces not found") } + pendingNoncesMap[chain.ChainId] = pendingNonces // insert pending nonces and update lowest height if pendingNonces.NonceLow < pendingNonces.NonceHigh { - pendingNoncesMap[chain.ChainId] = &pendingNonces cctx, err := getCctxByChainIDAndNonce(k, ctx, tss.TssPubkey, chain.ChainId, pendingNonces.NonceLow) if err != nil { return nil, err @@ -191,12 +191,12 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que // query forwards for pending cctxs for each foreign chain for _, chain := range chains { - // query the pending cctxs in range [NonceLow, NonceHigh) pendingNonces := pendingNoncesMap[chain.ChainId] // #nosec G701 always in range totalPending += uint64(pendingNonces.NonceHigh - pendingNonces.NonceLow) + // query the pending cctxs in range [NonceLow, NonceHigh) for nonce := pendingNonces.NonceLow; nonce < pendingNonces.NonceHigh; nonce++ { cctx, err := getCctxByChainIDAndNonce(k, ctx, tss.TssPubkey, chain.ChainId, nonce) if err != nil { diff --git a/x/observer/types/chain_params.go b/x/observer/types/chain_params.go index e32ee9e597..5664decc51 100644 --- a/x/observer/types/chain_params.go +++ b/x/observer/types/chain_params.go @@ -291,11 +291,11 @@ func GetDefaultGoerliLocalnetChainParams() *ChainParams { ConnectorContractAddress: "0xD28D6A0b8189305551a0A8bd247a6ECa9CE781Ca", Erc20CustodyContractAddress: "0xff3135df4F2775f4091b81f4c7B6359CfA07862a", InTxTicker: 2, - OutTxTicker: 2, + OutTxTicker: 1, WatchUtxoTicker: 0, GasPriceTicker: 5, - OutboundTxScheduleInterval: 2, - OutboundTxScheduleLookahead: 5, + OutboundTxScheduleInterval: 1, + OutboundTxScheduleLookahead: 50, BallotThreshold: DefaultBallotThreshold, MinObserverDelegation: DefaultMinObserverDelegation, IsSupported: false,