From 7c3c676e01fcc756dde7fea967689eb70c47b5e1 Mon Sep 17 00:00:00 2001 From: alan747271363-art Date: Wed, 13 May 2026 23:26:19 +0700 Subject: [PATCH 1/2] fix(zetaclient): guard signer amount conversions --- zetaclient/chains/solana/signer/amount.go | 15 +++++++++++ .../chains/solana/signer/amount_test.go | 27 +++++++++++++++++++ zetaclient/chains/solana/signer/execute.go | 10 ++++--- .../chains/solana/signer/execute_spl.go | 10 ++++--- .../chains/solana/signer/increment_nonce.go | 10 ++++--- zetaclient/chains/solana/signer/withdraw.go | 10 ++++--- .../chains/solana/signer/withdraw_spl.go | 10 ++++--- zetaclient/chains/sui/signer/amount.go | 15 +++++++++++ zetaclient/chains/sui/signer/amount_test.go | 27 +++++++++++++++++++ zetaclient/chains/sui/signer/signer_tx.go | 6 ++++- 10 files changed, 124 insertions(+), 16 deletions(-) create mode 100644 zetaclient/chains/solana/signer/amount.go create mode 100644 zetaclient/chains/solana/signer/amount_test.go create mode 100644 zetaclient/chains/sui/signer/amount.go create mode 100644 zetaclient/chains/sui/signer/amount_test.go diff --git a/zetaclient/chains/solana/signer/amount.go b/zetaclient/chains/solana/signer/amount.go new file mode 100644 index 0000000000..dd986fbd0e --- /dev/null +++ b/zetaclient/chains/solana/signer/amount.go @@ -0,0 +1,15 @@ +package signer + +import ( + cosmoserrors "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + cctypes "github.com/zeta-chain/node/x/crosschain/types" +) + +func outboundAmountUint64(amount sdkmath.Uint) (uint64, error) { + if amount.BigInt().BitLen() > 64 { + return 0, cosmoserrors.Wrap(cctypes.ErrInvalidWithdrawalAmount, "amount exceeds uint64 range") + } + return amount.Uint64(), nil +} diff --git a/zetaclient/chains/solana/signer/amount_test.go b/zetaclient/chains/solana/signer/amount_test.go new file mode 100644 index 0000000000..2561dc97ff --- /dev/null +++ b/zetaclient/chains/solana/signer/amount_test.go @@ -0,0 +1,27 @@ +package signer + +import ( + "math/big" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + cctypes "github.com/zeta-chain/node/x/crosschain/types" +) + +func TestOutboundAmountUint64(t *testing.T) { + t.Run("returns amount within uint64 range", func(t *testing.T) { + maxUint64 := ^uint64(0) + amount, err := outboundAmountUint64(sdkmath.NewUint(maxUint64)) + require.NoError(t, err) + require.Equal(t, maxUint64, amount) + }) + + t.Run("fails if amount exceeds uint64 range", func(t *testing.T) { + amount := sdkmath.NewUintFromBigInt(new(big.Int).Lsh(big.NewInt(1), 64)) + + _, err := outboundAmountUint64(amount) + require.ErrorIs(t, err, cctypes.ErrInvalidWithdrawalAmount) + }) +} diff --git a/zetaclient/chains/solana/signer/execute.go b/zetaclient/chains/solana/signer/execute.go index d2a6db12aa..9e9d7d4d5d 100644 --- a/zetaclient/chains/solana/signer/execute.go +++ b/zetaclient/chains/solana/signer/execute.go @@ -156,11 +156,15 @@ func (signer *Signer) createMsgExecute( // #nosec G115 always positive chainID := uint64(signer.Chain().ChainId) nonce := params.TssNonce - amount := params.Amount.Uint64() + amount := uint64(0) // zero out the amount if cancelTx is set. It's legal to withdraw 0 lamports through the gateway. - if cancelTx { - amount = 0 + if !cancelTx { + var err error + amount, err = outboundAmountUint64(params.Amount) + if err != nil { + return nil, nil, err + } } // prepare data for msg execute diff --git a/zetaclient/chains/solana/signer/execute_spl.go b/zetaclient/chains/solana/signer/execute_spl.go index 563a42a768..fb7773ba5d 100644 --- a/zetaclient/chains/solana/signer/execute_spl.go +++ b/zetaclient/chains/solana/signer/execute_spl.go @@ -63,11 +63,15 @@ func (signer *Signer) createMsgExecuteSPL( // #nosec G115 always positive chainID := uint64(signer.Chain().ChainId) nonce := params.TssNonce - amount := params.Amount.Uint64() + amount := uint64(0) // zero out the amount if cancelTx is set. It's legal to withdraw 0 spl through the gateway. - if cancelTx { - amount = 0 + if !cancelTx { + var err error + amount, err = outboundAmountUint64(params.Amount) + if err != nil { + return nil, nil, err + } } // get mint details to get decimals diff --git a/zetaclient/chains/solana/signer/increment_nonce.go b/zetaclient/chains/solana/signer/increment_nonce.go index 5520e043cc..80b67e24b6 100644 --- a/zetaclient/chains/solana/signer/increment_nonce.go +++ b/zetaclient/chains/solana/signer/increment_nonce.go @@ -70,11 +70,15 @@ func (signer *Signer) createAndSignMsgIncrementNonce( // #nosec G115 always positive chainID := uint64(signer.Chain().ChainId) nonce := params.TssNonce - amount := params.Amount.Uint64() + amount := uint64(0) // zero out the amount if cancelTx is set. It's legal to withdraw 0 lamports through the gateway. - if cancelTx { - amount = 0 + if !cancelTx { + var err error + amount, err = outboundAmountUint64(params.Amount) + if err != nil { + return nil, err + } } // prepare increment_nonce msg and compute hash diff --git a/zetaclient/chains/solana/signer/withdraw.go b/zetaclient/chains/solana/signer/withdraw.go index 40296e728b..622494f588 100644 --- a/zetaclient/chains/solana/signer/withdraw.go +++ b/zetaclient/chains/solana/signer/withdraw.go @@ -53,11 +53,15 @@ func (signer *Signer) createMsgWithdraw( // #nosec G115 always positive chainID := uint64(signer.Chain().ChainId) nonce := params.TssNonce - amount := params.Amount.Uint64() + amount := uint64(0) // zero out the amount if cancelTx is set. It's legal to withdraw 0 lamports through the gateway. - if cancelTx { - amount = 0 + if !cancelTx { + var err error + amount, err = outboundAmountUint64(params.Amount) + if err != nil { + return nil, nil, err + } } // check receiver address diff --git a/zetaclient/chains/solana/signer/withdraw_spl.go b/zetaclient/chains/solana/signer/withdraw_spl.go index 5d07bc807c..c9b8e04b7a 100644 --- a/zetaclient/chains/solana/signer/withdraw_spl.go +++ b/zetaclient/chains/solana/signer/withdraw_spl.go @@ -60,11 +60,15 @@ func (signer *Signer) createMsgWithdrawSPL( // #nosec G115 always positive chainID := uint64(signer.Chain().ChainId) nonce := params.TssNonce - amount := params.Amount.Uint64() + amount := uint64(0) // zero out the amount if cancelTx is set. It's legal to withdraw 0 spl through the gateway. - if cancelTx { - amount = 0 + if !cancelTx { + var err error + amount, err = outboundAmountUint64(params.Amount) + if err != nil { + return nil, nil, err + } } // get mint details to get decimals diff --git a/zetaclient/chains/sui/signer/amount.go b/zetaclient/chains/sui/signer/amount.go new file mode 100644 index 0000000000..dd986fbd0e --- /dev/null +++ b/zetaclient/chains/sui/signer/amount.go @@ -0,0 +1,15 @@ +package signer + +import ( + cosmoserrors "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + cctypes "github.com/zeta-chain/node/x/crosschain/types" +) + +func outboundAmountUint64(amount sdkmath.Uint) (uint64, error) { + if amount.BigInt().BitLen() > 64 { + return 0, cosmoserrors.Wrap(cctypes.ErrInvalidWithdrawalAmount, "amount exceeds uint64 range") + } + return amount.Uint64(), nil +} diff --git a/zetaclient/chains/sui/signer/amount_test.go b/zetaclient/chains/sui/signer/amount_test.go new file mode 100644 index 0000000000..2561dc97ff --- /dev/null +++ b/zetaclient/chains/sui/signer/amount_test.go @@ -0,0 +1,27 @@ +package signer + +import ( + "math/big" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + cctypes "github.com/zeta-chain/node/x/crosschain/types" +) + +func TestOutboundAmountUint64(t *testing.T) { + t.Run("returns amount within uint64 range", func(t *testing.T) { + maxUint64 := ^uint64(0) + amount, err := outboundAmountUint64(sdkmath.NewUint(maxUint64)) + require.NoError(t, err) + require.Equal(t, maxUint64, amount) + }) + + t.Run("fails if amount exceeds uint64 range", func(t *testing.T) { + amount := sdkmath.NewUintFromBigInt(new(big.Int).Lsh(big.NewInt(1), 64)) + + _, err := outboundAmountUint64(amount) + require.ErrorIs(t, err, cctypes.ErrInvalidWithdrawalAmount) + }) +} diff --git a/zetaclient/chains/sui/signer/signer_tx.go b/zetaclient/chains/sui/signer/signer_tx.go index ea130f8219..44aabff6bc 100644 --- a/zetaclient/chains/sui/signer/signer_tx.go +++ b/zetaclient/chains/sui/signer/signer_tx.go @@ -159,6 +159,10 @@ func (s *Signer) buildWithdrawAndCallTx( payloadHex string, ) (models.TxnMetaData, error) { params := cctx.GetCurrentOutboundParam() + amount, err := outboundAmountUint64(params.Amount) + if err != nil { + return models.TxnMetaData{}, err + } // decode and parse the payload into object IDs and on_call arguments payloadBytes, err := hex.DecodeString(payloadHex) @@ -181,7 +185,7 @@ func (s *Signer) buildWithdrawAndCallTx( args := withdrawAndCallPTBArgs{ withdrawAndCallObjRefs: wacRefs, coinType: coinType, - amount: params.Amount.Uint64(), + amount: amount, nonce: params.TssNonce, gasBudget: gasBudget, sender: ethcommon.HexToAddress(cctx.InboundParams.Sender).Hex(), From 5acdb9d8bdd13ac6d9f33c5c75bac0f6cf4b5306 Mon Sep 17 00:00:00 2001 From: alan747271363-art Date: Tue, 19 May 2026 23:59:38 +0700 Subject: [PATCH 2/2] refactor(zetaclient): share outbound amount guard --- .../chains/{solana/signer => base}/amount.go | 6 +++-- .../{solana/signer => base}/amount_test.go | 6 ++--- zetaclient/chains/solana/signer/execute.go | 3 ++- .../chains/solana/signer/execute_spl.go | 3 ++- .../chains/solana/signer/increment_nonce.go | 3 ++- zetaclient/chains/solana/signer/withdraw.go | 3 ++- .../chains/solana/signer/withdraw_spl.go | 3 ++- zetaclient/chains/sui/signer/amount.go | 15 ----------- zetaclient/chains/sui/signer/amount_test.go | 27 ------------------- zetaclient/chains/sui/signer/signer_tx.go | 3 ++- 10 files changed, 19 insertions(+), 53 deletions(-) rename zetaclient/chains/{solana/signer => base}/amount.go (65%) rename zetaclient/chains/{solana/signer => base}/amount_test.go (82%) delete mode 100644 zetaclient/chains/sui/signer/amount.go delete mode 100644 zetaclient/chains/sui/signer/amount_test.go diff --git a/zetaclient/chains/solana/signer/amount.go b/zetaclient/chains/base/amount.go similarity index 65% rename from zetaclient/chains/solana/signer/amount.go rename to zetaclient/chains/base/amount.go index dd986fbd0e..e9221d80b8 100644 --- a/zetaclient/chains/solana/signer/amount.go +++ b/zetaclient/chains/base/amount.go @@ -1,4 +1,4 @@ -package signer +package base import ( cosmoserrors "cosmossdk.io/errors" @@ -7,9 +7,11 @@ import ( cctypes "github.com/zeta-chain/node/x/crosschain/types" ) -func outboundAmountUint64(amount sdkmath.Uint) (uint64, error) { +// OutboundAmountUint64 validates an amount can fit into uint64 and returns it. +func OutboundAmountUint64(amount sdkmath.Uint) (uint64, error) { if amount.BigInt().BitLen() > 64 { return 0, cosmoserrors.Wrap(cctypes.ErrInvalidWithdrawalAmount, "amount exceeds uint64 range") } + return amount.Uint64(), nil } diff --git a/zetaclient/chains/solana/signer/amount_test.go b/zetaclient/chains/base/amount_test.go similarity index 82% rename from zetaclient/chains/solana/signer/amount_test.go rename to zetaclient/chains/base/amount_test.go index 2561dc97ff..a61d33e905 100644 --- a/zetaclient/chains/solana/signer/amount_test.go +++ b/zetaclient/chains/base/amount_test.go @@ -1,4 +1,4 @@ -package signer +package base import ( "math/big" @@ -13,7 +13,7 @@ import ( func TestOutboundAmountUint64(t *testing.T) { t.Run("returns amount within uint64 range", func(t *testing.T) { maxUint64 := ^uint64(0) - amount, err := outboundAmountUint64(sdkmath.NewUint(maxUint64)) + amount, err := OutboundAmountUint64(sdkmath.NewUint(maxUint64)) require.NoError(t, err) require.Equal(t, maxUint64, amount) }) @@ -21,7 +21,7 @@ func TestOutboundAmountUint64(t *testing.T) { t.Run("fails if amount exceeds uint64 range", func(t *testing.T) { amount := sdkmath.NewUintFromBigInt(new(big.Int).Lsh(big.NewInt(1), 64)) - _, err := outboundAmountUint64(amount) + _, err := OutboundAmountUint64(amount) require.ErrorIs(t, err, cctypes.ErrInvalidWithdrawalAmount) }) } diff --git a/zetaclient/chains/solana/signer/execute.go b/zetaclient/chains/solana/signer/execute.go index 9e9d7d4d5d..18294eda60 100644 --- a/zetaclient/chains/solana/signer/execute.go +++ b/zetaclient/chains/solana/signer/execute.go @@ -17,6 +17,7 @@ import ( "github.com/zeta-chain/node/pkg/chains" contracts "github.com/zeta-chain/node/pkg/contracts/solana" "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/zetaclient/chains/base" zctx "github.com/zeta-chain/node/zetaclient/context" ) @@ -161,7 +162,7 @@ func (signer *Signer) createMsgExecute( // zero out the amount if cancelTx is set. It's legal to withdraw 0 lamports through the gateway. if !cancelTx { var err error - amount, err = outboundAmountUint64(params.Amount) + amount, err = base.OutboundAmountUint64(params.Amount) if err != nil { return nil, nil, err } diff --git a/zetaclient/chains/solana/signer/execute_spl.go b/zetaclient/chains/solana/signer/execute_spl.go index fb7773ba5d..98c0838091 100644 --- a/zetaclient/chains/solana/signer/execute_spl.go +++ b/zetaclient/chains/solana/signer/execute_spl.go @@ -12,6 +12,7 @@ import ( "github.com/zeta-chain/node/pkg/chains" contracts "github.com/zeta-chain/node/pkg/contracts/solana" "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/zetaclient/chains/base" ) // prepareExecuteSPLTx prepares execute spl outbound @@ -68,7 +69,7 @@ func (signer *Signer) createMsgExecuteSPL( // zero out the amount if cancelTx is set. It's legal to withdraw 0 spl through the gateway. if !cancelTx { var err error - amount, err = outboundAmountUint64(params.Amount) + amount, err = base.OutboundAmountUint64(params.Amount) if err != nil { return nil, nil, err } diff --git a/zetaclient/chains/solana/signer/increment_nonce.go b/zetaclient/chains/solana/signer/increment_nonce.go index 80b67e24b6..9ebb400ce2 100644 --- a/zetaclient/chains/solana/signer/increment_nonce.go +++ b/zetaclient/chains/solana/signer/increment_nonce.go @@ -11,6 +11,7 @@ import ( "github.com/zeta-chain/node/pkg/coin" contracts "github.com/zeta-chain/node/pkg/contracts/solana" "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/zetaclient/chains/base" "github.com/zeta-chain/node/zetaclient/compliance" ) @@ -75,7 +76,7 @@ func (signer *Signer) createAndSignMsgIncrementNonce( // zero out the amount if cancelTx is set. It's legal to withdraw 0 lamports through the gateway. if !cancelTx { var err error - amount, err = outboundAmountUint64(params.Amount) + amount, err = base.OutboundAmountUint64(params.Amount) if err != nil { return nil, err } diff --git a/zetaclient/chains/solana/signer/withdraw.go b/zetaclient/chains/solana/signer/withdraw.go index 622494f588..a8c5f29fea 100644 --- a/zetaclient/chains/solana/signer/withdraw.go +++ b/zetaclient/chains/solana/signer/withdraw.go @@ -11,6 +11,7 @@ import ( "github.com/zeta-chain/node/pkg/chains" contracts "github.com/zeta-chain/node/pkg/contracts/solana" "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/zetaclient/chains/base" ) // prepareWithdrawTx prepares withdraw outbound @@ -58,7 +59,7 @@ func (signer *Signer) createMsgWithdraw( // zero out the amount if cancelTx is set. It's legal to withdraw 0 lamports through the gateway. if !cancelTx { var err error - amount, err = outboundAmountUint64(params.Amount) + amount, err = base.OutboundAmountUint64(params.Amount) if err != nil { return nil, nil, err } diff --git a/zetaclient/chains/solana/signer/withdraw_spl.go b/zetaclient/chains/solana/signer/withdraw_spl.go index c9b8e04b7a..bbfa862fc0 100644 --- a/zetaclient/chains/solana/signer/withdraw_spl.go +++ b/zetaclient/chains/solana/signer/withdraw_spl.go @@ -12,6 +12,7 @@ import ( "github.com/zeta-chain/node/pkg/chains" contracts "github.com/zeta-chain/node/pkg/contracts/solana" "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/zetaclient/chains/base" ) // prepareWithdrawSPLTx prepares withdraw spl outbound @@ -65,7 +66,7 @@ func (signer *Signer) createMsgWithdrawSPL( // zero out the amount if cancelTx is set. It's legal to withdraw 0 spl through the gateway. if !cancelTx { var err error - amount, err = outboundAmountUint64(params.Amount) + amount, err = base.OutboundAmountUint64(params.Amount) if err != nil { return nil, nil, err } diff --git a/zetaclient/chains/sui/signer/amount.go b/zetaclient/chains/sui/signer/amount.go deleted file mode 100644 index dd986fbd0e..0000000000 --- a/zetaclient/chains/sui/signer/amount.go +++ /dev/null @@ -1,15 +0,0 @@ -package signer - -import ( - cosmoserrors "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" - - cctypes "github.com/zeta-chain/node/x/crosschain/types" -) - -func outboundAmountUint64(amount sdkmath.Uint) (uint64, error) { - if amount.BigInt().BitLen() > 64 { - return 0, cosmoserrors.Wrap(cctypes.ErrInvalidWithdrawalAmount, "amount exceeds uint64 range") - } - return amount.Uint64(), nil -} diff --git a/zetaclient/chains/sui/signer/amount_test.go b/zetaclient/chains/sui/signer/amount_test.go deleted file mode 100644 index 2561dc97ff..0000000000 --- a/zetaclient/chains/sui/signer/amount_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package signer - -import ( - "math/big" - "testing" - - sdkmath "cosmossdk.io/math" - "github.com/stretchr/testify/require" - - cctypes "github.com/zeta-chain/node/x/crosschain/types" -) - -func TestOutboundAmountUint64(t *testing.T) { - t.Run("returns amount within uint64 range", func(t *testing.T) { - maxUint64 := ^uint64(0) - amount, err := outboundAmountUint64(sdkmath.NewUint(maxUint64)) - require.NoError(t, err) - require.Equal(t, maxUint64, amount) - }) - - t.Run("fails if amount exceeds uint64 range", func(t *testing.T) { - amount := sdkmath.NewUintFromBigInt(new(big.Int).Lsh(big.NewInt(1), 64)) - - _, err := outboundAmountUint64(amount) - require.ErrorIs(t, err, cctypes.ErrInvalidWithdrawalAmount) - }) -} diff --git a/zetaclient/chains/sui/signer/signer_tx.go b/zetaclient/chains/sui/signer/signer_tx.go index 44aabff6bc..aa8ad94a39 100644 --- a/zetaclient/chains/sui/signer/signer_tx.go +++ b/zetaclient/chains/sui/signer/signer_tx.go @@ -15,6 +15,7 @@ import ( "github.com/zeta-chain/node/pkg/coin" zetasui "github.com/zeta-chain/node/pkg/contracts/sui" cctypes "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/zetaclient/chains/base" "github.com/zeta-chain/node/zetaclient/chains/sui/client" "github.com/zeta-chain/node/zetaclient/logs" ) @@ -159,7 +160,7 @@ func (s *Signer) buildWithdrawAndCallTx( payloadHex string, ) (models.TxnMetaData, error) { params := cctx.GetCurrentOutboundParam() - amount, err := outboundAmountUint64(params.Amount) + amount, err := base.OutboundAmountUint64(params.Amount) if err != nil { return models.TxnMetaData{}, err }