From 39742e2f16b0039691a4811d66ad933601fcabb4 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:57:38 +0300 Subject: [PATCH] Fix withdraw e2e localtest (#35) --- localtest/signer/signer_secp256k1.go | 58 +++++------------------- localtest/tests.go | 66 ++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 60 deletions(-) diff --git a/localtest/signer/signer_secp256k1.go b/localtest/signer/signer_secp256k1.go index 7b4cfa2..78b6530 100644 --- a/localtest/signer/signer_secp256k1.go +++ b/localtest/signer/signer_secp256k1.go @@ -6,7 +6,7 @@ import ( "crypto/sha256" "encoding/base64" "encoding/hex" - "log" + "fmt" "math/big" "github.com/block-vision/sui-go-sdk/models" @@ -83,6 +83,8 @@ func (s *SignerSecp256k1) GetPublicKey() []byte { return append([]byte{prefix}, paddedX...) } +// Address derives SUI address based on ECDSA public key +// See https://docs.sui.io/concepts/cryptography/transaction-auth/signatures func (s *SignerSecp256k1) Address() string { // Get the public key bytes pubKeyBytes := s.GetPublicKey() @@ -107,46 +109,6 @@ func (s *SignerSecp256k1) Address() string { return "0x" + hex.EncodeToString(addrBytes) } -type SignedMessageSerializedSig struct { - Message string `json:"message"` - Signature string `json:"signature"` -} - -// // https://docs.sui.io/concepts/cryptography/transaction-auth/signatures -// func (s *SignerSecp256k1) SignMessage(data string, scope constant.IntentScope) (*SignedMessageSerializedSig, error) { -// txBytes, _ := base64.StdEncoding.DecodeString(data) -// message := models.NewMessageWithIntent(txBytes, scope) -// digest := blake2b.Sum256(message) -// var noHash crypto.Hash -// sigBytes, err := s.privkey.Sign(rand.Reader, digest[:], noHash) -// if err != nil { -// return nil, err -// } - -// ret := &SignedMessageSerializedSig{ -// Message: data, -// Signature: ToSerializedSignature(sigBytes, s.GetPublicKey()), -// } -// return ret, nil -// } - -// func (s *SignerSecp256k1) SignTransaction(b64TxBytes string) (*models.SignedTransactionSerializedSig, error) { -// result, err := s.SignMessage(b64TxBytes, constant.PersonalMessageIntentScope) -// if err != nil { -// return nil, err -// } - -// return &models.SignedTransactionSerializedSig{ -// TxBytes: result.Message, -// Signature: result.Signature, -// }, nil -// } - -// func (s *SignerSecp256k1) SignPersonalMessage(message string) (*SignedMessageSerializedSig, error) { -// b64Message := base64.StdEncoding.EncodeToString([]byte(message)) -// return s.SignMessage(b64Message, constant.PersonalMessageIntentScope) -// } - func ToSerializedSignature(signature, pubKey []byte) string { signatureLen := len(signature) pubKeyLen := len(pubKey) @@ -157,9 +119,13 @@ func ToSerializedSignature(signature, pubKey []byte) string { return base64.StdEncoding.EncodeToString(serializedSignature) } -// SignAndExecuteTransactionBlock sign a transaction block and submit to the Fullnode for execution. -// adapted from sui-go-sdk/sui/signer.go for secp256k1 -func (s *SignerSecp256k1) SignAndExecuteTransactionBlock(ctx context.Context, cli sui.ISuiAPI, req models.SignAndExecuteTransactionBlockRequest) (models.SuiTransactionBlockResponse, error) { +// SignAndExecuteTransactionBlock signs a tx block and submits it to RPC. +// Adapted from sui-go-sdk/sui/signer.go for secp256k1 +func (s *SignerSecp256k1) SignAndExecuteTransactionBlock( + ctx context.Context, + cli sui.ISuiAPI, + req models.SignAndExecuteTransactionBlockRequest, +) (models.SuiTransactionBlockResponse, error) { txBytes, _ := base64.StdEncoding.DecodeString(req.TxnMetaData.TxBytes) message := messageWithIntent(txBytes) digest1 := blake2b.Sum256(message) @@ -169,9 +135,9 @@ func (s *SignerSecp256k1) SignAndExecuteTransactionBlock(ctx context.Context, cl // privBytes := crypto.FromECDSA(s.privkey) sigBytes, err := crypto2.Sign(digest2[:], s.privkey) if err != nil { - log.Printf("SignAndExecuteTransactionBlock: %v", err) - return models.SuiTransactionBlockResponse{}, err + return models.SuiTransactionBlockResponse{}, fmt.Errorf("failed to sign tx: %w", err) } + sigBytes = sigBytes[:64] signature := ToSerializedSignature(sigBytes, s.GetPublicKey()) diff --git a/localtest/tests.go b/localtest/tests.go index e09a415..6597c5d 100644 --- a/localtest/tests.go +++ b/localtest/tests.go @@ -10,11 +10,14 @@ import ( func TestDeposit(ts *TestSuite) { // ARRANGE + // Request some SUI from the faucet ts.RequestLocalNetSuiFromFaucet(string(ts.TSS.Address())) + // Get TSS coin object id coinObjectId, err := filterOwnedObject(ts.Client, ts.TSS.Address(), "0x2::coin::Coin<0x2::sui::SUI>") require.NoError(ts, err) + // Given deposit tx zetaEthAddress := "0x7c125C1d515b8945841b3d5144a060115C58725F" tx, err := ts.Client.MoveCall(ts.Ctx, models.MoveCallRequest{ Signer: ts.TSS.Address(), @@ -27,9 +30,11 @@ func TestDeposit(ts *TestSuite) { }) require.NoError(ts, err) + // ACT + // Deposit to the gateway resp, err := ts.TSS.SignAndExecuteTransactionBlock(ts.Ctx, ts.Client, models.SignAndExecuteTransactionBlockRequest{ - // not used; the TSS' own private scep256k1 key is used - PriKey: ts.Signer.PriKey, + // not used; the TSS' own private ecdsa key is used + PriKey: nil, TxnMetaData: tx, Options: models.SuiTransactionBlockOptions{ ShowEffects: true, @@ -38,9 +43,12 @@ func TestDeposit(ts *TestSuite) { }, RequestType: "WaitForLocalExecution", }) + + // ASSERT require.NoError(ts, err) require.Equal(ts, "success", resp.Effects.Status.Status) + // Check amount amtStr := resp.Events[0].ParsedJson["amount"].(string) ts.Log("Deposit amount: %s", amtStr) @@ -48,22 +56,49 @@ func TestDeposit(ts *TestSuite) { require.NoError(ts, err) require.NotEmpty(ts, amount) + // Check receiver receiverAddrHex := resp.Events[0].ParsedJson["receiver"].(string) - require.Equal(ts, zetaEthAddress, receiverAddrHex) - - ts.Log("Event match! receiver address: %s", receiverAddrHex) } func TestWithdrawal(ts *TestSuite) { - // acquire the WithdrawCap object first - typeName := fmt.Sprintf("%s::gateway::WithdrawCap", ts.PackageID) - withdrawCapId, err := filterOwnedObject(ts.Client, ts.Signer.Address, typeName) + // ARRANGE + // Given "withdraw capability" tx + withdrawCapType := fmt.Sprintf("%s::gateway::WithdrawCap", ts.PackageID) + withdrawCapID, err := filterOwnedObject(ts.Client, ts.Signer.Address, withdrawCapType) + require.NoError(ts, err) + + ts.Log("WithdrawCap object id %s", withdrawCapID) + require.NotEmpty(ts, withdrawCapID) + + // Note that the Gateway was deployed by SUI wallet (ts.Signer); + // We want to transfer its ownership to TSS to mimic the real behavior. + ts.Log("Transfer ownership of WithdrawCap to TSS") + + // Given withdrawCap ownership transfer tx from Signer to TSS + transferTx, err := ts.Client.MoveCall(ts.Ctx, models.MoveCallRequest{ + Signer: ts.Signer.Address, + PackageObjectId: "0x2", + Module: "transfer", + Function: "public_transfer", + TypeArguments: []any{withdrawCapType}, + Arguments: []any{withdrawCapID, ts.TSS.Address()}, + GasBudget: "5000000000", + }) require.NoError(ts, err) - ts.Log("WithdrawCap id %s", withdrawCapId) - require.NotEmpty(ts, withdrawCapId) + // Execute the transfer of withdrawCap ownership + resp, err := ts.Client.SignAndExecuteTransactionBlock(ts.Ctx, models.SignAndExecuteTransactionBlockRequest{ + PriKey: ts.Signer.PriKey, + TxnMetaData: transferTx, + Options: models.SuiTransactionBlockOptions{ShowEffects: true}, + RequestType: "WaitForLocalExecution", + }) + require.NoError(ts, err) + require.Equal(ts, "success", resp.Effects.Status.Status, "failed %+v", resp.Effects.Status) + + // Given withdraw tx var ( bob = "0x12030d7d9a343d7c31856da0bf6c5706b34035a610284ff5a47e11e990ce4c5b" amt = "12345" @@ -71,20 +106,21 @@ func TestWithdrawal(ts *TestSuite) { ) tx, err := ts.Client.MoveCall(ts.Ctx, models.MoveCallRequest{ - Signer: ts.Signer.Address, + Signer: ts.TSS.Address(), PackageObjectId: ts.PackageID, Module: "gateway", Function: "withdraw", TypeArguments: []any{"0x2::sui::SUI"}, - Arguments: []any{ts.GatewayObjectID, amt, nonce, bob, withdrawCapId}, + Arguments: []any{ts.GatewayObjectID, amt, nonce, bob, withdrawCapID}, GasBudget: "5000000000", }) require.NoError(ts, err) - resp, err := ts.Client.SignAndExecuteTransactionBlock(ts.Ctx, models.SignAndExecuteTransactionBlockRequest{ + // ACT + // Withdraw on behalf of TSS + resp, err = ts.TSS.SignAndExecuteTransactionBlock(ts.Ctx, ts.Client, models.SignAndExecuteTransactionBlockRequest{ TxnMetaData: tx, - PriKey: ts.Signer.PriKey, Options: models.SuiTransactionBlockOptions{ ShowEffects: true, ShowBalanceChanges: true, @@ -93,9 +129,11 @@ func TestWithdrawal(ts *TestSuite) { RequestType: "WaitForLocalExecution", }) + // ASSERT require.NoError(ts, err) require.Equal(ts, "success", resp.Effects.Status.Status) + // Check amount for _, change := range resp.BalanceChanges { if change.Owner.AddressOwner == bob { ts.Log("Withdraw amount: %s", change.Amount)