diff --git a/changelog.md b/changelog.md index 176355afc1..42de5f2e74 100644 --- a/changelog.md +++ b/changelog.md @@ -75,6 +75,7 @@ * [2243](https://github.com/zeta-chain/node/pull/2243) - fix incorrect bitcoin outbound height in the CCTX outbound parameter * [2256](https://github.com/zeta-chain/node/pull/2256) - fix rate limiter falsely included reverted non-withdraw cctxs * [2327](https://github.com/zeta-chain/node/pull/2327) - partially cherry picked the fix to Bitcoin outbound dust amount +* [2362](https://github.com/zeta-chain/node/pull/2362) - set 1000 satoshis as minimum BTC amount that can be withdrawn from zEVM ### CI diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index dbc2d2dbb1..2fc6310800 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -21,6 +21,7 @@ import ( "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/pkg/constant" "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -286,15 +287,20 @@ func ValidateZrc20WithdrawEvent(event *zrc20.ZRC20Withdrawal, chainID int64) err // The event was parsed; that means the user has deposited tokens to the contract. if chains.IsBitcoinChain(chainID) { - if event.Value.Cmp(big.NewInt(0)) <= 0 { - return fmt.Errorf("ParseZRC20WithdrawalEvent: invalid amount %s", event.Value.String()) + if event.Value.Cmp(big.NewInt(constant.BTCWithdrawalDustAmount)) < 0 { + return errorsmod.Wrapf( + types.ErrInvalidWithdrawalAmount, + "withdraw amount %s is less than minimum amount %d", + event.Value.String(), + constant.BTCWithdrawalDustAmount, + ) } addr, err := chains.DecodeBtcAddress(string(event.To), chainID) if err != nil { - return fmt.Errorf("ParseZRC20WithdrawalEvent: invalid address %s: %s", event.To, err) + return errorsmod.Wrapf(types.ErrInvalidAddress, "invalid address %s", string(event.To)) } if !chains.IsBtcAddressSupported(addr) { - return fmt.Errorf("ParseZRC20WithdrawalEvent: unsupported address %s", string(event.To)) + return errorsmod.Wrapf(types.ErrInvalidAddress, "unsupported address %s", string(event.To)) } } return nil diff --git a/x/crosschain/keeper/evm_hooks_test.go b/x/crosschain/keeper/evm_hooks_test.go index bc67f34a99..20b1c87e47 100644 --- a/x/crosschain/keeper/evm_hooks_test.go +++ b/x/crosschain/keeper/evm_hooks_test.go @@ -15,6 +15,7 @@ import ( "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/constant" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" "github.com/zeta-chain/zetacore/testutil/sample" crosschainkeeper "github.com/zeta-chain/zetacore/x/crosschain/keeper" @@ -164,14 +165,21 @@ func TestValidateZrc20WithdrawEvent(t *testing.T) { require.NoError(t, err) }) - t.Run("unable to validate a event with an invalid amount", func(t *testing.T) { + t.Run("unable to validate a btc withdrawal event with an invalid amount", func(t *testing.T) { btcMainNetWithdrawalEvent, err := crosschainkeeper.ParseZRC20WithdrawalEvent( *sample.GetValidZRC20WithdrawToBTC(t).Logs[3], ) require.NoError(t, err) - btcMainNetWithdrawalEvent.Value = big.NewInt(0) + + // 1000 satoshis is the minimum amount that can be withdrawn + btcMainNetWithdrawalEvent.Value = big.NewInt(constant.BTCWithdrawalDustAmount) err = crosschainkeeper.ValidateZrc20WithdrawEvent(btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) - require.ErrorContains(t, err, "ParseZRC20WithdrawalEvent: invalid amount") + require.NoError(t, err) + + // 999 satoshis cannot be withdrawn + btcMainNetWithdrawalEvent.Value = big.NewInt(constant.BTCWithdrawalDustAmount - 1) + err = crosschainkeeper.ValidateZrc20WithdrawEvent(btcMainNetWithdrawalEvent, chains.BitcoinMainnet.ChainId) + require.ErrorContains(t, err, "less than minimum amount") }) t.Run("unable to validate a event with an invalid chain ID", func(t *testing.T) { @@ -822,7 +830,7 @@ func TestKeeper_ProcessLogs(t *testing.T) { } err := k.ProcessLogs(ctx, block.Logs, sample.EthAddress(), "") - require.ErrorContains(t, err, "ParseZRC20WithdrawalEvent: invalid address") + require.ErrorContains(t, err, "invalid address") cctxList := k.GetAllCrossChainTx(ctx) require.Len(t, cctxList, 0) }) diff --git a/x/crosschain/types/errors.go b/x/crosschain/types/errors.go index 232bf229db..6f430e5e1a 100644 --- a/x/crosschain/types/errors.go +++ b/x/crosschain/types/errors.go @@ -49,4 +49,5 @@ var ( ErrInvalidRateLimiterFlags = errorsmod.Register(ModuleName, 1152, "invalid rate limiter flags") ErrMaxTxOutTrackerHashesReached = errorsmod.Register(ModuleName, 1153, "max tx out tracker hashes reached") ErrInitiatitingOutbound = errorsmod.Register(ModuleName, 1154, "cannot initiate outbound") + ErrInvalidWithdrawalAmount = errorsmod.Register(ModuleName, 1155, "invalid withdrawal amount") )