diff --git a/data/tokens.go b/data/tokens.go index 4018c6ea..e878facf 100644 --- a/data/tokens.go +++ b/data/tokens.go @@ -2,6 +2,8 @@ package data import ( "time" + + "github.com/multiversx/mx-chain-core-go/core" ) const metaESDT = "MetaESDT" @@ -9,13 +11,17 @@ const metaESDT = "MetaESDT" // NFTDataUpdate will contain the update information for an NFT or SFT type NFTDataUpdate struct { Identifier string + Address string + NewCreator string URIsToAdd [][]byte NewAttributes []byte - Address string Freeze bool UnFreeze bool Pause bool UnPause bool + SetURIs bool + NewRoyalties core.OptionalUint32 + NewMetaData *TokenMetaData } // ResponseTokens is the structure for the tokens response @@ -51,6 +57,7 @@ type TokenInfo struct { Data *TokenMetaData `json:"data,omitempty"` OwnersHistory []*OwnerData `json:"ownersHistory,omitempty"` TransferOwnership bool `json:"-"` + ChangeToDynamic bool `json:"-"` Properties *TokenProperties `json:"properties,omitempty"` } diff --git a/go.mod b/go.mod index ec7d974b..46f09b26 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/multiversx/mx-chain-communication-go v1.0.15-0.20240508074652-e128a1c05c8e github.com/multiversx/mx-chain-core-go v1.2.21-0.20240508071047-fefea5737840 github.com/multiversx/mx-chain-logger-go v1.0.15-0.20240508072523-3f00a726af57 - github.com/multiversx/mx-chain-vm-common-go v1.5.13-0.20240509103544-247ce5639c7a + github.com/multiversx/mx-chain-vm-common-go v1.5.13-0.20240619122724-2bd2e64cebdc github.com/prometheus/client_model v0.4.0 github.com/prometheus/common v0.37.0 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index 5a3744d9..ad53f92a 100644 --- a/go.sum +++ b/go.sum @@ -254,8 +254,8 @@ github.com/multiversx/mx-chain-core-go v1.2.21-0.20240508071047-fefea5737840/go. github.com/multiversx/mx-chain-crypto-go v1.2.12-0.20240508074452-cc21c1b505df h1:clihfi78bMEOWk/qw6WA4uQbCM2e2NGliqswLAvw19k= github.com/multiversx/mx-chain-logger-go v1.0.15-0.20240508072523-3f00a726af57 h1:g9t410dqjcb7UUptbVd/H6Ua12sEzWU4v7VplyNvRZ0= github.com/multiversx/mx-chain-logger-go v1.0.15-0.20240508072523-3f00a726af57/go.mod h1:cY6CIXpndW5g5PTPn4WzPwka/UBEf+mgw+PSY5pHGAU= -github.com/multiversx/mx-chain-vm-common-go v1.5.13-0.20240509103544-247ce5639c7a h1:7M+jXVlnl43zd2NuimL1KnAVAdpUr/QoHqG0TUKoyaM= -github.com/multiversx/mx-chain-vm-common-go v1.5.13-0.20240509103544-247ce5639c7a/go.mod h1:RgGmPei0suQcFTHfO4cS5dxJSiokp2SM5lmNgp1icMo= +github.com/multiversx/mx-chain-vm-common-go v1.5.13-0.20240619122724-2bd2e64cebdc h1:KpLloX0pIclo3axCQVOm3wZE+U9cfeHgPWGvDuUohTk= +github.com/multiversx/mx-chain-vm-common-go v1.5.13-0.20240619122724-2bd2e64cebdc/go.mod h1:RgGmPei0suQcFTHfO4cS5dxJSiokp2SM5lmNgp1icMo= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= diff --git a/integrationtests/issueTokenAndChangeType_test.go b/integrationtests/issueTokenAndChangeType_test.go new file mode 100644 index 00000000..a368634b --- /dev/null +++ b/integrationtests/issueTokenAndChangeType_test.go @@ -0,0 +1,96 @@ +//go:build integrationtests + +package integrationtests + +import ( + "context" + "encoding/hex" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/alteredAccount" + dataBlock "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/outport" + "github.com/multiversx/mx-chain-core-go/data/transaction" + indexerdata "github.com/multiversx/mx-chain-es-indexer-go/process/dataindexer" + "github.com/stretchr/testify/require" +) + +func TestIssueTokenAndChangeToDynamic(t *testing.T) { + setLogLevelDebug() + + esClient, err := createESClient(esURL) + require.Nil(t, err) + + esProc, err := CreateElasticProcessor(esClient) + require.Nil(t, err) + + body := &dataBlock.Body{} + header := &dataBlock.Header{ + Round: 50, + TimeStamp: 5040, + ShardID: core.MetachainShardId, + } + + address1 := "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv" + pool := &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Address: decodeAddress(address1), + Events: []*transaction.Event{ + { + Address: decodeAddress(address1), + Identifier: []byte("issueSemiFungible"), + Topics: [][]byte{[]byte("CON-abcd"), []byte("semi-token"), []byte("SEMI"), []byte(core.SemiFungibleESDT)}, + }, + { + Address: decodeAddress(address1), + Identifier: []byte("upgradeProperties"), + Topics: [][]byte{[]byte("CON-abcd"), big.NewInt(0).Bytes(), []byte("canUpgrade"), []byte("true")}, + }, + nil, + }, + }, + }, + }, + } + + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, map[string]*alteredAccount.AlteredAccount{}, testNumOfShards)) + require.Nil(t, err) + + ids := []string{"CON-abcd"} + genericResponse := &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/issueTokenAndChangeType/token.json"), string(genericResponse.Docs[0].Source)) + + header.TimeStamp = 10_000 + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Address: decodeAddress(address1), + Events: []*transaction.Event{ + { + Address: decodeAddress(address1), + Identifier: []byte("changeToDynamic"), + Topics: [][]byte{[]byte("CON-abcd"), []byte("semi-token"), []byte("SEMI"), []byte(core.DynamicSFTESDT)}, + }, + nil, + }, + }, + }, + }, + } + + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, map[string]*alteredAccount.AlteredAccount{}, testNumOfShards)) + require.Nil(t, err) + + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/issueTokenAndChangeType/token-with-new-type.json"), string(genericResponse.Docs[0].Source)) +} diff --git a/integrationtests/testdata/issueTokenAndChangeType/token-with-new-type.json b/integrationtests/testdata/issueTokenAndChangeType/token-with-new-type.json new file mode 100644 index 00000000..8f19fc4b --- /dev/null +++ b/integrationtests/testdata/issueTokenAndChangeType/token-with-new-type.json @@ -0,0 +1,29 @@ +{ + "name": "semi-token", + "ticker": "SEMI", + "token": "CON-abcd", + "issuer": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "currentOwner": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "numDecimals": 0, + "type": "DynamicSemiFungibleESDT", + "timestamp": 5040, + "ownersHistory": [ + { + "address": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "timestamp": 5040 + } + ], + "properties": { + "canMint": false, + "canBurn": false, + "canUpgrade": true, + "canTransferNFTCreateRole": false, + "canAddSpecialRoles": false, + "canPause": false, + "canFreeze": false, + "canWipe": false, + "canChangeOwner": false, + "canCreateMultiShard": false + }, + "changedToDynamicTimestamp": 10000 +} diff --git a/integrationtests/testdata/issueTokenAndChangeType/token.json b/integrationtests/testdata/issueTokenAndChangeType/token.json new file mode 100644 index 00000000..8b33340d --- /dev/null +++ b/integrationtests/testdata/issueTokenAndChangeType/token.json @@ -0,0 +1,28 @@ +{ + "name": "semi-token", + "ticker": "SEMI", + "token": "CON-abcd", + "issuer": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "currentOwner": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "numDecimals": 0, + "type": "SemiFungibleESDT", + "timestamp": 5040, + "ownersHistory": [ + { + "address": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "timestamp": 5040 + } + ], + "properties": { + "canMint": false, + "canBurn": false, + "canUpgrade": true, + "canTransferNFTCreateRole": false, + "canAddSpecialRoles": false, + "canPause": false, + "canFreeze": false, + "canWipe": false, + "canChangeOwner": false, + "canCreateMultiShard": false + } +} diff --git a/integrationtests/testdata/mappings/esdts.json b/integrationtests/testdata/mappings/esdts.json index bcdaec16..da8bc0bd 100644 --- a/integrationtests/testdata/mappings/esdts.json +++ b/integrationtests/testdata/mappings/esdts.json @@ -110,6 +110,10 @@ "type": "date", "format": "epoch_second" }, + "changedToDynamicTimestamp": { + "type": "date", + "format": "epoch_second" + }, "token": { "type": "text" }, diff --git a/integrationtests/testdata/updateNFT/token-after-new-creator.json b/integrationtests/testdata/updateNFT/token-after-new-creator.json new file mode 100644 index 00000000..ac967297 --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-after-new-creator.json @@ -0,0 +1,21 @@ +{ + "identifier": "NFT-abcd-0e", + "token": "NFT-abcd", + "numDecimals": 0, + "nonce": 14, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp", + "dXJp", + "dXJp", + "dXJp", + "dXJp" + ], + "nonEmptyURIs": true, + "whiteListedStorage": false, + "attributes": "c29tZXRoaW5n", + "creator": "erd12m3x8jp6dl027pj5f2nw6ght2cyhhjfrs86cdwsa8xn83r375qfqrwpdx0" + }, + "frozen": false +} diff --git a/integrationtests/testdata/updateNFT/token-after-new-royalties.json b/integrationtests/testdata/updateNFT/token-after-new-royalties.json new file mode 100644 index 00000000..b8eb8dbc --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-after-new-royalties.json @@ -0,0 +1,22 @@ +{ + "identifier": "NFT-abcd-0e", + "token": "NFT-abcd", + "numDecimals": 0, + "nonce": 14, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp", + "dXJp", + "dXJp", + "dXJp", + "dXJp" + ], + "nonEmptyURIs": true, + "whiteListedStorage": false, + "attributes": "c29tZXRoaW5n", + "creator": "erd12m3x8jp6dl027pj5f2nw6ght2cyhhjfrs86cdwsa8xn83r375qfqrwpdx0", + "royalties": 100 + }, + "frozen": false +} diff --git a/integrationtests/testdata/updateNFT/token-after-recreate.json b/integrationtests/testdata/updateNFT/token-after-recreate.json new file mode 100644 index 00000000..3b2385f6 --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-after-recreate.json @@ -0,0 +1,16 @@ +{ + "identifier": "NEW-abcd-64", + "token": "NEW-abcd", + "numDecimals": 0, + "nonce": 100, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp" + ], + "whiteListedStorage": false, + "name": "token", + "nonEmptyURIs": true, + "hash": "aGFzaA==" + } +} diff --git a/integrationtests/testdata/updateNFT/token-after-set-new-uris.json b/integrationtests/testdata/updateNFT/token-after-set-new-uris.json new file mode 100644 index 00000000..c3b3ad8a --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-after-set-new-uris.json @@ -0,0 +1,20 @@ +{ + "identifier": "NFT-abcd-0e", + "token": "NFT-abcd", + "numDecimals": 0, + "nonce": 14, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp", + "dXJp", + "dXJp", + "dXJp", + "dXJp" + ], + "nonEmptyURIs": true, + "whiteListedStorage": false, + "attributes": "c29tZXRoaW5n" + }, + "frozen": false +} diff --git a/integrationtests/testdata/updateNFT/token-after-update.json b/integrationtests/testdata/updateNFT/token-after-update.json new file mode 100644 index 00000000..ebb9c835 --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-after-update.json @@ -0,0 +1,16 @@ +{ + "identifier": "NEW-abcd-64", + "token": "NEW-abcd", + "numDecimals": 0, + "nonce": 100, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp" + ], + "whiteListedStorage": false, + "name": "token-second", + "nonEmptyURIs": true, + "hash": "aGFzaA==" + } +} diff --git a/integrationtests/testdata/updateNFT/token-before-recreate.json b/integrationtests/testdata/updateNFT/token-before-recreate.json new file mode 100644 index 00000000..8abec9d2 --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-before-recreate.json @@ -0,0 +1,16 @@ +{ + "identifier": "NEW-abcd-64", + "token": "NEW-abcd", + "numDecimals": 0, + "nonce": 100, + "timestamp": 5040, + "data": { + "name": "token-token-token", + "uris": [ + "dXJp", + "dXJp" + ], + "nonEmptyURIs": true, + "whiteListedStorage": false + } +} diff --git a/integrationtests/updateNFT_test.go b/integrationtests/updateNFT_test.go index 723fddfc..11fb2b84 100644 --- a/integrationtests/updateNFT_test.go +++ b/integrationtests/updateNFT_test.go @@ -225,4 +225,206 @@ func TestNFTUpdateMetadata(t *testing.T) { err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) require.Nil(t, err) require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-un-freeze.json"), string(genericResponse.Docs[0].Source)) + + // Set new uris + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.ESDTSetNewURIs), + Topics: [][]byte{[]byte("NFT-abcd"), big.NewInt(14).Bytes(), big.NewInt(0).Bytes(), []byte("uri"), []byte("uri"), []byte("uri"), []byte("uri"), []byte("uri")}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + ids = []string{"NFT-abcd-0e"} + genericResponse = &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-set-new-uris.json"), string(genericResponse.Docs[0].Source)) + + // new creator + newCreator := "erd12m3x8jp6dl027pj5f2nw6ght2cyhhjfrs86cdwsa8xn83r375qfqrwpdx0" + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(newCreator), + Identifier: []byte(core.ESDTModifyCreator), + Topics: [][]byte{[]byte("NFT-abcd"), big.NewInt(14).Bytes(), big.NewInt(0).Bytes()}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + ids = []string{"NFT-abcd-0e"} + genericResponse = &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-new-creator.json"), string(genericResponse.Docs[0].Source)) + + // new royalties + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.ESDTModifyRoyalties), + Topics: [][]byte{[]byte("NFT-abcd"), big.NewInt(14).Bytes(), big.NewInt(0).Bytes(), big.NewInt(100).Bytes()}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + ids = []string{"NFT-abcd-0e"} + genericResponse = &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-new-royalties.json"), string(genericResponse.Docs[0].Source)) +} + +func TestCreateNFTAndMetaDataRecreate(t *testing.T) { + setLogLevelDebug() + + esClient, err := createESClient(esURL) + require.Nil(t, err) + + esdtCreateData := &esdt.ESDigitalToken{ + TokenMetaData: &esdt.MetaData{ + Name: []byte("token-token-token"), + URIs: [][]byte{[]byte("uri"), []byte("uri")}, + }, + } + marshalizedCreate, _ := json.Marshal(esdtCreateData) + + esProc, err := CreateElasticProcessor(esClient) + require.Nil(t, err) + + header := &dataBlock.Header{ + Round: 50, + TimeStamp: 5040, + ShardID: 1, + } + body := &dataBlock.Body{} + + // CREATE NFT data + address := "erd1w7jyzuj6cv4ngw8luhlkakatjpmjh3ql95lmxphd3vssc4vpymks6k5th7" + pool := &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.BuiltInFunctionESDTNFTCreate), + Topics: [][]byte{[]byte("NEW-abcd"), big.NewInt(100).Bytes(), big.NewInt(1).Bytes(), marshalizedCreate}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + + ids := []string{"NEW-abcd-64"} + genericResponse := &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-before-recreate.json"), string(genericResponse.Docs[0].Source)) + + // RECREATE + reCreate := &esdt.ESDigitalToken{ + TokenMetaData: &esdt.MetaData{ + Name: []byte("token"), + URIs: [][]byte{[]byte("uri")}, + Hash: []byte("hash"), + }, + } + marshalizedReCreate, _ := json.Marshal(reCreate) + + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.ESDTMetaDataRecreate), + Topics: [][]byte{[]byte("NEW-abcd"), big.NewInt(100).Bytes(), big.NewInt(0).Bytes(), marshalizedReCreate}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + + genericResponse = &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-recreate.json"), string(genericResponse.Docs[0].Source)) + + // UPDATE + update := &esdt.ESDigitalToken{ + TokenMetaData: &esdt.MetaData{ + Name: []byte("token-second"), + URIs: [][]byte{[]byte("uri")}, + Hash: []byte("hash"), + }, + } + marshalizedUpdate, _ := json.Marshal(update) + + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.ESDTMetaDataUpdate), + Topics: [][]byte{[]byte("NEW-abcd"), big.NewInt(100).Bytes(), big.NewInt(0).Bytes(), marshalizedUpdate}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + + genericResponse = &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-update.json"), string(genericResponse.Docs[0].Source)) } diff --git a/integrationtests/utils.go b/integrationtests/utils.go index 27f07c23..5fe2aaf8 100644 --- a/integrationtests/utils.go +++ b/integrationtests/utils.go @@ -3,6 +3,7 @@ package integrationtests import ( "encoding/json" "fmt" + "io" "io/ioutil" "net/http" "net/url" @@ -68,7 +69,7 @@ func CreateElasticProcessor( // nolint func readExpectedResult(path string) string { jsonFile, _ := os.Open(path) - byteValue, _ := ioutil.ReadAll(jsonFile) + byteValue, _ := io.ReadAll(jsonFile) return string(byteValue) } diff --git a/process/elasticproc/converters/tokenMetaData.go b/process/elasticproc/converters/tokenMetaData.go index 09f5f845..b51a620b 100644 --- a/process/elasticproc/converters/tokenMetaData.go +++ b/process/elasticproc/converters/tokenMetaData.go @@ -89,6 +89,20 @@ func PrepareNFTUpdateData(buffSlice *data.BufferSlice, updateNFTData []*data.NFT if pauseOrUnPauseTokenIndex { return buffSlice.PutData(metaData, prepareSerializedDataForPauseAndUnPause(nftUpdate)) } + if nftUpdate.NewMetaData != nil { + serializedData, err := prepareSerializedDataForMetaDataRecreate(nftUpdate) + if err != nil { + return err + } + return buffSlice.PutData(metaData, serializedData) + } + + if nftUpdate.NewCreator != "" { + return buffSlice.PutData(metaData, prepareSerializeDataForNewCreator(nftUpdate)) + } + if nftUpdate.NewRoyalties.HasValue { + return buffSlice.PutData(metaData, prepareSerializeDataForNewRoyalties(nftUpdate)) + } truncatedAttributes := TruncateFieldIfExceedsMaxLengthBase64(string(nftUpdate.NewAttributes)) base64Attr := base64.StdEncoding.EncodeToString([]byte(truncatedAttributes)) @@ -134,7 +148,7 @@ func PrepareNFTUpdateData(buffSlice *data.BufferSlice, updateNFTData []*data.NFT codeToExecute = ` if (ctx._source.containsKey('data')) { - if (!ctx._source.data.containsKey('uris')) { + if ((!ctx._source.data.containsKey('uris')) || (params.set)) { ctx._source.data.uris = params.uris; } else { int i; @@ -155,7 +169,7 @@ func PrepareNFTUpdateData(buffSlice *data.BufferSlice, updateNFTData []*data.NFT ctx._source.data.nonEmptyURIs = true; } ` - serializedData = []byte(fmt.Sprintf(`{"script": {"source": "%s","lang": "painless","params": {"uris": %s}},"upsert": {}}`, FormatPainlessSource(codeToExecute), marshalizedURIS)) + serializedData = []byte(fmt.Sprintf(`{"script": {"source": "%s","lang": "painless","params": {"uris": %s, "set":%t}},"upsert": {}}`, FormatPainlessSource(codeToExecute), marshalizedURIS, nftUpdate.SetURIs)) } err := buffSlice.PutData(metaData, serializedData) @@ -190,3 +204,45 @@ func prepareSerializedDataForPauseAndUnPause(nftUpdateData *data.NFTDataUpdate) return serializedData } + +func prepareSerializedDataForMetaDataRecreate(nftUpdateData *data.NFTDataUpdate) ([]byte, error) { + tokenMetaDataBytes, err := json.Marshal(nftUpdateData.NewMetaData) + if err != nil { + return nil, err + } + + codeToExecute := ` + ctx._source.data = params.metaData; +` + serializedData := []byte(fmt.Sprintf(`{"script": {"source": "%s","lang": "painless","params": {"metaData": %s}}, "upsert": {}}`, + FormatPainlessSource(codeToExecute), tokenMetaDataBytes), + ) + + return serializedData, nil +} + +func prepareSerializeDataForNewRoyalties(nftUpdateData *data.NFTDataUpdate) []byte { + codeToExecute := ` + if (ctx._source.containsKey('data')) { + ctx._source.data.royalties = params.royalties; + } +` + serializedData := []byte(fmt.Sprintf(`{"script": {"source": "%s","lang": "painless","params": {"royalties": %d}}, "upsert": {}}`, + FormatPainlessSource(codeToExecute), nftUpdateData.NewRoyalties.Value), + ) + + return serializedData +} + +func prepareSerializeDataForNewCreator(nftUpdateData *data.NFTDataUpdate) []byte { + codeToExecute := ` + if (ctx._source.containsKey('data')) { + ctx._source.data.creator = params.creator; + } +` + serializedData := []byte(fmt.Sprintf(`{"script": {"source": "%s","lang": "painless","params": {"creator": "%s"}}, "upsert": {}}`, + FormatPainlessSource(codeToExecute), nftUpdateData.NewCreator), + ) + + return serializedData +} diff --git a/process/elasticproc/converters/tokenMetaData_test.go b/process/elasticproc/converters/tokenMetaData_test.go index b68c6551..a6bacfab 100644 --- a/process/elasticproc/converters/tokenMetaData_test.go +++ b/process/elasticproc/converters/tokenMetaData_test.go @@ -59,7 +59,7 @@ func TestPrepareNFTUpdateData(t *testing.T) { require.Equal(t, `{"update":{ "_index":"tokens","_id":"MYTKN-abcd-01"}} {"script": {"source": "if (ctx._source.containsKey('data')) {ctx._source.data.attributes = params.attributes;if (!params.metadata.isEmpty() ) {ctx._source.data.metadata = params.metadata} else {if (ctx._source.data.containsKey('metadata')) {ctx._source.data.remove('metadata')}}if (params.tags != null) {ctx._source.data.tags = params.tags} else {if (ctx._source.data.containsKey('tags')) {ctx._source.data.remove('tags')}}}","lang": "painless","params": {"attributes": "YWFhYQ==", "metadata": "", "tags": null}}, "upsert": {}} {"update":{ "_index":"tokens","_id":"TOKEN-1234-1a"}} -{"script": {"source": "if (ctx._source.containsKey('data')) {if (!ctx._source.data.containsKey('uris')) {ctx._source.data.uris = params.uris;} else {int i;for ( i = 0; i < params.uris.length; i++) {boolean found = false;int j;for ( j = 0; j < ctx._source.data.uris.length; j++) {if ( params.uris.get(i) == ctx._source.data.uris.get(j) ) {found = true;break}}if ( !found ) {ctx._source.data.uris.add(params.uris.get(i))}}}ctx._source.data.nonEmptyURIs = true;}","lang": "painless","params": {"uris": ["dXJpMQ==","dXJpMg=="]}},"upsert": {}} +{"script": {"source": "if (ctx._source.containsKey('data')) {if ((!ctx._source.data.containsKey('uris')) || (params.set)) {ctx._source.data.uris = params.uris;} else {int i;for ( i = 0; i < params.uris.length; i++) {boolean found = false;int j;for ( j = 0; j < ctx._source.data.uris.length; j++) {if ( params.uris.get(i) == ctx._source.data.uris.get(j) ) {found = true;break}}if ( !found ) {ctx._source.data.uris.add(params.uris.get(i))}}}ctx._source.data.nonEmptyURIs = true;}","lang": "painless","params": {"uris": ["dXJpMQ==","dXJpMg=="], "set":false}},"upsert": {}} `, buffSlice.Buffers()[0].String()) } diff --git a/process/elasticproc/logsevents/esdtIssueProcessor.go b/process/elasticproc/logsevents/esdtIssueProcessor.go index 2af66180..6f5c9fa3 100644 --- a/process/elasticproc/logsevents/esdtIssueProcessor.go +++ b/process/elasticproc/logsevents/esdtIssueProcessor.go @@ -11,13 +11,16 @@ import ( const ( numIssueLogTopics = 4 - issueFungibleESDTFunc = "issue" - issueSemiFungibleESDTFunc = "issueSemiFungible" - issueNonFungibleESDTFunc = "issueNonFungible" - registerMetaESDTFunc = "registerMetaESDT" - changeSFTToMetaESDTFunc = "changeSFTToMetaESDT" - transferOwnershipFunc = "transferOwnership" - registerAndSetRolesFunc = "registerAndSetAllRoles" + issueFungibleESDTFunc = "issue" + issueSemiFungibleESDTFunc = "issueSemiFungible" + issueNonFungibleESDTFunc = "issueNonFungible" + registerMetaESDTFunc = "registerMetaESDT" + changeSFTToMetaESDTFunc = "changeSFTToMetaESDT" + changeToDynamicESDTFunc = "changeToDynamic" + transferOwnershipFunc = "transferOwnership" + registerAndSetRolesFunc = "registerAndSetAllRoles" + registerDynamicFunc = "registerDynamic" + registerAndSetRolesDynamicFunc = "registerAndSetAllRolesDynamic" ) type esdtIssueProcessor struct { @@ -29,13 +32,16 @@ func newESDTIssueProcessor(pubkeyConverter core.PubkeyConverter) *esdtIssueProce return &esdtIssueProcessor{ pubkeyConverter: pubkeyConverter, issueOperationsIdentifiers: map[string]struct{}{ - issueFungibleESDTFunc: {}, - issueSemiFungibleESDTFunc: {}, - issueNonFungibleESDTFunc: {}, - registerMetaESDTFunc: {}, - changeSFTToMetaESDTFunc: {}, - transferOwnershipFunc: {}, - registerAndSetRolesFunc: {}, + issueFungibleESDTFunc: {}, + issueSemiFungibleESDTFunc: {}, + issueNonFungibleESDTFunc: {}, + registerMetaESDTFunc: {}, + changeSFTToMetaESDTFunc: {}, + transferOwnershipFunc: {}, + registerAndSetRolesFunc: {}, + registerDynamicFunc: {}, + registerAndSetRolesDynamicFunc: {}, + changeToDynamicESDTFunc: {}, }, } } @@ -95,6 +101,10 @@ func (eip *esdtIssueProcessor) processEvent(args *argsProcessEvent) argOutputPro Properties: &data.TokenProperties{}, } + if identifierStr == changeToDynamicESDTFunc { + tokenInfo.ChangeToDynamic = true + } + if identifierStr == transferOwnershipFunc && len(topics) >= numIssueLogTopics+1 { newOwner := eip.pubkeyConverter.SilentEncode(topics[4], log) tokenInfo.TransferOwnership = true diff --git a/process/elasticproc/logsevents/logsAndEventsProcessor.go b/process/elasticproc/logsevents/logsAndEventsProcessor.go index d3076699..b80e6714 100644 --- a/process/elasticproc/logsevents/logsAndEventsProcessor.go +++ b/process/elasticproc/logsevents/logsAndEventsProcessor.go @@ -69,7 +69,7 @@ func createEventsProcessors(args ArgsLogsAndEventsProcessor) []eventsProcessor { nftsProc := newNFTsProcessor(args.PubKeyConverter, args.Marshalizer) scDeploysProc := newSCDeploysProcessor(args.PubKeyConverter) informativeProc := newInformativeLogsProcessor() - updateNFTProc := newNFTsPropertiesProcessor(args.PubKeyConverter) + updateNFTProc := newNFTsPropertiesProcessor(args.PubKeyConverter, args.Marshalizer) esdtPropProc := newEsdtPropertiesProcessor(args.PubKeyConverter) esdtIssueProc := newESDTIssueProcessor(args.PubKeyConverter) delegatorsProcessor := newDelegatorsProcessor(args.PubKeyConverter, args.BalanceConverter) diff --git a/process/elasticproc/logsevents/nftsProcessor.go b/process/elasticproc/logsevents/nftsProcessor.go index 95716f15..4189f964 100644 --- a/process/elasticproc/logsevents/nftsProcessor.go +++ b/process/elasticproc/logsevents/nftsProcessor.go @@ -144,7 +144,7 @@ func (np *nftsProcessor) processNFTEventOnSender( return } - tokenMetaData := converters.PrepareTokenMetaData(np.convertMetaData(esdtToken.TokenMetaData)) + tokenMetaData := converters.PrepareTokenMetaData(convertMetaData(np.pubKeyConverter, esdtToken.TokenMetaData)) tokensCreateInfo.Add(&data.TokenInfo{ Token: token, Identifier: converters.ComputeTokenIdentifier(token, nonceBig.Uint64()), @@ -154,11 +154,11 @@ func (np *nftsProcessor) processNFTEventOnSender( }) } -func (np *nftsProcessor) convertMetaData(metaData *esdt.MetaData) *alteredAccount.TokenMetaData { +func convertMetaData(pubKeyConverter core.PubkeyConverter, metaData *esdt.MetaData) *alteredAccount.TokenMetaData { if metaData == nil { return nil } - encodedCreatorAddr, err := np.pubKeyConverter.Encode(metaData.Creator) + encodedCreatorAddr, err := pubKeyConverter.Encode(metaData.Creator) if err != nil { log.Warn("nftsProcessor.convertMetaData", "cannot encode creator address", "error", err, "address", metaData.Creator) } diff --git a/process/elasticproc/logsevents/nftsPropertiesProcessor.go b/process/elasticproc/logsevents/nftsPropertiesProcessor.go index 990e844e..8b40f891 100644 --- a/process/elasticproc/logsevents/nftsPropertiesProcessor.go +++ b/process/elasticproc/logsevents/nftsPropertiesProcessor.go @@ -4,6 +4,8 @@ import ( "math/big" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/esdt" + "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-es-indexer-go/data" "github.com/multiversx/mx-chain-es-indexer-go/process/elasticproc/converters" ) @@ -11,12 +13,14 @@ import ( const minTopicsUpdate = 4 type nftsPropertiesProc struct { + marshaller marshal.Marshalizer pubKeyConverter core.PubkeyConverter propertiesChangeOperations map[string]struct{} } -func newNFTsPropertiesProcessor(pubKeyConverter core.PubkeyConverter) *nftsPropertiesProc { +func newNFTsPropertiesProcessor(pubKeyConverter core.PubkeyConverter, marshaller marshal.Marshalizer) *nftsPropertiesProc { return &nftsPropertiesProc{ + marshaller: marshaller, pubKeyConverter: pubKeyConverter, propertiesChangeOperations: map[string]struct{}{ core.BuiltInFunctionESDTNFTAddURI: {}, @@ -25,6 +29,11 @@ func newNFTsPropertiesProcessor(pubKeyConverter core.PubkeyConverter) *nftsPrope core.BuiltInFunctionESDTUnFreeze: {}, core.BuiltInFunctionESDTPause: {}, core.BuiltInFunctionESDTUnPause: {}, + core.ESDTMetaDataRecreate: {}, + core.ESDTMetaDataUpdate: {}, + core.ESDTSetNewURIs: {}, + core.ESDTModifyCreator: {}, + core.ESDTModifyRoyalties: {}, }, } } @@ -54,7 +63,10 @@ func (npp *nftsPropertiesProc) processEvent(args *argsProcessEvent) argOutputPro // [1] --> nonce of the NFT (bytes) // [2] --> value // [3:] --> modified data - if len(topics) < minTopicsUpdate { + // [3] --> ESDT token data in case of ESDTMetaDataRecreate + + isModifyCreator := len(topics) == minTopicsUpdate-1 && eventIdentifier == core.ESDTModifyCreator + if len(topics) < minTopicsUpdate && !isModifyCreator { return argOutputProcessEvent{ processed: true, } @@ -86,10 +98,23 @@ func (npp *nftsPropertiesProc) processEvent(args *argsProcessEvent) argOutputPro updateNFT.NewAttributes = topics[3] case core.BuiltInFunctionESDTNFTAddURI: updateNFT.URIsToAdd = topics[3:] + case core.ESDTSetNewURIs: + updateNFT.SetURIs = true + updateNFT.URIsToAdd = topics[3:] case core.BuiltInFunctionESDTFreeze: updateNFT.Freeze = true case core.BuiltInFunctionESDTUnFreeze: updateNFT.UnFreeze = true + case core.ESDTMetaDataRecreate, core.ESDTMetaDataUpdate: + npp.processMetaDataUpdate(updateNFT, topics[3]) + case core.ESDTModifyCreator: + updateNFT.NewCreator = callerAddress + case core.ESDTModifyRoyalties: + newRoyalties := uint32(big.NewInt(0).SetBytes(topics[3]).Uint64()) + updateNFT.NewRoyalties = core.OptionalUint32{ + Value: newRoyalties, + HasValue: true, + } } return argOutputProcessEvent{ @@ -98,6 +123,18 @@ func (npp *nftsPropertiesProc) processEvent(args *argsProcessEvent) argOutputPro } } +func (npp *nftsPropertiesProc) processMetaDataUpdate(updateNFT *data.NFTDataUpdate, esdtTokenBytes []byte) { + esdtToken := &esdt.ESDigitalToken{} + err := npp.marshaller.Unmarshal(esdtToken, esdtTokenBytes) + if err != nil { + log.Warn("nftsPropertiesProc.processMetaDataRecreate() cannot urmarshal", "error", err.Error()) + return + } + + tokenMetaData := converters.PrepareTokenMetaData(convertMetaData(npp.pubKeyConverter, esdtToken.TokenMetaData)) + updateNFT.NewMetaData = tokenMetaData +} + func (npp *nftsPropertiesProc) processPauseAndUnPauseEvent(eventIdentifier string, token string) argOutputProcessEvent { var updateNFT *data.NFTDataUpdate diff --git a/process/elasticproc/logsevents/nftsPropertiesProcessor_test.go b/process/elasticproc/logsevents/nftsPropertiesProcessor_test.go index 32f039c6..7ee39dda 100644 --- a/process/elasticproc/logsevents/nftsPropertiesProcessor_test.go +++ b/process/elasticproc/logsevents/nftsPropertiesProcessor_test.go @@ -1,10 +1,13 @@ package logsevents import ( + "encoding/hex" + "encoding/json" "math/big" "testing" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/esdt" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-es-indexer-go/data" "github.com/multiversx/mx-chain-es-indexer-go/mock" @@ -24,7 +27,7 @@ func TestProcessNFTProperties_Update(t *testing.T) { event: event, } - nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}) + nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}, &mock.MarshalizerMock{}) res := nftsPropertiesP.processEvent(args) require.True(t, res.processed) @@ -48,7 +51,7 @@ func TestProcessNFTProperties_AddUris(t *testing.T) { event: event, } - nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}) + nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}, &mock.MarshalizerMock{}) res := nftsPropertiesP.processEvent(args) require.True(t, res.processed) @@ -59,6 +62,33 @@ func TestProcessNFTProperties_AddUris(t *testing.T) { }, res.updatePropNFT) } +func TestProcessNFTMetaDataRecreate(t *testing.T) { + nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}, &mock.MarshalizerMock{}) + + esdtData := &esdt.ESDigitalToken{ + TokenMetaData: &esdt.MetaData{ + Creator: []byte("creator"), + }, + } + esdtDataBytes, _ := json.Marshal(esdtData) + + nonce := uint64(19) + event := &transaction.Event{ + Address: []byte("addr"), + Identifier: []byte(core.ESDTMetaDataRecreate), + Topics: [][]byte{[]byte("my-token"), big.NewInt(0).SetUint64(nonce).Bytes(), big.NewInt(1).Bytes(), esdtDataBytes}, + } + args := &argsProcessEvent{ + timestamp: 1234, + event: event, + } + + res := nftsPropertiesP.processEvent(args) + require.True(t, res.processed) + require.NotNil(t, res.updatePropNFT.NewMetaData) + require.Equal(t, hex.EncodeToString([]byte("creator")), res.updatePropNFT.NewMetaData.Creator) +} + func TestProcessNFTProperties_FreezeAndUnFreeze(t *testing.T) { t.Parallel() @@ -73,7 +103,7 @@ func TestProcessNFTProperties_FreezeAndUnFreeze(t *testing.T) { event: event, } - nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}) + nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}, &mock.MarshalizerMock{}) res := nftsPropertiesP.processEvent(args) require.True(t, res.processed) diff --git a/process/elasticproc/logsevents/serialize.go b/process/elasticproc/logsevents/serialize.go index a96a125a..22508c3c 100644 --- a/process/elasticproc/logsevents/serialize.go +++ b/process/elasticproc/logsevents/serialize.go @@ -202,6 +202,9 @@ func serializeToken(tokenData *data.TokenInfo, index string) ([]byte, []byte, er if tokenData.TransferOwnership { return serializeTokenTransferOwnership(tokenData, index) } + if tokenData.ChangeToDynamic { + return serializeTokenChangeType(tokenData, index) + } meta := []byte(fmt.Sprintf(`{ "update" : { "_index":"%s", "_id" : "%s" } }%s`, index, converters.JsonEscape(tokenData.Token), "\n")) serializedTokenData, err := json.Marshal(tokenData) @@ -226,6 +229,23 @@ func serializeToken(tokenData *data.TokenInfo, index string) ([]byte, []byte, er return meta, []byte(serializedDataStr), nil } +func serializeTokenChangeType(tokenData *data.TokenInfo, index string) ([]byte, []byte, error) { + meta := []byte(fmt.Sprintf(`{ "update" : { "_index":"%s", "_id" : "%s" } }%s`, index, converters.JsonEscape(tokenData.Token), "\n")) + + codeToExecute := ` + ctx._source.type = params.type; + ctx._source.changedToDynamicTimestamp = params.timestamp; +` + serializedDataStr := fmt.Sprintf(`{"script": {`+ + `"source": "%s",`+ + `"lang": "painless",`+ + `"params": {"type": "%s", "timestamp": %d }},`+ + `"upsert": {}}`, + converters.FormatPainlessSource(codeToExecute), tokenData.Type, tokenData.Timestamp) + + return meta, []byte(serializedDataStr), nil +} + func serializeTokenTransferOwnership(tokenData *data.TokenInfo, index string) ([]byte, []byte, error) { meta := []byte(fmt.Sprintf(`{ "update" : { "_index":"%s", "_id" : "%s" } }%s`, index, converters.JsonEscape(tokenData.Token), "\n")) tokenDataSerialized, err := json.Marshal(tokenData) diff --git a/templates/noKibana/esdts.go b/templates/noKibana/esdts.go index 072d215a..75d0bfa5 100644 --- a/templates/noKibana/esdts.go +++ b/templates/noKibana/esdts.go @@ -36,6 +36,10 @@ var ESDTs = Object{ "type": "date", "format": "epoch_second", }, + "changedToDynamicTimestamp": Object{ + "type": "date", + "format": "epoch_second", + }, "ownersHistory": Object{ "type": "nested", "properties": Object{ diff --git a/templates/noKibana/tokens.go b/templates/noKibana/tokens.go index f528f0bd..0035de16 100644 --- a/templates/noKibana/tokens.go +++ b/templates/noKibana/tokens.go @@ -161,6 +161,10 @@ var Tokens = Object{ "type": "date", "format": "epoch_second", }, + "changedToDynamicTimestamp": Object{ + "type": "date", + "format": "epoch_second", + }, "token": Object{ "type": "text", },