diff --git a/README.md b/README.md index c223765..b219d4e 100644 --- a/README.md +++ b/README.md @@ -271,9 +271,8 @@ If successful, the balance response should look like this: ```bash address: 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9 -chainID: 2F1QmuxSSVntNHXnEevYHBZzyhsNGvAE5Y2pqJW2a4iBugTMWd -uri: http://127.0.0.1:9650/ext/bc/nuklaivm -address: 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9 balance: 853000000.000000000 NAI +chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq +address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 853000000.000000000 NAI ``` You can also check the balance of another address by passing in the address as the argument @@ -285,15 +284,8 @@ You can also check the balance of another address by passing in the address as t Should give output ```bash -address: 01b27c7ce992cdb7ff039294d7901851902394bb85fa4f3dc4cbb960b07284b7f9 -chainID: 2F1QmuxSSVntNHXnEevYHBZzyhsNGvAE5Y2pqJW2a4iBugTMWd -assetID (use NAI for native token): NAI -assetID: 11111111111111111111111111111111LpoYY -name: nuklaivm -symbol: NAI -balance: 0 -please send funds to 01b27c7ce992cdb7ff039294d7901851902394bb85fa4f3dc4cbb960b07284b7f9 -exiting... +chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq +address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 0.000000000 NAI ``` ### Generate Another Address @@ -311,8 +303,8 @@ Note that we are now generating a key with curve secp256r1 instead of ed25519 li If successful, the `nuklai-cli` will emit the new address: ```bash -created address: 01b27c7ce992cdb7ff039294d7901851902394bb85fa4f3dc4cbb960b07284b7f9 -Private Key String: h3geZh5VQ4JEe829BTvaKaECnrVUGA1fEUY4imgnzbg= +created address: 011ddbf62f227dd32deea73b31945d65bb6676cccae6cf0b829dfc21b290387bac +Private Key String(Base64): J5kwBjyvMuvV4PfjTUOO8ZF40Db3KzhFidhH7ER+9Jg= ``` We can also generate a bls key doing @@ -331,11 +323,14 @@ the following command to set it back to `demo.pk`: You should see something like this: ```bash -chainID: 2F1QmuxSSVntNHXnEevYHBZzyhsNGvAE5Y2pqJW2a4iBugTMWd +chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq stored keys: 3 -0) address: 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9 balance: 853000000.000000000 NAI -1) address: 01b27c7ce992cdb7ff039294d7901851902394bb85fa4f3dc4cbb960b07284b7f9 balance: 0.000000000 NAI -2) address: 023d4f5711f36fc407e114a86b513b8c51ae124224d11eee5430c27e2d8e673893 balance: 0.000000000 NAI +chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq +0) address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 853000000.000000000 NAI +chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq +1) address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 0.000000000 NAI +chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq +2) address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 0.000000000 NAI set default key: 0 ``` @@ -376,15 +371,14 @@ The `nuklai-cli` will emit the following logs when the transfer is successful: ```bash address: 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9 -chainID: 2hKDi8QVgngBxCbakibVqQFa3EV8YzA957q7nPT5vrQRpx8Z9E -assetID (use NAI for native token): NAI -balance: 853000000.000000000 NAI -recipient: 00fa92500595699234176c32afbf5c6558df21deb10ba4d2d691e5e5148658c64a -amount: 1 +chainID: PAroBtUb83kcU5m3XiD37D263cB8kaZsiZ1rG7DLKxs8EX7Cq +assetAddress (use NAI for native token): NAI +address: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb balance: 853000000.000000000 NAI +✔ amount: 1█ continue (y/n): y -✅ txID: X4HTZbkvrXy8Ka3VBdEJLGj1eUVNQf9LoWZyLZCxham1D1zND -fee consumed: 0.000032100 -output: &{SenderBalance:852999998999967900 ReceiverBalance:1000000000} +✅ txID: wjzqXJeYedVSyBWfapoGiHYC9EQr2HgkA7xrVW4KZQyP1jxxG +txID: wjzqXJeYedVSyBWfapoGiHYC9EQr2HgkA7xrVW4KZQyP1jxxG +fee consumed: 0.000048500 NAI ``` ### Bonus: Watch Activity in Real-Time @@ -408,7 +402,7 @@ select chainID: 0 [auto-selected] uri: http://127.0.0.1:9650/ext/bc/nuklaivm watching for new blocks on 2F1QmuxSSVntNHXnEevYHBZzyhsNGvAE5Y2pqJW2a4iBugTMWd 👀 height:3003 txs:1 root:uNXBoJRGNo8JCJ8XDEioqVnJjwrVSSKBMgbaTd9AWFiUke2vE size:0.30KB units consumed: [bandwidth=224 compute=7 storage(read)=14 storage(allocate)=50 storage(write)=26] unit prices: [bandwidth=100 compute=100 storage(read)=100 storage(allocate)=100 storage(write)=100] [TPS:0.10 latency:66ms gap:142ms] -✅ X3VcRchV8E8CoK38qEWYG9mNFLTwxm5V1mhZzsF4KCdkuwfPB actor: 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9 summary (*actions.Transfer): [assetID: 11111111111111111111111111111111LpoYY amount: 100000000000 -> 01b27c7ce992cdb7ff039294d7901851902394bb85fa4f3dc4cbb960b07284b7f9 +✅ 2deLZJJpfXm1Wrad1f8uZL1aZnrCkEtBZ6aFXNcR7stFYN8Rm8 actor: 00c4cb545f748a28770042f893784ce85b107389004d6a0e0d6d7518eeae1292d9 summary (*actions.Transfer): [assetID: 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb amount: 1000000000 -> 00cf77495ce1bdbf11e5e45463fad5a862cb6cc0a20e00e658c4ac3355dcdc64bb ] ``` diff --git a/actions/claim_marketplace_payment_test.go b/actions/claim_marketplace_payment_test.go new file mode 100644 index 0000000..b3a0324 --- /dev/null +++ b/actions/claim_marketplace_payment_test.go @@ -0,0 +1,221 @@ +// Copyright (C) 2024, Nuklai. All rights reserved. +// See the file LICENSE for licensing terms. + +package actions + +import ( + "context" + "testing" + + "github.com/nuklai/nuklaivm/emission" + "github.com/nuklai/nuklaivm/storage" + "github.com/nuklai/nuklaivm/utils" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/hypersdk/chain/chaintest" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/codec/codectest" + "github.com/ava-labs/hypersdk/state" + + nconsts "github.com/nuklai/nuklaivm/consts" +) + +func TestClaimMarketplacePaymentAction(t *testing.T) { + actor := codectest.NewRandomAddress() + datasetAddress := storage.AssetAddress(nconsts.AssetFractionalTokenID, []byte("Valid Name"), []byte("DATASET"), 0, []byte("metadata"), actor) + baseAssetAddress := storage.NAIAddress + marketplaceAssetAddress := storage.AssetAddressFractional(datasetAddress) + + emission.MockNewEmission(&emission.MockEmission{LastAcceptedBlockHeight: 100}) + + tests := []chaintest.ActionTest{ + { + Name: "WrongOwner", + Actor: codectest.NewRandomAddress(), // Not the owner of the asset + Action: &ClaimMarketplacePayment{ + MarketplaceAssetAddress: marketplaceAssetAddress, + PaymentAssetAddress: baseAssetAddress, + }, + State: func() state.Mutable { + store := chaintest.NewInMemoryStore() + metadata := map[string]string{ + "datasetAddress": datasetAddress.String(), + "marketplaceAssetAddress": marketplaceAssetAddress.String(), + "datasetPricePerBlock": "100", + "paymentAssetAddress": baseAssetAddress.String(), + "publisher": actor.String(), + "lastClaimedBlock": "0", + "subscriptions": "0", + "paymentRemaining": "0", + "paymentClaimed": "0", + } + metadataBytes, err := utils.MapToBytes(metadata) + require.NoError(t, err) + require.NoError(t, storage.SetAssetInfo(context.Background(), store, marketplaceAssetAddress, nconsts.AssetMarketplaceTokenID, []byte("name"), []byte("SYM"), 0, metadataBytes, []byte(marketplaceAssetAddress.String()), 0, 0, actor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress)) + return store + }(), + ExpectedErr: ErrWrongOwner, + }, + { + Name: "BaseAssetNotSupported", + Actor: actor, + Action: &ClaimMarketplacePayment{ + MarketplaceAssetAddress: marketplaceAssetAddress, + PaymentAssetAddress: codec.EmptyAddress, // Invalid base asset ID + }, + State: func() state.Mutable { + store := chaintest.NewInMemoryStore() + metadata := map[string]string{ + "datasetAddress": datasetAddress.String(), + "marketplaceAssetAddress": marketplaceAssetAddress.String(), + "datasetPricePerBlock": "100", + "paymentAssetAddress": baseAssetAddress.String(), + "publisher": actor.String(), + "lastClaimedBlock": "0", + "subscriptions": "0", + "paymentRemaining": "0", + "paymentClaimed": "0", + } + metadataBytes, err := utils.MapToBytes(metadata) + require.NoError(t, err) + require.NoError(t, storage.SetAssetInfo(context.Background(), store, marketplaceAssetAddress, nconsts.AssetMarketplaceTokenID, []byte("name"), []byte("SYM"), 0, metadataBytes, []byte(marketplaceAssetAddress.String()), 0, 0, actor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress)) + return store + }(), + ExpectedErr: ErrPaymentAssetNotSupported, + }, + { + Name: "NoPaymentRemaining", + Actor: actor, + Action: &ClaimMarketplacePayment{ + MarketplaceAssetAddress: marketplaceAssetAddress, + PaymentAssetAddress: baseAssetAddress, + }, + State: func() state.Mutable { + store := chaintest.NewInMemoryStore() + metadata := map[string]string{ + "datasetAddress": datasetAddress.String(), + "marketplaceAssetAddress": marketplaceAssetAddress.String(), + "datasetPricePerBlock": "100", + "paymentAssetAddress": baseAssetAddress.String(), + "publisher": actor.String(), + "lastClaimedBlock": "50", + "subscriptions": "0", + "paymentRemaining": "0", + "paymentClaimed": "0", + } + metadataBytes, err := utils.MapToBytes(metadata) + require.NoError(t, err) + require.NoError(t, storage.SetAssetInfo(context.Background(), store, marketplaceAssetAddress, nconsts.AssetMarketplaceTokenID, []byte("name"), []byte("SYM"), 0, metadataBytes, []byte(marketplaceAssetAddress.String()), 0, 0, actor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress)) + return store + }(), + ExpectedErr: ErrNoPaymentRemaining, + }, + { + Name: "ValidPaymentClaim", + Actor: actor, + Action: &ClaimMarketplacePayment{ + MarketplaceAssetAddress: marketplaceAssetAddress, + PaymentAssetAddress: baseAssetAddress, + }, + State: func() state.Mutable { + store := chaintest.NewInMemoryStore() + metadata := map[string]string{ + "datasetAddress": datasetAddress.String(), + "marketplaceAssetAddress": marketplaceAssetAddress.String(), + "datasetPricePerBlock": "100", + "paymentAssetAddress": baseAssetAddress.String(), + "publisher": actor.String(), + "lastClaimedBlock": "0", + "subscriptions": "0", + "paymentRemaining": "100", + "paymentClaimed": "0", + } + metadataBytes, err := utils.MapToBytes(metadata) + require.NoError(t, err) + require.NoError(t, storage.SetAssetInfo(context.Background(), store, marketplaceAssetAddress, nconsts.AssetMarketplaceTokenID, []byte("name"), []byte("SYM"), 0, metadataBytes, []byte(marketplaceAssetAddress.String()), 0, 0, actor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress)) + return store + }(), + Assertion: func(ctx context.Context, t *testing.T, store state.Mutable) { + // Check if the payment was correctly claimed + balance, err := storage.GetAssetAccountBalanceNoController(ctx, store, baseAssetAddress, actor) + require.NoError(t, err) + require.Equal(t, uint64(100), balance) // 100 units claimed + + // Check if metadata was updated correctly + _, _, _, _, metadata, _, _, _, _, _, _, _, _, err := storage.GetAssetInfoNoController(ctx, store, marketplaceAssetAddress) + require.NoError(t, err) + metadataMap, err := utils.BytesToMap(metadata) + require.NoError(t, err) + require.Equal(t, "0", metadataMap["paymentRemaining"]) // 1000 - 100 claimed + require.Equal(t, "100", metadataMap["paymentClaimed"]) + require.Equal(t, "100", metadataMap["lastClaimedBlock"]) + }, + ExpectedOutputs: &ClaimMarketplacePaymentResult{ + LastClaimedBlock: 100, + PaymentClaimed: 100, + PaymentRemaining: 0, + DistributedReward: 100, + DistributedTo: actor, + }, + }, + } + + for _, tt := range tests { + tt.Run(context.Background(), t) + } +} + +func BenchmarkClaimMarketplacePayment(b *testing.B) { + require := require.New(b) + actor := codectest.NewRandomAddress() + datasetAddress := storage.AssetAddress(nconsts.AssetFractionalTokenID, []byte("Valid Name"), []byte("DATASET"), 0, []byte("metadata"), actor) + baseAssetAddress := storage.NAIAddress + marketplaceAssetAddress := storage.AssetAddressFractional(datasetAddress) + + emission.MockNewEmission(&emission.MockEmission{LastAcceptedBlockHeight: 100}) + + claimMarketplacePaymentBenchmark := &chaintest.ActionBenchmark{ + Name: "ClaimMarketplacePaymentBenchmark", + Actor: actor, + Action: &ClaimMarketplacePayment{ + MarketplaceAssetAddress: marketplaceAssetAddress, + PaymentAssetAddress: baseAssetAddress, + }, + CreateState: func() state.Mutable { + store := chaintest.NewInMemoryStore() + metadata := map[string]string{ + "datasetAddress": datasetAddress.String(), + "marketplaceAssetAddress": marketplaceAssetAddress.String(), + "datasetPricePerBlock": "100", + "paymentAssetAddress": baseAssetAddress.String(), + "publisher": actor.String(), + "lastClaimedBlock": "0", + "subscriptions": "0", + "paymentRemaining": "100", + "paymentClaimed": "0", + } + metadataBytes, err := utils.MapToBytes(metadata) + require.NoError(err) + require.NoError(storage.SetAssetInfo(context.Background(), store, marketplaceAssetAddress, nconsts.AssetMarketplaceTokenID, []byte("name"), []byte("SYM"), 0, metadataBytes, []byte(marketplaceAssetAddress.String()), 0, 0, actor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress)) + return store + }, + Assertion: func(ctx context.Context, b *testing.B, store state.Mutable) { + // Check if the payment was correctly claimed + balance, err := storage.GetAssetAccountBalanceNoController(ctx, store, baseAssetAddress, actor) + require.NoError(err) + require.Equal(uint64(100), balance) // 100 units claimed + + // Check if metadata was updated correctly + _, _, _, _, metadata, _, _, _, _, _, _, _, _, err := storage.GetAssetInfoNoController(ctx, store, marketplaceAssetAddress) + require.NoError(err) + metadataMap, err := utils.BytesToMap(metadata) + require.NoError(err) + require.Equal("0", metadataMap["paymentRemaining"]) // 1000 - 100 claimed + require.Equal("100", metadataMap["paymentClaimed"]) + require.Equal("100", metadataMap["lastClaimedBlock"]) + }, + } + + ctx := context.Background() + claimMarketplacePaymentBenchmark.Run(ctx, b) +} diff --git a/actions/complete_contribute_dataset.go b/actions/complete_contribute_dataset.go index 995631c..41ab6b3 100644 --- a/actions/complete_contribute_dataset.go +++ b/actions/complete_contribute_dataset.go @@ -98,7 +98,7 @@ func (d *CompleteContributeDataset) Execute( } // Retrieve the asset info - assetType, name, symbol, _, _, _, totalSupply, _, _, _, _, _, _, err := storage.GetAssetInfoNoController(ctx, mu, d.DatasetAddress) + _, name, symbol, _, _, _, totalSupply, _, _, _, _, _, _, err := storage.GetAssetInfoNoController(ctx, mu, d.DatasetAddress) if err != nil { return nil, err } @@ -117,7 +117,7 @@ func (d *CompleteContributeDataset) Execute( } nftAddress := codec.CreateAddress(nconsts.AssetFractionalTokenID, d.DatasetContributionID) symbol = utils.CombineWithSuffix(symbol, totalSupply, storage.MaxSymbolSize) - if err := storage.SetAssetInfo(ctx, mu, nftAddress, assetType, name, symbol, 0, metadataNFT, []byte(d.DatasetAddress.String()), 0, 1, d.DatasetContributor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress); err != nil { + if err := storage.SetAssetInfo(ctx, mu, nftAddress, nconsts.AssetNonFungibleTokenID, name, symbol, 0, metadataNFT, []byte(d.DatasetAddress.String()), 0, 1, d.DatasetContributor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress); err != nil { return nil, err } if _, err := storage.MintAsset(ctx, mu, nftAddress, d.DatasetContributor, 1); err != nil { diff --git a/actions/complete_contribute_dataset_test.go b/actions/complete_contribute_dataset_test.go index a18f5ea..d7fe9f8 100644 --- a/actions/complete_contribute_dataset_test.go +++ b/actions/complete_contribute_dataset_test.go @@ -156,7 +156,7 @@ func TestCompleteContributeDatasetAction(t *testing.T) { // Check if NFT was created correctly assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := storage.GetAssetInfoNoController(ctx, store, nftAddress) require.NoError(t, err) - require.Equal(t, nconsts.AssetFractionalTokenID, assetType) + require.Equal(t, nconsts.AssetNonFungibleTokenID, assetType) require.Equal(t, "name", string(name)) require.Equal(t, "SYM-1", string(symbol)) require.Equal(t, uint8(0), decimals) @@ -238,7 +238,7 @@ func BenchmarkCompleteContributeDataset(b *testing.B) { // Check if NFT was created correctly assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := storage.GetAssetInfoNoController(ctx, store, nftAddress) require.NoError(err) - require.Equal(nconsts.AssetFractionalTokenID, assetType) + require.Equal(nconsts.AssetNonFungibleTokenID, assetType) require.Equal("name", string(name)) require.Equal("SYM-1", string(symbol)) require.Equal(uint8(0), decimals) diff --git a/actions/create_asset.go b/actions/create_asset.go index ec866b2..80dfb5c 100644 --- a/actions/create_asset.go +++ b/actions/create_asset.go @@ -154,7 +154,7 @@ func (c *CreateAsset) Execute( nftAddress := storage.AssetAddressNFT(assetAddress, []byte(c.Metadata), actor) output.DatasetParentNftAddress = nftAddress symbol := utils.CombineWithSuffix([]byte(c.Symbol), 0, storage.MaxSymbolSize) - if err := storage.SetAssetInfo(ctx, mu, nftAddress, c.AssetType, []byte(c.Name), symbol, 0, []byte(c.Metadata), []byte(assetAddress.String()), 0, 1, actor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress); err != nil { + if err := storage.SetAssetInfo(ctx, mu, nftAddress, nconsts.AssetNonFungibleTokenID, []byte(c.Name), symbol, 0, []byte(c.Metadata), []byte(assetAddress.String()), 0, 1, actor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress); err != nil { return nil, err } newBalance, err := storage.MintAsset(ctx, mu, nftAddress, actor, amountOfToken) diff --git a/actions/create_asset_test.go b/actions/create_asset_test.go index f7af4da..bc05925 100644 --- a/actions/create_asset_test.go +++ b/actions/create_asset_test.go @@ -214,7 +214,7 @@ func TestCreateAssetAction(t *testing.T) { // Check if NFT was created correctly assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err = storage.GetAssetInfoNoController(ctx, store, nftAddress) require.NoError(t, err) - require.Equal(t, nconsts.AssetFractionalTokenID, assetType) + require.Equal(t, nconsts.AssetNonFungibleTokenID, assetType) require.Equal(t, "name", string(name)) require.Equal(t, "SYM-0", string(symbol)) require.Equal(t, uint8(0), decimals) @@ -273,7 +273,7 @@ func BenchmarkCreateAsset(b *testing.B) { // Check if the asset was created correctly assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := storage.GetAssetInfoNoController(ctx, store, assetFT) require.NoError(err) - require.Equal(nconsts.AssetFungibleTokenID, assetType) + require.Equal(nconsts.AssetNonFungibleTokenID, assetType) require.Equal("name", string(name)) require.Equal("SYM", string(symbol)) require.Equal(uint8(9), decimals) diff --git a/actions/initiate_contribute_dataset_test.go b/actions/initiate_contribute_dataset_test.go index db138d9..a80b8ef 100644 --- a/actions/initiate_contribute_dataset_test.go +++ b/actions/initiate_contribute_dataset_test.go @@ -8,7 +8,6 @@ import ( "strings" "testing" - nconsts "github.com/nuklai/nuklaivm/consts" "github.com/nuklai/nuklaivm/dataset" "github.com/nuklai/nuklaivm/storage" "github.com/stretchr/testify/require" @@ -17,6 +16,8 @@ import ( "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/codec/codectest" "github.com/ava-labs/hypersdk/state" + + nconsts "github.com/nuklai/nuklaivm/consts" ) func TestInitiateContributeDatasetAction(t *testing.T) { diff --git a/actions/publish_dataset_marketplace.go b/actions/publish_dataset_marketplace.go index a902635..ba77d3d 100644 --- a/actions/publish_dataset_marketplace.go +++ b/actions/publish_dataset_marketplace.go @@ -99,7 +99,7 @@ func (d *PublishDatasetMarketplace) Execute( return nil, err } // Create a new marketplace asset - if err := storage.SetAssetInfo(ctx, mu, marketplaceAssetAddress, nconsts.AssetMarketplaceTokenID, []byte(storage.MarketplaceAssetName), []byte(storage.MarketplaceAssetSymbol), 0, metadata, []byte(marketplaceAssetAddress.String()), 0, 0, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress); err != nil { + if err := storage.SetAssetInfo(ctx, mu, marketplaceAssetAddress, nconsts.AssetMarketplaceTokenID, []byte(storage.MarketplaceAssetName), []byte(storage.MarketplaceAssetSymbol), 0, metadata, []byte(marketplaceAssetAddress.String()), 0, 0, actor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress); err != nil { return nil, err } diff --git a/actions/publish_dataset_marketplace_test.go b/actions/publish_dataset_marketplace_test.go new file mode 100644 index 0000000..1dbbd82 --- /dev/null +++ b/actions/publish_dataset_marketplace_test.go @@ -0,0 +1,186 @@ +// Copyright (C) 2024, Nuklai. All rights reserved. +// See the file LICENSE for licensing terms. + +package actions + +import ( + "context" + "testing" + + "github.com/nuklai/nuklaivm/storage" + "github.com/nuklai/nuklaivm/utils" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/hypersdk/chain/chaintest" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/codec/codectest" + "github.com/ava-labs/hypersdk/state" + + nconsts "github.com/nuklai/nuklaivm/consts" +) + +func TestPublishDatasetMarketplaceAction(t *testing.T) { + actor := codectest.NewRandomAddress() + datasetAddress := storage.AssetAddress(nconsts.AssetFractionalTokenID, []byte("Valid Name"), []byte("DATASET"), 0, []byte("metadata"), actor) + baseAssetAddress := storage.NAIAddress + marketplaceAssetAddress := storage.AssetAddressFractional(datasetAddress) + + tests := []chaintest.ActionTest{ + { + Name: "MarketplaceAssetAlreadyExists", + Actor: actor, + Action: &PublishDatasetMarketplace{ + DatasetAddress: datasetAddress, + PaymentAssetAddress: baseAssetAddress, + DatasetPricePerBlock: 100, + }, + State: func() state.Mutable { + store := chaintest.NewInMemoryStore() + require.NoError(t, storage.SetAssetInfo(context.Background(), store, marketplaceAssetAddress, nconsts.AssetMarketplaceTokenID, []byte("name"), []byte("SYM"), 9, []byte("metadata"), []byte("uri"), 0, 0, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress)) + return store + }(), + ExpectedErr: ErrAssetExists, + }, + { + Name: "WrongOwner", + Actor: codectest.NewRandomAddress(), // Not the owner of the dataset + Action: &PublishDatasetMarketplace{ + DatasetAddress: datasetAddress, + PaymentAssetAddress: baseAssetAddress, + DatasetPricePerBlock: 100, + }, + State: func() state.Mutable { + store := chaintest.NewInMemoryStore() + // Set the dataset with a different owner + require.NoError(t, storage.SetDatasetInfo(context.Background(), store, datasetAddress, []byte("Valid Name"), []byte("Valid Description"), []byte("Science"), []byte("MIT"), []byte("MIT"), []byte("http://license-url.com"), []byte("Metadata"), true, codec.EmptyAddress, codec.EmptyAddress, 0, 100, 0, 100, 0, actor)) + return store + }(), + ExpectedErr: ErrWrongOwner, + }, + { + Name: "ValidPublishDataset", + Actor: actor, + Action: &PublishDatasetMarketplace{ + DatasetAddress: datasetAddress, + PaymentAssetAddress: baseAssetAddress, + DatasetPricePerBlock: 100, + }, + State: func() state.Mutable { + store := chaintest.NewInMemoryStore() + // Set the base asset with the required details + require.NoError(t, storage.SetDatasetInfo(context.Background(), store, datasetAddress, []byte("Valid Name"), []byte("Valid Description"), []byte("Science"), []byte("MIT"), []byte("MIT"), []byte("http://license-url.com"), []byte("Metadata"), true, codec.EmptyAddress, codec.EmptyAddress, 0, 100, 0, 100, 0, actor)) + return store + }(), + Assertion: func(ctx context.Context, t *testing.T, store state.Mutable) { + // Check if the marketplace asset was created correctly + assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := storage.GetAssetInfoNoController(ctx, store, marketplaceAssetAddress) + require.NoError(t, err) + require.Equal(t, nconsts.AssetMarketplaceTokenID, assetType) + require.Equal(t, storage.MarketplaceAssetName, string(name)) + require.Equal(t, storage.MarketplaceAssetSymbol, string(symbol)) + require.Equal(t, uint8(0), decimals) + require.Equal(t, marketplaceAssetAddress.String(), string(uri)) + require.Equal(t, uint64(0), totalSupply) + require.Equal(t, uint64(0), maxSupply) + require.Equal(t, actor, owner) + require.Equal(t, codec.EmptyAddress, mintAdmin) + require.Equal(t, codec.EmptyAddress, pauseUnpauseAdmin) + require.Equal(t, codec.EmptyAddress, freezeUnfreezeAdmin) + require.Equal(t, codec.EmptyAddress, enableDisableKYCAccountAdmin) + + // Check metadata + metadataMap, err := utils.BytesToMap(metadata) + require.NoError(t, err) + require.Equal(t, datasetAddress.String(), metadataMap["datasetAddress"]) + require.Equal(t, marketplaceAssetAddress.String(), metadataMap["marketplaceAssetAddress"]) + require.Equal(t, "100", metadataMap["datasetPricePerBlock"]) + require.Equal(t, baseAssetAddress.String(), metadataMap["paymentAssetAddress"]) + require.Equal(t, actor.String(), metadataMap["publisher"]) + require.Equal(t, "0", metadataMap["lastClaimedBlock"]) + require.Equal(t, "0", metadataMap["subscriptions"]) + require.Equal(t, "0", metadataMap["paymentRemaining"]) + require.Equal(t, "0", metadataMap["paymentClaimed"]) + + // Check if the dataset was updated correctly + _, _, _, _, _, _, _, _, mAddr, baseAsset, basePrice, _, _, _, _, _, err := storage.GetDatasetInfoNoController(ctx, store, datasetAddress) + require.NoError(t, err) + require.Equal(t, marketplaceAssetAddress, mAddr) + require.Equal(t, baseAssetAddress, baseAsset) + require.Equal(t, uint64(100), basePrice) + }, + ExpectedOutputs: &PublishDatasetMarketplaceResult{ + MarketplaceAssetAddress: marketplaceAssetAddress, + PaymentAssetAddress: baseAssetAddress, + DatasetPricePerBlock: 100, + Publisher: actor, + }, + }, + } + + for _, tt := range tests { + tt.Run(context.Background(), t) + } +} + +func BenchmarkPublishDatasetMarketplace(b *testing.B) { + require := require.New(b) + actor := codectest.NewRandomAddress() + datasetAddress := storage.AssetAddress(nconsts.AssetFractionalTokenID, []byte("Valid Name"), []byte("DATASET"), 0, []byte("metadata"), actor) + baseAssetAddress := storage.NAIAddress + marketplaceAssetAddress := storage.AssetAddressFractional(datasetAddress) + + publishDatasetMarketplaceBenchmark := &chaintest.ActionBenchmark{ + Name: "PublishDatasetMarketplaceBenchmark", + Actor: actor, + Action: &PublishDatasetMarketplace{ + DatasetAddress: datasetAddress, + PaymentAssetAddress: baseAssetAddress, + DatasetPricePerBlock: 100, + }, + CreateState: func() state.Mutable { + store := chaintest.NewInMemoryStore() + require.NoError(storage.SetDatasetInfo(context.Background(), store, datasetAddress, []byte("Valid Name"), []byte("Valid Description"), []byte("Science"), []byte("MIT"), []byte("MIT"), []byte("http://license-url.com"), []byte("Metadata"), true, codec.EmptyAddress, codec.EmptyAddress, 0, 100, 0, 100, 0, actor)) + return store + }, + Assertion: func(ctx context.Context, b *testing.B, store state.Mutable) { + // Check if the marketplace asset was created correctly + assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := storage.GetAssetInfoNoController(ctx, store, marketplaceAssetAddress) + require.NoError(err) + require.Equal(nconsts.AssetMarketplaceTokenID, assetType) + require.Equal(storage.MarketplaceAssetName, string(name)) + require.Equal(storage.MarketplaceAssetSymbol, string(symbol)) + require.Equal(uint8(0), decimals) + require.Equal(marketplaceAssetAddress.String(), string(uri)) + require.Equal(uint64(0), totalSupply) + require.Equal(uint64(0), maxSupply) + require.Equal(actor, owner) + require.Equal(codec.EmptyAddress, mintAdmin) + require.Equal(codec.EmptyAddress, pauseUnpauseAdmin) + require.Equal(codec.EmptyAddress, freezeUnfreezeAdmin) + require.Equal(codec.EmptyAddress, enableDisableKYCAccountAdmin) + + // Check metadata + metadataMap, err := utils.BytesToMap(metadata) + require.NoError(err) + require.Equal(datasetAddress.String(), metadataMap["datasetAddress"]) + require.Equal(marketplaceAssetAddress.String(), metadataMap["marketplaceAssetAddress"]) + require.Equal("100", metadataMap["datasetPricePerBlock"]) + require.Equal(baseAssetAddress.String(), metadataMap["paymentAssetAddress"]) + require.Equal(actor.String(), metadataMap["publisher"]) + require.Equal("0", metadataMap["lastClaimedBlock"]) + require.Equal("0", metadataMap["subscriptions"]) + require.Equal("0", metadataMap["paymentRemaining"]) + require.Equal("0", metadataMap["paymentClaimed"]) + + // Check if the dataset was updated correctly + _, _, _, _, _, _, _, _, mAddr, baseAsset, basePrice, _, _, _, _, _, err := storage.GetDatasetInfoNoController(ctx, store, datasetAddress) + require.NoError(err) + require.Equal(marketplaceAssetAddress, mAddr) + require.Equal(baseAssetAddress, baseAsset) + require.Equal(uint64(100), basePrice) + }, + } + + ctx := context.Background() + publishDatasetMarketplaceBenchmark.Run(ctx, b) +} diff --git a/actions/subscribe_dataset_marketplace.go b/actions/subscribe_dataset_marketplace.go index 8f6ccaa..1c9276c 100644 --- a/actions/subscribe_dataset_marketplace.go +++ b/actions/subscribe_dataset_marketplace.go @@ -98,10 +98,6 @@ func (d *SubscribeDatasetMarketplace) Execute( if err != nil { return nil, err } - // Check if the marketplaceAssetAddress is correct - if metadataMap["marketplaceAssetAddress"] != d.MarketplaceAssetAddress.String() { - return nil, ErrMarketplaceAssetAddressInvalid - } // Ensure paymentAssetAddress is supported if metadataMap["paymentAssetAddress"] != d.PaymentAssetAddress.String() { return nil, ErrPaymentAssetNotSupported @@ -169,7 +165,7 @@ func (d *SubscribeDatasetMarketplace) Execute( metadataNFTMap["datasetAddress"] = metadataMap["datasetAddress"] metadataNFTMap["marketplaceAssetAddress"] = d.MarketplaceAssetAddress.String() metadataNFTMap["datasetPricePerBlock"] = metadataMap["datasetPricePerBlock"] - metadataNFTMap["assetForPayment"] = d.PaymentAssetAddress.String() + metadataNFTMap["paymentAssetAddress"] = d.PaymentAssetAddress.String() metadataNFTMap["totalCost"] = fmt.Sprint(totalCost) metadataNFTMap["issuanceBlock"] = fmt.Sprint(currentBlock) metadataNFTMap["numBlocksToSubscribe"] = fmt.Sprint(d.NumBlocksToSubscribe) @@ -184,7 +180,7 @@ func (d *SubscribeDatasetMarketplace) Execute( if _, err := storage.MintAsset(ctx, mu, d.MarketplaceAssetAddress, actor, 1); err != nil { return nil, err } - symbol = utils.CombineWithSuffix(symbol, totalSupply+1, storage.MaxSymbolSize) + symbol = utils.CombineWithSuffix(symbol, totalSupply, storage.MaxSymbolSize) if err := storage.SetAssetInfo(ctx, mu, nftAddress, nconsts.AssetNonFungibleTokenID, name, symbol, 0, metadataNFT, []byte(d.MarketplaceAssetAddress.String()), 0, 1, actor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress); err != nil { return nil, err } diff --git a/actions/subscribe_dataset_marketplace_test.go b/actions/subscribe_dataset_marketplace_test.go new file mode 100644 index 0000000..4377883 --- /dev/null +++ b/actions/subscribe_dataset_marketplace_test.go @@ -0,0 +1,250 @@ +// Copyright (C) 2024, Nuklai. All rights reserved. +// See the file LICENSE for licensing terms. + +package actions + +import ( + "context" + "fmt" + "testing" + + "github.com/nuklai/nuklaivm/emission" + "github.com/nuklai/nuklaivm/storage" + "github.com/nuklai/nuklaivm/utils" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/hypersdk/chain/chaintest" + "github.com/ava-labs/hypersdk/codec" + "github.com/ava-labs/hypersdk/codec/codectest" + "github.com/ava-labs/hypersdk/state" + + nconsts "github.com/nuklai/nuklaivm/consts" +) + +func TestSubscribeDatasetMarketplaceAction(t *testing.T) { + mockEmission := emission.MockNewEmission(&emission.MockEmission{LastAcceptedBlockHeight: 1}) + + actor := codectest.NewRandomAddress() + datasetAddress := storage.AssetAddress(nconsts.AssetFractionalTokenID, []byte("Valid Name"), []byte("DATASET"), 0, []byte("metadata"), actor) + baseAssetAddress := storage.NAIAddress + marketplaceAssetAddress := storage.AssetAddressFractional(datasetAddress) + nftAddress := storage.AssetAddressNFT(marketplaceAssetAddress, nil, actor) + + tests := []chaintest.ActionTest{ + { + Name: "UserAlreadySubscribed", + Actor: actor, + Action: &SubscribeDatasetMarketplace{ + MarketplaceAssetAddress: marketplaceAssetAddress, + PaymentAssetAddress: baseAssetAddress, + NumBlocksToSubscribe: 10, + }, + State: func() state.Mutable { + store := chaintest.NewInMemoryStore() + require.NoError(t, storage.SetAssetInfo(context.Background(), store, nftAddress, nconsts.AssetNonFungibleTokenID, []byte("name"), []byte("SYM"), 0, []byte("metadata"), []byte(marketplaceAssetAddress.String()), 0, 0, actor, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress)) + return store + }(), + ExpectedErr: ErrUserAlreadySubscribed, + }, + { + Name: "BaseAssetNotSupported", + Actor: actor, + Action: &SubscribeDatasetMarketplace{ + MarketplaceAssetAddress: marketplaceAssetAddress, + PaymentAssetAddress: codec.EmptyAddress, // Invalid base asset ID + NumBlocksToSubscribe: 10, + }, + State: func() state.Mutable { + store := chaintest.NewInMemoryStore() + metadata := map[string]string{ + "datasetAddress": datasetAddress.String(), + "marketplaceAssetAddress": marketplaceAssetAddress.String(), + "datasetPricePerBlock": "100", + "paymentAssetAddress": baseAssetAddress.String(), + "publisher": actor.String(), + "lastClaimedBlock": "0", + "subscriptions": "0", + "paymentRemaining": "0", + "paymentClaimed": "0", + } + metadataBytes, err := utils.MapToBytes(metadata) + require.NoError(t, err) + require.NoError(t, storage.SetAssetInfo(context.Background(), store, marketplaceAssetAddress, nconsts.AssetMarketplaceTokenID, []byte("name"), []byte("SYM"), 0, metadataBytes, []byte(marketplaceAssetAddress.String()), 0, 0, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress)) + return store + }(), + ExpectedErr: ErrPaymentAssetNotSupported, + }, + { + Name: "ValidSubscription", + Actor: actor, + Action: &SubscribeDatasetMarketplace{ + MarketplaceAssetAddress: marketplaceAssetAddress, + PaymentAssetAddress: baseAssetAddress, + NumBlocksToSubscribe: 10, + }, + State: func() state.Mutable { + store := chaintest.NewInMemoryStore() + metadata := map[string]string{ + "datasetAddress": datasetAddress.String(), + "marketplaceAssetAddress": marketplaceAssetAddress.String(), + "datasetPricePerBlock": "100", + "paymentAssetAddress": baseAssetAddress.String(), + "publisher": actor.String(), + "lastClaimedBlock": "0", + "subscriptions": "0", + "paymentRemaining": "0", + "paymentClaimed": "0", + } + metadataBytes, err := utils.MapToBytes(metadata) + require.NoError(t, err) + require.NoError(t, storage.SetAssetInfo(context.Background(), store, marketplaceAssetAddress, nconsts.AssetMarketplaceTokenID, []byte("name"), []byte("SYM"), 0, metadataBytes, []byte(marketplaceAssetAddress.String()), 0, 0, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress)) + // Set base asset balance to sufficient amount + require.NoError(t, storage.SetAssetAccountBalance(context.Background(), store, baseAssetAddress, actor, 5000)) + return store + }(), + Assertion: func(ctx context.Context, t *testing.T, store state.Mutable) { + // Check if balance is correctly deducted + balance, err := storage.GetAssetAccountBalanceNoController(ctx, store, baseAssetAddress, actor) + require.NoError(t, err) + require.Equal(t, uint64(4000), balance) // 5000 - 1000 = 4000 + + // Check if the subscription NFT was created correctly + assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := storage.GetAssetInfoNoController(ctx, store, nftAddress) + require.NoError(t, err) + require.Equal(t, nconsts.AssetNonFungibleTokenID, assetType) + require.Equal(t, "name", string(name)) + require.Equal(t, "SYM-0", string(symbol)) + require.Equal(t, uint8(0), decimals) + require.Equal(t, marketplaceAssetAddress.String(), string(uri)) + require.Equal(t, uint64(1), totalSupply) + require.Equal(t, uint64(1), maxSupply) + require.Equal(t, actor, owner) + require.Equal(t, codec.EmptyAddress, mintAdmin) + require.Equal(t, codec.EmptyAddress, pauseUnpauseAdmin) + require.Equal(t, codec.EmptyAddress, freezeUnfreezeAdmin) + require.Equal(t, codec.EmptyAddress, enableDisableKYCAccountAdmin) + // Check metadata of NFT + metadataMap, err := utils.BytesToMap(metadata) + require.NoError(t, err) + require.Equal(t, datasetAddress.String(), metadataMap["datasetAddress"]) + require.Equal(t, marketplaceAssetAddress.String(), metadataMap["marketplaceAssetAddress"]) + require.Equal(t, "100", metadataMap["datasetPricePerBlock"]) + require.Equal(t, baseAssetAddress.String(), metadataMap["paymentAssetAddress"]) + require.Equal(t, "1000", metadataMap["totalCost"]) + require.Equal(t, fmt.Sprint(mockEmission.GetLastAcceptedBlockHeight()), metadataMap["issuanceBlock"]) + require.Equal(t, "10", metadataMap["numBlocksToSubscribe"]) + require.Equal(t, fmt.Sprint(mockEmission.GetLastAcceptedBlockHeight()+10), metadataMap["expirationBlock"]) + + // Check if the marketplace asset metadata was updated correctly + _, _, _, _, metadata, _, _, _, _, _, _, _, _, err = storage.GetAssetInfoNoController(ctx, store, marketplaceAssetAddress) + require.NoError(t, err) + metadataMap, err = utils.BytesToMap(metadata) + require.NoError(t, err) + require.Equal(t, "1000", metadataMap["paymentRemaining"]) + require.Equal(t, "1", metadataMap["subscriptions"]) + require.Equal(t, fmt.Sprint(mockEmission.GetLastAcceptedBlockHeight()), metadataMap["lastClaimedBlock"]) + }, + ExpectedOutputs: &SubscribeDatasetMarketplaceResult{ + MarketplaceAssetAddress: marketplaceAssetAddress, + MarketplaceAssetNumSubscriptions: 1, + SubscriptionNftAddress: nftAddress, + PaymentAssetAddress: baseAssetAddress, + DatasetPricePerBlock: 100, + TotalCost: 1000, + NumBlocksToSubscribe: 10, + IssuanceBlock: mockEmission.GetLastAcceptedBlockHeight(), + ExpirationBlock: mockEmission.GetLastAcceptedBlockHeight() + 10, + }, + }, + } + + for _, tt := range tests { + tt.Run(context.Background(), t) + } +} + +func BenchmarkSubscribeDatasetMarketplace(b *testing.B) { + require := require.New(b) + mockEmission := emission.MockNewEmission(&emission.MockEmission{LastAcceptedBlockHeight: 1}) + + actor := codectest.NewRandomAddress() + datasetAddress := storage.AssetAddress(nconsts.AssetFractionalTokenID, []byte("Valid Name"), []byte("DATASET"), 0, []byte("metadata"), actor) + baseAssetAddress := storage.NAIAddress + marketplaceAssetAddress := storage.AssetAddressFractional(datasetAddress) + nftAddress := storage.AssetAddressNFT(marketplaceAssetAddress, nil, actor) + + subscribeDatasetMarketplaceBenchmark := &chaintest.ActionBenchmark{ + Name: "SubscribeDatasetMarketplaceBenchmark", + Actor: actor, + Action: &SubscribeDatasetMarketplace{ + MarketplaceAssetAddress: marketplaceAssetAddress, + PaymentAssetAddress: baseAssetAddress, + NumBlocksToSubscribe: 10, + }, + CreateState: func() state.Mutable { + store := chaintest.NewInMemoryStore() + metadata := map[string]string{ + "datasetAddress": datasetAddress.String(), + "marketplaceAssetAddress": marketplaceAssetAddress.String(), + "datasetPricePerBlock": "100", + "paymentAssetAddress": baseAssetAddress.String(), + "publisher": actor.String(), + "lastClaimedBlock": "0", + "subscriptions": "0", + "paymentRemaining": "0", + "paymentClaimed": "0", + } + metadataBytes, err := utils.MapToBytes(metadata) + require.NoError(err) + require.NoError(storage.SetAssetInfo(context.Background(), store, marketplaceAssetAddress, nconsts.AssetMarketplaceTokenID, []byte("name"), []byte("SYM"), 0, metadataBytes, []byte(marketplaceAssetAddress.String()), 0, 0, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress, codec.EmptyAddress)) + // Set base asset balance to sufficient amount + require.NoError(storage.SetAssetAccountBalance(context.Background(), store, baseAssetAddress, actor, 5000)) + return store + }, + Assertion: func(ctx context.Context, b *testing.B, store state.Mutable) { + // Check if balance is correctly deducted + balance, err := storage.GetAssetAccountBalanceNoController(ctx, store, baseAssetAddress, actor) + require.NoError(err) + require.Equal(uint64(4000), balance) // 5000 - 1000 = 4000 + + // Check if the subscription NFT was created correctly + assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := storage.GetAssetInfoNoController(ctx, store, nftAddress) + require.NoError(err) + require.Equal(nconsts.AssetNonFungibleTokenID, assetType) + require.Equal("name", string(name)) + require.Equal("SYM-0", string(symbol)) + require.Equal(uint8(0), decimals) + require.Equal(marketplaceAssetAddress.String(), string(uri)) + require.Equal(uint64(1), totalSupply) + require.Equal(uint64(1), maxSupply) + require.Equal(actor, owner) + require.Equal(codec.EmptyAddress, mintAdmin) + require.Equal(codec.EmptyAddress, pauseUnpauseAdmin) + require.Equal(codec.EmptyAddress, freezeUnfreezeAdmin) + require.Equal(codec.EmptyAddress, enableDisableKYCAccountAdmin) + // Check metadata of NFT + metadataMap, err := utils.BytesToMap(metadata) + require.NoError(err) + require.Equal(datasetAddress.String(), metadataMap["datasetAddress"]) + require.Equal(marketplaceAssetAddress.String(), metadataMap["marketplaceAssetAddress"]) + require.Equal("100", metadataMap["datasetPricePerBlock"]) + require.Equal(baseAssetAddress.String(), metadataMap["paymentAssetAddress"]) + require.Equal("1000", metadataMap["totalCost"]) + require.Equal(fmt.Sprint(mockEmission.GetLastAcceptedBlockHeight()), metadataMap["issuanceBlock"]) + require.Equal("10", metadataMap["numBlocksToSubscribe"]) + require.Equal(fmt.Sprint(mockEmission.GetLastAcceptedBlockHeight()+10), metadataMap["expirationBlock"]) + + // Check if the marketplace asset metadata was updated correctly + _, _, _, _, metadata, _, _, _, _, _, _, _, _, err = storage.GetAssetInfoNoController(ctx, store, marketplaceAssetAddress) + require.NoError(err) + metadataMap, err = utils.BytesToMap(metadata) + require.NoError(err) + require.Equal("1000", metadataMap["paymentRemaining"]) + require.Equal("1", metadataMap["subscriptions"]) + require.Equal(fmt.Sprint(mockEmission.GetLastAcceptedBlockHeight()), metadataMap["lastClaimedBlock"]) + }, + } + + ctx := context.Background() + subscribeDatasetMarketplaceBenchmark.Run(ctx, b) +} diff --git a/cmd/nuklai-cli/cmd/action.go b/cmd/nuklai-cli/cmd/action.go index c479c1a..b18e999 100644 --- a/cmd/nuklai-cli/cmd/action.go +++ b/cmd/nuklai-cli/cmd/action.go @@ -16,6 +16,7 @@ import ( "github.com/near/borsh-go" "github.com/nuklai/nuklaivm/actions" "github.com/nuklai/nuklaivm/consts" + "github.com/nuklai/nuklaivm/storage" "github.com/spf13/cobra" "github.com/status-im/keycard-go/hexutils" @@ -47,14 +48,14 @@ var transferCmd = &cobra.Command{ return err } - // Get assetID - assetID, err := prompt.Asset("assetID", consts.Symbol, true) + // Get assetAddress + assetAddress, err := parseAsset("assetAddress") if err != nil { return err } // Get balance info - balance, _, _, _, decimals, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(ctx, ncli, priv.Address, assetID, true) + balance, _, _, _, decimals, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(ctx, ncli, priv.Address, assetAddress, true, false, -1) if balance == 0 || err != nil { return err } @@ -79,7 +80,7 @@ var transferCmd = &cobra.Command{ // Generate transaction result, txID, err := sendAndWait(ctx, []chain.Action{&actions.Transfer{ - AssetAddress: assetID.String(), + AssetAddress: assetAddress, To: recipient, Value: amount, }}, cli, ncli, ws, factory) @@ -299,7 +300,7 @@ var registerValidatorStakeCmd = &cobra.Command{ if err != nil { return err } - balance, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(ctx, nclients[0], validatorSignerKey.Address, ids.Empty, true) + balance, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(ctx, nclients[0], validatorSignerKey.Address, storage.NAIAddress, true, false, -1) if err != nil { return err } @@ -358,7 +359,7 @@ var registerValidatorStakeCmd = &cobra.Command{ } // Get balance info - balance, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(ctx, ncli, priv.Address, ids.Empty, true) + balance, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(ctx, ncli, priv.Address, storage.NAIAddress, true, false, -1) if balance == 0 || err != nil { return err } @@ -691,7 +692,7 @@ var delegateUserStakeCmd = &cobra.Command{ nodeID := validatorChosen.NodeID // Get balance info - balance, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(ctx, ncli, priv.Address, ids.Empty, true) + balance, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(ctx, ncli, priv.Address, storage.NAIAddress, true, false, -1) if balance == 0 || err != nil { return err } diff --git a/cmd/nuklai-cli/cmd/asset.go b/cmd/nuklai-cli/cmd/asset.go index 33481d5..ad27d84 100644 --- a/cmd/nuklai-cli/cmd/asset.go +++ b/cmd/nuklai-cli/cmd/asset.go @@ -14,8 +14,6 @@ import ( "github.com/ava-labs/hypersdk/cli/prompt" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/utils" - - nutils "github.com/nuklai/nuklaivm/utils" ) var assetCmd = &cobra.Command{ @@ -79,19 +77,12 @@ var createAssetCmd = &cobra.Command{ return err } - // Generate transaction - assetID, err := nutils.GenerateRandomID() - if err != nil { - return err - } result, _, err := sendAndWait(ctx, []chain.Action{&actions.CreateAsset{ - AssetID: assetID.String(), AssetType: uint8(assetType), Name: name, Symbol: symbol, Decimals: uint8(decimals), // already constrain above to prevent overflow Metadata: metadata, - URI: "https://nukl.ai", MaxSupply: uint64(0), MintAdmin: owner, PauseUnpauseAdmin: owner, @@ -115,19 +106,19 @@ var updateAssetCmd = &cobra.Command{ } // Select asset ID to update - assetID, err := prompt.ID("assetID") + assetAddress, err := prompt.Address("assetAddress") if err != nil { return err } // Add name to token - name, err := prompt.String("name", 1, actions.MaxMetadataSize) + name, err := prompt.String("name", 1, storage.MaxNameSize) if err != nil { return err } // Add symbol to token - symbol, err := prompt.String("symbol", 1, actions.MaxTextSize) + symbol, err := prompt.String("symbol", 1, storage.MaxSymbolSize) if err != nil { return err } @@ -140,9 +131,9 @@ var updateAssetCmd = &cobra.Command{ // Generate transaction result, _, err := sendAndWait(ctx, []chain.Action{&actions.UpdateAsset{ - AssetAddress: assetID, - Name: []byte(name), - Symbol: []byte(symbol), + AssetAddress: assetAddress, + Name: name, + Symbol: symbol, }}, cli, ncli, ws, factory) if err != nil { return err @@ -161,21 +152,16 @@ var mintAssetFTCmd = &cobra.Command{ } // Select token to mint - assetID, err := prompt.ID("assetID") + assetAddress, err := prompt.Address("assetAddress") if err != nil { return err } - exists, assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := ncli.Asset(ctx, assetID.String(), false) + assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := ncli.Asset(ctx, assetAddress.String(), false) if err != nil { return err } - if !exists { - utils.Outf("{{red}}assetID:%s does not exist{{/}}\n", assetID) - utils.Outf("{{red}}exiting...{{/}}\n") - return nil - } if mintAdmin != priv.Address.String() { - utils.Outf("{{red}}%s has permission to mint asset '%s' with assetID '%s', you are not{{/}}\n", mintAdmin, name, assetID) + utils.Outf("{{red}}%s has permission to mint asset '%s' with assetID '%s', you are not{{/}}\n", mintAdmin, name, assetAddress) utils.Outf("{{red}}exiting...{{/}}\n") return nil } @@ -216,7 +202,7 @@ var mintAssetFTCmd = &cobra.Command{ // Generate transaction result, _, err := sendAndWait(ctx, []chain.Action{&actions.MintAssetFT{ - AssetAddress: assetID, + AssetAddress: assetAddress, To: recipient, Value: amount, }}, cli, ncli, ws, factory) @@ -237,21 +223,16 @@ var mintAssetNFTCmd = &cobra.Command{ } // Select nft collection id to mint to - assetID, err := prompt.ID("assetID") + assetAddress, err := prompt.Address("assetAddress") if err != nil { return err } - exists, assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := ncli.Asset(ctx, assetID.String(), false) + assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := ncli.Asset(ctx, assetAddress.String(), false) if err != nil { return err } - if !exists { - utils.Outf("{{red}}name: %s with assetID:%s does not exist{{/}}\n", name, assetID) - utils.Outf("{{red}}exiting...{{/}}\n") - return nil - } if mintAdmin != priv.Address.String() { - utils.Outf("{{red}}%s has permission to mint asset '%s' with assetID '%s', you are not{{/}}\n", mintAdmin, name, assetID) + utils.Outf("{{red}}%s has permission to mint asset '%s' with assetID '%s', you are not{{/}}\n", mintAdmin, name, assetAddress) utils.Outf("{{red}}exiting...{{/}}\n") return nil } @@ -278,14 +259,8 @@ var mintAssetNFTCmd = &cobra.Command{ return err } - // Choose unique id for the NFT - uniqueID, err := prompt.Int("unique nft #", consts.MaxInt) - if err != nil { - return err - } - // Add metadata for the NFT - metadataNFT, err := prompt.String("metadata", 1, actions.MaxMetadataSize) + metadataNFT, err := prompt.String("metadata", 1, storage.MaxAssetMetadataSize) if err != nil { return err } @@ -298,11 +273,9 @@ var mintAssetNFTCmd = &cobra.Command{ // Generate transaction result, _, err := sendAndWait(ctx, []chain.Action{&actions.MintAssetNFT{ - AssetAddress: assetID, - UniqueID: uint64(uniqueID), + AssetAddress: assetAddress, To: recipient, - URI: []byte(metadataNFT), - Metadata: []byte(metadataNFT), + Metadata: metadataNFT, }}, cli, ncli, ws, factory) if err != nil { return err @@ -324,19 +297,14 @@ var burnAssetFTCmd = &cobra.Command{ } // Select token to burn - assetID, err := prompt.ID("assetID") + assetAddress, err := prompt.Address("assetAddress") if err != nil { return err } - exists, assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := ncli.Asset(ctx, assetID.String(), false) + assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := ncli.Asset(ctx, assetAddress.String(), false) if err != nil { return err } - if !exists { - utils.Outf("{{red}}assetID:%s does not exist{{/}}\n", assetID) - utils.Outf("{{red}}exiting...{{/}}\n") - return nil - } utils.Outf( "{{blue}}assetType:{{/}} %s name:{{/}} %s {{blue}}symbol:{{/}} %s {{blue}}decimals:{{/}} %d {{blue}}metadata:{{/}} %s {{blue}}uri:{{/}} %s {{blue}}totalSupply:{{/}} %d {{blue}}maxSupply:{{/}} %d {{blue}}admin:{{/}} %s {{blue}}mintActor:{{/}} %s {{blue}}pauseUnpauseActor:{{/}} %s {{blue}}freezeUnfreezeActor:{{/}} %s {{blue}}enableDisableKYCAccountActor:{{/}} %s\n", assetType, @@ -368,7 +336,7 @@ var burnAssetFTCmd = &cobra.Command{ // Generate transaction result, _, err := sendAndWait(ctx, []chain.Action{&actions.BurnAssetFT{ - AssetAddress: assetID, + AssetAddress: assetAddress, Value: amount, }}, cli, ncli, ws, factory) if err != nil { @@ -388,18 +356,18 @@ var burnAssetNFTCmd = &cobra.Command{ } // Select asset ID to burn - assetID, err := prompt.ID("assetID") + assetAddress, err := prompt.Address("assetAddress") if err != nil { return err } // Select nft ID to burn - nftID, err := prompt.ID("nftID") + nftAddress, err := prompt.Address("nftAddress") if err != nil { return err } - if _, _, _, _, _, _, err = handler.GetAssetNFTInfo(context.TODO(), ncli, priv.Address, nftID, true); err != nil { + if _, _, _, _, _, _, _, err = handler.GetAssetNFTInfo(context.TODO(), ncli, priv.Address, nftAddress, true); err != nil { return err } @@ -411,8 +379,8 @@ var burnAssetNFTCmd = &cobra.Command{ // Generate transaction result, _, err := sendAndWait(ctx, []chain.Action{&actions.BurnAssetNFT{ - AssetAddress: assetID, - AssetNftAddress: nftID, + AssetAddress: assetAddress, + AssetNftAddress: nftAddress, }}, cli, ncli, ws, factory) if err != nil { return err diff --git a/cmd/nuklai-cli/cmd/dataset.go b/cmd/nuklai-cli/cmd/dataset.go index db2e195..c910c9f 100644 --- a/cmd/nuklai-cli/cmd/dataset.go +++ b/cmd/nuklai-cli/cmd/dataset.go @@ -5,15 +5,13 @@ package cmd import ( "context" - "strconv" - "github.com/ava-labs/avalanchego/ids" "github.com/nuklai/nuklaivm/actions" + "github.com/nuklai/nuklaivm/storage" "github.com/spf13/cobra" "github.com/ava-labs/hypersdk/chain" "github.com/ava-labs/hypersdk/cli/prompt" - "github.com/ava-labs/hypersdk/consts" hutils "github.com/ava-labs/hypersdk/utils" ) @@ -93,20 +91,20 @@ var createDatasetFromExistingAssetCmd = &cobra.Command{ return err } - // Select asset ID - assetID, err := prompt.ID("assetID") + // Select asset + assetAddress, err := prompt.Address("assetAddress") if err != nil { return err } // Add name to dataset - name, err := prompt.String("name", 1, actions.MaxMetadataSize) + name, err := prompt.String("name", 1, storage.MaxNameSize) if err != nil { return err } // Add description to dataset - description, err := prompt.String("description", 1, actions.MaxMetadataSize) + description, err := prompt.String("description", 1, storage.MaxTextSize) if err != nil { return err } @@ -118,7 +116,7 @@ var createDatasetFromExistingAssetCmd = &cobra.Command{ } // Add metadata to dataset - metadata, err := prompt.String("metadata", 1, actions.MaxDatasetMetadataSize) + metadata, err := prompt.String("metadata", 1, storage.MaxDatasetMetadataSize) if err != nil { return err } @@ -131,14 +129,14 @@ var createDatasetFromExistingAssetCmd = &cobra.Command{ // Generate transaction result, _, err := sendAndWait(ctx, []chain.Action{&actions.CreateDataset{ - AssetAddress: assetID, - Name: []byte(name), - Description: []byte(description), - Categories: []byte(name), - LicenseName: []byte("MIT"), - LicenseSymbol: []byte("MIT"), - LicenseURL: []byte("https://opensource.org/licenses/MIT"), - Metadata: []byte(metadata), + AssetAddress: assetAddress, + Name: name, + Description: description, + Categories: name, + LicenseName: "MIT", + LicenseSymbol: "MIT", + LicenseURL: "https://opensource.org/licenses/MIT", + Metadata: metadata, IsCommunityDataset: isCommunityDataset, }}, cli, ncli, ws, factory) if err != nil { @@ -158,13 +156,13 @@ var updateDatasetCmd = &cobra.Command{ } // Select dataset ID to update - datasetID, err := prompt.ID("datasetID") + datasetAddress, err := prompt.Address("datasetAddress") if err != nil { return err } // Update name to dataset - name, err := prompt.String("name", 1, actions.MaxMetadataSize) + name, err := prompt.String("name", 1, storage.MaxNameSize) if err != nil { return err } @@ -183,8 +181,8 @@ var updateDatasetCmd = &cobra.Command{ // Generate transaction result, _, err := sendAndWait(ctx, []chain.Action{&actions.UpdateDataset{ - DatasetAddress: datasetID, - Name: []byte(name), + DatasetAddress: datasetAddress, + Name: name, IsCommunityDataset: isCommunityDataset, }}, cli, ncli, ws, factory) if err != nil { @@ -211,21 +209,21 @@ var getDatasetCmd = &cobra.Command{ ncli := nclients[0] // Select dataset to look up - datasetID, err := prompt.ID("datasetID") + datasetAddress, err := prompt.Address("datasetAddress") if err != nil { return err } // Get dataset info - hutils.Outf("Retrieving dataset info for datasetID: %s\n", datasetID) - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, err = handler.GetDatasetInfo(ctx, ncli, datasetID) + hutils.Outf("Retrieving dataset info for datasetID: %s\n", datasetAddress) + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, err = handler.GetDatasetInfo(ctx, ncli, datasetAddress) if err != nil { return err } // Get asset info - hutils.Outf("Retrieving asset info for assetID: %s\n", datasetID) - _, _, _, _, _, _, _, _, _, _, _, _, _, err = handler.GetAssetInfo(ctx, ncli, priv.Address, datasetID, true) + hutils.Outf("Retrieving asset info for assetID: %s\n", datasetAddress) + _, _, _, _, _, _, _, _, _, _, _, _, _, err = handler.GetAssetInfo(ctx, ncli, priv.Address, datasetAddress, true, true, -1) return err }, } @@ -239,14 +237,14 @@ var initiateContributeDatasetCmd = &cobra.Command{ return err } - // Select dataset ID to contribute to - datasetID, err := prompt.ID("datasetID") + // Select dataset to contribute to + datasetAddress, err := prompt.Address("datasetAddress") if err != nil { return err } // Add data identifier to dataset - dataIdentifier, err := prompt.String("dataIdentifier", 1, actions.MaxMetadataSize-actions.MaxTextSize) + dataIdentifier, err := prompt.String("dataIdentifier", 1, storage.MaxAssetMetadataSize-storage.MaxDatasetDataLocationSize) if err != nil { return err } @@ -259,9 +257,9 @@ var initiateContributeDatasetCmd = &cobra.Command{ // Generate transaction result, _, err := sendAndWait(ctx, []chain.Action{&actions.InitiateContributeDataset{ - DatasetAddress: datasetID, - DataLocation: []byte("default"), - DataIdentifier: []byte(dataIdentifier), + DatasetAddress: datasetAddress, + DataLocation: "default", + DataIdentifier: dataIdentifier, }}, cli, ncli, ws, factory) if err != nil { return err @@ -283,25 +281,17 @@ var getDataContributionPendingCmd = &cobra.Command{ ncli := nclients[0] // Select dataset to look up - datasetIDStr, err := prompt.String("datasetID", 1, consts.MaxInt) - if err != nil { - return err - } - datasetID, err := ids.FromString(datasetIDStr) + contributionID, err := prompt.ID("contributionID") if err != nil { return err } // Get pending data contributions info - hutils.Outf("Retrieving pending data contributions info for datasetID: %s\n", datasetID) - contributions, err := handler.GetDataContributionPendingInfo(ctx, ncli, datasetID) + hutils.Outf("Retrieving pending data contributions info for datasetID: %s\n", contributionID) + _, _, _, _, _, err = handler.GetDataContributionInfo(ctx, ncli, contributionID) if err != nil { return err } - if len(contributions) == 0 { - hutils.Outf("{{red}}This contribution does not exist{{/}}\n") - hutils.Outf("{{red}}exiting...{{/}}\n") - } return nil }, } @@ -316,23 +306,19 @@ var completeContributeDatasetCmd = &cobra.Command{ } // Select dataset ID - datasetID, err := prompt.ID("datasetID") + datasetAddress, err := prompt.Address("datasetAddress") if err != nil { return err } - // Select the contributor - contributor, err := prompt.Address("contributor") + // Select contribution ID + contributionID, err := prompt.ID("contributionID") if err != nil { return err } - // Choose unique id for the NFT to be minted - uniqueIDStr, err := prompt.String("unique nft #", 1, actions.MaxTextSize) - if err != nil { - return err - } - uniqueID, err := strconv.ParseUint(uniqueIDStr, 10, 64) + // Select the contributor + contributor, err := prompt.Address("contributor") if err != nil { return err } @@ -345,9 +331,9 @@ var completeContributeDatasetCmd = &cobra.Command{ // Generate transaction result, _, err := sendAndWait(ctx, []chain.Action{&actions.CompleteContributeDataset{ - DatasetAddress: datasetID, - DatasetContributor: contributor, - UniqueContributorNFTMetadata: uniqueID, + DatasetContributionID: contributionID, + DatasetAddress: datasetAddress, + DatasetContributor: contributor, }}, cli, ncli, ws, factory) if err != nil { return err diff --git a/cmd/nuklai-cli/cmd/handler.go b/cmd/nuklai-cli/cmd/handler.go index 2a0b097..5f94cba 100644 --- a/cmd/nuklai-cli/cmd/handler.go +++ b/cmd/nuklai-cli/cmd/handler.go @@ -6,10 +6,12 @@ package cmd import ( "context" "encoding/base64" + "fmt" "github.com/ava-labs/avalanchego/ids" "github.com/nuklai/nuklaivm/consts" "github.com/nuklai/nuklaivm/emission" + "github.com/nuklai/nuklaivm/storage" "github.com/nuklai/nuklaivm/vm" "github.com/ava-labs/hypersdk/api/jsonrpc" @@ -40,6 +42,46 @@ func (h *Handler) Root() *cli.Handler { return h.h } +func (h *Handler) SetKey() error { + keys, err := h.h.GetKeys() + if err != nil { + return err + } + if len(keys) == 0 { + utils.Outf("{{red}}no stored keys{{/}}\n") + return nil + } + _, uris, err := h.h.GetDefaultChain(true) + if err != nil { + return err + } + if len(uris) == 0 { + utils.Outf("{{red}}no available chains{{/}}\n") + return nil + } + utils.Outf("{{cyan}}stored keys:{{/}} %d\n", len(keys)) + for i := 0; i < len(keys); i++ { + addrStr := keys[i].Address + nclients, err := handler.DefaultNuklaiVMJSONRPCClient(checkAllChains) + if err != nil { + return err + } + for _, ncli := range nclients { + if _, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(context.TODO(), ncli, addrStr, storage.NAIAddress, true, false, i); err != nil { + return err + } + } + } + + // Select key + keyIndex, err := prompt.Choice("set default key", len(keys)) + if err != nil { + return err + } + key := keys[keyIndex] + return h.h.StoreDefaultKey(key.Address) +} + func (h *Handler) ImportChain(uri string) error { client := jsonrpc.NewJSONRPCClient(uri) _, _, chainID, err := client.Network(context.TODO()) @@ -202,27 +244,31 @@ func (*Handler) GetAssetInfo( actor codec.Address, assetAddress codec.Address, checkBalance bool, + printOutput bool, + index int, ) (uint64, string, string, string, uint8, string, uint64, uint64, string, string, string, string, string, error) { assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, err := cli.Asset(ctx, assetAddress.String(), false) if err != nil { return 0, "", "", "", 0, "", 0, 0, "", "", "", "", "", err } - utils.Outf( - "{{blue}}assetType: {{/}} %s name:{{/}} %s {{blue}}symbol:{{/}} %s {{blue}}decimals:{{/}} %d {{blue}}metadata:{{/}} %s {{blue}}uri:{{/}} %s {{blue}}totalSupply:{{/}} %d {{blue}}maxSupply:{{/}} %d {{blue}}owner:{{/}} %s {{blue}}mintAdmin:{{/}} %s {{blue}}pauseUnpauseAdmin:{{/}} %s {{blue}}freezeUnfreezeAdmin:{{/}} %s {{blue}}enableDisableKYCAccountAdmin:{{/}} %s\n", - assetType, - name, - symbol, - decimals, - metadata, - uri, - totalSupply, - maxSupply, - owner, - mintAdmin, - pauseUnpauseAdmin, - freezeUnfreezeAdmin, - enableDisableKYCAccountAdmin, - ) + if printOutput { + utils.Outf( + "{{blue}}assetType: {{/}} %s name:{{/}} %s {{blue}}symbol:{{/}} %s {{blue}}decimals:{{/}} %d {{blue}}metadata:{{/}} %s {{blue}}uri:{{/}} %s {{blue}}totalSupply:{{/}} %d {{blue}}maxSupply:{{/}} %d {{blue}}owner:{{/}} %s {{blue}}mintAdmin:{{/}} %s {{blue}}pauseUnpauseAdmin:{{/}} %s {{blue}}freezeUnfreezeAdmin:{{/}} %s {{blue}}enableDisableKYCAccountAdmin:{{/}} %s\n", + assetType, + name, + symbol, + decimals, + metadata, + uri, + totalSupply, + maxSupply, + owner, + mintAdmin, + pauseUnpauseAdmin, + freezeUnfreezeAdmin, + enableDisableKYCAccountAdmin, + ) + } if !checkBalance { return 0, assetType, name, symbol, decimals, metadata, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, nil @@ -231,20 +277,13 @@ func (*Handler) GetAssetInfo( if err != nil { return 0, "", "", "", 0, "", 0, 0, "", "", "", "", "", err } - if balance == 0 { - utils.Outf("{{red}}assetID:{{/}} %s\n", assetAddress) - utils.Outf("{{red}}name:{{/}} %s\n", name) - utils.Outf("{{red}}symbol:{{/}} %s\n", symbol) - utils.Outf("{{red}}balance:{{/}} 0\n") - utils.Outf("{{red}}please send funds to %s{{/}}\n", actor.String()) - utils.Outf("{{red}}exiting...{{/}}\n") - } else { - utils.Outf( - "{{blue}}balance:{{/}} %s %s\n", - nutils.FormatBalance(balance, decimals), - symbol, - ) + output := "" + if index >= 0 { + output += fmt.Sprintf("%d) ", index) } + output += fmt.Sprintf("{{cyan}}address:{{/}} %s {{cyan}}balance:{{/}} %s %s\n", actor, utils.FormatBalance(balance), symbol) + utils.Outf(output) + return balance, assetType, name, symbol, decimals, metadata, totalSupply, maxSupply, owner, mintAdmin, pauseUnpauseAdmin, freezeUnfreezeAdmin, enableDisableKYCAccountAdmin, nil } @@ -425,9 +464,9 @@ func (*Handler) GetUserStake(ctx context.Context, func (*Handler) GetDatasetInfo( ctx context.Context, cli *vm.JSONRPCClient, - datasetID ids.ID, + datasetAddress codec.Address, ) (string, string, string, string, string, string, string, bool, string, string, uint64, uint8, uint8, uint8, uint8, string, error) { - name, description, categories, licenseName, licenseSymbol, licenseURL, metadata, isCommunityDataset, saleID, baseAsset, basePrice, revenueModelDataShare, revenueModelMetadataShare, revenueModelDataOwnerCut, revenueModelMetadataOwnerCut, owner, err := cli.Dataset(ctx, datasetID.String(), false) + name, description, categories, licenseName, licenseSymbol, licenseURL, metadata, isCommunityDataset, saleID, baseAsset, basePrice, revenueModelDataShare, revenueModelMetadataShare, revenueModelDataOwnerCut, revenueModelMetadataOwnerCut, owner, err := cli.Dataset(ctx, datasetAddress.String(), false) if err != nil { return "", "", "", "", "", "", "", false, "", "", 0, 0, 0, 0, 0, "", err } @@ -454,7 +493,7 @@ func (*Handler) GetDatasetInfo( return name, description, categories, licenseName, licenseSymbol, licenseURL, metadata, isCommunityDataset, saleID, baseAsset, basePrice, revenueModelDataShare, revenueModelMetadataShare, revenueModelDataOwnerCut, revenueModelMetadataOwnerCut, owner, err } -func (*Handler) GetDataContributionPendingInfo( +func (*Handler) GetDataContributionInfo( ctx context.Context, cli *vm.JSONRPCClient, contributionID ids.ID, diff --git a/cmd/nuklai-cli/cmd/key.go b/cmd/nuklai-cli/cmd/key.go index af6adb4..04476f7 100644 --- a/cmd/nuklai-cli/cmd/key.go +++ b/cmd/nuklai-cli/cmd/key.go @@ -130,27 +130,34 @@ var importKeyCmd = &cobra.Command{ var setKeyCmd = &cobra.Command{ Use: "set", RunE: func(*cobra.Command, []string) error { - return handler.Root().SetKey() + return handler.SetKey() }, } var balanceKeyCmd = &cobra.Command{ Use: "balance [address]", RunE: func(_ *cobra.Command, args []string) error { + var ( + addr codec.Address + err error + ) if len(args) != 1 { - return handler.Root().Balance(checkAllChains) - } - addr, err := codec.StringToAddress(args[0]) - if err != nil { - return err + addr, _, err = handler.h.GetDefaultKey(true) + if err != nil { + return err + } + } else { + addr, err = codec.StringToAddress(args[0]) + if err != nil { + return err + } } - utils.Outf("{{yellow}}address:{{/}} %s\n", addr) nclients, err := handler.DefaultNuklaiVMJSONRPCClient(checkAllChains) if err != nil { return err } for _, ncli := range nclients { - if _, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(context.TODO(), ncli, addr, storage.NAIAddress, true); err != nil { + if _, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(context.TODO(), ncli, addr, storage.NAIAddress, true, false, -1); err != nil { return err } } @@ -165,7 +172,7 @@ func lookupKeyBalance(uri string, addr codec.Address, assetAddress codec.Address } else { _, _, _, _, _, _, _, _, _, _, _, _, _, err = handler.GetAssetInfo( context.TODO(), vm.NewJSONRPCClient(uri), - addr, assetAddress, true) + addr, assetAddress, true, false, -1) } return err } @@ -180,7 +187,6 @@ var balanceFTKeyCmd = &cobra.Command{ if err != nil { return err } - utils.Outf("{{yellow}}address:{{/}} %s\n", addr) nclients, err := handler.DefaultNuklaiVMJSONRPCClient(checkAllChains) if err != nil { return err @@ -190,7 +196,7 @@ var balanceFTKeyCmd = &cobra.Command{ return err } for _, ncli := range nclients { - if _, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(context.TODO(), ncli, addr, assetAddress, true); err != nil { + if _, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(context.TODO(), ncli, addr, assetAddress, true, false, -1); err != nil { return err } } @@ -208,7 +214,6 @@ var balanceNFTKeyCmd = &cobra.Command{ if err != nil { return err } - utils.Outf("{{yellow}}address:{{/}} %s\n", addr) nclients, err := handler.DefaultNuklaiVMJSONRPCClient(checkAllChains) if err != nil { return err diff --git a/cmd/nuklai-cli/cmd/marketplace.go b/cmd/nuklai-cli/cmd/marketplace.go index 7e56255..a9b5a2a 100644 --- a/cmd/nuklai-cli/cmd/marketplace.go +++ b/cmd/nuklai-cli/cmd/marketplace.go @@ -5,10 +5,7 @@ package cmd import ( "context" - "fmt" - "strings" - "github.com/ava-labs/avalanchego/ids" "github.com/nuklai/nuklaivm/actions" "github.com/spf13/cobra" @@ -17,8 +14,6 @@ import ( "github.com/ava-labs/hypersdk/consts" hutils "github.com/ava-labs/hypersdk/utils" - nconsts "github.com/nuklai/nuklaivm/consts" - nutils "github.com/nuklai/nuklaivm/utils" ) var marketplaceCmd = &cobra.Command{ @@ -37,19 +32,19 @@ var publishDatasetMarketplaceCmd = &cobra.Command{ return err } - // Select dataset ID - datasetID, err := prompt.ID("datasetID") + // Select dataset + datasetAddress, err := prompt.Address("datasetAddress") if err != nil { return err } - // Select assetForPayment ID - assetForPayment, err := prompt.Asset("assetForPayment", nconsts.Symbol, true) + // Select paymentAssetAddress + paymentAssetAddress, err := parseAsset("paymentAssetAddress") if err != nil { return err } - balance, _, _, _, decimals, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(ctx, ncli, priv.Address, assetForPayment, true) + balance, _, _, _, decimals, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(ctx, ncli, priv.Address, paymentAssetAddress, true, true, -1) if balance == 0 || err != nil { return err } @@ -66,15 +61,9 @@ var publishDatasetMarketplaceCmd = &cobra.Command{ return err } - // Generate transaction - assetID, err := nutils.GenerateRandomID() - if err != nil { - return err - } result, _, err := sendAndWait(ctx, []chain.Action{&actions.PublishDatasetMarketplace{ - MarketplaceAssetID: assetID, - DatasetAddress: datasetID, - PaymentAssetAddress: assetForPayment, + DatasetAddress: datasetAddress, + PaymentAssetAddress: paymentAssetAddress, DatasetPricePerBlock: priceAmountPerBlock, }}, cli, ncli, ws, factory) if err != nil { @@ -88,36 +77,22 @@ var subscribeDatasetMarketplaceCmd = &cobra.Command{ Use: "subscribe", RunE: func(*cobra.Command, []string) error { ctx := context.Background() - _, priv, factory, cli, ncli, ws, err := handler.DefaultActor() - if err != nil { - return err - } - - // Select dataset ID - datasetID, err := prompt.ID("datasetID") + _, _, factory, cli, ncli, ws, err := handler.DefaultActor() if err != nil { return err } - // Get dataset info - hutils.Outf("Retrieving dataset info for datasetID: %s\n", datasetID) - _, _, _, _, _, _, _, _, saleID, baseAsset, basePrice, _, _, _, _, _, err := handler.GetDatasetInfo(ctx, ncli, datasetID) - if err != nil { - return err - } - marketplaceID, err := ids.FromString(saleID) + // Select marketplaceAddress + marketplaceAddress, err := prompt.Address("marketplaceAddress") if err != nil { return err } - // Select assetForPayment ID - assetForPayment, err := prompt.Asset("assetForPayment", nconsts.Symbol, true) + // Select paymentAssetAddress + paymentAssetAddress, err := parseAsset("paymentAssetAddress") if err != nil { return err } - if !strings.EqualFold(assetForPayment.String(), baseAsset) { - return fmt.Errorf("assetForPayment must be the same as the dataset's baseAsset. BaseAsset: %s", baseAsset) - } // Get numBlocksToSubscribe numBlocksToSubscribe, err := prompt.Int("numBlocksToSubscribe", consts.MaxInt) @@ -125,15 +100,6 @@ var subscribeDatasetMarketplaceCmd = &cobra.Command{ return err } - // Ensure user has enough balance - balance, _, _, _, _, _, _, _, _, _, _, _, _, err := handler.GetAssetInfo(ctx, ncli, priv.Address, assetForPayment, true) - if err != nil { - return err - } - if balance < basePrice*uint64(numBlocksToSubscribe) { - return fmt.Errorf("insufficient balance. Required: %d", basePrice*uint64(numBlocksToSubscribe)) - } - // Confirm action cont, err := prompt.Continue() if !cont || err != nil { @@ -142,9 +108,8 @@ var subscribeDatasetMarketplaceCmd = &cobra.Command{ // Generate transaction result, _, err := sendAndWait(ctx, []chain.Action{&actions.SubscribeDatasetMarketplace{ - DatasetID: datasetID, - MarketplaceAssetAddress: marketplaceID, - PaymentAssetAddress: assetForPayment, + MarketplaceAssetAddress: marketplaceAddress, + PaymentAssetAddress: paymentAssetAddress, NumBlocksToSubscribe: uint64(numBlocksToSubscribe), }}, cli, ncli, ws, factory) if err != nil { @@ -165,14 +130,14 @@ var infoDatasetMarketplaceCmd = &cobra.Command{ } ncli := nclients[0] - // Select dataset ID - datasetID, err := prompt.ID("datasetID") + // Select datasetAddress + datasetAddress, err := prompt.Address("datasetAddress") if err != nil { return err } // Get dataset info from the marketplace - hutils.Outf("Retrieving dataset info from the marketplace: %s\n", datasetID) - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, err = handler.GetDatasetInfoFromMarketplace(ctx, ncli, datasetID) + hutils.Outf("Retrieving dataset info from the marketplace: %s\n", datasetAddress) + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, err = handler.GetDatasetInfoFromMarketplace(ctx, ncli, datasetAddress) if err != nil { return err } @@ -189,26 +154,13 @@ var claimPaymentMarketplaceCmd = &cobra.Command{ return err } - // Select dataset ID - datasetID, err := prompt.ID("datasetID") - if err != nil { - return err - } - - // Get dataset info - // Get dataset info from the marketplace - hutils.Outf("Retrieving dataset info from the marketplace: %s\n", datasetID) - _, _, _, saleID, baseAsset, _, _, _, _, _, _, _, _, _, _, err := handler.GetDatasetInfoFromMarketplace(ctx, ncli, datasetID) - if err != nil { - return err - } - marketplaceID, err := ids.FromString(saleID) + marketplaceAddress, err := prompt.Address("marketplaceAddress") if err != nil { return err } - // Select assetForPayment ID - assetForPayment, err := ids.FromString(baseAsset) + // Select paymentAssetAddress + paymentAssetAddress, err := parseAsset("paymentAssetAddress") if err != nil { return err } @@ -221,9 +173,8 @@ var claimPaymentMarketplaceCmd = &cobra.Command{ // Generate transaction result, _, err := sendAndWait(ctx, []chain.Action{&actions.ClaimMarketplacePayment{ - DatasetID: datasetID, - MarketplaceAssetAddress: marketplaceID, - PaymentAssetAddress: assetForPayment, + MarketplaceAssetAddress: marketplaceAddress, + PaymentAssetAddress: paymentAssetAddress, }}, cli, ncli, ws, factory) if err != nil { return err diff --git a/cmd/nuklai-cli/cmd/prompt.go b/cmd/nuklai-cli/cmd/prompt.go index e330c5a..e4cb481 100644 --- a/cmd/nuklai-cli/cmd/prompt.go +++ b/cmd/nuklai-cli/cmd/prompt.go @@ -4,12 +4,16 @@ package cmd import ( + "fmt" "strings" "github.com/manifoldco/promptui" + "github.com/nuklai/nuklaivm/consts" + "github.com/nuklai/nuklaivm/storage" "github.com/nuklai/nuklaivm/utils" "github.com/ava-labs/hypersdk/cli/prompt" + "github.com/ava-labs/hypersdk/codec" ) func parseAmount( @@ -40,3 +44,36 @@ func parseAmount( rawAmount = strings.TrimSpace(rawAmount) return utils.ParseBalance(rawAmount, decimals) } + +func parseAsset(label string) (codec.Address, error) { + text := fmt.Sprintf("%s (use %s for native token)", label, consts.Symbol) + promptText := promptui.Prompt{ + Label: text, + Validate: func(input string) error { + if len(input) == 0 { + return prompt.ErrInputEmpty + } + if input == consts.Symbol { + return nil + } + _, err := codec.StringToAddress(input) + return err + }, + } + asset, err := promptText.Run() + if err != nil { + return codec.EmptyAddress, err + } + asset = strings.TrimSpace(asset) + assetAddress := storage.NAIAddress + if asset != consts.Symbol { + assetAddress, err = codec.StringToAddress(asset) + if err != nil { + return codec.EmptyAddress, err + } + } + if assetAddress == codec.EmptyAddress { + return codec.EmptyAddress, prompt.ErrInvalidChoice + } + return assetAddress, nil +} diff --git a/cmd/nuklai-cli/cmd/resolutions.go b/cmd/nuklai-cli/cmd/resolutions.go index 99288da..56881cb 100644 --- a/cmd/nuklai-cli/cmd/resolutions.go +++ b/cmd/nuklai-cli/cmd/resolutions.go @@ -94,7 +94,7 @@ func handleTx(tx *chain.Transaction, result *chain.Result) { case *actions.ContractCall: summaryStr = fmt.Sprintf("contractAddress: %s value: %d function: %s calldata: %s\n", act.ContractAddress, act.Value, act.Function, string(act.CallData)) case *actions.CreateAsset: - assetAddress := storage.AssetAddress(act.AssetType, []byte(act.Name), []byte(act.Symbol), act.Decimals, []byte(act.Metadata), []byte(act.URI), actor) + assetAddress := storage.AssetAddress(act.AssetType, []byte(act.Name), []byte(act.Symbol), act.Decimals, []byte(act.Metadata), actor) summaryStr = fmt.Sprintf("assetAddress: %s symbol: %s decimals: %d metadata: %s\n", assetAddress, act.Symbol, act.Decimals, act.Metadata) case *actions.UpdateAsset: summaryStr = fmt.Sprintf("assetAddress: %s updated\n", act.AssetAddress) diff --git a/genesis/genesis.go b/genesis/genesis.go index d0082e7..eea4029 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -17,8 +17,6 @@ import ( "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/genesis" "github.com/ava-labs/hypersdk/state" - - safemath "github.com/ava-labs/avalanchego/utils/math" ) var ( @@ -51,25 +49,8 @@ func (g *Genesis) InitializeState(ctx context.Context, tracer trace.Tracer, mu s _, span := tracer.Start(ctx, "Nuklai Genesis.InitializeState") defer span.End() - // Initialize state from the DefaultGenesis first - if err := g.DefaultGenesis.InitializeState(ctx, tracer, mu, balanceHandler); err != nil { - return err - } - - // Get totalSupply - var ( - totalSupply uint64 - err error - ) - for _, alloc := range g.CustomAllocation { - totalSupply, err = safemath.Add(totalSupply, alloc.Balance) - if err != nil { - return err - } - } - // Set the asset info for NAI using storage.SetAsset - return storage.SetAssetInfo( + if err := storage.SetAssetInfo( ctx, mu, storage.NAIAddress, // Asset Address @@ -79,14 +60,19 @@ func (g *Genesis) InitializeState(ctx context.Context, tracer trace.Tracer, mu s consts.Decimals, // Decimals []byte(consts.Metadata), // Metadata []byte(storage.NAIAddress.String()), // URI - totalSupply, // Initial supply + 0, // Initial total supply g.EmissionBalancer.MaxSupply, // Max supply codec.EmptyAddress, // Owner address codec.EmptyAddress, // MintAdmin address codec.EmptyAddress, // PauseUnpauseAdmin address codec.EmptyAddress, // FreezeUnfreezeAdmin address codec.EmptyAddress, // EnableDisableKYCAccountAdmin address - ) + ); err != nil { + return err + } + + // Initialize state from the DefaultGenesis first + return g.DefaultGenesis.InitializeState(ctx, tracer, mu, balanceHandler) } func (g *Genesis) GetStateBranchFactor() merkledb.BranchFactor { diff --git a/storage/state_manager.go b/storage/state_manager.go index f473372..1278da7 100644 --- a/storage/state_manager.go +++ b/storage/state_manager.go @@ -46,7 +46,7 @@ func (*StateManager) AddBalance( addr codec.Address, mu state.Mutable, amount uint64, - createAccount bool, + _ bool, ) error { _, err := MintAsset(ctx, mu, NAIAddress, addr, amount) return err @@ -54,6 +54,7 @@ func (*StateManager) AddBalance( func (*StateManager) SponsorStateKeys(addr codec.Address) state.Keys { return state.Keys{ + string(AssetInfoKey(NAIAddress)): state.All, string(AssetAccountBalanceKey(NAIAddress, addr)): state.All, } } diff --git a/tests/workload/workload.go b/tests/workload/workload.go index e326c35..a198929 100644 --- a/tests/workload/workload.go +++ b/tests/workload/workload.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/nuklai/nuklaivm/actions" "github.com/nuklai/nuklaivm/consts" + "github.com/nuklai/nuklaivm/storage" "github.com/nuklai/nuklaivm/vm" "github.com/stretchr/testify/require" @@ -40,7 +41,7 @@ var ( _ workload.TxWorkloadIterator = (*simpleTxWorkload)(nil) ed25519HexKeys = []string{ "323b1d8f4eed5f0da9da93071b034f2dce9d2d22692c172f3cb252a64ddfafd01b057de320297c29ad0c1f589ea216869cf1938d88c9fbd70d6748323dbf2fa7", //nolint:lll - "8a7be2e0c9a2d09ac2861c34326d6fe5a461d920ba9c2b345ae28e603d517df148735063f8d5d8ba79ea4668358943e5c80bc09e9b2b9a15b5b15db6c1862e88", //nolint:lll + // "8a7be2e0c9a2d09ac2861c34326d6fe5a461d920ba9c2b345ae28e603d517df148735063f8d5d8ba79ea4668358943e5c80bc09e9b2b9a15b5b15db6c1862e88", //nolint:lll } ed25519PrivKeys = make([]ed25519.PrivateKey, len(ed25519HexKeys)) ed25519Addrs = make([]codec.Address, len(ed25519HexKeys)) @@ -134,8 +135,9 @@ func (g *simpleTxWorkload) GenerateTxWithAssertion(ctx context.Context) (*chain. ctx, parser, []chain.Action{&actions.Transfer{ - To: aother, - Value: 1, + To: aother, + AssetAddress: storage.NAIAddress, + Value: 1, }}, g.factory, ) @@ -229,8 +231,9 @@ func (g *mixedAuthWorkload) GenerateTxWithAssertion(ctx context.Context) (*chain ctx, parser, []chain.Action{&actions.Transfer{ - To: receiver.address, - Value: expectedBalance, + To: receiver.address, + AssetAddress: storage.NAIAddress, + Value: expectedBalance, }}, sender.authFactory, )