From 07120f14f6403e2f6b651eef576598e61d76f187 Mon Sep 17 00:00:00 2001 From: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:30:03 -0600 Subject: [PATCH 1/5] test: configure Solana gateway program id for E2E tests (#3154) * configure Solana gateway program id in E2E test * add changelog entry --- changelog.md | 1 + cmd/zetae2e/config/config.go | 2 +- cmd/zetae2e/config/contracts.go | 3 +-- cmd/zetae2e/config/local.yml | 5 ++++- cmd/zetae2e/config/localnet.yml | 6 +++++- cmd/zetae2e/local/local.go | 5 ++++- e2e/config/config.go | 2 +- e2e/runner/setup_solana.go | 6 +++--- e2e/runner/solana.go | 6 ++---- pkg/contracts/solana/gateway.go | 9 ++++++--- pkg/contracts/solana/inbound_test.go | 4 ++-- zetaclient/chains/solana/observer/inbound_test.go | 2 +- zetaclient/orchestrator/orchestrator_test.go | 5 ++--- zetaclient/testutils/constant.go | 10 ++++++---- 14 files changed, 39 insertions(+), 27 deletions(-) diff --git a/changelog.md b/changelog.md index 244bc9d08d..07b7d10bc5 100644 --- a/changelog.md +++ b/changelog.md @@ -12,6 +12,7 @@ * [3075](https://github.com/zeta-chain/node/pull/3075) - ton: withdraw concurrent, deposit & revert. * [3105](https://github.com/zeta-chain/node/pull/3105) - split Bitcoin E2E tests into two runners for deposit and withdraw +* [3154](https://github.com/zeta-chain/node/pull/3154) - configure Solana gateway program id for E2E tests ### Refactor * [3118](https://github.com/zeta-chain/node/pull/3118) - zetaclient: remove hsm signer diff --git a/cmd/zetae2e/config/config.go b/cmd/zetae2e/config/config.go index 65e5418f53..cffee5943b 100644 --- a/cmd/zetae2e/config/config.go +++ b/cmd/zetae2e/config/config.go @@ -55,7 +55,7 @@ func RunnerFromConfig( // ExportContractsFromRunner export contracts from the runner to config using a source config func ExportContractsFromRunner(r *runner.E2ERunner, conf config.Config) config.Config { // copy contracts from deployer runner - conf.Contracts.Solana.GatewayProgramID = r.GatewayProgram.String() + conf.Contracts.Solana.GatewayProgramID = config.DoubleQuotedString(r.GatewayProgram.String()) conf.Contracts.Solana.SPLAddr = config.DoubleQuotedString(r.SPLAddr.String()) conf.Contracts.EVM.ZetaEthAddr = config.DoubleQuotedString(r.ZetaEthAddr.Hex()) diff --git a/cmd/zetae2e/config/contracts.go b/cmd/zetae2e/config/contracts.go index 5c46cdc047..6b508096b5 100644 --- a/cmd/zetae2e/config/contracts.go +++ b/cmd/zetae2e/config/contracts.go @@ -31,7 +31,7 @@ func setContractsFromConfig(r *runner.E2ERunner, conf config.Config) error { // set Solana contracts if c := conf.Contracts.Solana.GatewayProgramID; c != "" { - r.GatewayProgram = solana.MustPublicKeyFromBase58(c) + r.GatewayProgram = solana.MustPublicKeyFromBase58(c.String()) } if c := conf.Contracts.Solana.SPLAddr; c != "" { @@ -242,7 +242,6 @@ func setContractsFromConfig(r *runner.E2ERunner, conf config.Config) error { } // v2 contracts - if c := conf.Contracts.EVM.Gateway; c != "" { r.GatewayEVMAddr, err = c.AsEVMAddress() if err != nil { diff --git a/cmd/zetae2e/config/local.yml b/cmd/zetae2e/config/local.yml index 5cc87be394..e4ec147e49 100644 --- a/cmd/zetae2e/config/local.yml +++ b/cmd/zetae2e/config/local.yml @@ -116,4 +116,7 @@ contracts: custody: "0xff3135df4F2775f4091b81f4c7B6359CfA07862a" erc20: "0xbD1e64A22B9F92D9Ce81aA9B4b0fFacd80215564" test_dapp: "0xBFF76e77D56B3C1202107f059425D56f0AEF87Ed" - gateway: "0xF0deebCB0E9C829519C4baa794c5445171973826" \ No newline at end of file + gateway: "0xF0deebCB0E9C829519C4baa794c5445171973826" + solana: + gateway_program_id: "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d" + spl: "" \ No newline at end of file diff --git a/cmd/zetae2e/config/localnet.yml b/cmd/zetae2e/config/localnet.yml index 24e51223ef..15f1e332f6 100644 --- a/cmd/zetae2e/config/localnet.yml +++ b/cmd/zetae2e/config/localnet.yml @@ -98,4 +98,8 @@ rpcs: ton_sidecar_url: "http://ton:8000" zetacore_grpc: "zetacore0:9090" zetacore_rpc: "http://zetacore0:26657" -# contracts will be populated on first run +contracts: +# configure localnet solana gateway program id + solana: + gateway_program_id: "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d" + spl: "" \ No newline at end of file diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 5507d95014..6dc466cef8 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -224,7 +224,10 @@ func localE2ETest(cmd *cobra.Command, _ []string) { deployerRunner.SetupEVMV2() if testSolana { - deployerRunner.SetupSolana(conf.AdditionalAccounts.UserSolana.SolanaPrivateKey.String()) + deployerRunner.SetupSolana( + conf.Contracts.Solana.GatewayProgramID.String(), + conf.AdditionalAccounts.UserSolana.SolanaPrivateKey.String(), + ) } deployerRunner.SetZEVMSystemContracts() diff --git a/e2e/config/config.go b/e2e/config/config.go index 33a8c2bea8..22382c1fa1 100644 --- a/e2e/config/config.go +++ b/e2e/config/config.go @@ -120,7 +120,7 @@ type Contracts struct { // Solana contains the addresses of predeployed contracts and accounts on the Solana chain type Solana struct { - GatewayProgramID string `yaml:"gateway_program_id"` + GatewayProgramID DoubleQuotedString `yaml:"gateway_program_id"` SPLAddr DoubleQuotedString `yaml:"spl"` } diff --git a/e2e/runner/setup_solana.go b/e2e/runner/setup_solana.go index 1a46326d02..28a4fb65fa 100644 --- a/e2e/runner/setup_solana.go +++ b/e2e/runner/setup_solana.go @@ -27,11 +27,11 @@ func (r *E2ERunner) SetupSolanaAccount() { } // SetupSolana sets Solana contracts and params -func (r *E2ERunner) SetupSolana(deployerPrivateKey string) { +func (r *E2ERunner) SetupSolana(gatewayID, deployerPrivateKey string) { r.Logger.Print("⚙️ initializing gateway program on Solana") // set Solana contracts - r.GatewayProgram = solana.MustPublicKeyFromBase58(solanacontracts.SolanaGatewayProgramID) + r.GatewayProgram = solana.MustPublicKeyFromBase58(gatewayID) // get deployer account balance privkey, err := solana.PrivateKeyFromBase58(deployerPrivateKey) @@ -141,7 +141,7 @@ func (r *E2ERunner) ensureSolanaChainParams() error { BallotThreshold: observertypes.DefaultBallotThreshold, MinObserverDelegation: observertypes.DefaultMinObserverDelegation, IsSupported: true, - GatewayAddress: solanacontracts.SolanaGatewayProgramID, + GatewayAddress: r.GatewayProgram.String(), } updateMsg := observertypes.NewMsgUpdateChainParams(creator, chainParams) diff --git a/e2e/runner/solana.go b/e2e/runner/solana.go index d395c60d76..380219dee4 100644 --- a/e2e/runner/solana.go +++ b/e2e/runner/solana.go @@ -21,8 +21,7 @@ import ( // ComputePdaAddress computes the PDA address for the gateway program func (r *E2ERunner) ComputePdaAddress() solana.PublicKey { seed := []byte(solanacontract.PDASeed) - GatewayProgramID := solana.MustPublicKeyFromBase58(solanacontract.SolanaGatewayProgramID) - pdaComputed, bump, err := solana.FindProgramAddress([][]byte{seed}, GatewayProgramID) + pdaComputed, bump, err := solana.FindProgramAddress([][]byte{seed}, r.GatewayProgram) require.NoError(r, err) r.Logger.Info("computed pda: %s, bump %d\n", pdaComputed, bump) @@ -33,8 +32,7 @@ func (r *E2ERunner) ComputePdaAddress() solana.PublicKey { // SolanaRentPayerPDA computes the rent payer PDA (Program Derived Address) address for the gateway program func (r *E2ERunner) SolanaRentPayerPDA() solana.PublicKey { seed := []byte(solanacontract.RentPayerPDASeed) - GatewayProgramID := solana.MustPublicKeyFromBase58(solanacontract.SolanaGatewayProgramID) - pdaComputed, bump, err := solana.FindProgramAddress([][]byte{seed}, GatewayProgramID) + pdaComputed, bump, err := solana.FindProgramAddress([][]byte{seed}, r.GatewayProgram) require.NoError(r, err) r.Logger.Info("computed rent payer pda: %s, bump %d\n", pdaComputed, bump) diff --git a/pkg/contracts/solana/gateway.go b/pkg/contracts/solana/gateway.go index bf674aa081..7465893149 100644 --- a/pkg/contracts/solana/gateway.go +++ b/pkg/contracts/solana/gateway.go @@ -8,9 +8,6 @@ import ( ) const ( - // SolanaGatewayProgramID is the program ID of the Solana gateway program - SolanaGatewayProgramID = "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d" - // PDASeed is the seed for the Solana gateway program derived address PDASeed = "meta" @@ -29,16 +26,22 @@ const ( var ( // DiscriminatorInitialize returns the discriminator for Solana gateway 'initialize' instruction DiscriminatorInitialize = idlgateway.IDLGateway.GetDiscriminator("initialize") + // DiscriminatorInitializeRentPayer returns the discriminator for Solana gateway 'initialize_rent_payer' instruction DiscriminatorInitializeRentPayer = idlgateway.IDLGateway.GetDiscriminator("initialize_rent_payer") + // DiscriminatorDeposit returns the discriminator for Solana gateway 'deposit' instruction DiscriminatorDeposit = idlgateway.IDLGateway.GetDiscriminator("deposit") + // DiscriminatorDepositSPL returns the discriminator for Solana gateway 'deposit_spl_token' instruction DiscriminatorDepositSPL = idlgateway.IDLGateway.GetDiscriminator("deposit_spl_token") + // DiscriminatorWithdraw returns the discriminator for Solana gateway 'withdraw' instruction DiscriminatorWithdraw = idlgateway.IDLGateway.GetDiscriminator("withdraw") + // DiscriminatorWithdrawSPL returns the discriminator for Solana gateway 'withdraw_spl_token' instruction DiscriminatorWithdrawSPL = idlgateway.IDLGateway.GetDiscriminator("withdraw_spl_token") + // DiscriminatorWhitelist returns the discriminator for Solana gateway 'whitelist_spl_mint' instruction DiscriminatorWhitelistSplMint = idlgateway.IDLGateway.GetDiscriminator("whitelist_spl_mint") ) diff --git a/pkg/contracts/solana/inbound_test.go b/pkg/contracts/solana/inbound_test.go index 00bb17f994..7b19badf7a 100644 --- a/pkg/contracts/solana/inbound_test.go +++ b/pkg/contracts/solana/inbound_test.go @@ -47,7 +47,7 @@ func Test_ParseInboundAsDeposit(t *testing.T) { // create observer chainParams := sample.ChainParams(chain.ChainId) - chainParams.GatewayAddress = testutils.GatewayAddresses[chain.ChainId] + chainParams.GatewayAddress = testutils.OldSolanaGatewayAddressDevnet require.NoError(t, err) // expected result @@ -79,7 +79,7 @@ func Test_ParseInboundAsDepositSPL(t *testing.T) { // create observer chainParams := sample.ChainParams(chain.ChainId) - chainParams.GatewayAddress = testutils.GatewayAddresses[chain.ChainId] + chainParams.GatewayAddress = testutils.OldSolanaGatewayAddressDevnet // expected result // solana e2e deployer account diff --git a/zetaclient/chains/solana/observer/inbound_test.go b/zetaclient/chains/solana/observer/inbound_test.go index c38d3ae281..28c31f04db 100644 --- a/zetaclient/chains/solana/observer/inbound_test.go +++ b/zetaclient/chains/solana/observer/inbound_test.go @@ -71,7 +71,7 @@ func Test_FilterInboundEvents(t *testing.T) { // create observer chainParams := sample.ChainParams(chain.ChainId) - chainParams.GatewayAddress = testutils.GatewayAddresses[chain.ChainId] + chainParams.GatewayAddress = testutils.OldSolanaGatewayAddressDevnet ob, err := observer.NewObserver(chain, nil, *chainParams, nil, nil, 60, database, base.DefaultLogger(), nil) require.NoError(t, err) diff --git a/zetaclient/orchestrator/orchestrator_test.go b/zetaclient/orchestrator/orchestrator_test.go index b86e85cc15..4b85ccbb03 100644 --- a/zetaclient/orchestrator/orchestrator_test.go +++ b/zetaclient/orchestrator/orchestrator_test.go @@ -14,7 +14,6 @@ import ( "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" - solanacontracts "github.com/zeta-chain/node/pkg/contracts/solana" "github.com/zeta-chain/node/testutil/sample" crosschainkeeper "github.com/zeta-chain/node/x/crosschain/keeper" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" @@ -41,7 +40,7 @@ func Test_GetUpdatedSigner(t *testing.T) { tonChainParams = mocks.MockChainParams(tonChain.ChainId, 100) ) - solChainParams.GatewayAddress = solanacontracts.SolanaGatewayProgramID + solChainParams.GatewayAddress = testutils.GatewayAddresses[solChain.ChainId] // new chain params in AppContext evmChainParamsNew := mocks.MockChainParams(evmChainParams.ChainId, 100) @@ -126,7 +125,7 @@ func Test_GetUpdatedChainObserver(t *testing.T) { tonChainParams = mocks.MockChainParams(tonChain.ChainId, 100) ) - solChainParams.GatewayAddress = solanacontracts.SolanaGatewayProgramID + solChainParams.GatewayAddress = testutils.GatewayAddresses[solChain.ChainId] tonChainParams.GatewayAddress = sample.GenerateTONAccountID().ToRaw() // new chain params in AppContext diff --git a/zetaclient/testutils/constant.go b/zetaclient/testutils/constant.go index f776c7019f..304cd859c3 100644 --- a/zetaclient/testutils/constant.go +++ b/zetaclient/testutils/constant.go @@ -39,12 +39,14 @@ const ( EventERC20Withdraw = "Withdrawn" ) +// OldSolanaGatewayAddressDevnet is the old gateway address deployed on Solana devnet +const OldSolanaGatewayAddressDevnet = "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d" + // GatewayAddresses contains constants gateway addresses for testing var GatewayAddresses = map[int64]string{ - // Gateway address on Solana devnet - // NOTE: currently different deployer key pair is used for development compared to live networks - // as live networks key pair is sensitive information at this point, can be unified once we have deployments completed - chains.SolanaDevnet.ChainId: "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d", + // Solana gateway addresses + chains.SolanaDevnet.ChainId: "ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis", + chains.SolanaMainnet.ChainId: "ZETAjseVjuFsxdRxo6MmTCvqFwb3ZHUx56Co3vCmGis", } // ConnectorAddresses contains constants ERC20 connector addresses for testing From 420f83a25ff17eafd573aab32844fc28f5487d99 Mon Sep 17 00:00:00 2001 From: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:35:24 -0600 Subject: [PATCH 2/5] fix: abort cctx on dust amount in revert outbound (Bitcoin and Solana) (#3149) * fix dust amount for revert case in Bitcoin and Solana * add changelog entry for the fix * verify dust amount in the CCTX revert outbound * add error message check for aborted CCTX caused by dust amount * Update changelog.md Co-authored-by: Tanmay --------- Co-authored-by: Tanmay --- changelog.md | 1 + cmd/zetae2e/local/local.go | 3 +- e2e/e2etests/e2etests.go | 33 +++++++++++------- ...tcoin_deposit_and_call_revert_with_dust.go | 18 +++++----- ...olana_deposit_and_call_revert_with_dust.go | 34 +++++++++++++++++++ e2e/e2etests/test_solana_deposit_refund.go | 4 +-- e2e/utils/zetacore.go | 14 ++++++++ .../cctx_orchestrator_validate_outbound.go | 13 +++++++ 8 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 e2e/e2etests/test_solana_deposit_and_call_revert_with_dust.go diff --git a/changelog.md b/changelog.md index 07b7d10bc5..4facc05f04 100644 --- a/changelog.md +++ b/changelog.md @@ -26,6 +26,7 @@ * [3041](https://github.com/zeta-chain/node/pull/3041) - replace libp2p public DHT with private gossip peer discovery and connection gater for inbound connections * [3106](https://github.com/zeta-chain/node/pull/3106) - prevent blocked CCTX on out of gas during omnichain calls * [3139](https://github.com/zeta-chain/node/pull/3139) - fix config resolution in orchestrator +* [3149](https://github.com/zeta-chain/node/pull/3149) - abort the cctx if dust amount is detected in the revert outbound ## v21.0.0 diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 6dc466cef8..a752c0e388 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -436,7 +436,8 @@ func localE2ETest(cmd *cobra.Command, _ []string) { e2etests.TestSolanaDepositName, e2etests.TestSolanaWithdrawName, e2etests.TestSolanaDepositAndCallName, - e2etests.TestSolanaDepositAndCallRefundName, + e2etests.TestSolanaDepositAndCallRevertName, + e2etests.TestSolanaDepositAndCallRevertWithDustName, e2etests.TestSolanaDepositRestrictedName, e2etests.TestSolanaWithdrawRestrictedName, // TODO move under admin tests diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index ee4b8d92ce..52c7022be5 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -55,16 +55,17 @@ const ( /* * Solana tests */ - TestSolanaDepositName = "solana_deposit" - TestSolanaWithdrawName = "solana_withdraw" - TestSolanaDepositAndCallName = "solana_deposit_and_call" - TestSolanaDepositAndCallRefundName = "solana_deposit_and_call_refund" - TestSolanaDepositRestrictedName = "solana_deposit_restricted" - TestSolanaWithdrawRestrictedName = "solana_withdraw_restricted" - TestSPLDepositName = "spl_deposit" - TestSPLDepositAndCallName = "spl_deposit_and_call" - TestSPLWithdrawName = "spl_withdraw" - TestSPLWithdrawAndCreateReceiverAtaName = "spl_withdraw_and_create_receiver_ata" + TestSolanaDepositName = "solana_deposit" + TestSolanaWithdrawName = "solana_withdraw" + TestSolanaDepositAndCallName = "solana_deposit_and_call" + TestSolanaDepositAndCallRevertName = "solana_deposit_and_call_revert" + TestSolanaDepositAndCallRevertWithDustName = "solana_deposit_and_call_revert_with_dust" + TestSolanaDepositRestrictedName = "solana_deposit_restricted" + TestSolanaWithdrawRestrictedName = "solana_withdraw_restricted" + TestSPLDepositName = "spl_deposit" + TestSPLDepositAndCallName = "spl_deposit_and_call" + TestSPLWithdrawName = "spl_withdraw" + TestSPLWithdrawAndCreateReceiverAtaName = "spl_withdraw_and_create_receiver_ata" /** * TON tests @@ -453,12 +454,18 @@ var AllE2ETests = []runner.E2ETest{ TestSPLWithdrawAndCreateReceiverAta, ), runner.NewE2ETest( - TestSolanaDepositAndCallRefundName, - "deposit SOL into ZEVM and call a contract that reverts; should refund", + TestSolanaDepositAndCallRevertName, + "deposit SOL into ZEVM and call a contract that reverts", []runner.ArgDefinition{ {Description: "amount in lamport", DefaultValue: "1200000"}, }, - TestSolanaDepositAndCallRefund, + TestSolanaDepositAndCallRevert, + ), + runner.NewE2ETest( + TestSolanaDepositAndCallRevertWithDustName, + "deposit SOL into ZEVM; revert with dust amount that aborts the CCTX", + []runner.ArgDefinition{}, + TestSolanaDepositAndCallRevertWithDust, ), runner.NewE2ETest( TestSolanaDepositRestrictedName, diff --git a/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go b/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go index 1dec399a9b..cc5f5451dc 100644 --- a/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go +++ b/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go @@ -1,6 +1,8 @@ package e2etests import ( + "strings" + "github.com/stretchr/testify/require" "github.com/zeta-chain/node/e2e/runner" @@ -38,16 +40,14 @@ func TestBitcoinDepositAndCallRevertWithDust(r *runner.E2ERunner, args []string) // Send BTC to TSS address with a dummy memo // zetacore should revert cctx if call is made on a non-existing address nonExistReceiver := sample.EthAddress() - badMemo := append(nonExistReceiver.Bytes(), []byte("gibberish-memo")...) - txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos, badMemo) + anyMemo := append(nonExistReceiver.Bytes(), []byte("gibberish-memo")...) + txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos, anyMemo) require.NoError(r, err) require.NotEmpty(r, txHash) - // wait for the cctx to be mined - cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) - r.Logger.CCTX(*cctx, "deposit_and_revert") - utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_Reverted) - - // check the test was effective: the revert outbound amount is less than the dust amount - require.Less(r, cctx.GetCurrentOutboundParam().Amount.Uint64(), uint64(constant.BTCWithdrawalDustAmount)) + // ASSERT + // Now we want to make sure the cctx is aborted with expected error message + cctx := utils.WaitCctxAbortedByInboundHash(r.Ctx, r, txHash.String(), r.CctxClient) + require.True(r, cctx.GetCurrentOutboundParam().Amount.Uint64() < constant.BTCWithdrawalDustAmount) + require.True(r, strings.Contains(cctx.CctxStatus.ErrorMessage, crosschaintypes.ErrInvalidWithdrawalAmount.Error())) } diff --git a/e2e/e2etests/test_solana_deposit_and_call_revert_with_dust.go b/e2e/e2etests/test_solana_deposit_and_call_revert_with_dust.go new file mode 100644 index 0000000000..2ffe5ce9c6 --- /dev/null +++ b/e2e/e2etests/test_solana_deposit_and_call_revert_with_dust.go @@ -0,0 +1,34 @@ +package e2etests + +import ( + "math/big" + "strings" + + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + "github.com/zeta-chain/node/pkg/constant" + "github.com/zeta-chain/node/testutil/sample" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" +) + +// TestSolanaDepositAndCallRevertWithDust tests Solana deposit and call that reverts with a dust amount in the revert outbound. +func TestSolanaDepositAndCallRevertWithDust(r *runner.E2ERunner, args []string) { + require.Len(r, args, 0) + + // deposit the rent exempt amount which will result in a dust amount (after fee deduction) in the revert outbound + depositAmount := big.NewInt(constant.SolanaWalletRentExempt) + + // ACT + // execute the deposit and call transaction + nonExistReceiver := sample.EthAddress() + data := []byte("dust lamports should abort cctx") + sig := r.SOLDepositAndCall(nil, nonExistReceiver, depositAmount, data) + + // ASSERT + // Now we want to make sure cctx is aborted. + cctx := utils.WaitCctxAbortedByInboundHash(r.Ctx, r, sig.String(), r.CctxClient) + require.True(r, cctx.GetCurrentOutboundParam().Amount.Uint64() < constant.SolanaWalletRentExempt) + require.True(r, strings.Contains(cctx.CctxStatus.ErrorMessage, crosschaintypes.ErrInvalidWithdrawalAmount.Error())) +} diff --git a/e2e/e2etests/test_solana_deposit_refund.go b/e2e/e2etests/test_solana_deposit_refund.go index 0a62b70ac6..c8724e72f8 100644 --- a/e2e/e2etests/test_solana_deposit_refund.go +++ b/e2e/e2etests/test_solana_deposit_refund.go @@ -9,8 +9,8 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -// TestSolanaDepositAndCallRefund tests deposit of lamports calling a example contract -func TestSolanaDepositAndCallRefund(r *runner.E2ERunner, args []string) { +// TestSolanaDepositAndCallRevert tests deposit of lamports calling a example contract that reverts. +func TestSolanaDepositAndCallRevert(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) // parse deposit amount (in lamports) diff --git a/e2e/utils/zetacore.go b/e2e/utils/zetacore.go index 0b4d5217ed..34e53e61b0 100644 --- a/e2e/utils/zetacore.go +++ b/e2e/utils/zetacore.go @@ -227,6 +227,20 @@ func WaitCctxRevertedByInboundHash( return cctxs[0] } +// WaitCctxAbortedByInboundHash waits until cctx is aborted by inbound hash. +func WaitCctxAbortedByInboundHash( + ctx context.Context, + t require.TestingT, + hash string, + c CCTXClient, +) crosschaintypes.CrossChainTx { + // wait for cctx to be aborted + cctxs := WaitCctxByInboundHash(ctx, t, hash, c, MatchStatus(crosschaintypes.CctxStatus_Aborted)) + require.Len(t, cctxs, 1) + + return cctxs[0] +} + // WaitCctxByInboundHash waits until cctx appears by inbound hash. func WaitCctxByInboundHash( ctx context.Context, diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go index 2f3acdd744..52c8c1e10d 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go @@ -182,6 +182,7 @@ func (k Keeper) processFailedOutboundOnExternalChain( return cosmoserrors.Wrap(err, "AddRevertOutbound") } + // pay revert outbound gas fee err = k.PayGasAndUpdateCctx( ctx, cctx.InboundParams.SenderChainId, @@ -192,6 +193,18 @@ func (k Keeper) processFailedOutboundOnExternalChain( if err != nil { return err } + + // validate data of the revert outbound + err = k.validateZRC20Withdrawal( + ctx, + cctx.GetCurrentOutboundParam().ReceiverChainId, + cctx.GetCurrentOutboundParam().Amount.BigInt(), + []byte(cctx.GetCurrentOutboundParam().Receiver), + ) + if err != nil { + return err + } + err = k.SetObserverOutboundInfo(ctx, cctx.InboundParams.SenderChainId, cctx) if err != nil { return err From 0b6addd2effd03c87d4edfd6b223ddbc8d199114 Mon Sep 17 00:00:00 2001 From: Christopher Fuka <97121270+CryptoFewka@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:25:00 -0600 Subject: [PATCH 3/5] Bump download-artifact to v4 (#3104) --- .github/actions/upgrade-testing/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/upgrade-testing/action.yml b/.github/actions/upgrade-testing/action.yml index 55ba9f8524..9c3eeaf58d 100644 --- a/.github/actions/upgrade-testing/action.yml +++ b/.github/actions/upgrade-testing/action.yml @@ -14,7 +14,7 @@ runs: with: python-version: 'pypy3.9' - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 #v3 reaches deprecation on November 30, 2024 with: name: binaries-${{ github.sha }} path: ./ From 6fb64cafa758d8b3a1b0df9fb2625b8fefcefb99 Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Thu, 14 Nov 2024 11:59:19 -0800 Subject: [PATCH 4/5] refactor: use major version in release upgrade handlers (#3159) * refactor: use major version in release upgrade handlers * use + rather than - for semver compliance * fmt * docs * fix lint * more semver 2.0 compliance * remove check-upgrade-handler-updated --- .github/workflows/publish-release.yml | 33 ------------------- app/setup_handlers.go | 26 +++++++++++++-- cmd/zetacored/root.go | 1 + cmd/zetacored/version.go | 19 +++++++++++ .../scripts/start-upgrade-orchestrator.sh | 2 +- docs/cli/zetacored/cli.md | 29 ++++++++++++++++ version.sh | 4 +-- 7 files changed, 75 insertions(+), 39 deletions(-) create mode 100644 cmd/zetacored/version.go diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index a8b324de6b..924706af29 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -94,38 +94,6 @@ jobs: run: | echo "continue" - check-upgrade-handler-updated: - needs: - - check-branch - runs-on: ubuntu-22.04 - timeout-minutes: 10 - steps: - - - uses: actions/checkout@v4 - if: inputs.skip_checks != true - with: - fetch-depth: 0 - - - name: Major Version in Upgrade Handler Must Match Tag - if: inputs.skip_checks != true - run: | - UPGRADE_HANDLER_MAJOR_VERSION=$(cat app/setup_handlers.go | grep "const releaseVersion" | cut -d ' ' -f4 | tr -d '"' | cut -d '.' -f 1 | tr -d '\n') - USER_INPUT_VERSION=$(echo "${{ inputs.version }}" | cut -d '.' -f 1 | tr -d '\n') - echo "Upgrade Handler Major Version: ${UPGRADE_HANDLER_MAJOR_VERSION}" - echo "User Inputted Release Version: ${USER_INPUT_VERSION}" - if [ ${USER_INPUT_VERSION} != $UPGRADE_HANDLER_MAJOR_VERSION ]; then - echo "ERROR: The input version doesn't match the release handler for the branch selected. Please ensure the upgrade handler of the branch you selected when you ran the pipeline matches the input version." - echo "Did you forget to update the 'releaseVersion' in app/setup_handlers.go?" - exit 1 - fi - echo "The major version found in 'releaseVersion' in app/setup_handlers.go matches this tagged release - Moving Forward!" - - - name: Mark Job Complete Skipped - if: inputs.skip_checks == true - shell: bash - run: | - echo "continue" - publish-release: permissions: id-token: write @@ -134,7 +102,6 @@ jobs: if: inputs.skip_release != true needs: - check-changelog - - check-upgrade-handler-updated - check-branch - check-goreleaser runs-on: ${{ vars.RELEASE_RUNNER }} diff --git a/app/setup_handlers.go b/app/setup_handlers.go index 04a94da668..1468f83fe2 100644 --- a/app/setup_handlers.go +++ b/app/setup_handlers.go @@ -7,10 +7,28 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/upgrade/types" + "golang.org/x/mod/semver" "github.com/zeta-chain/node/pkg/constant" ) +// GetDefaultUpgradeHandlerVersion prints the default upgrade handler version +// +// There may be multiple upgrade handlers configured on some releases if different +// migrations needto be run in different environment +func GetDefaultUpgradeHandlerVersion() string { + // semver must have v prefix, but we store without prefix + vVersion := "v" + constant.Version + + // development builds always use the full version in the release handlers + if semver.Build(vVersion) != "" || semver.Prerelease(vVersion) != "" { + return constant.Version + } + + // release builds use just the major version (v22.0.0 -> v22) + return semver.Major(vVersion) +} + func SetupHandlers(app *App) { allUpgrades := upgradeTracker{ upgrades: []upgradeTrackerItem{ @@ -50,10 +68,12 @@ func SetupHandlers(app *App) { upgradeHandlerFns, storeUpgrades = allUpgrades.mergeAllUpgrades() } + upgradeHandlerVersion := GetDefaultUpgradeHandlerVersion() + app.UpgradeKeeper.SetUpgradeHandler( - constant.Version, + upgradeHandlerVersion, func(ctx sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) { - app.Logger().Info("Running upgrade handler for " + constant.Version) + app.Logger().Info("Running upgrade handler for " + upgradeHandlerVersion) var err error for _, upgradeHandler := range upgradeHandlerFns { @@ -71,7 +91,7 @@ func SetupHandlers(app *App) { if err != nil { panic(err) } - if upgradeInfo.Name == constant.Version && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + if upgradeInfo.Name == upgradeHandlerVersion && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { // Use upgrade store loader for the initial loading of all stores when app starts, // it checks if version == upgradeHeight and applies store upgrades before loading the stores, // so that new stores start with the correct version (the current height of chain), diff --git a/cmd/zetacored/root.go b/cmd/zetacored/root.go index 93ae4ba469..1f7115e57a 100644 --- a/cmd/zetacored/root.go +++ b/cmd/zetacored/root.go @@ -145,6 +145,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig types.EncodingConfig) { GetPubKeyCmd(), CollectObserverInfoCmd(), AddrConversionCmd(), + UpgradeHandlerVersionCmd(), tmcli.NewCompletionCmd(rootCmd, true), ethermintclient.NewTestnetCmd(app.ModuleBasics, banktypes.GenesisBalancesIterator{}), diff --git a/cmd/zetacored/version.go b/cmd/zetacored/version.go new file mode 100644 index 0000000000..d9da8ac484 --- /dev/null +++ b/cmd/zetacored/version.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/zeta-chain/node/app" +) + +func UpgradeHandlerVersionCmd() *cobra.Command { + return &cobra.Command{ + Use: "upgrade-handler-version", + Short: "Print the default upgrade handler version", + Run: func(_ *cobra.Command, _ []string) { + fmt.Println(app.GetDefaultUpgradeHandlerVersion()) + }, + } +} diff --git a/contrib/localnet/scripts/start-upgrade-orchestrator.sh b/contrib/localnet/scripts/start-upgrade-orchestrator.sh index 1f8089bc3d..13d09a447f 100755 --- a/contrib/localnet/scripts/start-upgrade-orchestrator.sh +++ b/contrib/localnet/scripts/start-upgrade-orchestrator.sh @@ -39,7 +39,7 @@ fi # get new zetacored version curl -L -o /tmp/zetacored.new "${ZETACORED_URL}" chmod +x /tmp/zetacored.new -UPGRADE_NAME=$(/tmp/zetacored.new version) +UPGRADE_NAME=$(/tmp/zetacored.new upgrade-handler-version) # if explicit upgrade height not provided, use dumb estimator if [[ -z $UPGRADE_HEIGHT ]]; then diff --git a/docs/cli/zetacored/cli.md b/docs/cli/zetacored/cli.md index d7a594d94f..96c6e71510 100644 --- a/docs/cli/zetacored/cli.md +++ b/docs/cli/zetacored/cli.md @@ -39,6 +39,7 @@ Zetacore Daemon (server) * [zetacored tendermint](#zetacored-tendermint) - Tendermint subcommands * [zetacored testnet](#zetacored-testnet) - subcommands for starting or configuring local testnets * [zetacored tx](#zetacored-tx) - Transactions subcommands +* [zetacored upgrade-handler-version](#zetacored-upgrade-handler-version) - Print the default upgrade handler version * [zetacored validate-genesis](#zetacored-validate-genesis) - validates the genesis file at the default location or at the location passed as an arg * [zetacored version](#zetacored-version) - Print the application binary version information @@ -14346,6 +14347,34 @@ zetacored tx vesting create-vesting-account [to_address] [amount] [end_time] [fl * [zetacored tx vesting](#zetacored-tx-vesting) - Vesting transaction subcommands +## zetacored upgrade-handler-version + +Print the default upgrade handler version + +``` +zetacored upgrade-handler-version [flags] +``` + +### Options + +``` + -h, --help help for upgrade-handler-version +``` + +### Options inherited from parent commands + +``` + --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) + --log_no_color Disable colored logs + --trace print out full stack trace on errors +``` + +### SEE ALSO + +* [zetacored](#zetacored) - Zetacore Daemon (server) + ## zetacored validate-genesis validates the genesis file at the default location or at the location passed as an arg diff --git a/version.sh b/version.sh index aedbfa5eeb..002f5cc990 100755 --- a/version.sh +++ b/version.sh @@ -22,8 +22,8 @@ short_commit=$(git rev-parse --short HEAD) # append -dirty for dirty builds if ! git diff --no-ext-diff --quiet --exit-code ; then - echo "0.0.${commit_timestamp}-${short_commit}-dirty" + echo "0.0.${commit_timestamp}+${short_commit}.dirty" exit fi -echo "0.0.${commit_timestamp}-${short_commit}" \ No newline at end of file +echo "0.0.${commit_timestamp}+${short_commit}" \ No newline at end of file From 46f3d4472c9c2a61eda408550db666f1a81c9843 Mon Sep 17 00:00:00 2001 From: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:54:38 -0600 Subject: [PATCH 5/5] fix: skip depositor fee calculation on irrelevant transactions (#3162) * skip depositor fee calculation on irrelevant txs * add changelog entry * correct PR number in changelog --- changelog.md | 1 + zetaclient/chains/bitcoin/fee.go | 3 + zetaclient/chains/bitcoin/observer/inbound.go | 40 ++++++----- .../chains/bitcoin/observer/inbound_test.go | 68 ++++++++++++++----- zetaclient/chains/bitcoin/observer/witness.go | 8 ++- .../chains/bitcoin/observer/witness_test.go | 34 ++++++++-- 6 files changed, 107 insertions(+), 47 deletions(-) diff --git a/changelog.md b/changelog.md index 4facc05f04..2ac17bbc71 100644 --- a/changelog.md +++ b/changelog.md @@ -27,6 +27,7 @@ * [3106](https://github.com/zeta-chain/node/pull/3106) - prevent blocked CCTX on out of gas during omnichain calls * [3139](https://github.com/zeta-chain/node/pull/3139) - fix config resolution in orchestrator * [3149](https://github.com/zeta-chain/node/pull/3149) - abort the cctx if dust amount is detected in the revert outbound +* [3162](https://github.com/zeta-chain/node/pull/3162) - skip depositor fee calculation if transaction does not involve TSS address ## v21.0.0 diff --git a/zetaclient/chains/bitcoin/fee.go b/zetaclient/chains/bitcoin/fee.go index f56a479364..7ce483a5bf 100644 --- a/zetaclient/chains/bitcoin/fee.go +++ b/zetaclient/chains/bitcoin/fee.go @@ -56,6 +56,9 @@ var ( DefaultDepositorFee = DepositorFee(defaultDepositorFeeRate) ) +// DepositorFeeCalculator is a function type to calculate the Bitcoin depositor fee +type DepositorFeeCalculator func(interfaces.BTCRPCClient, *btcjson.TxRawResult, *chaincfg.Params) (float64, error) + // FeeRateToSatPerByte converts a fee rate in BTC/KB to sat/byte. func FeeRateToSatPerByte(rate float64) *big.Int { // #nosec G115 always in range diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 3cd1ab3945..aa4f5667ad 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -255,12 +255,6 @@ func (ob *Observer) CheckReceiptForBtcTxHash(ctx context.Context, txHash string, return "", fmt.Errorf("block %d is not confirmed yet", blockVb.Height) } - // calculate depositor fee - depositorFee, err := bitcoin.CalcDepositorFee(ob.btcClient, tx, ob.netParams) - if err != nil { - return "", errors.Wrapf(err, "error calculating depositor fee for inbound %s", tx.Txid) - } - // #nosec G115 always positive event, err := GetBtcEvent( ob.btcClient, @@ -269,7 +263,7 @@ func (ob *Observer) CheckReceiptForBtcTxHash(ctx context.Context, txHash string, uint64(blockVb.Height), ob.logger.Inbound, ob.netParams, - depositorFee, + bitcoin.CalcDepositorFee, ) if err != nil { return "", err @@ -323,13 +317,7 @@ func FilterAndParseIncomingTx( continue // the first tx is coinbase; we do not process coinbase tx } - // calculate depositor fee - depositorFee, err := bitcoin.CalcDepositorFee(rpcClient, &txs[idx], netParams) - if err != nil { - return nil, errors.Wrapf(err, "error calculating depositor fee for inbound %s", tx.Txid) - } - - event, err := GetBtcEvent(rpcClient, tx, tssAddress, blockNumber, logger, netParams, depositorFee) + event, err := GetBtcEvent(rpcClient, tx, tssAddress, blockNumber, logger, netParams, bitcoin.CalcDepositorFee) if err != nil { // unable to parse the tx, the caller should retry return nil, errors.Wrapf(err, "error getting btc event for tx %s in block %d", tx.Txid, blockNumber) @@ -391,12 +379,12 @@ func GetBtcEvent( blockNumber uint64, logger zerolog.Logger, netParams *chaincfg.Params, - depositorFee float64, + feeCalculator bitcoin.DepositorFeeCalculator, ) (*BTCInboundEvent, error) { if netParams.Name == chaincfg.MainNetParams.Name { - return GetBtcEventWithoutWitness(rpcClient, tx, tssAddress, blockNumber, logger, netParams, depositorFee) + return GetBtcEventWithoutWitness(rpcClient, tx, tssAddress, blockNumber, logger, netParams, feeCalculator) } - return GetBtcEventWithWitness(rpcClient, tx, tssAddress, blockNumber, logger, netParams, depositorFee) + return GetBtcEventWithWitness(rpcClient, tx, tssAddress, blockNumber, logger, netParams, feeCalculator) } // GetBtcEventWithoutWitness either returns a valid BTCInboundEvent or nil @@ -409,11 +397,15 @@ func GetBtcEventWithoutWitness( blockNumber uint64, logger zerolog.Logger, netParams *chaincfg.Params, - depositorFee float64, + feeCalculator bitcoin.DepositorFeeCalculator, ) (*BTCInboundEvent, error) { - found := false - var value float64 - var memo []byte + var ( + found bool + value float64 + depositorFee float64 + memo []byte + ) + if len(tx.Vout) >= 2 { // 1st vout must have tss address as receiver with p2wpkh scriptPubKey vout0 := tx.Vout[0] @@ -430,6 +422,12 @@ func GetBtcEventWithoutWitness( return nil, nil } + // calculate depositor fee + depositorFee, err = feeCalculator(rpcClient, &tx, netParams) + if err != nil { + return nil, errors.Wrapf(err, "error calculating depositor fee for inbound %s", tx.Txid) + } + // deposit amount has to be no less than the minimum depositor fee if vout0.Value < depositorFee { logger.Info(). diff --git a/zetaclient/chains/bitcoin/observer/inbound_test.go b/zetaclient/chains/bitcoin/observer/inbound_test.go index 838315b7b8..7ec938aab0 100644 --- a/zetaclient/chains/bitcoin/observer/inbound_test.go +++ b/zetaclient/chains/bitcoin/observer/inbound_test.go @@ -23,6 +23,7 @@ import ( "github.com/zeta-chain/node/testutil/sample" "github.com/zeta-chain/node/zetaclient/chains/bitcoin" "github.com/zeta-chain/node/zetaclient/chains/bitcoin/observer" + "github.com/zeta-chain/node/zetaclient/chains/interfaces" clientcommon "github.com/zeta-chain/node/zetaclient/common" "github.com/zeta-chain/node/zetaclient/keys" "github.com/zeta-chain/node/zetaclient/testutils" @@ -30,6 +31,13 @@ import ( "github.com/zeta-chain/node/zetaclient/testutils/testrpc" ) +// mockDepositFeeCalculator returns a mock depositor fee calculator that returns the given fee and error. +func mockDepositFeeCalculator(fee float64, err error) bitcoin.DepositorFeeCalculator { + return func(interfaces.BTCRPCClient, *btcjson.TxRawResult, *chaincfg.Params) (float64, error) { + return fee, err + } +} + func TestAvgFeeRateBlock828440(t *testing.T) { // load archived block 828440 var blockVb btcjson.GetBlockVerboseTxResult @@ -278,6 +286,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { // fee rate of above tx is 28 sat/vB depositorFee := bitcoin.DepositorFee(28 * clientcommon.BTCOutboundGasPriceMultiplier) + feeCalculator := mockDepositFeeCalculator(depositorFee, nil) // expected result memo, err := hex.DecodeString(tx.Vout[1].ScriptPubKey.Hex[4:]) @@ -309,7 +318,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Equal(t, eventExpected, event) @@ -333,7 +342,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Equal(t, eventExpected, event) @@ -357,7 +366,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Equal(t, eventExpected, event) @@ -381,7 +390,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Equal(t, eventExpected, event) @@ -405,7 +414,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Equal(t, eventExpected, event) @@ -425,7 +434,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Nil(t, event) @@ -445,7 +454,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Nil(t, event) @@ -459,7 +468,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Nil(t, event) @@ -479,12 +488,31 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Nil(t, event) }) + t.Run("should return error if RPC failed to calculate depositor fee", func(t *testing.T) { + // load tx + tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) + + // get BTC event + rpcClient := mocks.NewBTCRPCClient(t) + event, err := observer.GetBtcEventWithoutWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + mockDepositFeeCalculator(0.0, errors.New("rpc error")), + ) + require.ErrorContains(t, err, "rpc error") + require.Nil(t, event) + }) + t.Run("should skip tx if amount is less than depositor fee", func(t *testing.T) { // load tx and modify amount to less than depositor fee tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) @@ -499,7 +527,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Nil(t, event) @@ -519,7 +547,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Nil(t, event) @@ -539,7 +567,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Nil(t, event) @@ -570,7 +598,7 @@ func TestGetBtcEventWithoutWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Nil(t, event) @@ -588,6 +616,7 @@ func TestGetBtcEventErrors(t *testing.T) { // fee rate of above tx is 28 sat/vB depositorFee := bitcoin.DepositorFee(28 * clientcommon.BTCOutboundGasPriceMultiplier) + feeCalculator := mockDepositFeeCalculator(depositorFee, nil) t.Run("should return error on invalid Vout[0] script", func(t *testing.T) { // load tx and modify Vout[0] script to invalid script @@ -603,7 +632,7 @@ func TestGetBtcEventErrors(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.Error(t, err) require.Nil(t, event) @@ -623,7 +652,7 @@ func TestGetBtcEventErrors(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.ErrorContains(t, err, "no input found") require.Nil(t, event) @@ -645,7 +674,7 @@ func TestGetBtcEventErrors(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.ErrorContains(t, err, "error getting sender address") require.Nil(t, event) @@ -662,6 +691,8 @@ func TestGetBtcEvent(t *testing.T) { net := &chaincfg.MainNetParams // 2.992e-05, see avgFeeRate https://mempool.space/api/v1/blocks/835640 depositorFee := bitcoin.DepositorFee(22 * clientcommon.BTCOutboundGasPriceMultiplier) + feeCalculator := mockDepositFeeCalculator(depositorFee, nil) + txHash2 := "37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8" tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash2, false) rpcClient := mocks.NewBTCRPCClient(t) @@ -673,7 +704,7 @@ func TestGetBtcEvent(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Equal(t, (*observer.BTCInboundEvent)(nil), event) @@ -693,6 +724,7 @@ func TestGetBtcEvent(t *testing.T) { // fee rate of above tx is 28 sat/vB depositorFee := bitcoin.DepositorFee(28 * clientcommon.BTCOutboundGasPriceMultiplier) + feeCalculator := mockDepositFeeCalculator(depositorFee, nil) // expected result memo, err := hex.DecodeString(tx.Vout[1].ScriptPubKey.Hex[4:]) @@ -723,7 +755,7 @@ func TestGetBtcEvent(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Equal(t, eventExpected, event) diff --git a/zetaclient/chains/bitcoin/observer/witness.go b/zetaclient/chains/bitcoin/observer/witness.go index 9625ad3caa..bb85bdc47b 100644 --- a/zetaclient/chains/bitcoin/observer/witness.go +++ b/zetaclient/chains/bitcoin/observer/witness.go @@ -24,7 +24,7 @@ func GetBtcEventWithWitness( blockNumber uint64, logger zerolog.Logger, netParams *chaincfg.Params, - depositorFee float64, + feeCalculator bitcoin.DepositorFeeCalculator, ) (*BTCInboundEvent, error) { if len(tx.Vout) < 1 { logger.Debug().Msgf("no output %s", tx.Txid) @@ -40,6 +40,12 @@ func GetBtcEventWithWitness( return nil, nil } + // calculate depositor fee + depositorFee, err := feeCalculator(client, &tx, netParams) + if err != nil { + return nil, errors.Wrapf(err, "error calculating depositor fee for inbound %s", tx.Txid) + } + isAmountValid, amount := isValidAmount(tx.Vout[0].Value, depositorFee) if !isAmountValid { logger.Info(). diff --git a/zetaclient/chains/bitcoin/observer/witness_test.go b/zetaclient/chains/bitcoin/observer/witness_test.go index af14427c6e..745f2003a9 100644 --- a/zetaclient/chains/bitcoin/observer/witness_test.go +++ b/zetaclient/chains/bitcoin/observer/witness_test.go @@ -61,6 +61,7 @@ func TestGetBtcEventWithWitness(t *testing.T) { // fee rate of above tx is 28 sat/vB depositorFee := bitcoin.DepositorFee(28 * clientcommon.BTCOutboundGasPriceMultiplier) + feeCalculator := mockDepositFeeCalculator(depositorFee, nil) t.Run("decode OP_RETURN ok", func(t *testing.T) { tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) @@ -93,7 +94,7 @@ func TestGetBtcEventWithWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Equal(t, eventExpected, event) @@ -131,7 +132,7 @@ func TestGetBtcEventWithWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Equal(t, eventExpected, event) @@ -172,7 +173,7 @@ func TestGetBtcEventWithWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Equal(t, event, eventExpected) @@ -192,12 +193,31 @@ func TestGetBtcEventWithWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Nil(t, event) }) + t.Run("should return error if RPC failed to calculate depositor fee", func(t *testing.T) { + // load tx + tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) + + // get BTC event + rpcClient := mocks.NewBTCRPCClient(t) + event, err := observer.GetBtcEventWithWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + mockDepositFeeCalculator(0.0, errors.New("rpc error")), + ) + require.ErrorContains(t, err, "rpc error") + require.Nil(t, event) + }) + t.Run("should skip tx if amount is less than depositor fee", func(t *testing.T) { // load tx and modify amount to less than depositor fee tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) @@ -212,7 +232,7 @@ func TestGetBtcEventWithWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Nil(t, event) @@ -234,7 +254,7 @@ func TestGetBtcEventWithWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.ErrorContains(t, err, "rpc error") require.Nil(t, event) @@ -268,7 +288,7 @@ func TestGetBtcEventWithWitness(t *testing.T) { blockNumber, log.Logger, net, - depositorFee, + feeCalculator, ) require.NoError(t, err) require.Nil(t, event)