diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index dc14c9b0ff..e66ead74b9 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -302,6 +302,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { e2etests.TestBitcoinDepositName, e2etests.TestBitcoinDepositAndCallName, e2etests.TestBitcoinDepositAndCallRevertName, + e2etests.TestBitcoinDepositAndCallRevertWithDustName, e2etests.TestBitcoinWithdrawSegWitName, e2etests.TestBitcoinWithdrawInvalidAddressName, e2etests.TestZetaWithdrawBTCRevertName, diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 98eac4397d..872f217cd8 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -75,6 +75,7 @@ const ( TestBitcoinDepositName = "bitcoin_deposit" TestBitcoinDepositAndCallName = "bitcoin_deposit_and_call" TestBitcoinDepositAndCallRevertName = "bitcoin_deposit_and_call_revert" + TestBitcoinDepositAndCallRevertWithDustName = "bitcoin_deposit_and_call_revert_with_dust" TestBitcoinDonationName = "bitcoin_donation" TestBitcoinStdMemoDepositName = "bitcoin_std_memo_deposit" TestBitcoinStdMemoDepositAndCallName = "bitcoin_std_memo_deposit_and_call" @@ -508,6 +509,11 @@ var AllE2ETests = []runner.E2ETest{ }, TestBitcoinDepositAndCallRevert, ), + runner.NewE2ETest( + TestBitcoinDepositAndCallRevertWithDustName, + "deposit Bitcoin into ZEVM; revert with dust amount that aborts the CCTX", []runner.ArgDefinition{}, + TestBitcoinDepositAndCallRevertWithDust, + ), runner.NewE2ETest( TestBitcoinStdMemoDepositName, "deposit Bitcoin into ZEVM with standard memo", 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 new file mode 100644 index 0000000000..474b706553 --- /dev/null +++ b/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go @@ -0,0 +1,54 @@ +package e2etests + +import ( + "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" + zetabitcoin "github.com/zeta-chain/node/zetaclient/chains/bitcoin" +) + +// TestBitcoinDepositAndCallRevertWithDust sends a Bitcoin deposit that reverts with a dust amount in the revert outbound. +func TestBitcoinDepositAndCallRevertWithDust(r *runner.E2ERunner, args []string) { + // ARRANGE + // Given BTC address + r.SetBtcAddress(r.Name, false) + + require.Len(r, args, 0) + + // Given "Live" BTC network + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + + // 0.002 BTC is consumed in a deposit and revert, the dust is set to 1000 satoshis in the protocol + // Therefore the deposit amount should be 0.002 + 0.000001 = 0.00200100 should trigger the condition + // As only 100 satoshis are left after the deposit + + amount := 0.00200100 + amount += zetabitcoin.DefaultDepositorFee + + // Given a list of UTXOs + utxos, err := r.ListDeployerUTXOs() + require.NoError(r, err) + require.NotEmpty(r, utxos) + + // ACT + // 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) + 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)) +} diff --git a/zetaclient/chains/bitcoin/observer/outbound.go b/zetaclient/chains/bitcoin/observer/outbound.go index 16c8d24b81..a66559b62e 100644 --- a/zetaclient/chains/bitcoin/observer/outbound.go +++ b/zetaclient/chains/bitcoin/observer/outbound.go @@ -13,6 +13,7 @@ import ( "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" + "github.com/zeta-chain/node/pkg/constant" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" "github.com/zeta-chain/node/zetaclient/chains/bitcoin" "github.com/zeta-chain/node/zetaclient/chains/bitcoin/rpc" @@ -531,8 +532,8 @@ func (ob *Observer) checkTssOutboundResult( return errors.Wrapf(err, "checkTssOutboundResult: invalid TSS Vin in outbound %s nonce %d", hash, nonce) } - // differentiate between normal and restricted cctx - if compliance.IsCctxRestricted(cctx) { + // differentiate between normal and cancelled cctx + if compliance.IsCctxRestricted(cctx) || params.Amount.Uint64() < constant.BTCWithdrawalDustAmount { err = ob.checkTSSVoutCancelled(params, rawResult.Vout) if err != nil { return errors.Wrapf( diff --git a/zetaclient/chains/bitcoin/signer/signer.go b/zetaclient/chains/bitcoin/signer/signer.go index 90257e019e..50481a1edd 100644 --- a/zetaclient/chains/bitcoin/signer/signer.go +++ b/zetaclient/chains/bitcoin/signer/signer.go @@ -21,6 +21,7 @@ import ( "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" + "github.com/zeta-chain/node/pkg/constant" "github.com/zeta-chain/node/x/crosschain/types" observertypes "github.com/zeta-chain/node/x/observer/types" "github.com/zeta-chain/node/zetaclient/chains/base" @@ -413,13 +414,25 @@ func (signer *Signer) TryProcessOutbound( gasprice.Add(gasprice, satPerByte) // compliance check - cancelTx := compliance.IsCctxRestricted(cctx) - if cancelTx { + restrictedCCTX := compliance.IsCctxRestricted(cctx) + if restrictedCCTX { compliance.PrintComplianceLog(logger, signer.Logger().Compliance, true, chain.ChainId, cctx.Index, cctx.InboundParams.Sender, params.Receiver, "BTC") - amount = 0.0 // zero out the amount to cancel the tx } - logger.Info().Msgf("SignGasWithdraw: to %s, value %d sats", to.EncodeAddress(), params.Amount.Uint64()) + + // check dust amount + dustAmount := params.Amount.Uint64() < constant.BTCWithdrawalDustAmount + if dustAmount { + logger.Warn().Msgf("dust amount %d sats, canceling tx", params.Amount.Uint64()) + } + + // set the amount to 0 when the tx should be cancelled + cancelTx := restrictedCCTX || dustAmount + if cancelTx { + amount = 0.0 + } else { + logger.Info().Msgf("SignGasWithdraw: to %s, value %d sats", to.EncodeAddress(), params.Amount.Uint64()) + } // sign withdraw tx tx, err := signer.SignWithdrawTx( diff --git a/zetaclient/zetacore/client_monitor.go b/zetaclient/zetacore/client_monitor.go index 90b720d0b9..5b3c823916 100644 --- a/zetaclient/zetacore/client_monitor.go +++ b/zetaclient/zetacore/client_monitor.go @@ -81,7 +81,7 @@ func (c *Client) monitorVoteOutboundResult( } } default: - c.logger.Debug().Fields(logFields).Msg("monitorVoteOutboundResult: successful") + c.logger.Info().Fields(logFields).Msg("monitorVoteOutboundResult: successful") } return nil @@ -159,7 +159,7 @@ func (c *Client) monitorVoteInboundResult( } default: - c.logger.Debug().Fields(logFields).Msgf("monitorVoteInboundResult: successful") + c.logger.Info().Fields(logFields).Msgf("monitorVoteInboundResult: successful") } return nil diff --git a/zetaclient/zetacore/constant.go b/zetaclient/zetacore/constant.go index ab13e741d0..bcc174667e 100644 --- a/zetaclient/zetacore/constant.go +++ b/zetaclient/zetacore/constant.go @@ -19,7 +19,7 @@ const ( PostTSSGasLimit = 500_000 // PostVoteInboundExecutionGasLimit is the gas limit for voting on observed inbound tx and executing it - PostVoteInboundExecutionGasLimit = 7_000_000 + PostVoteInboundExecutionGasLimit = 10_000_000 // PostVoteInboundMessagePassingExecutionGasLimit is the gas limit for voting on, and executing ,observed inbound tx related to message passing (coin_type == zeta) PostVoteInboundMessagePassingExecutionGasLimit = 4_000_000 @@ -47,7 +47,7 @@ const ( // PostVoteOutboundRevertGasLimit is the gas limit for voting on observed outbound tx for revert (when outbound fails) // The value needs to be higher because reverting implies interacting with the EVM to perform swaps for the gas token - PostVoteOutboundRevertGasLimit = 4_000_000 + PostVoteOutboundRevertGasLimit = 7_000_000 ) // constants for monitoring tx results