| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224 |
- package txverifier
- import (
- "context"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "math/big"
- "testing"
- "github.com/stretchr/testify/assert"
- "github.com/wormhole-foundation/wormhole/sdk/vaa"
- "go.uber.org/zap"
- )
- // Tokens
- const (
- EthereumUsdcAddress = "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
- SuiUsdcAddress = "5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf"
- )
- func newTestSuiTransferVerifier(connection SuiApiInterface) *SuiTransferVerifier {
- suiCoreContract := "0x5306f64e312b581766351c07af79c72fcb1cd25147157fdc2f8ad76de9a3fb6a"
- suiTokenBridgeContract := "0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d"
- suiTokenBridgeEmitter := "0xccceeb29348f71bdd22ffef43a2a19c1f5b5e17c5cca5411529120182672ade5"
- return NewSuiTransferVerifier(suiCoreContract, suiTokenBridgeEmitter, suiTokenBridgeContract, connection)
- }
- type MockSuiApiConnection struct {
- // The events to be returned by QueryEvents
- Events []SuiEvent
- ObjectsResponses []SuiTryMultiGetPastObjectsResponse
- }
- type ResultTestCase struct {
- decimals uint8
- tokenChain string
- tokenAddress string
- wrapped bool
- newBalance string
- oldBalance string
- drop bool
- }
- func NewMockSuiApiConnection(events []SuiEvent) *MockSuiApiConnection {
- return &MockSuiApiConnection{
- Events: events,
- ObjectsResponses: nil,
- }
- }
- func (mock *MockSuiApiConnection) SetEvents(events []SuiEvent) {
- mock.Events = events
- }
- func (mock *MockSuiApiConnection) SetObjectsResponse(ObjectResponse SuiTryMultiGetPastObjectsResponse) {
- mock.ObjectsResponses = append(mock.ObjectsResponses, ObjectResponse)
- }
- func (mock *MockSuiApiConnection) ClearObjectResponses() {
- mock.ObjectsResponses = []SuiTryMultiGetPastObjectsResponse{}
- }
- func (mock *MockSuiApiConnection) QueryEvents(ctx context.Context, filter string, cursor string, limit int, descending bool) (SuiQueryEventsResponse, error) {
- return SuiQueryEventsResponse{}, nil
- }
- func (mock *MockSuiApiConnection) GetTransactionBlock(ctx context.Context, txDigest string) (SuiGetTransactionBlockResponse, error) {
- objectChanges := []ObjectChange{}
- // Create new nested object that unwraps some of it
- for _, objectResponse := range mock.ObjectsResponses {
- objectType, _ := objectResponse.GetObjectType()
- objectId, _ := objectResponse.GetObjectId()
- version, _ := objectResponse.GetVersion()
- previousVersion, _ := objectResponse.GetPreviousVersion()
- obj := ObjectChange{
- ObjectType: objectType,
- ObjectId: objectId,
- Version: version,
- PreviousVersion: previousVersion,
- }
- objectChanges = append(objectChanges, obj)
- }
- return SuiGetTransactionBlockResponse{Result: SuiGetTransactionBlockResult{Events: mock.Events, ObjectChanges: objectChanges}}, nil
- }
- func (mock *MockSuiApiConnection) TryMultiGetPastObjects(ctx context.Context, objectId string, version string, previousVersion string) (SuiTryMultiGetPastObjectsResponse, error) {
- for _, response := range mock.ObjectsResponses {
- keyIn := fmt.Sprintf("%s-%s-%s", objectId, version, previousVersion)
- objectId, err0 := response.GetObjectId()
- version, err1 := response.GetVersion()
- previousVersion, err2 := response.GetPreviousVersion()
- if err0 != nil || err1 != nil || err2 != nil {
- return SuiTryMultiGetPastObjectsResponse{}, fmt.Errorf("Error processing version data")
- }
- keyCur := fmt.Sprintf("%s-%s-%s", objectId, version, previousVersion)
- if keyIn == keyCur {
- return response, nil
- }
- }
- return SuiTryMultiGetPastObjectsResponse{}, fmt.Errorf("Can't find entry")
- }
- func TestNewSuiApiConnection(t *testing.T) {
- sampleUrl := "http://localhost:8080"
- api := NewSuiApiConnection(sampleUrl)
- if rpc, ok := api.(*SuiApiConnection); ok {
- assert.Equal(t, sampleUrl, rpc.rpc)
- } else {
- t.Errorf("Unable to get RPC from SuiApiConnection")
- }
- }
- func nextSequenceNumber(seq *uint64) *string {
- (*seq)++
- seqStr := fmt.Sprintf("%d", *seq)
- return &seqStr
- }
- func TestProcessEvents(t *testing.T) {
- connection := NewMockSuiApiConnection([]SuiEvent{})
- suiTxVerifier := newTestSuiTransferVerifier(connection)
- arbitraryEventType := "arbitrary::EventType"
- arbitraryEmitter := "0x3117"
- sequenceNumber := uint64(0)
- logger := zap.NewNop()
- // Constants used throughout the tests
- suiEventType := suiTxVerifier.suiEventType
- suiTokenBridgeEmitter := suiTxVerifier.suiTokenBridgeEmitter
- // Define test cases
- tests := []struct {
- name string
- events []SuiEvent
- expectedResult map[string]*big.Int
- expectedCount int
- }{
- {
- name: "TestNoEvents",
- events: []SuiEvent{},
- expectedResult: map[string]*big.Int{},
- expectedCount: 0,
- },
- {
- name: "TestSingleEthereumUSDCEvent",
- events: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(100), EthereumUsdcAddress, 2),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- },
- expectedResult: map[string]*big.Int{
- fmt.Sprintf(KEY_FORMAT, EthereumUsdcAddress, vaa.ChainIDEthereum): big.NewInt(100),
- },
- expectedCount: 1,
- },
- {
- name: "TestMultipleEthereumUSDCEvents",
- events: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(100), EthereumUsdcAddress, uint16(vaa.ChainIDEthereum)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(100), EthereumUsdcAddress, uint16(vaa.ChainIDEthereum)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- },
- expectedResult: map[string]*big.Int{
- fmt.Sprintf(KEY_FORMAT, EthereumUsdcAddress, vaa.ChainIDEthereum): big.NewInt(200),
- },
- expectedCount: 2,
- },
- {
- name: "TestMixedEthereumAndSuiUSDCEvents",
- events: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(100), EthereumUsdcAddress, uint16(vaa.ChainIDEthereum)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(100), SuiUsdcAddress, uint16(vaa.ChainIDSui)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- },
- expectedResult: map[string]*big.Int{
- fmt.Sprintf(KEY_FORMAT, EthereumUsdcAddress, vaa.ChainIDEthereum): big.NewInt(100),
- fmt.Sprintf(KEY_FORMAT, SuiUsdcAddress, vaa.ChainIDSui): big.NewInt(100),
- },
- expectedCount: 2,
- },
- {
- name: "TestIncorrectSender",
- events: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &arbitraryEmitter,
- Payload: generatePayload(1, big.NewInt(100), EthereumUsdcAddress, uint16(vaa.ChainIDEthereum)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- },
- expectedResult: map[string]*big.Int{},
- expectedCount: 0,
- },
- {
- name: "TestSkipNonWormholeEvents",
- events: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(100), EthereumUsdcAddress, uint16(vaa.ChainIDEthereum)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- {
- Type: &arbitraryEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(100), SuiUsdcAddress, uint16(vaa.ChainIDSui)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- },
- expectedResult: map[string]*big.Int{
- fmt.Sprintf(KEY_FORMAT, EthereumUsdcAddress, vaa.ChainIDEthereum): big.NewInt(100),
- },
- expectedCount: 1,
- },
- {
- name: "TestInvalidWormholePayloads",
- events: []SuiEvent{
- { // Invalid payload type
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(0, big.NewInt(100), EthereumUsdcAddress, uint16(vaa.ChainIDEthereum)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- { // Empty payload
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: []byte{},
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- },
- expectedResult: map[string]*big.Int{},
- expectedCount: 0,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- requests := suiTxVerifier.extractBridgeRequestsFromEvents(tt.events, logger)
- assert.Equal(t, tt.expectedCount, len(requests))
- // assert.Equal(t, tt.expectedResult, result)
- // assert.Equal(t, tt.expectedCount, count)
- })
- }
- }
- func TestProcessObjectUpdates(t *testing.T) {
- suiTxVerifier := newTestSuiTransferVerifier(nil)
- ctx := context.TODO()
- logger := zap.NewNop() // zap.Must(zap.NewDevelopment())
- // Constants used throughout the tests
- normalObjectNativeType := "0x2::dynamic_field::Field<0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::token_registry::Key<0x2::sui::SUI>, 0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::native_asset::NativeAsset<0x2::sui::SUI>>"
- normalObjectForeignType := "0x2::dynamic_field::Field<0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::token_registry::Key<0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>, 0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::wrapped_asset::WrappedAsset<0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>>"
- normalVersion := "6565"
- normalPreviousVersion := "4040"
- normalObjectNativeId := "0x831c45a8d512c9cf46e7a8a947f7cbbb5e0a59829aa72450ff26fb1873fd0e94"
- normalObjectForeignId := "0xf8f80c0d569fb076adb5fdc3a717dcb9ac14f7fd7512dc17efbf0f80a8b7fa8a"
- normalTokenAddressForeign := "0,0,0,0,0,0,0,0,0,0,0,0,160,184,105,145,198,33,139,54,193,209,157,74,46,158,176,206,54,6,235,72"
- normalTokenAddressNative := "146,88,24,31,92,234,200,219,255,183,3,8,144,36,60,174,214,154,149,153,210,136,109,149,122,156,183,101,106,243,189,179"
- normalChainIdNative := "21"
- normalChainIdForeign := "2"
- oneToken := new(big.Int)
- oneToken.SetString("1000000000000000000", 10)
- // Decimals, token chain, token address, wrapped or not, balance/custody
- tests := []struct {
- name string
- objectChanges []ObjectChange
- resultList []ResultTestCase
- expectedResult map[string]TransferIntoBridge
- expectedCount uint
- }{
- {
- name: "TestProcessObjectNativeBase",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectNativeType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "1000",
- oldBalance: "10",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{
- "9258181f5ceac8dbffb7030890243caed69a9599d2886d957a9cb7656af3bdb3-21": {Amount: big.NewInt(990)},
- },
- expectedCount: 1,
- },
- {
- name: "TestProcessObjectForeignBase",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectForeignType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectForeignId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdForeign,
- tokenAddress: normalTokenAddressForeign,
- wrapped: true,
- newBalance: "10",
- oldBalance: "1000",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{
- "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48-2": {Amount: big.NewInt(990)},
- },
- expectedCount: 1,
- },
- {
- name: "TestProcessObjectNativeNegative",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectNativeType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "10",
- oldBalance: "1000",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{
- "9258181f5ceac8dbffb7030890243caed69a9599d2886d957a9cb7656af3bdb3-21": {Amount: big.NewInt(-990)},
- },
- expectedCount: 1,
- },
- {
- name: "TestProcessObjectForeignNegative", // Unsure if this test case is possible from Sui API
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectForeignType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectForeignId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdForeign,
- tokenAddress: normalTokenAddressForeign,
- wrapped: true,
- newBalance: "1000",
- oldBalance: "10",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{
- "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48-2": {Amount: big.NewInt(-990)},
- },
- expectedCount: 1,
- },
- {
- name: "TestProcessObjectNativeMultiple",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectNativeType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- {
- ObjectType: "0x2::dynamic_field::Field<0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::token_registry::Key<0xb779486cfd6c19e9218cc7dc17c453014d2d9ba12d2ee4dbb0ec4e1e02ae1cca::spt::SPT>, 0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::native_asset::NativeAsset<0xb779486cfd6c19e9218cc7dc17c453014d2d9ba12d2ee4dbb0ec4e1e02ae1cca::spt::SPT>>",
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: "0x0063d37cdce648a7c6f72f69a75a114fbcc81ef23300e4ace60c7941521163db",
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "1000",
- oldBalance: "10",
- decimals: 8,
- },
- {
- tokenChain: normalChainIdNative,
- tokenAddress: "80,117,89,76,1,212,111,59,203,196,167,239,20,98,5,130,115,190,206,119,147,238,189,4,100,150,53,151,201,253,9,53",
- wrapped: false,
- newBalance: "5000",
- oldBalance: "50",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{
- "9258181f5ceac8dbffb7030890243caed69a9599d2886d957a9cb7656af3bdb3-21": {Amount: big.NewInt(990)},
- "5075594c01d46f3bcbc4a7ef1462058273bece7793eebd0464963597c9fd0935-21": {Amount: big.NewInt(4950)},
- },
- expectedCount: 2,
- },
- {
- name: "TestProcessObjectNativeAndForeign",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectNativeType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- {
- ObjectType: normalObjectForeignType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectForeignId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "1000",
- oldBalance: "10",
- decimals: 8,
- },
- {
- tokenChain: normalChainIdForeign,
- tokenAddress: normalTokenAddressForeign,
- wrapped: true,
- newBalance: "50",
- oldBalance: "5000",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{
- "9258181f5ceac8dbffb7030890243caed69a9599d2886d957a9cb7656af3bdb3-21": {Amount: big.NewInt(990)},
- "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48-2": {Amount: big.NewInt(4950)},
- },
- expectedCount: 2,
- },
- {
- name: "TestProcessObjectWrongPackageIdType",
- objectChanges: []ObjectChange{
- {
- ObjectType: "0x2::dynamic_field::Field<0xa340e3db1332c21f20f5c08bef0fa459e733575f9a7e2f5faca64f72cd5a54f2::token_registry::Key<0x2::sui::SUI>, 0xa340e3db1332c21f20f5c08bef0fa459e733575f9a7e2f5faca64f72cd5a54f2::native_asset::NativeAsset<0x2::sui::SUI>",
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "1000",
- oldBalance: "10",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{},
- expectedCount: 0,
- },
- {
- name: "TestProcessObjectNotDynamicField",
- objectChanges: []ObjectChange{
- {
- ObjectType: "0x11111111111111111111::dynamic_field::Field<0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::token_registry::Key<0x2::sui::SUI>, 0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::native_asset::NativeAsset<0x2::sui::SUI>",
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "1000",
- oldBalance: "10",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{},
- expectedCount: 0,
- },
- {
- name: "TestProcessObjectMismatchedCoinTypes",
- objectChanges: []ObjectChange{
- {
- ObjectType: "0x2::dynamic_field::Field<0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::token_registry::Key<0x2::sui::SUI>, 0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::native_asset::NativeAsset<0x11111111111111111111::sui::SUI>",
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "1000",
- oldBalance: "10",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{},
- expectedCount: 0,
- },
- {
- name: "TestProcessObjectNotAssetType",
- objectChanges: []ObjectChange{
- {
- ObjectType: "0x2::dynamic_field::Field<0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::token_registry::Key<0x2::sui::SUI>, 0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::not_native_asset::NativeAsset<0x2::sui::SUI>",
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "1000",
- oldBalance: "10",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{},
- expectedCount: 0,
- },
- {
- name: "TestProcessObjectOneGoodOneBad",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectForeignType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectForeignId,
- },
- {
- ObjectType: "0x2::dynamic_field::Field<0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::token_registry::Key<0x2::sui::SUI>, 0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::not_native_asset::NativeAsset<0x2::sui::SUI>",
- Version: fmt.Sprintf("%s111", normalVersion),
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdForeign,
- tokenAddress: normalTokenAddressForeign,
- wrapped: true,
- newBalance: "10",
- oldBalance: "1000",
- decimals: 8,
- },
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "1000",
- oldBalance: "10",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{
- "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48-2": {Amount: big.NewInt(990)},
- },
- expectedCount: 1,
- },
- {
- name: "TestProcessObjectRealNumbers",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectNativeType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "1000000000000000",
- oldBalance: "999999000000000",
- decimals: 8,
- },
- },
- expectedResult: map[string]TransferIntoBridge{
- "9258181f5ceac8dbffb7030890243caed69a9599d2886d957a9cb7656af3bdb3-21": {Amount: big.NewInt(1000000000)},
- },
- expectedCount: 1,
- },
- {
- name: "TestProcessObjectNormalize",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectNativeType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "101000000000000000000",
- oldBalance: "100000000000000000000",
- decimals: 18,
- },
- },
- expectedResult: map[string]TransferIntoBridge{
- "9258181f5ceac8dbffb7030890243caed69a9599d2886d957a9cb7656af3bdb3-21": {Amount: big.NewInt(100000000)},
- },
- expectedCount: 1,
- },
- {
- name: "TestProcessObjectMissingVersion",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectNativeType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "1000",
- oldBalance: "10",
- decimals: 8,
- drop: true,
- },
- },
- expectedResult: map[string]TransferIntoBridge{},
- expectedCount: 0,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- connection := NewMockSuiApiConnection([]SuiEvent{})
- suiTxVerifier.suiApiConnection = connection
- assert.Equal(t, len(tt.objectChanges), len(tt.resultList))
- // Add all changes to the mock Sui API for future lookups
- for index := 0; index < len(tt.objectChanges); index++ {
- change := tt.objectChanges[index]
- queryResult := tt.resultList[index]
- if !queryResult.drop {
- responseObject := generateResponsesObject(change.ObjectId, change.Version, change.ObjectType, change.PreviousVersion, queryResult.newBalance, queryResult.oldBalance, queryResult.tokenAddress, queryResult.tokenChain, queryResult.decimals, queryResult.wrapped)
- connection.SetObjectsResponse(responseObject)
- }
- }
- // Run function and check results
- transfers := suiTxVerifier.extractTransfersIntoBridgeFromObjectChanges(ctx, tt.objectChanges, logger)
- // Check that expectedResult and transfers have same number of keys
- assert.Equal(t, uint(len(tt.expectedResult)), uint(len(transfers)))
- // Check that each key in expectedResult exists in transfers and has the expected amount
- for key, expectedValue := range tt.expectedResult {
- actualValue, exists := transfers[key]
- if !exists {
- t.Errorf("Expected key %s not found in result", key)
- } else if actualValue.Amount.Cmp(expectedValue.Amount) != 0 {
- t.Errorf("For key %s, expected amount %s but got %s", key, expectedValue.Amount.String(), actualValue.Amount.String())
- }
- }
- })
- }
- }
- func TestProcessDigest(t *testing.T) {
- suiTxVerifier := newTestSuiTransferVerifier(nil)
- // Constants used throughout the tests
- normalObjectNativeType := "0x2::dynamic_field::Field<0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::token_registry::Key<0x2::sui::SUI>, 0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::native_asset::NativeAsset<0x2::sui::SUI>>"
- normalObjectForeignType := "0x2::dynamic_field::Field<0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::token_registry::Key<0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>, 0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::wrapped_asset::WrappedAsset<0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>>"
- normalVersion := "6565"
- normalPreviousVersion := "4040"
- normalObjectNativeId := "0x831c45a8d512c9cf46e7a8a947f7cbbb5e0a59829aa72450ff26fb1873fd0e94"
- normalObjectForeignId := "0xf8f80c0d569fb076adb5fdc3a717dcb9ac14f7fd7512dc17efbf0f80a8b7fa8a"
- normalTokenAddressForeign := "0,0,0,0,0,0,0,0,0,0,0,0,160,184,105,145,198,33,139,54,193,209,157,74,46,158,176,206,54,6,235,72"
- normalTokenAddressNative := "93,75,48,37,6,100,92,55,255,19,59,152,196,181,10,90,225,72,65,101,151,56,214,215,51,213,157,13,33,122,147,191"
- normalChainIdNative := "21"
- normalChainIdForeign := "2"
- suiEventType := suiTxVerifier.suiEventType
- suiTokenBridgeEmitter := suiTxVerifier.suiTokenBridgeEmitter
- sequenceNumber := uint64(0)
- logger := zap.Must(zap.NewDevelopment())
- // func processDigest(digest string, suiApiConnection SuiApiInterface, logger *zap.Logger) error {
- // Needs BOTH events and ObjectChange information to be updated
- tests := []struct {
- name string
- objectChanges []ObjectChange
- resultList []ResultTestCase
- suiEvents []SuiEvent
- expectedError error
- expectedCount uint
- }{
- {
- name: "TestProcessDigestNativeBase",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectNativeType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "1000",
- oldBalance: "10",
- decimals: 8,
- },
- },
- suiEvents: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(990), SuiUsdcAddress, uint16(vaa.ChainIDSui)),
- }),
- },
- },
- expectedError: nil,
- expectedCount: 1,
- },
- {
- name: "TestProcessDigestTakingMoreThanPuttingIn",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectNativeType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectNativeId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdNative,
- tokenAddress: normalTokenAddressNative,
- wrapped: false,
- newBalance: "100000",
- oldBalance: "100000",
- decimals: 8,
- },
- },
- suiEvents: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(100000), SuiUsdcAddress, uint16(vaa.ChainIDSui)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- },
- expectedError: &InvariantError{Msg: INVARIANT_INSUFFICIENT_DEPOSIT},
- expectedCount: 0,
- },
- {
- name: "TestProcessDigestNoEvents",
- objectChanges: []ObjectChange{},
- resultList: []ResultTestCase{},
- suiEvents: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(100000), SuiUsdcAddress, uint16(vaa.ChainIDSui)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- },
- expectedError: &InvariantError{Msg: INVARIANT_NO_DEPOSIT},
- expectedCount: 0,
- },
- {
- name: "TestProcessDigestForeignBase",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectForeignType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectForeignId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdForeign,
- tokenAddress: normalTokenAddressForeign,
- wrapped: true,
- newBalance: "10",
- oldBalance: "1000",
- decimals: 8,
- },
- },
- suiEvents: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(990), EthereumUsdcAddress, uint16(vaa.ChainIDEthereum)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- },
- expectedError: nil,
- expectedCount: 1,
- },
- {
- name: "TestProcessDigestNoEvents",
- objectChanges: []ObjectChange{},
- resultList: []ResultTestCase{},
- suiEvents: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(100000), SuiUsdcAddress, uint16(vaa.ChainIDSui)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- },
- expectedError: &InvariantError{Msg: INVARIANT_NO_DEPOSIT},
- expectedCount: 0,
- },
- {
- name: "TestProcessDigestMultipleEvents",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectForeignType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectForeignId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdForeign,
- tokenAddress: normalTokenAddressForeign,
- wrapped: true,
- newBalance: "10",
- oldBalance: "2000",
- decimals: 8,
- },
- },
- suiEvents: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(990), EthereumUsdcAddress, uint16(vaa.ChainIDEthereum)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(1000), EthereumUsdcAddress, uint16(vaa.ChainIDEthereum)),
- }),
- },
- },
- expectedError: nil,
- expectedCount: 2,
- },
- {
- name: "TestProcessDigestMultipleEventsOverWithdraw",
- objectChanges: []ObjectChange{
- {
- ObjectType: normalObjectForeignType,
- Version: normalVersion,
- PreviousVersion: normalPreviousVersion,
- ObjectId: normalObjectForeignId,
- },
- },
- resultList: []ResultTestCase{
- {
- tokenChain: normalChainIdForeign,
- tokenAddress: normalTokenAddressForeign,
- wrapped: true,
- newBalance: "10",
- oldBalance: "2000",
- decimals: 8,
- },
- },
- suiEvents: []SuiEvent{
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(990), EthereumUsdcAddress, uint16(vaa.ChainIDEthereum)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- {
- Type: &suiEventType,
- ParsedJson: uncheckedJsonMarshal(&WormholeMessage{
- Sender: &suiTokenBridgeEmitter,
- Payload: generatePayload(1, big.NewInt(1001), EthereumUsdcAddress, uint16(vaa.ChainIDEthereum)),
- Sequence: nextSequenceNumber(&sequenceNumber),
- }),
- },
- },
- expectedError: &InvariantError{Msg: INVARIANT_INSUFFICIENT_DEPOSIT},
- expectedCount: 0,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- ctx := context.TODO()
- assert.Equal(t, len(tt.objectChanges), len(tt.resultList))
- connection := NewMockSuiApiConnection(tt.suiEvents) // Set events for connection
- suiTxVerifier.suiApiConnection = connection
- // Add Object Response data for Sui connections
- for index := 0; index < len(tt.objectChanges); index++ {
- change := tt.objectChanges[index]
- queryResult := tt.resultList[index]
- responseObject := generateResponsesObject(change.ObjectId, change.Version, change.ObjectType, change.PreviousVersion, queryResult.newBalance, queryResult.oldBalance, queryResult.tokenAddress, queryResult.tokenChain, queryResult.decimals, queryResult.wrapped)
- connection.SetObjectsResponse(responseObject)
- }
- _, err := suiTxVerifier.processDigestInternal(ctx, "HASH", "", logger)
- assert.Equal(t, true, tt.expectedError == nil && err == nil || err != nil && err.Error() == tt.expectedError.Error())
- // assert.Equal(t, tt.expectedCount, numProcessed)
- })
- }
- }
- // Marshal the input to a json.RawMessage, and ignore the error message.
- func uncheckedJsonMarshal(v any) *json.RawMessage {
- data, _ := json.Marshal(v)
- return (*json.RawMessage)(&data)
- }
- // Generate WormholeMessage payload.
- //
- // Payload type: payload[0]
- // Amount: payload[1] for 32
- // Origin address: payload[33] for 32
- // Origin chain ID: payload[65] for 2
- func generatePayload(payloadType byte, amount *big.Int, originAddressHex string, originChainID uint16) []byte {
- originAddress, _ := hex.DecodeString(originAddressHex)
- payload := make([]byte, 0, 101)
- // Append payload type
- payload = append(payload, payloadType)
- // Append amount (32 bytes)
- amountBytes := amount.FillBytes(make([]byte, 32))
- payload = append(payload, amountBytes...)
- // Append origin address (32 bytes)
- payload = append(payload, originAddress...)
- // Append origin chain ID (2 bytes)
- originChainIDBytes := []byte{byte(originChainID >> 8), byte(originChainID & 0xff)}
- payload = append(payload, originChainIDBytes...)
- // Right-pad the payload to 101 bytes
- padding := make([]byte, 101-len(payload))
- payload = append(payload, padding...)
- return payload
- }
- /*
- JSON data
- Decimals, token chain, token address, wrapped or not, balance/custody
- */
- func generateResponsesObject(objectId string, version string, objectType string, previousVersion string, balanceAfter string, balanceBefore string, tokenAddress string, tokenChain string, decimals uint8, isWrapped bool) SuiTryMultiGetPastObjectsResponse {
- var newVersion string
- var oldVersion string
- if isWrapped == false {
- newVersion = generateResponseObjectNative(objectId, version, objectType, balanceAfter, tokenAddress, decimals)
- oldVersion = generateResponseObjectNative(objectId, previousVersion, objectType, balanceBefore, tokenAddress, decimals)
- } else {
- newVersion = generateResponseObjectForeign(objectId, version, objectType, balanceAfter, tokenAddress, tokenChain, decimals)
- oldVersion = generateResponseObjectForeign(objectId, previousVersion, objectType, balanceBefore, tokenAddress, tokenChain, decimals)
- }
- // Complete the rest of the response data
- responseString := fmt.Sprintf(`{"result": [{"details" : %s}, {"details" : %s}]}`, newVersion, oldVersion)
- data := SuiTryMultiGetPastObjectsResponse{}
- err := json.Unmarshal([]byte(responseString), &data)
- if err != nil {
- fmt.Println("Error in JSON parsing...")
- }
- return data
- }
- func generateResponseObjectNative(objectId string, version string, objectType string, balance string, tokenAddress string, decimals uint8) string {
- json_string_per_object := fmt.Sprintf(`{
- "objectId": "%s",
- "version": "%s",
- "digest": "4ne8fjG16hAXP8GxuXzoA5hBwuHz6C4D7cyf4TZza4Pa",
- "type": "%s",
- "owner": {
- "ObjectOwner": "0x334881831bd89287554a6121087e498fa023ce52c037001b53a4563a00a281a5"
- },
- "previousTransaction": "FRx1iHA3Wq2ybDe3hhMSkS5yqsKJ4wUDUWY3Xp8K6g18",
- "storageRebate": "3146400",
- "content": {
- "type": "%s",
- "fields": {
- "id": {
- "id": "0x831c45a8d512c9cf46e7a8a947f7cbbb5e0a59829aa72450ff26fb1873fd0e94"
- },
- "name": {
- "type": "0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::token_registry::Key<0x2::sui::SUI>",
- "fields": {
- "dummy_field": false
- }
- },
- "value": {
- "type": "0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::native_asset::NativeAsset<0x2::sui::SUI>",
- "fields": {
- "custody": "%s",
- "decimals": %d,
- "token_address": {
- "type": "0x5306f64e312b581766351c07af79c72fcb1cd25147157fdc2f8ad76de9a3fb6a::external_address::ExternalAddress",
- "fields": {
- "value": {
- "type": "0x5306f64e312b581766351c07af79c72fcb1cd25147157fdc2f8ad76de9a3fb6a::bytes32::Bytes32",
- "fields": {
- "data": [
- %s
- ]
- }
- }
- }
- }
- }
- }
- }}}`, objectId, version, objectType, objectType, balance, decimals, tokenAddress)
- return json_string_per_object
- }
- func generateResponseObjectForeign(objectId string, version string, objectType string, balance string, tokenAddress string, tokenChain string, decimals uint8) string {
- json_string_per_object := fmt.Sprintf(`{
- "objectId": "%s",
- "version": "%s",
- "digest": "CWXv7KJrNawMqREtVYCRT9PVF2H8cogW1WCLMd5iQchr",
- "type": "%s",
- "owner": {
- "ObjectOwner": "0x334881831bd89287554a6121087e498fa023ce52c037001b53a4563a00a281a5"
- },
- "previousTransaction": "EaqLzHQTeiPq2FjYCRobDH5E91DAVZgKgZzwQUJ5FaNU",
- "storageRebate": "4050800",
- "content": {
- "dataType": "moveObject",
- "type": "%s",
- "hasPublicTransfer": false,
- "fields": {
- "id": {
- "id": "0xf8f80c0d569fb076adb5fdc3a717dcb9ac14f7fd7512dc17efbf0f80a8b7fa8a"
- },
- "name": {
- "type": "0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::token_registry::Key<0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>",
- "fields": {
- "dummy_field": false
- }
- },
- "value": {
- "type": "0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::wrapped_asset::WrappedAsset<0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>",
- "fields": {
- "decimals": 6,
- "info": {
- "type": "0x26efee2b51c911237888e5dc6702868abca3c7ac12c53f76ef8eba0697695e3d::wrapped_asset::ForeignInfo<0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>",
- "fields": {
- "native_decimals": %d,
- "symbol": "USDC",
- "token_address": {
- "type": "0x5306f64e312b581766351c07af79c72fcb1cd25147157fdc2f8ad76de9a3fb6a::external_address::ExternalAddress",
- "fields": {
- "value": {
- "type": "0x5306f64e312b581766351c07af79c72fcb1cd25147157fdc2f8ad76de9a3fb6a::bytes32::Bytes32",
- "fields": {"data": [%s]
- }
- }
- }
- },
- "token_chain": %s
- }
- },
- "treasury_cap": {
- "type": "0x2::coin::TreasuryCap<0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>",
- "fields": {
- "id": {
- "id": "0xa5085139fdeae133cf6ca58f1f1cee138f24ad6fc54d8e24a519dc24f3b2b974"
- },
- "total_supply": {
- "type": "0x2::balance::Supply<0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>",
- "fields": {
- "value": "%s"
- }
- }
- }
- },
- "upgrade_cap": {
- "type": "0x2::package::UpgradeCap",
- "fields": {
- "id": {
- "id": "0x86ebd31cc715928671ac05e29e85b68ae1d96db02565b5413084fcb5afb695b1"
- },
- "package": "0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf",
- "policy": 0,
- "version": "1"
- }
- }
- }
- }
- }
- }
- }`, objectId, version, objectType, objectType, decimals, tokenAddress, tokenChain, balance)
- return json_string_per_object
- }
|