Skip to content

Commit 8418e1d

Browse files
authored
Merge pull request #448 from multiversx/execute-transfer-fix
Execute transfer fix
2 parents c99c759 + 95c924c commit 8418e1d

File tree

2 files changed

+97
-38
lines changed

2 files changed

+97
-38
lines changed

clients/sui/client.go

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/base64"
77
"fmt"
88
"math/big"
9+
"sort"
910
"sync"
1011

1112
"github.com/block-vision/sui-go-sdk/models"
@@ -264,8 +265,13 @@ func (c *client) GenerateMessageHash(batch *batchProcessor.ArgListsBatch, batchI
264265
}
265266

266267
var hash []byte
267-
groups := c.groupTransfersByTokenType(batch)
268-
for _, group := range groups {
268+
groups, sortedTokenTypes, err := c.groupTransfersByTokenType(batch)
269+
if err != nil {
270+
return nil, fmt.Errorf("error grouping transfers by token type: %v", err)
271+
}
272+
273+
for _, tokenType := range sortedTokenTypes {
274+
group := groups[tokenType]
269275
groupHash, err := c.getHashForTokenGroupData(group, batchId)
270276
if err != nil {
271277
return nil, fmt.Errorf("error getting hash for token group data: %v", err)
@@ -365,9 +371,12 @@ func (c *client) ExecuteTransfer(
365371
serializedSignatures = serializedSignatures[:quorum]
366372
}
367373

368-
tokenGroups := c.groupTransfersByTokenType(argLists)
374+
groups, sortedTokenTypes, err := c.groupTransfersByTokenType(argLists)
375+
if err != nil {
376+
return "", fmt.Errorf("error grouping transfers by token type: %v", err)
377+
}
369378

370-
err = c.processSignaturesOfRelayers(tokenGroups, serializedSignatures)
379+
err = c.processSignaturesOfRelayers(groups, sortedTokenTypes, serializedSignatures)
371380
if err != nil {
372381
return "", err
373382
}
@@ -377,15 +386,15 @@ func (c *client) ExecuteTransfer(
377386
return "", err
378387
}
379388

380-
calls, err := c.prepareExecuteTransferCallArgs(batchID, tokenGroups)
389+
calls, err := c.prepareExecuteTransferCallArgs(batchID, groups, sortedTokenTypes)
381390
if err != nil {
382391
return "", err
383392
}
384393

385394
return c.txHandler.SendTransactionReturnHash(ctx, gasCoin, calls)
386395
}
387396

388-
func (c *client) groupTransfersByTokenType(argLists *batchProcessor.ArgListsBatch) map[string]*TokenTransferGroup {
397+
func (c *client) groupTransfersByTokenType(argLists *batchProcessor.ArgListsBatch) (map[string]*TokenTransferGroup, []string, error) {
389398
groups := make(map[string]*TokenTransferGroup)
390399

391400
for i := 0; i < len(argLists.PeerTokens); i++ {
@@ -403,7 +412,7 @@ func (c *client) groupTransfersByTokenType(argLists *batchProcessor.ArgListsBatc
403412
suiAddress := suiAddressFromBytes(argLists.Recipients[i])
404413
suiAddressBytes, err := transaction.ConvertSuiAddressStringToBytes(models.SuiAddress(suiAddress))
405414
if err != nil {
406-
return nil
415+
return nil, nil, err
407416
}
408417

409418
groups[tokenTypeStr].Recipients = append(groups[tokenTypeStr].Recipients, *suiAddressBytes)
@@ -412,15 +421,17 @@ func (c *client) groupTransfersByTokenType(argLists *batchProcessor.ArgListsBatc
412421
groups[tokenTypeStr].Nonces = append(groups[tokenTypeStr].Nonces, argLists.Nonces[i].Uint64())
413422
}
414423

415-
return groups
416-
}
417-
418-
func (c *client) processSignaturesOfRelayers(tokenGroups map[string]*TokenTransferGroup, serializedSignatures [][]byte) error {
419-
var tokenTypes []string
420-
for key := range tokenGroups {
424+
// Sort token types for deterministic ordering
425+
tokenTypes := make([]string, 0, len(groups))
426+
for key := range groups {
421427
tokenTypes = append(tokenTypes, key)
422428
}
429+
sort.Strings(tokenTypes)
430+
431+
return groups, tokenTypes, nil
432+
}
423433

434+
func (c *client) processSignaturesOfRelayers(tokenGroups map[string]*TokenTransferGroup, sortedTokenTypes []string, serializedSignatures [][]byte) error {
424435
for _, serializedSigsOfRelayer := range serializedSignatures {
425436
n := len(serializedSigsOfRelayer) / EncodedSignatureLength
426437
for i := 0; i < n; i++ {
@@ -434,7 +445,7 @@ func (c *client) processSignaturesOfRelayers(tokenGroups map[string]*TokenTransf
434445
}
435446

436447
sig := [signatureLength]byte(_bytes[signatureSchemePrefixSize:]) // remove the signature scheme byte
437-
tokenGroups[tokenTypes[i]].Signatures = append(tokenGroups[tokenTypes[i]].Signatures, sig)
448+
tokenGroups[sortedTokenTypes[i]].Signatures = append(tokenGroups[sortedTokenTypes[i]].Signatures, sig)
438449
}
439450
}
440451

@@ -459,25 +470,24 @@ func (c *client) getGasCoinOfRelayer(ctx context.Context) (*transaction.SuiObjec
459470
)
460471
}
461472

462-
func (c *client) prepareExecuteTransferCallArgs(batchID uint64, tokenGroups map[string]*TokenTransferGroup) ([]bridgeCore.SuiPTBOperation, error) {
463-
i := 0
473+
func (c *client) prepareExecuteTransferCallArgs(batchID uint64, tokenGroups map[string]*TokenTransferGroup, sortedTokenTypes []string) ([]bridgeCore.SuiPTBOperation, error) {
464474
n := len(tokenGroups)
465475
calls := make([]bridgeCore.SuiPTBOperation, 0, n)
466-
for coinType, group := range tokenGroups {
467-
i++
468-
isBatchComplete := i == n
476+
for i, tokenType := range sortedTokenTypes {
477+
group := tokenGroups[tokenType]
478+
isBatchComplete := i == n-1
469479

470480
localGroup := group
471481
localIsBatchComplete := isBatchComplete
472482

473-
coinParts, err := parseCoinType(coinType)
483+
coinParts, err := parseCoinType(tokenType)
474484
if err != nil {
475-
return nil, fmt.Errorf("failed to parse coin type %s: %w", coinType, err)
485+
return nil, fmt.Errorf("failed to parse coin type %s: %w", tokenType, err)
476486
}
477487

478488
coinIdBytes, err := transaction.ConvertSuiAddressStringToBytes(models.SuiAddress(coinParts[0]))
479489
if err != nil {
480-
return nil, fmt.Errorf("failed to convert coin type %s: %w", coinType, err)
490+
return nil, fmt.Errorf("failed to convert coin type %s: %w", tokenType, err)
481491
}
482492

483493
coinIdBytesVal := *coinIdBytes

clients/sui/client_test.go

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -775,45 +775,94 @@ func TestClient_GenerateMessageHash(t *testing.T) {
775775
assert.NoError(t, err)
776776
assert.Equal(t, expectedHash, hash)
777777
})
778-
t.Run("should return correct hash for batch with deposits with different tokens", func(t *testing.T) {
778+
t.Run("should return same hash for batches with same deposits in different order", func(t *testing.T) {
779779
args := createMockSuiClientArgs()
780780
c, _ := NewSuiClient(args)
781781

782-
batch := &batchProcessor.ArgListsBatch{
782+
batchID := uint64(456)
783+
batch1 := &batchProcessor.ArgListsBatch{
783784
PeerTokens: [][]byte{[]byte("token1"), []byte("token2")},
784785
Recipients: [][]byte{[]byte("recipient1"), []byte("recipient2")},
785786
MvxTokenBytes: [][]byte{[]byte("mvxToken1"), []byte("mvxToken2")},
786787
Amounts: []*big.Int{big.NewInt(900), big.NewInt(560)},
787788
Nonces: []*big.Int{big.NewInt(640), big.NewInt(310)},
788789
Direction: batchProcessor.FromMultiversX,
789790
}
790-
batchID := uint64(456)
791+
batch2 := &batchProcessor.ArgListsBatch{
792+
PeerTokens: [][]byte{[]byte("token2"), []byte("token1")},
793+
Recipients: [][]byte{[]byte("recipient2"), []byte("recipient1")},
794+
MvxTokenBytes: [][]byte{[]byte("mvxToken2"), []byte("mvxToken1")},
795+
Amounts: []*big.Int{big.NewInt(560), big.NewInt(900)},
796+
Nonces: []*big.Int{big.NewInt(310), big.NewInt(640)},
797+
Direction: batchProcessor.FromMultiversX,
798+
}
791799

792-
suiAddress1 := suiAddressFromBytes(batch.Recipients[0])
800+
hash1, err := c.GenerateMessageHash(batch1, batchID)
801+
assert.NoError(t, err)
802+
assert.NotNil(t, hash1)
803+
804+
hash2, err := c.GenerateMessageHash(batch2, batchID)
805+
assert.NoError(t, err)
806+
assert.NotNil(t, hash2)
807+
808+
assert.True(t, bytes.Equal(hash1, hash2))
809+
810+
suiAddress1 := suiAddressFromBytes(batch1.Recipients[0])
793811
suiAddressBytes1, _ := transaction.ConvertSuiAddressStringToBytes(models.SuiAddress(suiAddress1))
794812

795-
suiAddress2 := suiAddressFromBytes(batch.Recipients[1])
813+
suiAddress2 := suiAddressFromBytes(batch1.Recipients[1])
796814
suiAddressBytes2, _ := transaction.ConvertSuiAddressStringToBytes(models.SuiAddress(suiAddress2))
797815

816+
// we use batch1 data to generate expected hashes because is ordered correctly
798817
expectedHashForToken1 := generateHashForTokenGroup(t, batchID, &TokenTransferGroup{
799-
Tokens: [][]byte{batch.PeerTokens[0]},
818+
Tokens: [][]byte{batch1.PeerTokens[0]},
800819
Recipients: []models.SuiAddressBytes{*suiAddressBytes1},
801-
Amounts: []uint64{batch.Amounts[0].Uint64()},
802-
Nonces: []uint64{batch.Nonces[0].Uint64()},
820+
Amounts: []uint64{batch1.Amounts[0].Uint64()},
821+
Nonces: []uint64{batch1.Nonces[0].Uint64()},
803822
})
804823

805824
expectedHashForToken2 := generateHashForTokenGroup(t, batchID, &TokenTransferGroup{
806-
Tokens: [][]byte{batch.PeerTokens[1]},
825+
Tokens: [][]byte{batch1.PeerTokens[1]},
807826
Recipients: []models.SuiAddressBytes{*suiAddressBytes2},
808-
Amounts: []uint64{batch.Amounts[1].Uint64()},
809-
Nonces: []uint64{batch.Nonces[1].Uint64()},
827+
Amounts: []uint64{batch1.Amounts[1].Uint64()},
828+
Nonces: []uint64{batch1.Nonces[1].Uint64()},
810829
})
811830

812-
hash, err := c.GenerateMessageHash(batch, batchID)
813-
assert.NoError(t, err)
814-
assert.True(t, bytes.Contains(hash, expectedHashForToken1))
815-
assert.True(t, bytes.Contains(hash, expectedHashForToken2))
816-
assert.Equal(t, len(expectedHashForToken1)+len(expectedHashForToken2), len(hash))
831+
assert.True(t, bytes.Equal(expectedHashForToken1, hash1[:32]))
832+
assert.True(t, bytes.Equal(expectedHashForToken2, hash1[32:]))
833+
})
834+
t.Run("should be deterministic over multiple invocations", func(t *testing.T) {
835+
args := createMockSuiClientArgs()
836+
c, _ := NewSuiClient(args)
837+
838+
batch := &batchProcessor.ArgListsBatch{
839+
PeerTokens: [][]byte{[]byte("token1"), []byte("token2")},
840+
Recipients: [][]byte{[]byte("recipient1"), []byte("recipient2")},
841+
MvxTokenBytes: [][]byte{[]byte("mvxToken1"), []byte("mvxToken2")},
842+
Amounts: []*big.Int{big.NewInt(900), big.NewInt(560)},
843+
Nonces: []*big.Int{big.NewInt(640), big.NewInt(310)},
844+
Direction: batchProcessor.FromMultiversX,
845+
}
846+
batchID := uint64(456)
847+
848+
const iterations = 100
849+
var hashes [][]byte
850+
851+
for i := 0; i < iterations; i++ {
852+
hash, err := c.GenerateMessageHash(batch, batchID)
853+
assert.NoError(t, err, "iteration %d failed", i)
854+
assert.NotNil(t, hash)
855+
assert.NotEmpty(t, hash)
856+
857+
hashes = append(hashes, hash)
858+
}
859+
860+
referenceHash := hashes[0]
861+
for i := 1; i < len(hashes); i++ {
862+
assert.True(t, bytes.Equal(referenceHash, hashes[i]),
863+
"Hash mismatch at iteration %d.\nExpected: %x\nGot: %x",
864+
i, referenceHash, hashes[i])
865+
}
817866
})
818867
}
819868

0 commit comments

Comments
 (0)