diff --git a/changelog.md b/changelog.md index 245fd6638a..1e0fe694f1 100644 --- a/changelog.md +++ b/changelog.md @@ -84,6 +84,7 @@ * [1985](https://github.com/zeta-chain/node/pull/1985) - improve fungible module coverage * [1992](https://github.com/zeta-chain/node/pull/1992) - remove setupKeeper from crosschain module * [2008](https://github.com/zeta-chain/node/pull/2008) - add test for connector bytecode update +* [2060](https://github.com/zeta-chain/node/pull/2060) - add unit test for rate limiter query ### Fixes diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index ed89dd8a5f..3103810305 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -308,6 +308,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } if testAdmin { eg.Go(adminTestRoutine(conf, deployerRunner, verbose, + e2etests.TestRateLimiterName, e2etests.TestPauseZRC20Name, e2etests.TestUpdateBytecodeZRC20Name, e2etests.TestUpdateBytecodeConnectorName, diff --git a/contrib/localnet/orchestrator/start-zetae2e.sh b/contrib/localnet/orchestrator/start-zetae2e.sh index 4f07e162a2..9d630950b7 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/docs/openapi/openapi.swagger.yaml b/docs/openapi/openapi.swagger.yaml index 1eb8fe4593..903cbd10b2 100644 --- a/docs/openapi/openapi.swagger.yaml +++ b/docs/openapi/openapi.swagger.yaml @@ -54013,6 +54013,11 @@ definitions: total_pending: type: string format: uint64 + current_withdraw_window: + type: string + format: int64 + current_withdraw_rate: + type: string rate_limit_exceeded: type: boolean crosschainQueryMessagePassingProtocolFeeResponse: 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_erc20_withdraw.go b/e2e/e2etests/test_erc20_withdraw.go index 26959c0eac..0d9e99c7c5 100644 --- a/e2e/e2etests/test_erc20_withdraw.go +++ b/e2e/e2etests/test_erc20_withdraw.go @@ -36,28 +36,10 @@ func TestERC20Withdraw(r *runner.E2ERunner, args []string) { r.Logger.Info("eth zrc20 approve receipt: status %d", receipt.Status) // withdraw - tx, err = r.ERC20ZRC20.Withdraw(r.ZEVMAuth, r.DeployerAddress.Bytes(), withdrawalAmount) - if err != nil { - panic(err) - } - receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - r.Logger.Info("Receipt txhash %s status %d", receipt.TxHash, receipt.Status) - for _, log := range receipt.Logs { - event, err := r.ERC20ZRC20.ParseWithdrawal(*log) - if err != nil { - continue - } - r.Logger.Info( - " logs: from %s, to %x, value %d, gasfee %d", - event.From.Hex(), - event.To, - event.Value, - event.Gasfee, - ) - } + tx = r.WithdrawERC20(withdrawalAmount) // verify the withdraw value - cctx := utils.WaitCctxMinedByInTxHash(r.Ctx, receipt.TxHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + cctx := utils.WaitCctxMinedByInTxHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) verifyTransferAmountFromCCTX(r, cctx, withdrawalAmount.Int64()) } 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_eth_withdraw.go b/e2e/e2etests/test_eth_withdraw.go index e9a4211461..998567419f 100644 --- a/e2e/e2etests/test_eth_withdraw.go +++ b/e2e/e2etests/test_eth_withdraw.go @@ -42,21 +42,10 @@ func TestEtherWithdraw(r *runner.E2ERunner, args []string) { r.Logger.EVMReceipt(*receipt, "approve") // withdraw - tx, err = r.ETHZRC20.Withdraw(r.ZEVMAuth, r.DeployerAddress.Bytes(), withdrawalAmount) - if err != nil { - panic(err) - } - r.Logger.EVMTransaction(*tx, "withdraw") - - receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) - if receipt.Status == 0 { - panic("withdraw failed") - } - r.Logger.EVMReceipt(*receipt, "withdraw") - r.Logger.ZRC20Withdrawal(r.ETHZRC20, *receipt, "withdraw") + tx = r.WithdrawEther(withdrawalAmount) // verify the withdraw value - cctx := utils.WaitCctxMinedByInTxHash(r.Ctx, receipt.TxHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + cctx := utils.WaitCctxMinedByInTxHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "withdraw") if cctx.CctxStatus.Status != crosschaintypes.CctxStatus_OutboundMined { panic("cctx status is not outbound mined") 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..f40c4d6b9f --- /dev/null +++ b/e2e/e2etests/test_rate_limiter.go @@ -0,0 +1,284 @@ +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" +) + +// WithdrawType is the type of withdraw to perform in the test +type withdrawType string + +const ( + withdrawTypeZETA withdrawType = "ZETA" + withdrawTypeETH withdrawType = "ETH" + withdrawTypeERC20 withdrawType = "ERC20" + + rateLimiterWithdrawNumber = 5 +) + +func TestRateLimiter(r *runner.E2ERunner, _ []string) { + r.Logger.Info("TestRateLimiter") + + // rateLimiterFlags are the rate limiter flags for the test + rateLimiterFlags := crosschaintypes.RateLimiterFlags{ + Enabled: true, + Rate: sdk.NewUint(1e17).MulUint64(5), // 0.5 ZETA this value is used so rate is reached + Window: 10, + Conversions: []crosschaintypes.Conversion{ + { + Zrc20: r.ETHZRC20Addr.Hex(), + Rate: sdk.NewDec(2), // 1 ETH = 2 ZETA + }, + { + Zrc20: r.ERC20ZRC20Addr.Hex(), + Rate: sdk.NewDec(1).QuoInt64(2), // 2 USDC = 1 ZETA + }, + }, + } + + // these are the amounts for the withdraws for the different types + // currently these are arbitrary values that can be fine-tuned for manual testing of rate limiter + // TODO: define more rigorous assertions with proper values + // https://github.com/zeta-chain/node/issues/2090 + zetaAmount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(3)) + ethAmount := big.NewInt(1e18) + erc20Amount := big.NewInt(1e6) + + // approve tokens for the tests + if err := approveTokens(r); err != nil { + panic(err) + } + + // 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, withdrawTypeZETA, zetaAmount); err != nil { + panic(err) + } + if err := createAndWaitWithdraws(r, withdrawTypeETH, ethAmount); err != nil { + panic(err) + } + if err := createAndWaitWithdraws(r, withdrawTypeERC20, erc20Amount); 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 and try again ZETA withdraws + r.Logger.Print("rate limiter disabled") + if err := createAndWaitWithdraws(r, withdrawTypeZETA, zetaAmount); err != nil { + panic(err) + } +} + +// createAndWaitWithdraws performs RateLimiterWithdrawNumber withdraws +func createAndWaitWithdraws(r *runner.E2ERunner, withdrawType withdrawType, withdrawAmount *big.Int) error { + startTime := time.Now() + + r.Logger.Print("starting %d %s withdraws", rateLimiterWithdrawNumber, withdrawType) + + // Perform RateLimiterWithdrawNumber withdraws to log time for completion + txs := make([]*ethtypes.Transaction, rateLimiterWithdrawNumber) + for i := 0; i < rateLimiterWithdrawNumber; i++ { + + // create a new withdraw depending on the type + switch withdrawType { + case withdrawTypeZETA: + txs[i] = r.WithdrawZeta(withdrawAmount, true) + case withdrawTypeETH: + txs[i] = r.WithdrawEther(withdrawAmount) + case withdrawTypeERC20: + txs[i] = r.WithdrawERC20(withdrawAmount) + default: + return fmt.Errorf("invalid withdraw type: %s", withdrawType) + } + } + + // 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 waitForWithdrawMined(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 +} + +// waitForWithdrawMined waits for a 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 waitForWithdrawMined(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, "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 +} + +// 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 +} + +// 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 +} + +// approveTokens approves the tokens for the tests +func approveTokens(r *runner.E2ERunner) error { + // deposit and approve 50 WZETA for the tests + approveAmount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(50)) + r.DepositAndApproveWZeta(approveAmount) + + // approve ETH for withdraws + tx, err := r.ETHZRC20.Approve(r.ZEVMAuth, r.ETHZRC20Addr, approveAmount) + if err != nil { + return fmt.Errorf("error approving ETH: %w", err) + } + r.Logger.EVMTransaction(*tx, "approve") + + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + if receipt.Status == 0 { + return fmt.Errorf("eth approve failed") + } + r.Logger.EVMReceipt(*receipt, "approve") + + // approve ETH for ERC20 withdraws (this is for the gas fees) + tx, err = r.ETHZRC20.Approve(r.ZEVMAuth, r.ERC20ZRC20Addr, approveAmount) + if err != nil { + return fmt.Errorf("error approving ERC20: %w", err) + } + + r.Logger.EVMTransaction(*tx, "approve") + + receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + if receipt.Status == 0 { + return fmt.Errorf("erc 20 approve failed") + } + r.Logger.EVMReceipt(*receipt, "approve") + + 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..20c31ff043 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,122 @@ 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 +} + +// WithdrawEther withdraws Ether from ZetaChain to the ZETA smart contract on EVM +func (runner *E2ERunner) WithdrawEther(amount *big.Int) *ethtypes.Transaction { + // withdraw + tx, err := runner.ETHZRC20.Withdraw(runner.ZEVMAuth, runner.DeployerAddress.Bytes(), amount) + if err != nil { + panic(err) + } + runner.Logger.EVMTransaction(*tx, "withdraw") + + receipt := utils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, tx, runner.Logger, runner.ReceiptTimeout) + if receipt.Status == 0 { + panic("withdraw failed") + } + runner.Logger.EVMReceipt(*receipt, "withdraw") + runner.Logger.ZRC20Withdrawal(runner.ETHZRC20, *receipt, "withdraw") + + return tx +} + +// WithdrawERC20 withdraws an ERC20 token from ZetaChain to the ZETA smart contract on EVM +func (runner *E2ERunner) WithdrawERC20(amount *big.Int) *ethtypes.Transaction { + tx, err := runner.ERC20ZRC20.Withdraw(runner.ZEVMAuth, runner.DeployerAddress.Bytes(), amount) + if err != nil { + panic(err) + } + runner.Logger.EVMTransaction(*tx, "withdraw") + + receipt := utils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, tx, runner.Logger, runner.ReceiptTimeout) + runner.Logger.Info("Receipt txhash %s status %d", receipt.TxHash, receipt.Status) + for _, log := range receipt.Logs { + event, err := runner.ERC20ZRC20.ParseWithdrawal(*log) + if err != nil { + continue + } + runner.Logger.Info( + " logs: from %s, to %x, value %d, gasfee %d", + event.From.Hex(), + event.To, + event.Value, + event.Gasfee, + ) + } + + 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/pkg/coin/coin.go b/pkg/coin/coin.go index 74bbca935b..a11f5b91bd 100644 --- a/pkg/coin/coin.go +++ b/pkg/coin/coin.go @@ -7,6 +7,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +func AzetaPerZeta() sdk.Dec { + return sdk.NewDec(1e18) +} + func GetCoinType(coin string) (CoinType, error) { coinInt, err := strconv.ParseInt(coin, 10, 32) if err != nil { diff --git a/pkg/coin/coin_test.go b/pkg/coin/coin_test.go index 6110c20538..be7808d7a2 100644 --- a/pkg/coin/coin_test.go +++ b/pkg/coin/coin_test.go @@ -7,6 +7,10 @@ import ( "github.com/stretchr/testify/require" ) +func Test_AzetaPerZeta(t *testing.T) { + require.Equal(t, sdk.NewDec(1e18), AzetaPerZeta()) +} + func Test_GetAzetaDecFromAmountInZeta(t *testing.T) { tt := []struct { name string diff --git a/proto/crosschain/query.proto b/proto/crosschain/query.proto index b017e2c523..07c65e4c31 100644 --- a/proto/crosschain/query.proto +++ b/proto/crosschain/query.proto @@ -270,7 +270,9 @@ message QueryListPendingCctxWithinRateLimitRequest { message QueryListPendingCctxWithinRateLimitResponse { repeated CrossChainTx cross_chain_tx = 1; uint64 total_pending = 2; - bool rate_limit_exceeded = 3; + int64 current_withdraw_window = 3; + string current_withdraw_rate = 4; + bool rate_limit_exceeded = 5; } message QueryLastZetaHeightRequest {} diff --git a/testutil/keeper/mocks/crosschain/fungible.go b/testutil/keeper/mocks/crosschain/fungible.go index 587c1676a6..a695f0d35d 100644 --- a/testutil/keeper/mocks/crosschain/fungible.go +++ b/testutil/keeper/mocks/crosschain/fungible.go @@ -254,12 +254,12 @@ func (_m *CrosschainFungibleKeeper) GetAllForeignCoinsForChain(ctx types.Context return r0 } -// GetAllForeignERC20CoinMap provides a mock function with given fields: ctx -func (_m *CrosschainFungibleKeeper) GetAllForeignERC20CoinMap(ctx types.Context) map[int64]map[string]fungibletypes.ForeignCoins { +// GetAllForeignCoinMap provides a mock function with given fields: ctx +func (_m *CrosschainFungibleKeeper) GetAllForeignCoinMap(ctx types.Context) map[int64]map[string]fungibletypes.ForeignCoins { ret := _m.Called(ctx) if len(ret) == 0 { - panic("no return value specified for GetAllForeignERC20CoinMap") + panic("no return value specified for GetAllForeignCoinMap") } var r0 map[int64]map[string]fungibletypes.ForeignCoins diff --git a/typescript/crosschain/query_pb.d.ts b/typescript/crosschain/query_pb.d.ts index 34b4a44f38..19877c30bd 100644 --- a/typescript/crosschain/query_pb.d.ts +++ b/typescript/crosschain/query_pb.d.ts @@ -910,7 +910,17 @@ export declare class QueryListPendingCctxWithinRateLimitResponse extends Message totalPending: bigint; /** - * @generated from field: bool rate_limit_exceeded = 3; + * @generated from field: int64 current_withdraw_window = 3; + */ + currentWithdrawWindow: bigint; + + /** + * @generated from field: string current_withdraw_rate = 4; + */ + currentWithdrawRate: string; + + /** + * @generated from field: bool rate_limit_exceeded = 5; */ rateLimitExceeded: boolean; 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.go b/x/crosschain/keeper/grpc_query_cctx.go index 2aeab7007a..688c9cb205 100644 --- a/x/crosschain/keeper/grpc_query_cctx.go +++ b/x/crosschain/keeper/grpc_query_cctx.go @@ -16,6 +16,9 @@ import ( const ( // MaxPendingCctxs is the maximum number of pending cctxs that can be queried MaxPendingCctxs = 500 + + // MaxLookbackNonce is the maximum number of nonces to look back to find missed pending cctxs + MaxLookbackNonce = 1000 ) func (k Keeper) ZetaAccounting(c context.Context, _ *types.QueryZetaAccountingRequest) (*types.QueryZetaAccountingResponse, error) { @@ -122,7 +125,7 @@ func (k Keeper) ListPendingCctx(c context.Context, req *types.QueryListPendingCc // now query the previous nonces up to 1000 prior to find any pending cctx that we might have missed // need this logic because a confirmation of higher nonce will automatically update the p.NonceLow // therefore might mask some lower nonce cctx that is still pending. - startNonce := pendingNonces.NonceLow - 1000 + startNonce := pendingNonces.NonceLow - MaxLookbackNonce if startNonce < 0 { startNonce = 0 } diff --git a/x/crosschain/keeper/grpc_query_cctx_rate_limit.go b/x/crosschain/keeper/grpc_query_cctx_rate_limit.go index b3b30ca939..b1aa54c46a 100644 --- a/x/crosschain/keeper/grpc_query_cctx_rate_limit.go +++ b/x/crosschain/keeper/grpc_query_cctx_rate_limit.go @@ -2,8 +2,10 @@ package keeper import ( "context" + "sort" "strings" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/x/crosschain/types" @@ -15,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") } @@ -30,7 +32,7 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que // define a few variables to be used in the query loops limitExceeded := false totalPending := uint64(0) - totalCctxValueInZeta := sdk.NewDec(0) + totalWithdrawInAzeta := sdkmath.NewInt(0) cctxs := make([]*types.CrossChainTx, 0) chains := k.zetaObserverKeeper.GetSupportedForeignChains(ctx) @@ -40,6 +42,9 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que if !found || !rateLimitFlags.Enabled { applyLimit = false } + if rateLimitFlags.Rate.IsNil() || rateLimitFlags.Rate.IsZero() { + applyLimit = false + } // fallback to non-rate-limited query if rate limiter is disabled if !applyLimit { @@ -68,7 +73,7 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que } // calculate the rate limiter sliding window left boundary (inclusive) - leftWindowBoundary := height - rateLimitFlags.Window + leftWindowBoundary := height - rateLimitFlags.Window + 1 if leftWindowBoundary < 0 { leftWindowBoundary = 0 } @@ -76,40 +81,76 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que // get the conversion rates for all foreign coins var gasCoinRates map[int64]sdk.Dec var erc20CoinRates map[int64]map[string]sdk.Dec - var erc20Coins map[int64]map[string]fungibletypes.ForeignCoins - var rateLimitInZeta sdk.Dec + var foreignCoinMap map[int64]map[string]fungibletypes.ForeignCoins + var blockLimitInAzeta sdkmath.Int + var windowLimitInAzeta sdkmath.Int if applyLimit { gasCoinRates, erc20CoinRates = k.GetRateLimiterRates(ctx) - erc20Coins = k.fungibleKeeper.GetAllForeignERC20CoinMap(ctx) - rateLimitInZeta = sdk.NewDecFromBigInt(rateLimitFlags.Rate.BigInt()) + foreignCoinMap = k.fungibleKeeper.GetAllForeignCoinMap(ctx) + + // initiate block limit and window limit in azeta + blockLimitInAzeta = sdkmath.NewIntFromBigInt(rateLimitFlags.Rate.BigInt()) + windowLimitInAzeta = blockLimitInAzeta.Mul(sdkmath.NewInt(rateLimitFlags.Window)) } // the criteria to stop adding cctxs to the rpc response - maxCCTXsReached := func() bool { + maxCCTXsReached := func(cctxs []*types.CrossChainTx) bool { // #nosec G701 len always positive return uint32(len(cctxs)) >= limit } - // query pending nonces for each foreign chain - // Note: The pending nonces could change during the RPC call, so query them beforehand - pendingNoncesMap := make(map[int64]*observertypes.PendingNonces) + // if a cctx falls within the rate limiter window + isCctxInWindow := func(cctx *types.CrossChainTx) bool { + // #nosec G701 checked positive + return cctx.InboundTxParams.InboundTxObservedExternalHeight >= uint64(leftWindowBoundary) + } + + // query pending nonces for each foreign chain and get the lowest height of the pending cctxs + lowestPendingCctxHeight := int64(0) + 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 + pendingNoncesMap[chain.ChainId] = pendingNonces + + // insert pending nonces and update lowest height + if pendingNonces.NonceLow < pendingNonces.NonceHigh { + cctx, err := getCctxByChainIDAndNonce(k, ctx, tss.TssPubkey, chain.ChainId, pendingNonces.NonceLow) + if err != nil { + return nil, err + } + // #nosec G701 len always in range + cctxHeight := int64(cctx.InboundTxParams.InboundTxObservedExternalHeight) + if lowestPendingCctxHeight == 0 || cctxHeight < lowestPendingCctxHeight { + lowestPendingCctxHeight = cctxHeight + } + } + } + + // invariant: for period of time >= `rateLimitFlags.Window`, the zetaclient-side average withdraw rate should be <= `blockLimitInZeta` + // otherwise, this query should return empty result and wait for the average rate to drop below `blockLimitInZeta` + withdrawWindow := rateLimitFlags.Window + withdrawLimitInAzeta := windowLimitInAzeta + if lowestPendingCctxHeight != 0 { + // `pendingCctxWindow` is the width of [lowestPendingCctxHeight, height] window + // if the window can be wider than `rateLimitFlags.Window`, we should adjust the total withdraw limit proportionally + pendingCctxWindow := height - lowestPendingCctxHeight + 1 + if pendingCctxWindow > rateLimitFlags.Window { + withdrawWindow = pendingCctxWindow + withdrawLimitInAzeta = blockLimitInAzeta.Mul(sdk.NewInt(pendingCctxWindow)) + } } // query backwards for potential missed pending cctxs for each foreign chain -LoopBackwards: for _, chain := range chains { // we should at least query 1000 prior to find any pending cctx that we might have missed // this logic is needed because a confirmation of higher nonce will automatically update the p.NonceLow // therefore might mask some lower nonce cctx that is still pending. pendingNonces := pendingNoncesMap[chain.ChainId] startNonce := pendingNonces.NonceLow - 1 - endNonce := pendingNonces.NonceLow - 1000 + endNonce := pendingNonces.NonceLow - MaxLookbackNonce if endNonce < 0 { endNonce = 0 } @@ -120,126 +161,140 @@ LoopBackwards: if err != nil { return nil, err } + inWindow := isCctxInWindow(cctx) - // We should at least go backwards by 1000 nonces to pick up missed pending cctxs - // We might go even further back if rate limiter is enabled and the endNonce hasn't hit the left window boundary yet - // There are two criteria to stop scanning backwards: - // criteria #1: we'll stop at the left window boundary if the `endNonce` hasn't hit it yet - // #nosec G701 always positive - if nonce < endNonce && cctx.InboundTxParams.InboundTxObservedExternalHeight < uint64(leftWindowBoundary) { + // we should at least go backwards by 1000 nonces to pick up missed pending cctxs + // we might go even further back if rate limiter is enabled and the endNonce hasn't hit the left window boundary yet + // stop at the left window boundary if the `endNonce` hasn't hit it yet + if nonce < endNonce && !inWindow { break } - // criteria #2: we should finish the RPC call if the rate limit is exceeded - if rateLimitExceeded(chain.ChainId, cctx, gasCoinRates, erc20CoinRates, erc20Coins, &totalCctxValueInZeta, rateLimitInZeta) { + // skip the cctx if rate limit is exceeded but still accumulate the total withdraw value + if inWindow && rateLimitExceeded(chain.ChainId, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap, &totalWithdrawInAzeta, withdrawLimitInAzeta) { limitExceeded = true - break LoopBackwards + continue } // only take a `limit` number of pending cctxs as result but still count the total pending cctxs if IsPending(cctx) { totalPending++ - if !maxCCTXsReached() { + if !maxCCTXsReached(cctxs) { cctxs = append(cctxs, cctx) } } } - - // add the pending nonces to the total pending - // Note: the `totalPending` may not be accurate only if the rate limiter triggers early exit - // `totalPending` is now used for metrics only and it's okay to trade off accuracy for performance - // #nosec G701 always in range - totalPending += uint64(pendingNonces.NonceHigh - pendingNonces.NonceLow) } + // remember the number of missed pending cctxs + missedPending := len(cctxs) + // query forwards for pending cctxs for each foreign chain -LoopForwards: 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 { return nil, err } - // only take a `limit` number of pending cctxs as result - if maxCCTXsReached() { - break LoopForwards - } - // criteria #2: we should finish the RPC call if the rate limit is exceeded - if rateLimitExceeded(chain.ChainId, cctx, gasCoinRates, erc20CoinRates, erc20Coins, &totalCctxValueInZeta, rateLimitInZeta) { + // skip the cctx if rate limit is exceeded but still accumulate the total withdraw value + if rateLimitExceeded(chain.ChainId, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap, &totalWithdrawInAzeta, withdrawLimitInAzeta) { limitExceeded = true - break LoopForwards + continue + } + // only take a `limit` number of pending cctxs as result + if maxCCTXsReached(cctxs) { + continue } cctxs = append(cctxs, cctx) } } + // if the rate limit is exceeded, only return the missed pending cctxs + if limitExceeded { + cctxs = cctxs[:missedPending] + } + + // sort the cctxs by chain ID and nonce (lower nonce holds higher priority for scheduling) + sort.Slice(cctxs, func(i, j int) bool { + if cctxs[i].GetCurrentOutTxParam().ReceiverChainId == cctxs[j].GetCurrentOutTxParam().ReceiverChainId { + return cctxs[i].GetCurrentOutTxParam().OutboundTxTssNonce < cctxs[j].GetCurrentOutTxParam().OutboundTxTssNonce + } + return cctxs[i].GetCurrentOutTxParam().ReceiverChainId < cctxs[j].GetCurrentOutTxParam().ReceiverChainId + }) + return &types.QueryListPendingCctxWithinRateLimitResponse{ - CrossChainTx: cctxs, - TotalPending: totalPending, - RateLimitExceeded: limitExceeded, + CrossChainTx: cctxs, + TotalPending: totalPending, + CurrentWithdrawWindow: withdrawWindow, + CurrentWithdrawRate: totalWithdrawInAzeta.Quo(sdk.NewInt(withdrawWindow)).String(), + RateLimitExceeded: limitExceeded, }, nil } -// convertCctxValue converts the value of the cctx in ZETA using given conversion rates -func convertCctxValue( +// ConvertCctxValue converts the value of the cctx to azeta using given conversion rates +func ConvertCctxValue( chainID int64, cctx *types.CrossChainTx, gasCoinRates map[int64]sdk.Dec, erc20CoinRates map[int64]map[string]sdk.Dec, - erc20Coins map[int64]map[string]fungibletypes.ForeignCoins, -) sdk.Dec { + foreignCoinMap map[int64]map[string]fungibletypes.ForeignCoins, +) sdkmath.Int { var rate sdk.Dec var decimals uint64 switch cctx.InboundTxParams.CoinType { case coin.CoinType_Zeta: // no conversion needed for ZETA - rate = sdk.NewDec(1) + return sdkmath.NewIntFromBigInt(cctx.GetCurrentOutTxParam().Amount.BigInt()) case coin.CoinType_Gas: rate = gasCoinRates[chainID] case coin.CoinType_ERC20: - // get the ERC20 coin decimals - _, found := erc20Coins[chainID] - if !found { - // skip if no coin found for this chainID - return sdk.NewDec(0) - } - fCoin, found := erc20Coins[chainID][strings.ToLower(cctx.InboundTxParams.Asset)] - if !found { - // skip if no coin found for this Asset - return sdk.NewDec(0) - } - // #nosec G701 always in range - decimals = uint64(fCoin.Decimals) - // get the ERC20 coin rate - _, found = erc20CoinRates[chainID] + _, found := erc20CoinRates[chainID] if !found { // skip if no rate found for this chainID - return sdk.NewDec(0) + return sdkmath.NewInt(0) } rate = erc20CoinRates[chainID][strings.ToLower(cctx.InboundTxParams.Asset)] default: // skip CoinType_Cmd - return sdk.NewDec(0) + return sdkmath.NewInt(0) } - // should not happen, return 0 to skip if it happens - if rate.LTE(sdk.NewDec(0)) { - return sdk.NewDec(0) + if rate.IsNil() || rate.LTE(sdk.NewDec(0)) { + return sdkmath.NewInt(0) + } + + // get foreign coin decimals + foreignCoinFromChainMap, found := foreignCoinMap[chainID] + if !found { + // skip if no coin found for this chainID + return sdkmath.NewInt(0) + } + foreignCoin, found := foreignCoinFromChainMap[strings.ToLower(cctx.InboundTxParams.Asset)] + if !found { + // skip if no coin found for this Asset + return sdkmath.NewInt(0) } + decimals = uint64(foreignCoin.Decimals) - // the reciprocal of `rate` is the amount of zrc20 needed to buy 1 ZETA - // for example, given rate = 0.8, the reciprocal is 1.25, which means 1.25 ZRC20 can buy 1 ZETA - // given decimals = 6, the `oneZeta` amount will be 1.25 * 10^6 = 1250000 - oneZrc20 := sdk.NewDec(1).Power(decimals) - oneZeta := oneZrc20.Quo(rate) + // the whole coin amounts of zeta and zrc20 + // given decimals = 6, the amount will be 10^6 = 1000000 + oneZeta := coin.AzetaPerZeta() + oneZrc20 := sdk.NewDec(10).Power(decimals) - // convert asset amount into ZETA - amountCctx := sdk.NewDecFromBigInt(cctx.GetCurrentOutTxParam().Amount.BigInt()) - amountZeta := amountCctx.Quo(oneZeta) - return amountZeta + // convert cctx asset amount into azeta amount + // given amountCctx = 2000000, rate = 0.8, decimals = 6 + // amountCctxDec: 2000000 * 0.8 = 1600000.0 + // amountAzetaDec: 1600000.0 * 10e18 / 10e6 = 1600000000000000000.0 + amountCctxDec := sdk.NewDecFromBigInt(cctx.GetCurrentOutTxParam().Amount.BigInt()) + amountAzetaDec := amountCctxDec.Mul(rate).Mul(oneZeta).Quo(oneZrc20) + return amountAzetaDec.TruncateInt() } // rateLimitExceeded accumulates the cctx value and then checks if the rate limit is exceeded @@ -249,11 +304,11 @@ func rateLimitExceeded( cctx *types.CrossChainTx, gasCoinRates map[int64]sdk.Dec, erc20CoinRates map[int64]map[string]sdk.Dec, - erc20Coins map[int64]map[string]fungibletypes.ForeignCoins, - currentCctxValue *sdk.Dec, - rateLimitValue sdk.Dec, + foreignCoinMap map[int64]map[string]fungibletypes.ForeignCoins, + currentCctxValue *sdkmath.Int, + withdrawLimitInAzeta sdkmath.Int, ) bool { - amountZeta := convertCctxValue(chainID, cctx, gasCoinRates, erc20CoinRates, erc20Coins) - *currentCctxValue = currentCctxValue.Add(amountZeta) - return currentCctxValue.GT(rateLimitValue) + cctxValueAzeta := ConvertCctxValue(chainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) + *currentCctxValue = currentCctxValue.Add(cctxValueAzeta) + return currentCctxValue.GT(withdrawLimitInAzeta) } diff --git a/x/crosschain/keeper/grpc_query_cctx_rate_limit_test.go b/x/crosschain/keeper/grpc_query_cctx_rate_limit_test.go index 31939862b5..6418e57c03 100644 --- a/x/crosschain/keeper/grpc_query_cctx_rate_limit_test.go +++ b/x/crosschain/keeper/grpc_query_cctx_rate_limit_test.go @@ -2,7 +2,7 @@ package keeper_test import ( "fmt" - "sort" + "strings" "testing" "cosmossdk.io/math" @@ -16,19 +16,29 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +var ( + // local eth chain ID + ethChainID = getValidEthChainID() + + // local btc chain ID + btcChainID = getValidBtcChainID() +) + // createTestRateLimiterFlags creates a custom rate limiter flags func createTestRateLimiterFlags( + window int64, + rate math.Uint, zrc20ETH string, zrc20BTC string, zrc20USDT string, ethRate string, btcRate string, usdtRate string, -) types.RateLimiterFlags { - var rateLimiterFlags = types.RateLimiterFlags{ +) *types.RateLimiterFlags { + return &types.RateLimiterFlags{ Enabled: true, - Window: 100, // 100 zeta blocks, 10 minutes - Rate: math.NewUint(1000), // 1000 ZETA + Window: window, // for instance: 500 zeta blocks, 50 minutes + Rate: rate, Conversions: []types.Conversion{ // ETH { @@ -47,19 +57,14 @@ func createTestRateLimiterFlags( }, }, } - return rateLimiterFlags } -// createCctxWithCopyTypeAndBlockRange +// createCctxsWithCoinTypeAndHeightRange // - create 1 cctx per block from lowBlock to highBlock (inclusive) // // return created cctxs -func createCctxWithCopyTypeAndHeightRange( +func createCctxsWithCoinTypeAndHeightRange( t *testing.T, - ctx sdk.Context, - k keeper.Keeper, - zk keepertest.ZetaKeepers, - tss observertypes.TSS, lowBlock uint64, highBlock uint64, chainID int64, @@ -77,36 +82,32 @@ func createCctxWithCopyTypeAndHeightRange( cctx.InboundTxParams.CoinType = coinType cctx.InboundTxParams.Asset = asset cctx.InboundTxParams.InboundTxObservedExternalHeight = i + cctx.GetCurrentOutTxParam().ReceiverChainId = chainID cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(amount) cctx.GetCurrentOutTxParam().OutboundTxTssNonce = nonce - k.SetCrossChainTx(ctx, *cctx) - zk.ObserverKeeper.SetNonceToCctx(ctx, observertypes.NonceToCctx{ - ChainId: chainID, - // #nosec G701 always in range for tests - Nonce: int64(nonce), - CctxIndex: cctx.Index, - Tss: tss.TssPubkey, - }) cctxs = append(cctxs, cctx) } return cctxs } -// setPendingNonces sets the pending nonces for the given chainID -func setPendingNonces( +// setCctxsInKeeper sets the given cctxs to the keeper +func setCctxsInKeeper( ctx sdk.Context, + k keeper.Keeper, zk keepertest.ZetaKeepers, - chainID int64, - nonceLow int64, - nonceHigh int64, - tssPubKey string, + tss observertypes.TSS, + cctxs []*types.CrossChainTx, ) { - zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ - ChainId: chainID, - NonceLow: nonceLow, - NonceHigh: nonceHigh, - Tss: tssPubKey, - }) + for _, cctx := range cctxs { + k.SetCrossChainTx(ctx, *cctx) + zk.ObserverKeeper.SetNonceToCctx(ctx, observertypes.NonceToCctx{ + ChainId: cctx.InboundTxParams.SenderChainId, + // #nosec G701 always in range for tests + Nonce: int64(cctx.GetCurrentOutTxParam().OutboundTxTssNonce), + CctxIndex: cctx.Index, + Tss: tss.TssPubkey, + }) + } } // setupForeignCoins adds ETH, BTC, USDT to the foreign coins store @@ -123,60 +124,498 @@ func setupForeignCoins( } } -func TestKeeper_ListPendingCctxWithinRateLimit(t *testing.T) { +func Test_ConvertCctxValue(t *testing.T) { // chain IDs ethChainID := getValidEthChainID() + btcChainID := getValidBtcChainID() + + // zrc20 addresses for ETH, BTC, USDT and asset for USDT + zrc20ETH := sample.EthAddress().Hex() + zrc20BTC := sample.EthAddress().Hex() + zrc20USDT := sample.EthAddress().Hex() + assetUSDT := sample.EthAddress().Hex() + + k, ctx, _, zk := keepertest.CrosschainKeeper(t) + + // Set TSS + tss := sample.Tss() + zk.ObserverKeeper.SetTSS(ctx, tss) + + // Set foreign coins + setupForeignCoins(t, ctx, zk, zrc20ETH, zrc20BTC, zrc20USDT, assetUSDT) + + // Set rate limiter flags + rateLimiterFlags := createTestRateLimiterFlags(500, math.NewUint(10), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8") + k.SetRateLimiterFlags(ctx, *rateLimiterFlags) + + // get rate limiter rates + gasCoinRates, erc20CoinRates := k.GetRateLimiterRates(ctx) + foreignCoinMap := zk.FungibleKeeper.GetAllForeignCoinMap(ctx) + + t.Run("should convert cctx ZETA value correctly", func(t *testing.T) { + // create cctx with 0.3 ZETA + cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) + cctx.InboundTxParams.CoinType = coin.CoinType_Zeta + cctx.InboundTxParams.Asset = "" + cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(3e17) // 0.3 ZETA + + // convert cctx value + value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) + require.Equal(t, sdk.NewInt(3e17), value) + }) + t.Run("should convert cctx ETH value correctly", func(t *testing.T) { + // create cctx with 0.003 ETH + cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) + cctx.InboundTxParams.CoinType = coin.CoinType_Gas + cctx.InboundTxParams.Asset = "" + cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(3e15) // 0.003 ETH + + // convert cctx value: 0.003 ETH * 2500 = 7.5 ZETA + value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) + require.Equal(t, sdk.NewInt(75e17), value) + }) + t.Run("should convert cctx BTC value correctly", func(t *testing.T) { + // create cctx with 0.0007 BTC + cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", btcChainID, 1)) + cctx.InboundTxParams.CoinType = coin.CoinType_Gas + cctx.InboundTxParams.Asset = "" + cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(70000) // 0.0007 BTC + + // convert cctx value: 0.0007 BTC * 50000 = 35.0 ZETA + value := keeper.ConvertCctxValue(btcChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) + require.Equal(t, sdk.NewInt(35).Mul(sdk.NewInt(1e18)), value) + }) + t.Run("should convert cctx USDT value correctly", func(t *testing.T) { + // create cctx with 3 USDT + cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) + cctx.InboundTxParams.CoinType = coin.CoinType_ERC20 + cctx.InboundTxParams.Asset = assetUSDT + cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(3e6) // 3 USDT + + // convert cctx value: 3 USDT * 0.8 = 2.4 ZETA + value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) + require.Equal(t, sdk.NewInt(24e17), value) + }) + t.Run("should return 0 if no rate found for chainID", func(t *testing.T) { + cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) + cctx.InboundTxParams.CoinType = coin.CoinType_ERC20 + cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100) + + // use nil erc20CoinRates map to convert cctx value + value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, nil, foreignCoinMap) + require.Equal(t, sdk.NewInt(0), value) + }) + t.Run("should return 0 if coinType is CoinType_Cmd", func(t *testing.T) { + cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) + cctx.InboundTxParams.CoinType = coin.CoinType_Cmd + cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100) + + // convert cctx value + value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) + require.Equal(t, sdk.NewInt(0), value) + }) + t.Run("should return 0 on nil rate or rate <= 0", func(t *testing.T) { + cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) + cctx.InboundTxParams.CoinType = coin.CoinType_Gas + cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100) + + // use nil gasCoinRates map to convert cctx value + value := keeper.ConvertCctxValue(ethChainID, cctx, nil, erc20CoinRates, foreignCoinMap) + require.Equal(t, sdk.NewInt(0), value) + + // set rate to 0 + zeroCoinRates, _ := k.GetRateLimiterRates(ctx) + zeroCoinRates[ethChainID] = sdk.NewDec(0) + + // convert cctx value + value = keeper.ConvertCctxValue(ethChainID, cctx, zeroCoinRates, erc20CoinRates, foreignCoinMap) + require.Equal(t, sdk.NewInt(0), value) + + // set rate to -1 + negativeCoinRates, _ := k.GetRateLimiterRates(ctx) + negativeCoinRates[ethChainID] = sdk.NewDec(-1) + + // convert cctx value + value = keeper.ConvertCctxValue(ethChainID, cctx, negativeCoinRates, erc20CoinRates, foreignCoinMap) + require.Equal(t, sdk.NewInt(0), value) + }) + t.Run("should return 0 if no coin found for chainID", func(t *testing.T) { + cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) + cctx.InboundTxParams.CoinType = coin.CoinType_Gas + cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100) + + // use empty foreignCoinMap to convert cctx value + value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, nil) + require.Equal(t, sdk.NewInt(0), value) + }) + t.Run("should return 0 if no coin found for asset", func(t *testing.T) { + cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) + cctx.InboundTxParams.CoinType = coin.CoinType_ERC20 + cctx.InboundTxParams.Asset = assetUSDT + cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100) + + // delete assetUSDT from foreignCoinMap for ethChainID + tempCoinMap := zk.FungibleKeeper.GetAllForeignCoinMap(ctx) + delete(tempCoinMap[ethChainID], strings.ToLower(assetUSDT)) + + // convert cctx value + value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, tempCoinMap) + require.Equal(t, sdk.NewInt(0), value) + }) +} + +func TestKeeper_ListPendingCctxWithinRateLimit(t *testing.T) { + // create sample TSS + tss := sample.Tss() + + // create sample zrc20 addresses for ETH, BTC, USDT + zrc20ETH := sample.EthAddress().Hex() + zrc20BTC := sample.EthAddress().Hex() + zrc20USDT := sample.EthAddress().Hex() + + // create Eth chain 999 mined and 200 pending cctxs for rate limiter test + // the number 999 is to make it less than `MaxLookbackNonce` so the LoopBackwards gets the chance to hit nonce 0 + ethMinedCctxs := createCctxsWithCoinTypeAndHeightRange(t, 1, 999, ethChainID, coin.CoinType_Gas, "", uint64(1e15), types.CctxStatus_OutboundMined) + ethPendingCctxs := createCctxsWithCoinTypeAndHeightRange(t, 1000, 1199, ethChainID, coin.CoinType_Gas, "", uint64(1e15), types.CctxStatus_PendingOutbound) - // define cctx status - statusPending := types.CctxStatus_PendingOutbound - statusMined := types.CctxStatus_OutboundMined + // create Btc chain 999 mined and 200 pending cctxs for rate limiter test + // the number 999 is to make it less than `MaxLookbackNonce` so the LoopBackwards gets the chance to hit nonce 0 + btcMinedCctxs := createCctxsWithCoinTypeAndHeightRange(t, 1, 999, btcChainID, coin.CoinType_Gas, "", 1000, types.CctxStatus_OutboundMined) + btcPendingCctxs := createCctxsWithCoinTypeAndHeightRange(t, 1000, 1199, btcChainID, coin.CoinType_Gas, "", 1000, types.CctxStatus_PendingOutbound) + // define test cases + tests := []struct { + name string + fallback bool + rateLimitFlags *types.RateLimiterFlags + + // Eth chain cctxs setup + ethMinedCctxs []*types.CrossChainTx + ethPendingCctxs []*types.CrossChainTx + ethPendingNonces observertypes.PendingNonces + + // Btc chain cctxs setup + btcMinedCctxs []*types.CrossChainTx + btcPendingCctxs []*types.CrossChainTx + btcPendingNonces observertypes.PendingNonces + + // current block height and limit + currentHeight int64 + queryLimit uint32 + + // expected results + expectedCctxs []*types.CrossChainTx + expectedTotalPending uint64 + expectedWithdrawWindow int64 + expectedWithdrawRate string + rateLimitExceeded bool + }{ + { + name: "should use fallback query if rate limiter is disabled", + fallback: true, + rateLimitFlags: nil, // no rate limiter flags set in the keeper + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, + queryLimit: keeper.MaxPendingCctxs, + expectedCctxs: append(append([]*types.CrossChainTx{}, btcPendingCctxs...), ethPendingCctxs...), + expectedTotalPending: 400, + }, + { + name: "should use fallback query if rate is 0", + fallback: true, + rateLimitFlags: createTestRateLimiterFlags(500, math.NewUint(0), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8"), + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, + queryLimit: keeper.MaxPendingCctxs, + expectedCctxs: append(append([]*types.CrossChainTx{}, btcPendingCctxs...), ethPendingCctxs...), + expectedTotalPending: 400, + }, + { + name: "can retrieve all pending cctx without exceeding rate limit", + rateLimitFlags: createTestRateLimiterFlags(500, math.NewUint(10*1e18), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8"), + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, + queryLimit: keeper.MaxPendingCctxs, + expectedCctxs: append(append([]*types.CrossChainTx{}, ethPendingCctxs...), btcPendingCctxs...), + expectedTotalPending: 400, + expectedWithdrawWindow: 500, // the sliding window + expectedWithdrawRate: sdk.NewInt(3e18).String(), // 3 ZETA, (2.5 + 0.5) per block + rateLimitExceeded: false, + }, + { + name: "can loop backwards all the way to endNonce 0", + rateLimitFlags: createTestRateLimiterFlags(500, math.NewUint(10*1e18), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8"), + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 999, // endNonce will be set to 0 (NonceLow - 1000 < 0) + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 999, // endNonce will be set to 0 (NonceLow - 1000 < 0) + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, + queryLimit: keeper.MaxPendingCctxs, + expectedCctxs: append(append([]*types.CrossChainTx{}, ethPendingCctxs...), btcPendingCctxs...), + expectedTotalPending: 400, + expectedWithdrawWindow: 500, // the sliding window + expectedWithdrawRate: sdk.NewInt(3e18).String(), // 3 ZETA, (2.5 + 0.5) per block + rateLimitExceeded: false, + }, + { + name: "set a low rate (rate < 2.4 ZETA) to exceed rate limit in backward loop", + rateLimitFlags: createTestRateLimiterFlags(500, math.NewUint(2*1e18), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8"), + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, + queryLimit: keeper.MaxPendingCctxs, + // return missed cctxs only if rate limit is exceeded + expectedCctxs: append(append([]*types.CrossChainTx{}, ethPendingCctxs[0:100]...), btcPendingCctxs[0:100]...), + expectedTotalPending: 400, + expectedWithdrawWindow: 500, // the sliding window + expectedWithdrawRate: sdk.NewInt(3e18).String(), // 3 ZETA, (2.5 + 0.5) per block + rateLimitExceeded: true, + }, + { + name: "set a lower gRPC request limit and reach the limit of the query in forward loop", + rateLimitFlags: createTestRateLimiterFlags(500, math.NewUint(10*1e18), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8"), + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, + queryLimit: 300, // 300 < keeper.MaxPendingCctxs + expectedCctxs: append(append([]*types.CrossChainTx{}, ethPendingCctxs[0:100]...), btcPendingCctxs...), + expectedTotalPending: 400, + expectedWithdrawWindow: 500, // the sliding window + expectedWithdrawRate: sdk.NewInt(3e18).String(), // 3 ZETA, (2.5 + 0.5) per block + rateLimitExceeded: false, + }, + { + name: "set a median rate (2.4 ZETA < rate < 3 ZETA) to exceed rate limit in forward loop", + rateLimitFlags: createTestRateLimiterFlags(500, math.NewUint(26*1e17), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8"), + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, + queryLimit: keeper.MaxPendingCctxs, + // return missed cctxs only if rate limit is exceeded + expectedCctxs: append(append([]*types.CrossChainTx{}, ethPendingCctxs[0:100]...), btcPendingCctxs[0:100]...), + expectedTotalPending: 400, + expectedWithdrawWindow: 500, // the sliding window + expectedWithdrawRate: sdk.NewInt(3e18).String(), // 3 ZETA, (2.5 + 0.5) per block + rateLimitExceeded: true, + }, + { + // the pending cctxs window is wider than the rate limiter sliding window in this test case. + name: "set low rate and narrow window to early exceed rate limit in forward loop", + // the left boundary will be 1149 (currentHeight-50), the pending nonces [1099, 1148] fall beyond the left boundary. + // `pendingCctxWindow` is 100 which is wider than rate limiter window 50. + // give a block rate of 2 ZETA/block, the max value allowed should be 100 * 2 = 200 ZETA + rateLimitFlags: createTestRateLimiterFlags(50, math.NewUint(2*1e18), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8"), + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, + queryLimit: keeper.MaxPendingCctxs, + // return missed cctxs only if rate limit is exceeded + expectedCctxs: append(append([]*types.CrossChainTx{}, ethPendingCctxs[0:100]...), btcPendingCctxs[0:100]...), + expectedTotalPending: 400, + expectedWithdrawWindow: 100, // 100 > sliding window 50 + expectedWithdrawRate: sdk.NewInt(3e18).String(), // 3 ZETA, (2.5 + 0.5) per block + rateLimitExceeded: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // create test keepers + k, ctx, _, zk := keepertest.CrosschainKeeper(t) + + // Set TSS + zk.ObserverKeeper.SetTSS(ctx, tss) + + // Set foreign coins + assetUSDT := sample.EthAddress().Hex() + setupForeignCoins(t, ctx, zk, zrc20ETH, zrc20BTC, zrc20USDT, assetUSDT) + + // Set rate limiter flags + if tt.rateLimitFlags != nil { + k.SetRateLimiterFlags(ctx, *tt.rateLimitFlags) + } + + // Set Eth chain mined cctxs, pending ccxts and pending nonces + setCctxsInKeeper(ctx, *k, zk, tss, tt.ethMinedCctxs) + setCctxsInKeeper(ctx, *k, zk, tss, tt.ethPendingCctxs) + zk.ObserverKeeper.SetPendingNonces(ctx, tt.ethPendingNonces) + + // Set Btc chain mined cctxs, pending ccxts and pending nonces + setCctxsInKeeper(ctx, *k, zk, tss, tt.btcMinedCctxs) + setCctxsInKeeper(ctx, *k, zk, tss, tt.btcPendingCctxs) + zk.ObserverKeeper.SetPendingNonces(ctx, tt.btcPendingNonces) + + // Set current block height + ctx = ctx.WithBlockHeight(tt.currentHeight) + + // Query pending cctxs + res, err := k.ListPendingCctxWithinRateLimit(ctx, &types.QueryListPendingCctxWithinRateLimitRequest{Limit: tt.queryLimit}) + require.NoError(t, err) + require.EqualValues(t, tt.expectedCctxs, res.CrossChainTx) + require.Equal(t, tt.expectedTotalPending, res.TotalPending) + + // check rate limiter related fields only if it's not a fallback query + if !tt.fallback { + require.Equal(t, tt.expectedWithdrawWindow, res.CurrentWithdrawWindow) + require.Equal(t, tt.expectedWithdrawRate, res.CurrentWithdrawRate) + require.Equal(t, tt.rateLimitExceeded, res.RateLimitExceeded) + } + }) + } +} + +func TestKeeper_ListPendingCctxWithinRateLimit_Errors(t *testing.T) { t.Run("should fail for empty req", func(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeper(t) _, err := k.ListPendingCctxWithinRateLimit(ctx, nil) require.ErrorContains(t, err, "invalid request") }) - t.Run("should use fallback query", func(t *testing.T) { - k, ctx, _, zk := keepertest.CrosschainKeeper(t) - - // Set TSS - tss := sample.Tss() - zk.ObserverKeeper.SetTSS(ctx, tss) + t.Run("height out of range", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeper(t) // Set rate limiter flags as disabled - rateLimiterFlags := sample.RateLimiterFlags() - rateLimiterFlags.Enabled = false - k.SetRateLimiterFlags(ctx, rateLimiterFlags) + rFlags := sample.RateLimiterFlags() + k.SetRateLimiterFlags(ctx, rFlags) - // Create cctxs [0~999] and [1000~1199] for Eth chain, 0.001 ETH per cctx - _ = createCctxWithCopyTypeAndHeightRange(t, ctx, *k, zk, tss, 1, 1000, ethChainID, coin.CoinType_Gas, "", uint64(1e15), statusMined) - cctxETH := createCctxWithCopyTypeAndHeightRange(t, ctx, *k, zk, tss, 1001, 1200, ethChainID, coin.CoinType_Gas, "", uint64(1e15), statusPending) + ctx = ctx.WithBlockHeight(0) + _, err := k.ListPendingCctxWithinRateLimit(ctx, &types.QueryListPendingCctxWithinRateLimitRequest{}) + require.ErrorContains(t, err, "height out of range") + }) + t.Run("tss not found", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeper(t) - // Set Eth chain pending nonces which contains 100 missing cctxs - setPendingNonces(ctx, zk, ethChainID, 1100, 1200, tss.TssPubkey) + // Set rate limiter flags as disabled + rFlags := sample.RateLimiterFlags() + k.SetRateLimiterFlags(ctx, rFlags) - // Query pending cctxs use small limit - res, err := k.ListPendingCctxWithinRateLimit(ctx, &types.QueryListPendingCctxWithinRateLimitRequest{Limit: 100}) - require.NoError(t, err) - require.Equal(t, 100, len(res.CrossChainTx)) + _, err := k.ListPendingCctxWithinRateLimit(ctx, &types.QueryListPendingCctxWithinRateLimitRequest{}) + require.ErrorContains(t, err, "tss not found") + }) + t.Run("pending nonces not found", func(t *testing.T) { + k, ctx, _, zk := keepertest.CrosschainKeeper(t) - // sort res.CrossChainTx by outbound nonce ascending so that we can compare with cctxETH - sort.Slice(res.CrossChainTx, func(i, j int) bool { - return res.CrossChainTx[i].GetCurrentOutTxParam().OutboundTxTssNonce < res.CrossChainTx[j].GetCurrentOutTxParam().OutboundTxTssNonce - }) - require.EqualValues(t, cctxETH[:100], res.CrossChainTx) - require.EqualValues(t, uint64(200), res.TotalPending) + // Set rate limiter flags as disabled + rFlags := sample.RateLimiterFlags() + k.SetRateLimiterFlags(ctx, rFlags) - // Query pending cctxs use max limit - res, err = k.ListPendingCctxWithinRateLimit(ctx, &types.QueryListPendingCctxWithinRateLimitRequest{Limit: keeper.MaxPendingCctxs}) - require.NoError(t, err) - require.Equal(t, 200, len(res.CrossChainTx)) + // Set TSS + tss := sample.Tss() + zk.ObserverKeeper.SetTSS(ctx, tss) - // sort res.CrossChainTx by outbound nonce ascending so that we can compare with cctxETH - sort.Slice(res.CrossChainTx, func(i, j int) bool { - return res.CrossChainTx[i].GetCurrentOutTxParam().OutboundTxTssNonce < res.CrossChainTx[j].GetCurrentOutTxParam().OutboundTxTssNonce - }) - require.EqualValues(t, cctxETH, res.CrossChainTx) - require.EqualValues(t, uint64(200), res.TotalPending) + _, err := k.ListPendingCctxWithinRateLimit(ctx, &types.QueryListPendingCctxWithinRateLimitRequest{}) + require.ErrorContains(t, err, "pending nonces not found") }) } diff --git a/x/crosschain/types/expected_keepers.go b/x/crosschain/types/expected_keepers.go index af4f580794..fb03a86281 100644 --- a/x/crosschain/types/expected_keepers.go +++ b/x/crosschain/types/expected_keepers.go @@ -96,7 +96,7 @@ type ObserverKeeper interface { type FungibleKeeper interface { GetForeignCoins(ctx sdk.Context, zrc20Addr string) (val fungibletypes.ForeignCoins, found bool) GetAllForeignCoins(ctx sdk.Context) (list []fungibletypes.ForeignCoins) - GetAllForeignERC20CoinMap(ctx sdk.Context) map[int64]map[string]fungibletypes.ForeignCoins + GetAllForeignCoinMap(ctx sdk.Context) map[int64]map[string]fungibletypes.ForeignCoins SetForeignCoins(ctx sdk.Context, foreignCoins fungibletypes.ForeignCoins) GetAllForeignCoinsForChain(ctx sdk.Context, foreignChainID int64) (list []fungibletypes.ForeignCoins) GetForeignCoinFromAsset(ctx sdk.Context, asset string, chainID int64) (fungibletypes.ForeignCoins, bool) diff --git a/x/crosschain/types/query.pb.go b/x/crosschain/types/query.pb.go index 879dcce901..8fce000ace 100644 --- a/x/crosschain/types/query.pb.go +++ b/x/crosschain/types/query.pb.go @@ -1636,9 +1636,11 @@ func (m *QueryListPendingCctxWithinRateLimitRequest) GetLimit() uint32 { } type QueryListPendingCctxWithinRateLimitResponse struct { - CrossChainTx []*CrossChainTx `protobuf:"bytes,1,rep,name=cross_chain_tx,json=crossChainTx,proto3" json:"cross_chain_tx,omitempty"` - TotalPending uint64 `protobuf:"varint,2,opt,name=total_pending,json=totalPending,proto3" json:"total_pending,omitempty"` - RateLimitExceeded bool `protobuf:"varint,3,opt,name=rate_limit_exceeded,json=rateLimitExceeded,proto3" json:"rate_limit_exceeded,omitempty"` + CrossChainTx []*CrossChainTx `protobuf:"bytes,1,rep,name=cross_chain_tx,json=crossChainTx,proto3" json:"cross_chain_tx,omitempty"` + TotalPending uint64 `protobuf:"varint,2,opt,name=total_pending,json=totalPending,proto3" json:"total_pending,omitempty"` + CurrentWithdrawWindow int64 `protobuf:"varint,3,opt,name=current_withdraw_window,json=currentWithdrawWindow,proto3" json:"current_withdraw_window,omitempty"` + CurrentWithdrawRate string `protobuf:"bytes,4,opt,name=current_withdraw_rate,json=currentWithdrawRate,proto3" json:"current_withdraw_rate,omitempty"` + RateLimitExceeded bool `protobuf:"varint,5,opt,name=rate_limit_exceeded,json=rateLimitExceeded,proto3" json:"rate_limit_exceeded,omitempty"` } func (m *QueryListPendingCctxWithinRateLimitResponse) Reset() { @@ -1692,6 +1694,20 @@ func (m *QueryListPendingCctxWithinRateLimitResponse) GetTotalPending() uint64 { return 0 } +func (m *QueryListPendingCctxWithinRateLimitResponse) GetCurrentWithdrawWindow() int64 { + if m != nil { + return m.CurrentWithdrawWindow + } + return 0 +} + +func (m *QueryListPendingCctxWithinRateLimitResponse) GetCurrentWithdrawRate() string { + if m != nil { + return m.CurrentWithdrawRate + } + return "" +} + func (m *QueryListPendingCctxWithinRateLimitResponse) GetRateLimitExceeded() bool { if m != nil { return m.RateLimitExceeded @@ -2102,126 +2118,130 @@ func init() { func init() { proto.RegisterFile("crosschain/query.proto", fileDescriptor_65a992045e92a606) } var fileDescriptor_65a992045e92a606 = []byte{ - // 1901 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x5a, 0xc1, 0x6f, 0xdc, 0x4a, - 0x19, 0xcf, 0x64, 0x9b, 0xf7, 0xd2, 0x49, 0xda, 0xbc, 0xcc, 0x0b, 0x25, 0xf8, 0x25, 0x9b, 0xd6, - 0xa1, 0x4d, 0x48, 0xc8, 0xfa, 0x25, 0x7d, 0xcd, 0xa3, 0x6d, 0x1e, 0x62, 0x93, 0xbe, 0xa4, 0x81, - 0xb4, 0x4d, 0x57, 0x41, 0x45, 0x45, 0xc8, 0x9a, 0x78, 0xa7, 0x5e, 0xab, 0x8e, 0x9d, 0xae, 0xbd, - 0xd5, 0xa6, 0x51, 0x2e, 0x3d, 0x70, 0x46, 0xea, 0x81, 0x0b, 0x57, 0x44, 0x0f, 0x1c, 0x38, 0x20, - 0x38, 0x20, 0x15, 0x21, 0xa0, 0xf4, 0x58, 0x09, 0x81, 0x10, 0x48, 0x08, 0xb5, 0xfc, 0x05, 0xfc, - 0x05, 0xc8, 0xe3, 0xcf, 0xbb, 0x63, 0xaf, 0xbd, 0x3b, 0xd9, 0x6c, 0x0f, 0x3d, 0x75, 0xed, 0x99, - 0xef, 0x9b, 0xdf, 0xef, 0x9b, 0x6f, 0x3e, 0x7f, 0xf3, 0x6b, 0xf0, 0x39, 0xa3, 0xea, 0x7a, 0x9e, - 0x51, 0xa1, 0x96, 0xa3, 0x3d, 0xaa, 0xb1, 0xea, 0x41, 0x61, 0xbf, 0xea, 0xfa, 0x2e, 0x99, 0x7c, - 0xc2, 0x7c, 0xca, 0x5f, 0x17, 0xf8, 0x2f, 0xb7, 0xca, 0x0a, 0xcd, 0xa9, 0xca, 0x9c, 0xe1, 0x7a, - 0x7b, 0xae, 0xa7, 0xed, 0x52, 0x8f, 0x85, 0x76, 0xda, 0xe3, 0xc5, 0x5d, 0xe6, 0xd3, 0x45, 0x6d, - 0x9f, 0x9a, 0x96, 0x43, 0x7d, 0xcb, 0x75, 0x42, 0x57, 0xca, 0x94, 0xb0, 0x04, 0xff, 0xa9, 0xf3, - 0xdf, 0xba, 0x5f, 0x87, 0x09, 0x8a, 0x30, 0xc1, 0xa4, 0x9e, 0xbe, 0x5f, 0xb5, 0x0c, 0x06, 0x63, - 0xd3, 0xc2, 0x18, 0xb7, 0xd1, 0x2b, 0xd4, 0xab, 0xe8, 0xbe, 0xab, 0x1b, 0x46, 0xc3, 0x41, 0xbe, - 0x65, 0x92, 0x5f, 0xa5, 0xc6, 0x43, 0x56, 0x85, 0x71, 0x55, 0x18, 0xb7, 0xa9, 0xe7, 0xeb, 0xbb, - 0xb6, 0x6b, 0x3c, 0xd4, 0x2b, 0xcc, 0x32, 0x2b, 0x7e, 0x0a, 0x4a, 0xb7, 0xe6, 0xb7, 0x3a, 0x11, - 0x91, 0x54, 0xa9, 0xcf, 0x74, 0xdb, 0xda, 0xb3, 0x7c, 0x56, 0xd5, 0x1f, 0xd8, 0xd4, 0xf4, 0x60, - 0xd2, 0x98, 0xe9, 0x9a, 0x2e, 0xff, 0xa9, 0x05, 0xbf, 0xe0, 0xed, 0x84, 0xe9, 0xba, 0xa6, 0xcd, - 0x34, 0xba, 0x6f, 0x69, 0xd4, 0x71, 0x5c, 0x9f, 0x87, 0x07, 0x6c, 0xd4, 0x09, 0xac, 0xdc, 0x0d, - 0x22, 0x78, 0x9f, 0xf9, 0xb4, 0x68, 0x18, 0x6e, 0xcd, 0xf1, 0x2d, 0xc7, 0x2c, 0xb1, 0x47, 0x35, - 0xe6, 0xf9, 0xea, 0x2d, 0xfc, 0x49, 0xea, 0xa8, 0xb7, 0xef, 0x3a, 0x1e, 0x23, 0x05, 0xfc, 0x31, - 0xdd, 0x75, 0xab, 0x3e, 0x2b, 0xeb, 0xc1, 0x3e, 0xe9, 0x74, 0x2f, 0x98, 0x31, 0x8e, 0xce, 0xa3, - 0xd9, 0xd3, 0xa5, 0x51, 0x18, 0xe2, 0xb6, 0x7c, 0xa0, 0xe1, 0x6e, 0x83, 0xf9, 0x77, 0x6a, 0xfe, - 0x4e, 0x7d, 0x27, 0xe4, 0x08, 0xab, 0x91, 0x71, 0xfc, 0x21, 0x67, 0xb8, 0x79, 0x83, 0xbb, 0xc8, - 0x95, 0xa2, 0x47, 0x32, 0x86, 0x07, 0x1c, 0xd7, 0x31, 0xd8, 0x78, 0xff, 0x79, 0x34, 0x7b, 0xaa, - 0x14, 0x3e, 0xa8, 0x35, 0x3c, 0x91, 0xee, 0x0e, 0xe0, 0x7d, 0x1f, 0x0f, 0xbb, 0xc2, 0x7b, 0xee, - 0x74, 0x68, 0x69, 0xbe, 0xd0, 0x36, 0xbb, 0x0a, 0xa2, 0xab, 0xd5, 0x53, 0xaf, 0xfe, 0x3d, 0xd5, - 0x57, 0x8a, 0xb9, 0x51, 0x19, 0xb0, 0x28, 0xda, 0x76, 0x1a, 0x8b, 0x75, 0x8c, 0x9b, 0x59, 0x08, - 0x6b, 0x5e, 0x2a, 0x84, 0x29, 0x5b, 0x08, 0x52, 0xb6, 0x10, 0xa6, 0x3a, 0xa4, 0x6c, 0x61, 0x9b, - 0x9a, 0x0c, 0x6c, 0x4b, 0x82, 0xa5, 0xfa, 0x02, 0x01, 0xbd, 0x96, 0x75, 0x32, 0xe9, 0xe5, 0x7a, - 0x40, 0x8f, 0x6c, 0xc4, 0xf0, 0xf7, 0x73, 0xfc, 0x33, 0x1d, 0xf1, 0x87, 0x98, 0x62, 0x04, 0x9e, - 0x22, 0xac, 0xa6, 0x11, 0x58, 0x3d, 0x58, 0x0b, 0x90, 0x44, 0xf1, 0x1a, 0xc3, 0x03, 0x1c, 0x19, - 0xec, 0x79, 0xf8, 0x90, 0x88, 0x62, 0x7f, 0xd7, 0x51, 0xfc, 0x33, 0xc2, 0xd3, 0x6d, 0x41, 0xbc, - 0x27, 0xc1, 0xfc, 0x31, 0xc2, 0x17, 0x22, 0x1e, 0x9b, 0x4e, 0x56, 0x2c, 0xbf, 0x86, 0x07, 0xc3, - 0xf2, 0x66, 0x95, 0xe3, 0x47, 0xa8, 0xdc, 0xb3, 0x80, 0xfe, 0x41, 0xd8, 0xd5, 0x34, 0x20, 0x10, - 0xcf, 0x12, 0x1e, 0xb2, 0x9c, 0x64, 0x38, 0xe7, 0x3a, 0x84, 0x53, 0xf4, 0x17, 0x46, 0x53, 0x74, - 0xd2, 0xbb, 0x60, 0x0a, 0x27, 0x58, 0x58, 0xd2, 0xeb, 0xf5, 0x09, 0xfe, 0x9d, 0x70, 0x82, 0xe3, - 0xeb, 0xbc, 0x0f, 0x41, 0xba, 0x8e, 0x27, 0xa3, 0xea, 0x1a, 0x2c, 0x79, 0x93, 0x7a, 0x95, 0x1d, - 0x77, 0xcd, 0xf0, 0xeb, 0x51, 0x98, 0x14, 0x3c, 0x68, 0xc1, 0x00, 0x94, 0xfc, 0xc6, 0xb3, 0x7a, - 0x84, 0xf3, 0x59, 0xc6, 0xc0, 0xfd, 0x87, 0xf8, 0xac, 0x15, 0x1b, 0x81, 0x40, 0x2f, 0x48, 0xd0, - 0x6f, 0x1a, 0x41, 0x04, 0x12, 0xae, 0xd4, 0x15, 0x58, 0x3e, 0x3e, 0xf9, 0x06, 0xf5, 0xa9, 0x0c, - 0xf8, 0x27, 0x78, 0x2a, 0xd3, 0x1a, 0xd0, 0xdf, 0xc3, 0x67, 0xd6, 0x02, 0x4c, 0x3c, 0xe9, 0x77, - 0xea, 0x9e, 0x64, 0xbd, 0x10, 0x6d, 0x00, 0x7a, 0xdc, 0x8f, 0x6a, 0x42, 0xd4, 0x21, 0x65, 0x5a, - 0xa3, 0xde, 0xab, 0xe4, 0x7c, 0x89, 0x20, 0x46, 0x29, 0x2b, 0xb5, 0xd9, 0xa2, 0x5c, 0x8f, 0xb6, - 0xa8, 0x77, 0x79, 0xaa, 0xe1, 0xaf, 0x46, 0xa9, 0xb6, 0x41, 0xbd, 0xed, 0xa0, 0x7d, 0x13, 0x3e, - 0x2d, 0x96, 0x53, 0x66, 0x75, 0xd8, 0xe1, 0xf0, 0x41, 0xd5, 0xf1, 0x78, 0xab, 0x01, 0x50, 0x5e, - 0xc3, 0x83, 0xd1, 0x3b, 0x88, 0xed, 0x4c, 0x07, 0xb2, 0x0d, 0x17, 0x0d, 0x43, 0x95, 0x02, 0xa2, - 0xa2, 0x6d, 0x27, 0x11, 0xf5, 0x6a, 0xf7, 0x9e, 0x23, 0x20, 0x11, 0x5b, 0x23, 0x95, 0x44, 0xae, - 0x2b, 0x12, 0xbd, 0xdb, 0x9f, 0xe5, 0x66, 0x29, 0xd8, 0xa2, 0x9e, 0xbf, 0x1a, 0x74, 0xbf, 0x37, - 0x79, 0xf3, 0xdb, 0x7e, 0x9b, 0x0e, 0xe1, 0x14, 0xa6, 0xd9, 0x01, 0xd1, 0x1f, 0xe0, 0x91, 0xc4, - 0x10, 0x84, 0xb4, 0xd0, 0x81, 0x6f, 0xd2, 0x61, 0xd2, 0x8d, 0x5a, 0x69, 0x1e, 0x8e, 0x0c, 0xd0, - 0xbd, 0xda, 0xc9, 0x3f, 0x21, 0xe0, 0x99, 0xb6, 0x54, 0x3b, 0x9e, 0xb9, 0x1e, 0xf0, 0xec, 0xdd, - 0x2e, 0xcf, 0xe3, 0x8f, 0xa3, 0xdd, 0x12, 0xab, 0x55, 0xfa, 0xd6, 0x6e, 0xc1, 0xa5, 0x03, 0x26, - 0xaf, 0x1e, 0xdc, 0x0e, 0xfa, 0xf9, 0x6e, 0xaf, 0x01, 0x26, 0x1e, 0x8b, 0x2f, 0x0d, 0x51, 0xbb, - 0x83, 0x87, 0xc5, 0xda, 0x2a, 0xd9, 0xfe, 0x8b, 0x26, 0xa5, 0x98, 0x03, 0xf5, 0x47, 0xc0, 0xb1, - 0x68, 0xdb, 0xef, 0xa2, 0x22, 0xff, 0x0a, 0x01, 0x91, 0x86, 0xff, 0x4c, 0x22, 0xb9, 0x13, 0x11, - 0xe9, 0xdd, 0xae, 0xdf, 0x86, 0x46, 0x6a, 0xcb, 0xf2, 0xfc, 0x6d, 0xe6, 0x94, 0x2d, 0xc7, 0x14, - 0x23, 0xd3, 0xa6, 0x1d, 0x1d, 0xc3, 0x03, 0xfc, 0x0a, 0xcb, 0x57, 0x3f, 0x53, 0x0a, 0x1f, 0xd4, - 0x67, 0x51, 0xc7, 0xd4, 0xe2, 0xf0, 0x5d, 0x85, 0x42, 0xc5, 0xc3, 0xbe, 0xeb, 0x53, 0x1b, 0x16, - 0x83, 0xcc, 0x8a, 0xbd, 0x53, 0x57, 0xf1, 0x5c, 0x1a, 0xa8, 0x7b, 0x96, 0x5f, 0xb1, 0x9c, 0x12, - 0xf5, 0xd9, 0x56, 0x00, 0x5e, 0x48, 0xf9, 0x90, 0x19, 0x12, 0x99, 0xfd, 0x0d, 0xe1, 0x79, 0x29, - 0x27, 0x40, 0xf4, 0x2e, 0x3e, 0x1b, 0x97, 0x2b, 0xba, 0xa2, 0x6a, 0x88, 0x54, 0xa7, 0xf1, 0x19, - 0x4e, 0x4b, 0xdf, 0xcf, 0xe6, 0x1a, 0x5c, 0xe9, 0x9b, 0xfa, 0x82, 0xce, 0xea, 0x06, 0x63, 0x65, - 0x56, 0x1e, 0xcf, 0x9d, 0x47, 0xb3, 0x83, 0xa5, 0xd1, 0x6a, 0x84, 0xf3, 0x4b, 0x18, 0x68, 0xe8, - 0x07, 0x41, 0x61, 0x09, 0x6e, 0xfa, 0xb1, 0x22, 0xa9, 0x5e, 0x89, 0xf2, 0x23, 0x31, 0x0a, 0x24, - 0xcf, 0xe1, 0x0f, 0x84, 0xb2, 0x9d, 0x2b, 0xc1, 0x93, 0xba, 0x03, 0x59, 0xb0, 0xe6, 0x3a, 0x8f, - 0x59, 0x35, 0xf8, 0x4a, 0xef, 0xb8, 0x81, 0x79, 0x4b, 0x85, 0x68, 0x49, 0x2b, 0x05, 0x0f, 0x9a, - 0xd4, 0xdb, 0x6a, 0x64, 0xd6, 0xe9, 0x52, 0xe3, 0x59, 0xfd, 0x39, 0x82, 0xde, 0xaa, 0xd5, 0x2d, - 0xe0, 0xf9, 0x26, 0x1e, 0x75, 0x6b, 0xfe, 0xae, 0x5b, 0x73, 0xca, 0x1b, 0xd4, 0xdb, 0x74, 0x82, - 0xc1, 0x48, 0xcd, 0x68, 0x19, 0x08, 0x66, 0x73, 0x0d, 0xc5, 0x70, 0xed, 0x75, 0xc6, 0x60, 0x76, - 0xb8, 0x68, 0xeb, 0x00, 0x99, 0xc5, 0x23, 0xc1, 0xbf, 0x62, 0x0d, 0xcf, 0xf1, 0xf8, 0x27, 0x5f, - 0xab, 0x33, 0xf8, 0x22, 0x87, 0x79, 0x8b, 0x79, 0x1e, 0x35, 0xd9, 0x36, 0xf5, 0x3c, 0xcb, 0x31, - 0xb7, 0x9b, 0x1e, 0xa3, 0xe8, 0xae, 0xe3, 0x4b, 0x9d, 0x26, 0x02, 0xb1, 0x09, 0x7c, 0xfa, 0x41, - 0x03, 0x62, 0x48, 0xa8, 0xf9, 0x42, 0xcd, 0x43, 0xb8, 0x1b, 0x59, 0xc8, 0xaa, 0xeb, 0x36, 0x35, - 0xa3, 0xfb, 0x50, 0x70, 0x91, 0x9f, 0xcc, 0x98, 0x00, 0xfe, 0x29, 0xfe, 0xa8, 0x9a, 0x18, 0x83, - 0x42, 0xa8, 0x75, 0xc8, 0xd7, 0xa4, 0x4b, 0xe8, 0x16, 0x5b, 0xdc, 0x2d, 0x3d, 0xbf, 0x80, 0x07, - 0x38, 0x08, 0xf2, 0x12, 0xe1, 0x61, 0xf1, 0xe2, 0x4d, 0xae, 0x75, 0x58, 0xa3, 0x8d, 0xe6, 0xa4, - 0x5c, 0xef, 0xca, 0x36, 0xa4, 0xad, 0x7e, 0xf1, 0xf4, 0xaf, 0xff, 0x7d, 0xd6, 0xff, 0x39, 0xb9, - 0xa2, 0x05, 0xa6, 0x0b, 0x82, 0xca, 0xd8, 0x90, 0xf2, 0x1a, 0x46, 0xda, 0x21, 0x7c, 0xc5, 0x8e, - 0xb4, 0x43, 0xfe, 0xdd, 0x3a, 0x22, 0xbf, 0x45, 0x78, 0x44, 0xf4, 0x5b, 0xb4, 0x6d, 0x39, 0x2e, - 0xe9, 0xca, 0x93, 0x1c, 0x97, 0x0c, 0x35, 0x49, 0x9d, 0xe7, 0x5c, 0x2e, 0x92, 0x69, 0x09, 0x2e, - 0xe4, 0x5f, 0x08, 0x9f, 0x4b, 0x20, 0x07, 0x01, 0x80, 0x14, 0xbb, 0x00, 0x11, 0x57, 0x31, 0x94, - 0xd5, 0x93, 0xb8, 0x00, 0x3a, 0xd7, 0x38, 0x9d, 0xcf, 0xc8, 0x92, 0x04, 0x1d, 0xb0, 0x85, 0x1d, - 0x3a, 0x22, 0xff, 0x44, 0xf8, 0x2b, 0xc2, 0x2d, 0x5b, 0x20, 0xf7, 0x1d, 0x49, 0x64, 0x99, 0x0a, - 0x8d, 0x52, 0x3c, 0x81, 0x07, 0xa0, 0xb6, 0xc2, 0xa9, 0x2d, 0x93, 0xcf, 0x32, 0xa8, 0x59, 0x4e, - 0x06, 0x33, 0xdd, 0x2a, 0x1f, 0x91, 0xdf, 0x20, 0x7c, 0x36, 0x4e, 0x4e, 0x3a, 0xe7, 0x52, 0xb4, - 0x12, 0xe9, 0x9c, 0x4b, 0xd3, 0x3f, 0x3a, 0xe6, 0x9c, 0xc0, 0xc4, 0x23, 0x7f, 0x01, 0xe0, 0xc2, - 0x1d, 0x72, 0x45, 0xf2, 0xf0, 0xa6, 0xde, 0xa4, 0x95, 0x2f, 0xba, 0xb4, 0x06, 0xf0, 0xdf, 0xe2, - 0xe0, 0x97, 0xc8, 0xa7, 0x6d, 0xc0, 0x37, 0xcd, 0xb4, 0xc3, 0xe8, 0xf9, 0x88, 0xfc, 0x1d, 0x61, - 0xd2, 0xaa, 0x2d, 0x10, 0x29, 0x3c, 0x99, 0x8a, 0x86, 0xf2, 0xed, 0x6e, 0xcd, 0x81, 0x4f, 0x91, - 0xf3, 0xb9, 0x4e, 0xae, 0x66, 0xf2, 0x49, 0xfe, 0x07, 0x88, 0x5e, 0xa6, 0x3e, 0x15, 0x89, 0xfd, - 0x1e, 0xe1, 0xd1, 0xf8, 0x0a, 0x41, 0x7a, 0xad, 0x1c, 0x23, 0x45, 0xba, 0xdc, 0xa5, 0x4c, 0x0d, - 0x43, 0x5d, 0xe0, 0xac, 0x66, 0xc8, 0x45, 0xa9, 0x5d, 0x22, 0xbf, 0x44, 0xcd, 0xbb, 0x33, 0x59, - 0x96, 0x4c, 0x90, 0xc4, 0x25, 0x5f, 0xf9, 0xfc, 0xd8, 0x76, 0x00, 0x56, 0xe3, 0x60, 0xbf, 0x41, - 0x66, 0x32, 0xc0, 0x9a, 0x60, 0x10, 0xc4, 0xbc, 0xcc, 0xea, 0x47, 0xe4, 0x17, 0x08, 0x0f, 0x45, - 0x5e, 0x82, 0x50, 0x2f, 0x4b, 0x06, 0xab, 0x2b, 0xc4, 0x29, 0x52, 0x83, 0x3a, 0xc3, 0x11, 0x5f, - 0x20, 0x53, 0x1d, 0x10, 0x93, 0x17, 0x08, 0x7f, 0x94, 0xec, 0xbb, 0x88, 0x54, 0xf1, 0xc8, 0x68, - 0x02, 0x95, 0x95, 0xee, 0x8c, 0x25, 0x43, 0x6d, 0x24, 0xb1, 0xbe, 0x44, 0x78, 0x48, 0x68, 0xad, - 0xc8, 0x0d, 0x99, 0xe5, 0x3b, 0xb5, 0x70, 0xca, 0x97, 0x27, 0xf4, 0x02, 0x6c, 0xe6, 0x38, 0x9b, - 0xaf, 0x13, 0x35, 0x83, 0x8d, 0xd0, 0x8e, 0x92, 0x57, 0xa8, 0x45, 0x4d, 0x20, 0xb2, 0xa5, 0x30, - 0x5d, 0x0b, 0x91, 0x2b, 0x3d, 0xd9, 0x3a, 0x8e, 0xba, 0xcc, 0xe1, 0x7f, 0x4a, 0x0a, 0x19, 0xf0, - 0xed, 0xb8, 0x5d, 0x23, 0xfd, 0xff, 0x88, 0x30, 0x49, 0xf8, 0x0c, 0x4e, 0x81, 0x6c, 0xc9, 0x38, - 0x09, 0x9b, 0x6c, 0xb5, 0x46, 0x2d, 0x70, 0x36, 0xb3, 0xe4, 0x92, 0x1c, 0x1b, 0xf2, 0x33, 0x84, - 0x4f, 0xf1, 0xe2, 0xb3, 0x24, 0x19, 0x46, 0xb1, 0x3c, 0x5e, 0x3e, 0x96, 0x8d, 0xe4, 0x77, 0xd7, - 0x80, 0x0f, 0x16, 0x0f, 0xf2, 0xaf, 0x11, 0x1e, 0x12, 0x54, 0x1a, 0x72, 0xf5, 0x18, 0x2b, 0xc6, - 0x95, 0x9d, 0xee, 0xc0, 0x5e, 0xe1, 0x60, 0x35, 0xb2, 0xd0, 0x16, 0x6c, 0x4b, 0x73, 0xfd, 0x53, - 0x84, 0x3f, 0x8c, 0xbe, 0x40, 0x4b, 0x92, 0x3b, 0x7a, 0xec, 0xc0, 0x26, 0x94, 0x1a, 0x75, 0x9a, - 0x63, 0x9d, 0x24, 0x9f, 0xb4, 0xc1, 0x1a, 0x74, 0x60, 0x23, 0x09, 0x15, 0x40, 0xae, 0x05, 0x4b, - 0x57, 0x59, 0xe4, 0x5a, 0xb0, 0x0c, 0x41, 0xa5, 0x73, 0xe5, 0x10, 0x40, 0xfe, 0x0f, 0xe1, 0x7c, - 0x7b, 0xf9, 0x82, 0x6c, 0x76, 0x81, 0x25, 0x5d, 0x47, 0x51, 0xbe, 0xdb, 0x0b, 0x57, 0xc0, 0xf2, - 0x2a, 0x67, 0x79, 0x99, 0x2c, 0x76, 0x66, 0x99, 0x64, 0x14, 0xf4, 0xcb, 0xf1, 0x3f, 0x7f, 0x90, - 0x3b, 0x01, 0xa9, 0x7f, 0x50, 0xa1, 0x5c, 0xeb, 0xc6, 0x54, 0xb2, 0x95, 0x79, 0x12, 0x47, 0x19, - 0x00, 0x8f, 0xeb, 0x2e, 0x72, 0xc0, 0x53, 0x95, 0x1c, 0x39, 0xe0, 0xe9, 0x32, 0x4f, 0x47, 0xe0, - 0x76, 0x1c, 0x65, 0xd0, 0x2a, 0x24, 0x65, 0x01, 0xb9, 0x56, 0x21, 0x43, 0xc0, 0x90, 0x6b, 0x15, - 0xb2, 0xc4, 0x8d, 0x8e, 0xad, 0x42, 0x52, 0xaa, 0x58, 0xfd, 0xde, 0xab, 0x37, 0x79, 0xf4, 0xfa, - 0x4d, 0x1e, 0xfd, 0xe7, 0x4d, 0x1e, 0xfd, 0xe4, 0x6d, 0xbe, 0xef, 0xf5, 0xdb, 0x7c, 0xdf, 0x3f, - 0xde, 0xe6, 0xfb, 0xee, 0x2f, 0x9a, 0x96, 0x5f, 0xa9, 0xed, 0x16, 0x0c, 0x77, 0x4f, 0x74, 0x16, - 0x61, 0xd2, 0xea, 0xa2, 0x5f, 0xff, 0x60, 0x9f, 0x79, 0xbb, 0x1f, 0xf0, 0x6f, 0xf7, 0xe5, 0xff, - 0x07, 0x00, 0x00, 0xff, 0xff, 0x25, 0x12, 0xcf, 0x1a, 0x2c, 0x25, 0x00, 0x00, + // 1957 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x5a, 0xcf, 0x6f, 0xdc, 0xc6, + 0x15, 0xf6, 0x68, 0xad, 0x44, 0x1e, 0xf9, 0x47, 0x3c, 0x96, 0x1d, 0x95, 0xb1, 0xd7, 0x36, 0x55, + 0x5b, 0xaa, 0x5d, 0x2f, 0x63, 0x39, 0x56, 0x6a, 0x5b, 0x29, 0xba, 0x92, 0x63, 0x47, 0xad, 0x92, + 0x28, 0x0b, 0x15, 0x2a, 0x5c, 0x14, 0xc4, 0x88, 0x3b, 0xe1, 0x12, 0xa1, 0x48, 0x85, 0x9c, 0x8d, + 0x56, 0x16, 0x74, 0xf1, 0xa1, 0xe7, 0x02, 0x39, 0xf4, 0xd2, 0x6b, 0xd1, 0x1c, 0x7a, 0xe8, 0xa1, + 0x68, 0x0f, 0x05, 0x52, 0x04, 0x6d, 0x5d, 0x1f, 0x03, 0x14, 0x28, 0x8a, 0x16, 0x28, 0x0a, 0xbb, + 0x7f, 0x41, 0xff, 0x82, 0x82, 0xc3, 0xc7, 0xdd, 0xe1, 0xaf, 0xdd, 0xd1, 0x6a, 0x73, 0xc8, 0xc9, + 0x4b, 0xce, 0xbc, 0x37, 0xdf, 0xf7, 0xde, 0x9b, 0xe1, 0x9b, 0xcf, 0xc2, 0xe7, 0xac, 0xc0, 0x0f, + 0x43, 0xab, 0x45, 0x1d, 0xcf, 0xf8, 0xb8, 0xcd, 0x82, 0xdd, 0xda, 0x76, 0xe0, 0x73, 0x9f, 0x5c, + 0x78, 0xcc, 0x38, 0x15, 0xaf, 0x6b, 0xe2, 0x97, 0x1f, 0xb0, 0x5a, 0x6f, 0xaa, 0x76, 0xcd, 0xf2, + 0xc3, 0x2d, 0x3f, 0x34, 0x36, 0x69, 0xc8, 0x62, 0x3b, 0xe3, 0x93, 0x9b, 0x9b, 0x8c, 0xd3, 0x9b, + 0xc6, 0x36, 0xb5, 0x1d, 0x8f, 0x72, 0xc7, 0xf7, 0x62, 0x57, 0xda, 0x45, 0x69, 0x09, 0xf1, 0xd3, + 0x14, 0xbf, 0x4d, 0xde, 0x81, 0x09, 0x9a, 0x34, 0xc1, 0xa6, 0xa1, 0xb9, 0x1d, 0x38, 0x16, 0x83, + 0xb1, 0x19, 0x69, 0x4c, 0xd8, 0x98, 0x2d, 0x1a, 0xb6, 0x4c, 0xee, 0x9b, 0x96, 0xd5, 0x75, 0x50, + 0xcd, 0x4d, 0xe2, 0x01, 0xb5, 0x3e, 0x62, 0x01, 0x8c, 0xeb, 0xd2, 0xb8, 0x4b, 0x43, 0x6e, 0x6e, + 0xba, 0xbe, 0xf5, 0x91, 0xd9, 0x62, 0x8e, 0xdd, 0xe2, 0x05, 0x28, 0xfd, 0x36, 0xcf, 0x3b, 0x91, + 0x91, 0x04, 0x94, 0x33, 0xd3, 0x75, 0xb6, 0x1c, 0xce, 0x02, 0xf3, 0x43, 0x97, 0xda, 0x21, 0x4c, + 0x9a, 0xb2, 0x7d, 0xdb, 0x17, 0x3f, 0x8d, 0xe8, 0x17, 0xbc, 0x3d, 0x6f, 0xfb, 0xbe, 0xed, 0x32, + 0x83, 0x6e, 0x3b, 0x06, 0xf5, 0x3c, 0x9f, 0x8b, 0xf0, 0x80, 0x8d, 0x7e, 0x1e, 0x6b, 0x1f, 0x44, + 0x11, 0x7c, 0xc4, 0x38, 0xad, 0x5b, 0x96, 0xdf, 0xf6, 0xb8, 0xe3, 0xd9, 0x0d, 0xf6, 0x71, 0x9b, + 0x85, 0x5c, 0x7f, 0x17, 0xbf, 0x56, 0x38, 0x1a, 0x6e, 0xfb, 0x5e, 0xc8, 0x48, 0x0d, 0x9f, 0xa1, + 0x9b, 0x7e, 0xc0, 0x59, 0xd3, 0x8c, 0xf2, 0x64, 0xd2, 0xad, 0x68, 0xc6, 0x34, 0xba, 0x84, 0xe6, + 0x8e, 0x35, 0x4e, 0xc3, 0x90, 0xb0, 0x15, 0x03, 0x5d, 0x77, 0x0f, 0x19, 0x7f, 0xbf, 0xcd, 0xd7, + 0x3b, 0xeb, 0x31, 0x47, 0x58, 0x8d, 0x4c, 0xe3, 0x97, 0x05, 0xc3, 0x95, 0xfb, 0xc2, 0x45, 0xa5, + 0x91, 0x3c, 0x92, 0x29, 0x3c, 0xee, 0xf9, 0x9e, 0xc5, 0xa6, 0xc7, 0x2e, 0xa1, 0xb9, 0xa3, 0x8d, + 0xf8, 0x41, 0x6f, 0xe3, 0xf3, 0xc5, 0xee, 0x00, 0xde, 0x0f, 0xf1, 0x71, 0x5f, 0x7a, 0x2f, 0x9c, + 0x4e, 0xce, 0x5f, 0xaf, 0xf5, 0xad, 0xae, 0x9a, 0xec, 0x6a, 0xe9, 0xe8, 0xb3, 0x7f, 0x5f, 0x3c, + 0xd2, 0x48, 0xb9, 0xd1, 0x19, 0xb0, 0xa8, 0xbb, 0x6e, 0x11, 0x8b, 0x07, 0x18, 0xf7, 0xaa, 0x10, + 0xd6, 0xbc, 0x5a, 0x8b, 0x4b, 0xb6, 0x16, 0x95, 0x6c, 0x2d, 0x2e, 0x75, 0x28, 0xd9, 0xda, 0x1a, + 0xb5, 0x19, 0xd8, 0x36, 0x24, 0x4b, 0xfd, 0x73, 0x04, 0xf4, 0x72, 0xeb, 0x94, 0xd2, 0xab, 0x8c, + 0x80, 0x1e, 0x79, 0x98, 0xc2, 0x3f, 0x26, 0xf0, 0xcf, 0x0e, 0xc4, 0x1f, 0x63, 0x4a, 0x11, 0x78, + 0x82, 0xb0, 0x5e, 0x44, 0x60, 0x69, 0x77, 0x39, 0x42, 0x92, 0xc4, 0x6b, 0x0a, 0x8f, 0x0b, 0x64, + 0x90, 0xf3, 0xf8, 0x21, 0x13, 0xc5, 0xb1, 0xa1, 0xa3, 0xf8, 0x17, 0x84, 0x67, 0xfa, 0x82, 0xf8, + 0x9a, 0x04, 0xf3, 0xa7, 0x08, 0x5f, 0x4e, 0x78, 0xac, 0x78, 0x65, 0xb1, 0xfc, 0x06, 0x9e, 0x88, + 0x8f, 0x37, 0xa7, 0x99, 0xde, 0x42, 0xcd, 0x91, 0x05, 0xf4, 0x0b, 0x29, 0xab, 0x45, 0x40, 0x20, + 0x9e, 0x0d, 0x3c, 0xe9, 0x78, 0xd9, 0x70, 0x5e, 0x1b, 0x10, 0x4e, 0xd9, 0x5f, 0x1c, 0x4d, 0xd9, + 0xc9, 0xe8, 0x82, 0x29, 0xed, 0x60, 0x69, 0xc9, 0x70, 0xd4, 0x3b, 0xf8, 0x0f, 0xd2, 0x0e, 0x4e, + 0xaf, 0xf3, 0x75, 0x08, 0xd2, 0x3d, 0x7c, 0x21, 0x39, 0x5d, 0xa3, 0x25, 0xdf, 0xa1, 0x61, 0x6b, + 0xdd, 0x5f, 0xb6, 0x78, 0x27, 0x09, 0x93, 0x86, 0x27, 0x1c, 0x18, 0x80, 0x23, 0xbf, 0xfb, 0xac, + 0xef, 0xe3, 0x6a, 0x99, 0x31, 0x70, 0xff, 0x31, 0x3e, 0xe9, 0xa4, 0x46, 0x20, 0xd0, 0x37, 0x14, + 0xe8, 0xf7, 0x8c, 0x20, 0x02, 0x19, 0x57, 0xfa, 0x22, 0x2c, 0x9f, 0x9e, 0x7c, 0x9f, 0x72, 0xaa, + 0x02, 0xfe, 0x31, 0xbe, 0x58, 0x6a, 0x0d, 0xe8, 0x37, 0xf0, 0x89, 0xe5, 0x08, 0x93, 0x28, 0xfa, + 0xf5, 0x4e, 0xa8, 0x78, 0x5e, 0xc8, 0x36, 0x00, 0x3d, 0xed, 0x47, 0xb7, 0x21, 0xea, 0x50, 0x32, + 0xf9, 0xa8, 0x8f, 0xaa, 0x38, 0x9f, 0x22, 0x88, 0x51, 0xc1, 0x4a, 0x7d, 0x52, 0x54, 0x19, 0x51, + 0x8a, 0x46, 0x57, 0xa7, 0x06, 0x7e, 0x35, 0x29, 0xb5, 0x87, 0x34, 0x5c, 0x8b, 0xda, 0x37, 0xe9, + 0xd3, 0xe2, 0x78, 0x4d, 0xd6, 0x81, 0x0c, 0xc7, 0x0f, 0xba, 0x89, 0xa7, 0xf3, 0x06, 0x40, 0x79, + 0x19, 0x4f, 0x24, 0xef, 0x20, 0xb6, 0xb3, 0x03, 0xc8, 0x76, 0x5d, 0x74, 0x0d, 0x75, 0x0a, 0x88, + 0xea, 0xae, 0x9b, 0x45, 0x34, 0xaa, 0xec, 0x7d, 0x86, 0x80, 0x44, 0x6a, 0x8d, 0x42, 0x12, 0x95, + 0xa1, 0x48, 0x8c, 0x2e, 0x3f, 0x0b, 0xbd, 0xa3, 0x60, 0x95, 0x86, 0x7c, 0x29, 0xea, 0x7e, 0xdf, + 0x11, 0xcd, 0x6f, 0xff, 0x34, 0xed, 0xc1, 0x2e, 0x2c, 0xb2, 0x03, 0xa2, 0x3f, 0xc2, 0xa7, 0x32, + 0x43, 0x10, 0xd2, 0xda, 0x00, 0xbe, 0x59, 0x87, 0x59, 0x37, 0x7a, 0xab, 0xb7, 0x39, 0x4a, 0x40, + 0x8f, 0x2a, 0x93, 0x7f, 0x46, 0xc0, 0xb3, 0x68, 0xa9, 0x7e, 0x3c, 0x2b, 0x23, 0xe0, 0x39, 0xba, + 0x2c, 0x5f, 0xc7, 0x67, 0x92, 0x6c, 0xc9, 0xa7, 0x55, 0x71, 0x6a, 0x57, 0xe1, 0xd2, 0x01, 0x93, + 0x97, 0x76, 0xdf, 0x8b, 0xfa, 0xf9, 0x61, 0xaf, 0x01, 0x36, 0x9e, 0x4a, 0x2f, 0x0d, 0x51, 0x7b, + 0x1f, 0x1f, 0x97, 0xcf, 0x56, 0xc5, 0xf6, 0x5f, 0x36, 0x69, 0xa4, 0x1c, 0xe8, 0x3f, 0x01, 0x8e, + 0x75, 0xd7, 0xfd, 0x2a, 0x4e, 0xe4, 0xdf, 0x20, 0x20, 0xd2, 0xf5, 0x5f, 0x4a, 0xa4, 0x72, 0x28, + 0x22, 0xa3, 0xcb, 0xfa, 0x7b, 0xd0, 0x48, 0xad, 0x3a, 0x21, 0x5f, 0x63, 0x5e, 0xd3, 0xf1, 0x6c, + 0x39, 0x32, 0x7d, 0xda, 0xd1, 0x29, 0x3c, 0x2e, 0xae, 0xb0, 0x62, 0xf5, 0x13, 0x8d, 0xf8, 0x41, + 0xff, 0x34, 0xe9, 0x98, 0x72, 0x0e, 0xbf, 0xaa, 0x50, 0xe8, 0xf8, 0x38, 0xf7, 0x39, 0x75, 0x61, + 0x31, 0xa8, 0xac, 0xd4, 0x3b, 0x7d, 0x09, 0x5f, 0x2b, 0x02, 0xb5, 0xe1, 0xf0, 0x96, 0xe3, 0x35, + 0x28, 0x67, 0xab, 0x11, 0x78, 0xa9, 0xe4, 0x63, 0x66, 0x48, 0x66, 0xf6, 0xc5, 0x18, 0xbe, 0xae, + 0xe4, 0x04, 0x88, 0x7e, 0x80, 0x4f, 0xa6, 0xe5, 0x8a, 0xa1, 0xa8, 0x5a, 0x32, 0xd5, 0x19, 0x7c, + 0x42, 0xd0, 0x32, 0xb7, 0xcb, 0xb9, 0x92, 0x05, 0xfc, 0xaa, 0xd5, 0x0e, 0x02, 0xe6, 0x71, 0x73, + 0xc7, 0xe1, 0xad, 0x66, 0x40, 0x77, 0xcc, 0x1d, 0xc7, 0x6b, 0xfa, 0x3b, 0xd3, 0x15, 0x91, 0xc1, + 0xb3, 0x30, 0xbc, 0x01, 0xa3, 0x1b, 0x62, 0x90, 0xcc, 0xe3, 0xb3, 0x39, 0xbb, 0x80, 0x72, 0x36, + 0x7d, 0x54, 0x6c, 0xfc, 0x33, 0x19, 0xab, 0x88, 0x30, 0xa9, 0xe1, 0x33, 0x3d, 0x2d, 0xc3, 0x64, + 0x1d, 0x8b, 0xb1, 0x26, 0x6b, 0x4e, 0x8f, 0x5f, 0x42, 0x73, 0x13, 0x8d, 0xd3, 0x41, 0x12, 0x93, + 0xb7, 0x61, 0xa0, 0xab, 0x55, 0x44, 0x87, 0xd8, 0x23, 0xc6, 0x69, 0xea, 0x40, 0xd6, 0x6f, 0x27, + 0xb5, 0x98, 0x19, 0x85, 0x80, 0x9e, 0xc3, 0x2f, 0x49, 0x9f, 0x88, 0x4a, 0x03, 0x9e, 0xf4, 0x75, + 0xa8, 0xb8, 0x65, 0xdf, 0xfb, 0x84, 0x05, 0x51, 0x47, 0xb0, 0xee, 0x47, 0xe6, 0xb9, 0xd3, 0x28, + 0x57, 0xc2, 0x1a, 0x9e, 0xb0, 0x69, 0xb8, 0xda, 0xad, 0xe2, 0x63, 0x8d, 0xee, 0xb3, 0xfe, 0x4b, + 0x04, 0x7d, 0x5c, 0xde, 0x2d, 0xe0, 0xf9, 0x36, 0x3e, 0xed, 0xb7, 0xf9, 0xa6, 0xdf, 0xf6, 0x9a, + 0x0f, 0x69, 0xb8, 0xe2, 0x45, 0x83, 0x89, 0x72, 0x92, 0x1b, 0x88, 0x66, 0x0b, 0xbd, 0xc6, 0xf2, + 0xdd, 0x07, 0x8c, 0xc1, 0xec, 0x78, 0xd1, 0xfc, 0x00, 0x99, 0xc3, 0xa7, 0xa2, 0x7f, 0xe5, 0xef, + 0x45, 0x45, 0xe4, 0x3a, 0xfb, 0x5a, 0x9f, 0xc5, 0x57, 0x04, 0xcc, 0x77, 0x59, 0x18, 0x52, 0x9b, + 0xad, 0xd1, 0x30, 0x74, 0x3c, 0x7b, 0xad, 0xe7, 0x31, 0x89, 0xee, 0x03, 0x7c, 0x75, 0xd0, 0x44, + 0x20, 0x76, 0x1e, 0x1f, 0xfb, 0xb0, 0x0b, 0x31, 0x26, 0xd4, 0x7b, 0xa1, 0x57, 0x21, 0xdc, 0xdd, + 0x8a, 0x67, 0xc1, 0x03, 0x97, 0xda, 0xc9, 0xdd, 0x4b, 0x7f, 0x92, 0x04, 0x2e, 0x3f, 0x01, 0xfc, + 0x53, 0xfc, 0x4a, 0x90, 0x19, 0x83, 0x43, 0xd7, 0x18, 0xb0, 0x37, 0xb2, 0x2e, 0xa1, 0x33, 0xcd, + 0xb9, 0x9b, 0xff, 0xec, 0x32, 0x1e, 0x17, 0x20, 0xc8, 0x53, 0x84, 0x8f, 0xcb, 0x97, 0x7c, 0x72, + 0x77, 0xc0, 0x1a, 0x7d, 0xf4, 0x2d, 0xed, 0xde, 0x50, 0xb6, 0x31, 0x6d, 0xfd, 0xad, 0x27, 0x7f, + 0xfb, 0xef, 0xa7, 0x63, 0x6f, 0x92, 0xdb, 0x46, 0x64, 0x7a, 0x43, 0x52, 0x34, 0xbb, 0xb2, 0x61, + 0xd7, 0xc8, 0xd8, 0x83, 0x2f, 0xe6, 0xbe, 0xb1, 0x27, 0xbe, 0x91, 0xfb, 0xe4, 0xf7, 0x08, 0x9f, + 0x92, 0xfd, 0xd6, 0x5d, 0x57, 0x8d, 0x4b, 0xb1, 0xca, 0xa5, 0xc6, 0xa5, 0x44, 0xb9, 0xd2, 0xaf, + 0x0b, 0x2e, 0x57, 0xc8, 0x8c, 0x02, 0x17, 0xf2, 0x2f, 0x84, 0xcf, 0x65, 0x90, 0x83, 0xd8, 0x40, + 0xea, 0x43, 0x80, 0x48, 0x2b, 0x26, 0xda, 0xd2, 0x61, 0x5c, 0x00, 0x9d, 0xbb, 0x82, 0xce, 0x1b, + 0x64, 0x5e, 0x81, 0x0e, 0xd8, 0x42, 0x86, 0xf6, 0xc9, 0x3f, 0x11, 0x3e, 0x2b, 0xdd, 0xe8, 0x25, + 0x72, 0xdf, 0x53, 0x44, 0x56, 0xaa, 0x06, 0x69, 0xf5, 0x43, 0x78, 0x00, 0x6a, 0x8b, 0x82, 0xda, + 0x02, 0x79, 0xa3, 0x84, 0x9a, 0xe3, 0x95, 0x30, 0x33, 0x9d, 0xe6, 0x3e, 0xf9, 0x1d, 0xc2, 0x27, + 0xd3, 0xe4, 0x94, 0x6b, 0xae, 0x40, 0x97, 0x51, 0xae, 0xb9, 0x22, 0xad, 0x65, 0x60, 0xcd, 0x49, + 0x4c, 0x42, 0xf2, 0x57, 0x00, 0x2e, 0xdd, 0x57, 0x17, 0x15, 0x37, 0x6f, 0xe1, 0xad, 0x5d, 0x7b, + 0x6b, 0x48, 0x6b, 0x00, 0xff, 0x1d, 0x01, 0x7e, 0x9e, 0xbc, 0xde, 0x07, 0x7c, 0xcf, 0xcc, 0xd8, + 0x4b, 0x9e, 0xf7, 0xc9, 0xdf, 0x11, 0x26, 0x79, 0x1d, 0x83, 0x28, 0xe1, 0x29, 0x55, 0x4f, 0xb4, + 0xef, 0x0e, 0x6b, 0x0e, 0x7c, 0xea, 0x82, 0xcf, 0x3d, 0x72, 0xa7, 0x94, 0x4f, 0xf6, 0x3f, 0x5b, + 0xcc, 0x26, 0xe5, 0x54, 0x26, 0xf6, 0x47, 0x84, 0x4f, 0xa7, 0x57, 0x88, 0xca, 0x6b, 0xf1, 0x00, + 0x25, 0x32, 0x64, 0x96, 0x4a, 0xf5, 0x12, 0xfd, 0x86, 0x60, 0x35, 0x4b, 0xae, 0x28, 0x65, 0x89, + 0xfc, 0x1a, 0xf5, 0xee, 0xe9, 0x64, 0x41, 0xb1, 0x40, 0x32, 0x82, 0x82, 0xf6, 0xe6, 0x81, 0xed, + 0x00, 0xac, 0x21, 0xc0, 0x7e, 0x8b, 0xcc, 0x96, 0x80, 0xb5, 0xc1, 0x20, 0x8a, 0x79, 0x93, 0x75, + 0xf6, 0xc9, 0xaf, 0x10, 0x9e, 0x4c, 0xbc, 0x44, 0xa1, 0x5e, 0x50, 0x0c, 0xd6, 0x50, 0x88, 0x0b, + 0x64, 0x0d, 0x7d, 0x56, 0x20, 0xbe, 0x4c, 0x2e, 0x0e, 0x40, 0x4c, 0x3e, 0x47, 0xf8, 0x95, 0x6c, + 0xdf, 0x45, 0x94, 0x0e, 0x8f, 0x92, 0x26, 0x50, 0x5b, 0x1c, 0xce, 0x58, 0x31, 0xd4, 0x56, 0x16, + 0xeb, 0x53, 0x84, 0x27, 0xa5, 0xd6, 0x8a, 0xdc, 0x57, 0x59, 0x7e, 0x50, 0x0b, 0xa7, 0xbd, 0x7d, + 0x48, 0x2f, 0xc0, 0xe6, 0x9a, 0x60, 0xf3, 0x4d, 0xa2, 0x97, 0xb0, 0x91, 0xda, 0x51, 0xf2, 0x0c, + 0xe5, 0x94, 0x0b, 0xa2, 0x7a, 0x14, 0x16, 0xeb, 0x2e, 0x6a, 0x47, 0x4f, 0xb9, 0x66, 0xa4, 0x2f, + 0x08, 0xf8, 0xaf, 0x93, 0x5a, 0x09, 0x7c, 0x37, 0x6d, 0xd7, 0x2d, 0xff, 0x3f, 0x21, 0x4c, 0x32, + 0x3e, 0xa3, 0x5d, 0xa0, 0x7a, 0x64, 0x1c, 0x86, 0x4d, 0xb9, 0x32, 0xa4, 0xd7, 0x04, 0x9b, 0x39, + 0x72, 0x55, 0x8d, 0x0d, 0xf9, 0x05, 0xc2, 0x47, 0xc5, 0xe1, 0x33, 0xaf, 0x18, 0x46, 0xf9, 0x78, + 0xbc, 0x75, 0x20, 0x1b, 0xc5, 0xef, 0xae, 0x05, 0x1f, 0x2c, 0x11, 0xe4, 0xdf, 0x22, 0x3c, 0x29, + 0x29, 0x42, 0xe4, 0xce, 0x01, 0x56, 0x4c, 0xab, 0x48, 0xc3, 0x81, 0xbd, 0x2d, 0xc0, 0x1a, 0xe4, + 0x46, 0x5f, 0xb0, 0xb9, 0xe6, 0xfa, 0xe7, 0x08, 0xbf, 0x9c, 0x7c, 0x81, 0xe6, 0x15, 0x33, 0x7a, + 0xe0, 0xc0, 0x66, 0x54, 0x21, 0x7d, 0x46, 0x60, 0xbd, 0x40, 0x5e, 0xeb, 0x83, 0x35, 0xea, 0xc0, + 0x4e, 0x65, 0x14, 0x07, 0xb5, 0x16, 0xac, 0x58, 0xd1, 0x51, 0x6b, 0xc1, 0x4a, 0xc4, 0x9b, 0xc1, + 0x27, 0x87, 0x04, 0xf2, 0x7f, 0x08, 0x57, 0xfb, 0x4b, 0x25, 0x64, 0x65, 0x08, 0x2c, 0xc5, 0x9a, + 0x8d, 0xf6, 0xfd, 0x51, 0xb8, 0x02, 0x96, 0x77, 0x04, 0xcb, 0x5b, 0xe4, 0xe6, 0x60, 0x96, 0x59, + 0x46, 0x51, 0xbf, 0x9c, 0xfe, 0x53, 0x0b, 0xb5, 0x1d, 0x50, 0xf8, 0xc7, 0x1b, 0xda, 0xdd, 0x61, + 0x4c, 0x15, 0x5b, 0x99, 0xc7, 0x69, 0x94, 0x11, 0xf0, 0xb4, 0xee, 0xa2, 0x06, 0xbc, 0x50, 0xc9, + 0x51, 0x03, 0x5e, 0x2c, 0xf3, 0x0c, 0x04, 0xee, 0xa6, 0x51, 0x46, 0xad, 0x42, 0x56, 0x16, 0x50, + 0x6b, 0x15, 0x4a, 0x04, 0x0c, 0xb5, 0x56, 0xa1, 0x4c, 0xdc, 0x18, 0xd8, 0x2a, 0x64, 0xa5, 0x8a, + 0xa5, 0x1f, 0x3c, 0x7b, 0x5e, 0x45, 0x5f, 0x3e, 0xaf, 0xa2, 0xff, 0x3c, 0xaf, 0xa2, 0x9f, 0xbd, + 0xa8, 0x1e, 0xf9, 0xf2, 0x45, 0xf5, 0xc8, 0x3f, 0x5e, 0x54, 0x8f, 0x3c, 0xba, 0x69, 0x3b, 0xbc, + 0xd5, 0xde, 0xac, 0x59, 0xfe, 0x96, 0xec, 0x2c, 0xc1, 0x64, 0x74, 0x64, 0xbf, 0x7c, 0x77, 0x9b, + 0x85, 0x9b, 0x2f, 0x89, 0x6f, 0xf7, 0xad, 0xff, 0x07, 0x00, 0x00, 0xff, 0xff, 0x13, 0xb9, 0xf7, + 0xad, 0x98, 0x25, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -4382,6 +4402,18 @@ func (m *QueryListPendingCctxWithinRateLimitResponse) MarshalToSizedBuffer(dAtA dAtA[i] = 0 } i-- + dAtA[i] = 0x28 + } + if len(m.CurrentWithdrawRate) > 0 { + i -= len(m.CurrentWithdrawRate) + copy(dAtA[i:], m.CurrentWithdrawRate) + i = encodeVarintQuery(dAtA, i, uint64(len(m.CurrentWithdrawRate))) + i-- + dAtA[i] = 0x22 + } + if m.CurrentWithdrawWindow != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.CurrentWithdrawWindow)) + i-- dAtA[i] = 0x18 } if m.TotalPending != 0 { @@ -5169,6 +5201,13 @@ func (m *QueryListPendingCctxWithinRateLimitResponse) Size() (n int) { if m.TotalPending != 0 { n += 1 + sovQuery(uint64(m.TotalPending)) } + if m.CurrentWithdrawWindow != 0 { + n += 1 + sovQuery(uint64(m.CurrentWithdrawWindow)) + } + l = len(m.CurrentWithdrawRate) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } if m.RateLimitExceeded { n += 2 } @@ -8535,6 +8574,57 @@ func (m *QueryListPendingCctxWithinRateLimitResponse) Unmarshal(dAtA []byte) err } } case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentWithdrawWindow", wireType) + } + m.CurrentWithdrawWindow = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CurrentWithdrawWindow |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentWithdrawRate", 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.CurrentWithdrawRate = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field RateLimitExceeded", wireType) } diff --git a/x/fungible/keeper/foreign_coins.go b/x/fungible/keeper/foreign_coins.go index 5f0a31da4a..5c52eb543e 100644 --- a/x/fungible/keeper/foreign_coins.go +++ b/x/fungible/keeper/foreign_coins.go @@ -81,20 +81,19 @@ func (k Keeper) GetAllForeignCoins(ctx sdk.Context) (list []types.ForeignCoins) return } -// GetAllForeignERC20CoinMap returns all foreign ERC20 coins in a map of chainID -> asset -> coin -func (k Keeper) GetAllForeignERC20CoinMap(ctx sdk.Context) map[int64]map[string]types.ForeignCoins { +// GetAllForeignCoinMap returns all foreign ERC20 coins in a map of chainID -> asset -> coin +// Note: DO NOT use this method outside of gRPC queries +func (k Keeper) GetAllForeignCoinMap(ctx sdk.Context) map[int64]map[string]types.ForeignCoins { allForeignCoins := k.GetAllForeignCoins(ctx) - erc20CoinMap := make(map[int64]map[string]types.ForeignCoins) + foreignCoinMap := make(map[int64]map[string]types.ForeignCoins) for _, c := range allForeignCoins { - if c.CoinType == coin.CoinType_ERC20 { - if _, found := erc20CoinMap[c.ForeignChainId]; !found { - erc20CoinMap[c.ForeignChainId] = make(map[string]types.ForeignCoins) - } - erc20CoinMap[c.ForeignChainId][strings.ToLower(c.Asset)] = c + if _, found := foreignCoinMap[c.ForeignChainId]; !found { + foreignCoinMap[c.ForeignChainId] = make(map[string]types.ForeignCoins) } + foreignCoinMap[c.ForeignChainId][strings.ToLower(c.Asset)] = c } - return erc20CoinMap + return foreignCoinMap } // GetGasCoinForForeignCoin returns the gas coin for a given chain diff --git a/x/fungible/keeper/foreign_coins_test.go b/x/fungible/keeper/foreign_coins_test.go index d9a98a4031..f3a6f6dacd 100644 --- a/x/fungible/keeper/foreign_coins_test.go +++ b/x/fungible/keeper/foreign_coins_test.go @@ -2,8 +2,10 @@ package keeper_test import ( "strconv" + "strings" "testing" + "cosmossdk.io/math" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/pkg/coin" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" @@ -152,3 +154,67 @@ func TestKeeperGetForeignCoinFromAsset(t *testing.T) { require.Equal(t, "foo", fc.Name) }) } + +func TestKeeperGetAllForeignCoinMap(t *testing.T) { + t.Run("can get all foreign foreign map", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + + // create foreign coins + coinFoo1 := types.ForeignCoins{ + Zrc20ContractAddress: sample.EthAddress().String(), + Asset: strings.ToLower(sample.EthAddress().String()), + ForeignChainId: 1, + Decimals: 6, + CoinType: coin.CoinType_ERC20, + Name: "foo", + LiquidityCap: math.NewUint(100), + } + coinBar1 := types.ForeignCoins{ + Zrc20ContractAddress: sample.EthAddress().String(), + Asset: "", + ForeignChainId: 1, + Decimals: 18, + CoinType: coin.CoinType_Gas, + Name: "bar", + LiquidityCap: math.NewUint(100), + } + coinFoo2 := types.ForeignCoins{ + Zrc20ContractAddress: sample.EthAddress().String(), + Asset: strings.ToLower(sample.EthAddress().String()), + ForeignChainId: 2, + Decimals: 8, + CoinType: coin.CoinType_ERC20, + Name: "foo", + LiquidityCap: math.NewUint(200), + } + coinBar2 := types.ForeignCoins{ + Zrc20ContractAddress: sample.EthAddress().String(), + Asset: "", + ForeignChainId: 2, + Decimals: 18, + CoinType: coin.CoinType_Gas, + Name: "bar", + LiquidityCap: math.NewUint(200), + } + + // populate and get + setForeignCoins(ctx, k, + coinFoo1, + coinBar1, + coinFoo2, + coinBar2, + ) + foreignCoinMap := k.GetAllForeignCoinMap(ctx) + + // check length + require.Len(t, foreignCoinMap, 2) + require.Len(t, foreignCoinMap[1], 2) + require.Len(t, foreignCoinMap[2], 2) + + // check coin + require.Equal(t, coinFoo1, foreignCoinMap[1][coinFoo1.Asset]) + require.Equal(t, coinBar1, foreignCoinMap[1][coinBar1.Asset]) + require.Equal(t, coinFoo2, foreignCoinMap[2][coinFoo2.Asset]) + require.Equal(t, coinBar2, foreignCoinMap[2][coinBar2.Asset]) + }) +} 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, diff --git a/zetaclient/interfaces/interfaces.go b/zetaclient/interfaces/interfaces.go index 0fbaba26b3..d40ac4689b 100644 --- a/zetaclient/interfaces/interfaces.go +++ b/zetaclient/interfaces/interfaces.go @@ -98,7 +98,7 @@ type ZetaCoreBridger interface { GetZetaBlockHeight() (int64, error) GetLastBlockHeightByChain(chain chains.Chain) (*crosschaintypes.LastBlockHeight, error) ListPendingCctx(chainID int64) ([]*crosschaintypes.CrossChainTx, uint64, error) - ListPendingCctxWithinRatelimit() ([]*crosschaintypes.CrossChainTx, uint64, bool, error) + ListPendingCctxWithinRatelimit() ([]*crosschaintypes.CrossChainTx, uint64, int64, string, bool, error) GetPendingNoncesByChain(chainID int64) (observertypes.PendingNonces, error) GetCctxByNonce(chainID int64, nonce uint64) (*crosschaintypes.CrossChainTx, error) GetOutTxTracker(chain chains.Chain, nonce uint64) (*crosschaintypes.OutTxTracker, error) diff --git a/zetaclient/testutils/stub/core_bridge.go b/zetaclient/testutils/stub/core_bridge.go index e66c309cc6..ec72eae7d8 100644 --- a/zetaclient/testutils/stub/core_bridge.go +++ b/zetaclient/testutils/stub/core_bridge.go @@ -121,11 +121,11 @@ func (z *MockZetaCoreBridge) ListPendingCctx(_ int64) ([]*cctxtypes.CrossChainTx return []*cctxtypes.CrossChainTx{}, 0, nil } -func (z *MockZetaCoreBridge) ListPendingCctxWithinRatelimit() ([]*cctxtypes.CrossChainTx, uint64, bool, error) { +func (z *MockZetaCoreBridge) ListPendingCctxWithinRatelimit() ([]*cctxtypes.CrossChainTx, uint64, int64, string, bool, error) { if z.paused { - return nil, 0, false, errors.New(ErrMsgPaused) + return nil, 0, 0, "", false, errors.New(ErrMsgPaused) } - return []*cctxtypes.CrossChainTx{}, 0, false, nil + return []*cctxtypes.CrossChainTx{}, 0, 0, "", false, nil } func (z *MockZetaCoreBridge) GetPendingNoncesByChain(_ int64) (observerTypes.PendingNonces, error) { diff --git a/zetaclient/zetabridge/query.go b/zetaclient/zetabridge/query.go index 09d3b20e82..9c313f364a 100644 --- a/zetaclient/zetabridge/query.go +++ b/zetaclient/zetabridge/query.go @@ -139,7 +139,7 @@ func (b *ZetaCoreBridge) ListPendingCctx(chainID int64) ([]*types.CrossChainTx, // ListPendingCctxWithinRatelimit returns a list of pending cctxs that do not exceed the outbound rate limit // - The max size of the list is crosschainkeeper.MaxPendingCctxs // - The returned `rateLimitExceeded` flag indicates if the rate limit is exceeded or not -func (b *ZetaCoreBridge) ListPendingCctxWithinRatelimit() ([]*types.CrossChainTx, uint64, bool, error) { +func (b *ZetaCoreBridge) ListPendingCctxWithinRatelimit() ([]*types.CrossChainTx, uint64, int64, string, bool, error) { client := types.NewQueryClient(b.grpcConn) maxSizeOption := grpc.MaxCallRecvMsgSize(32 * 1024 * 1024) resp, err := client.ListPendingCctxWithinRateLimit( @@ -148,9 +148,9 @@ func (b *ZetaCoreBridge) ListPendingCctxWithinRatelimit() ([]*types.CrossChainTx maxSizeOption, ) if err != nil { - return nil, 0, false, err + return nil, 0, 0, "", false, err } - return resp.CrossChainTx, resp.TotalPending, resp.RateLimitExceeded, nil + return resp.CrossChainTx, resp.TotalPending, resp.CurrentWithdrawWindow, resp.CurrentWithdrawRate, resp.RateLimitExceeded, nil } func (b *ZetaCoreBridge) GetAbortedZetaAmount() (string, error) { diff --git a/zetaclient/zetacore_observer.go b/zetaclient/zetacore_observer.go index c66f15f50a..535f741db1 100644 --- a/zetaclient/zetacore_observer.go +++ b/zetaclient/zetacore_observer.go @@ -134,10 +134,15 @@ func (co *CoreObserver) startCctxScheduler(appContext *appcontext.AppContext) { metrics.HotKeyBurnRate.Set(float64(co.ts.HotKeyBurnRate.GetBurnRate().Int64())) // query pending cctxs across all foreign chains with rate limit - cctxMap, err := co.getAllPendingCctxWithRatelimit() + cctxMap, withdrawWindow, withdrawRate, err := co.getAllPendingCctxWithRatelimit() if err != nil { co.logger.ZetaChainWatcher.Error().Err(err).Msgf("startCctxScheduler: queryPendingCctxWithRatelimit failed") } + // print value within rate limiter window every minute + if bn%10 == 0 { + co.logger.ZetaChainWatcher.Debug().Msgf( + "startCctxScheduler: withdraw window is %d, withdraw rate is %s", withdrawWindow, withdrawRate) + } // schedule keysign for pending cctxs on each chain coreContext := appContext.ZetaCoreContext() @@ -190,10 +195,10 @@ func (co *CoreObserver) startCctxScheduler(appContext *appcontext.AppContext) { } // getAllPendingCctxWithRatelimit get pending cctxs across all foreign chains with rate limit -func (co *CoreObserver) getAllPendingCctxWithRatelimit() (map[int64][]*types.CrossChainTx, error) { - cctxList, totalPending, rateLimitExceeded, err := co.bridge.ListPendingCctxWithinRatelimit() +func (co *CoreObserver) getAllPendingCctxWithRatelimit() (map[int64][]*types.CrossChainTx, int64, string, error) { + cctxList, totalPending, withdrawWindow, withdrawRate, rateLimitExceeded, err := co.bridge.ListPendingCctxWithinRatelimit() if err != nil { - return nil, err + return nil, 0, "", err } if rateLimitExceeded { co.logger.ZetaChainWatcher.Warn().Msgf("rate limit exceeded, fetched %d cctxs out of %d", len(cctxList), totalPending) @@ -209,7 +214,7 @@ func (co *CoreObserver) getAllPendingCctxWithRatelimit() (map[int64][]*types.Cro cctxMap[chainID] = append(cctxMap[chainID], cctx) } - return cctxMap, nil + return cctxMap, withdrawWindow, withdrawRate, nil } // scheduleCctxEVM schedules evm outtx keysign on each ZetaChain block (the ticker)