Skip to content

Commit

Permalink
Merge pull request #56 from Nuklai/publish-dataset
Browse files Browse the repository at this point in the history
Publish dataset
  • Loading branch information
kpachhai authored Sep 19, 2024
2 parents 88c71d7 + 3fb11fd commit d3f8940
Show file tree
Hide file tree
Showing 45 changed files with 4,768 additions and 2,936 deletions.
2 changes: 1 addition & 1 deletion actions/burn_asset_nft.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (b *BurnAssetNFT) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
}

func (*BurnAssetNFT) StateKeysMaxChunks() []uint16 {
return []uint16{storage.AssetChunks, storage.AssetNFTChunks, storage.BalanceChunks}
return []uint16{storage.AssetChunks, storage.AssetNFTChunks, storage.BalanceChunks, storage.BalanceChunks}
}

func (b *BurnAssetNFT) Execute(
Expand Down
227 changes: 227 additions & 0 deletions actions/claim_marketplace_payment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// Copyright (C) 2024, Nuklai. All rights reserved.
// See the file LICENSE for licensing terms.

package actions

import (
"context"
"encoding/json"
"fmt"
"strconv"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/hypersdk/chain"
"github.com/ava-labs/hypersdk/codec"
"github.com/ava-labs/hypersdk/consts"
"github.com/ava-labs/hypersdk/state"
hutils "github.com/ava-labs/hypersdk/utils"

nconsts "github.com/nuklai/nuklaivm/consts"
"github.com/nuklai/nuklaivm/emission"
"github.com/nuklai/nuklaivm/storage"
)

var _ chain.Action = (*ClaimMarketplacePayment)(nil)

type ClaimMarketplacePayment struct {
// Dataset ID
Dataset ids.ID `json:"dataset"`

// Marketplace ID(This is also the asset ID in the marketplace that represents the dataset)
MarketplaceID ids.ID `json:"marketplaceID"`

// Asset to use for the payment
AssetForPayment ids.ID `json:"assetForPayment"`
}

func (*ClaimMarketplacePayment) GetTypeID() uint8 {
return nconsts.ClaimMarketplacePaymentID
}

func (c *ClaimMarketplacePayment) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
return state.Keys{
string(storage.DatasetKey(c.Dataset)): state.Read,
string(storage.AssetKey(c.MarketplaceID)): state.Read | state.Write,
string(storage.AssetKey(c.AssetForPayment)): state.Read,
string(storage.BalanceKey(actor, c.AssetForPayment)): state.Allocate | state.Write,
}
}

func (*ClaimMarketplacePayment) StateKeysMaxChunks() []uint16 {
return []uint16{storage.DatasetChunks, storage.AssetChunks, storage.AssetChunks, storage.BalanceChunks}
}

func (c *ClaimMarketplacePayment) Execute(
ctx context.Context,
_ chain.Rules,
mu state.Mutable,
_ int64,
actor codec.Address,
_ ids.ID,
) ([][]byte, error) {
// Check if the dataset exists
exists, _, _, _, _, _, _, _, _, _, baseAsset, _, _, _, _, _, owner, err := storage.GetDataset(ctx, mu, c.Dataset)
if err != nil {
return nil, err
}
if !exists {
return nil, ErrDatasetNotFound
}

// Check if the user is the owner of the dataset
// Only the onwer can claim the payment
if owner != actor {
return nil, ErrOutputWrongOwner
}

// Ensure assetForPayment is supported
if c.AssetForPayment != baseAsset {
return nil, ErrBaseAssetNotSupported
}

// Check if the marketplace asset exists
exists, assetType, name, symbol, decimals, metadata, uri, totalSupply, maxSupply, admin, mintActor, pauseUnpauseActor, freezeUnfreezeActor, enableDisableKYCAccountActor, err := storage.GetAsset(ctx, mu, c.MarketplaceID)
if err != nil {
return nil, err
}
if !exists {
return nil, ErrOutputAssetMissing
}
if assetType != nconsts.AssetMarketplaceTokenID {
return nil, ErrOutputWrongAssetType
}

// Unmarshal the metadata JSON into a map
var metadataMap map[string]string
if err := json.Unmarshal(metadata, &metadataMap); err != nil {
return nil, err
}

// Parse existing values from the metadata
paymentRemaining, err := strconv.ParseUint(metadataMap["paymentRemaining"], 10, 64)
if err != nil {
return nil, err
}
if paymentRemaining == 0 {
return nil, ErrNoPaymentRemaining
}
paymentClaimed, err := strconv.ParseUint(metadataMap["paymentClaimed"], 10, 64)
if err != nil {
return nil, err
}
lastClaimedBlock, err := strconv.ParseUint(metadataMap["lastClaimedBlock"], 10, 64)
if err != nil {
return nil, err
}

// Store the initial total before updating
initialTotal := paymentRemaining + paymentClaimed
// Parse the value of 1 in the base unit according to the number of decimals
decimalsToUse := uint8(nconsts.Decimals)
if c.AssetForPayment != ids.Empty {
exists, _, _, _, decimals, _, _, _, _, _, _, _, _, _, err = storage.GetAsset(ctx, mu, c.AssetForPayment)
if err != nil {
return nil, err
}
if !exists {
return nil, ErrAssetNotFound
}
decimalsToUse = decimals
}
baseValueOfOneUnit, _ := hutils.ParseBalance("1", decimalsToUse)
// Get the current block height
currentBlockHeight := emission.GetEmission().GetLastAcceptedBlockHeight()
// Calculate the number of blocks the subscription has been active
numBlocksSubscribed := currentBlockHeight - lastClaimedBlock
// Calculate the total accumulated reward since the subscription started
totalAccumulatedReward := numBlocksSubscribed * baseValueOfOneUnit
// Cap the reward at the remaining payment if necessary
if totalAccumulatedReward > paymentRemaining {
totalAccumulatedReward = paymentRemaining
}
// Update paymentRemaining to reflect the updated balance
paymentRemaining -= totalAccumulatedReward
// Add the new accumulated reward to paymentClaimed
paymentClaimed += totalAccumulatedReward
// Update the lastClaimedBlock to the current block height so that next time we only accumulate from here
lastClaimedBlock = currentBlockHeight
finalTotal := paymentRemaining + paymentClaimed

// Ensure the final total is consistent with the initial total
if initialTotal != finalTotal {
return nil, fmt.Errorf("inconsistent state: initial total (%d) does not match final total (%d)", initialTotal, finalTotal)
}

// Now, paymentRemaining, paymentClaimed, and lastClaimedBlock are updated based on the reward accumulated per block
// You can now proceed to update these values in the relevant data structure or write them back to metadataMap
metadataMap["paymentRemaining"] = strconv.FormatUint(paymentRemaining, 10)
metadataMap["paymentClaimed"] = strconv.FormatUint(paymentClaimed, 10)
metadataMap["lastClaimedBlock"] = strconv.FormatUint(lastClaimedBlock, 10)

// Marshal the map back to a JSON byte slice
updatedMetadata, err := json.Marshal(metadataMap)
if err != nil {
return nil, err
}

// Update the asset with the updated metadata
if err := storage.SetAsset(ctx, mu, c.MarketplaceID, assetType, name, symbol, decimals, updatedMetadata, uri, totalSupply, maxSupply, admin, mintActor, pauseUnpauseActor, freezeUnfreezeActor, enableDisableKYCAccountActor); err != nil {
return nil, err
}

// TODO: Distribute the rewards to all the users who contributed to the dataset
if err := storage.AddBalance(ctx, mu, actor, c.AssetForPayment, totalAccumulatedReward, true); err != nil {
return nil, err
}

sr := &ClaimPaymentResult{totalAccumulatedReward}
output, err := sr.Marshal()
if err != nil {
return nil, err
}
return [][]byte{output}, nil
}

func (*ClaimMarketplacePayment) ComputeUnits(chain.Rules) uint64 {
return ClaimMarketplacePaymentComputeUnits
}

func (*ClaimMarketplacePayment) Size() int {
return ids.IDLen * 3
}

func (c *ClaimMarketplacePayment) Marshal(p *codec.Packer) {
p.PackID(c.Dataset)
p.PackID(c.MarketplaceID)
p.PackID(c.AssetForPayment)
}

func UnmarshalClaimMarketplacePayment(p *codec.Packer) (chain.Action, error) {
var claimPaymentResult ClaimMarketplacePayment
p.UnpackID(true, &claimPaymentResult.Dataset)
p.UnpackID(true, &claimPaymentResult.MarketplaceID)
p.UnpackID(false, &claimPaymentResult.AssetForPayment)
return &claimPaymentResult, p.Err()
}

func (*ClaimMarketplacePayment) ValidRange(chain.Rules) (int64, int64) {
// Returning -1, -1 means that the action is always valid.
return -1, -1
}

type ClaimPaymentResult struct {
RewardAmount uint64
}

func UnmarshalClaimPaymentResult(b []byte) (*ClaimPaymentResult, error) {
p := codec.NewReader(b, consts.Uint64Len)
var result ClaimPaymentResult
result.RewardAmount = p.UnpackUint64(false)
return &result, p.Err()
}

func (s *ClaimPaymentResult) Marshal() ([]byte, error) {
p := codec.NewWriter(consts.Uint64Len, consts.Uint64Len)
p.PackUint64(s.RewardAmount)
return p.Bytes(), p.Err()
}
13 changes: 9 additions & 4 deletions actions/complete_contribute_dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (*CompleteContributeDataset) GetTypeID() uint8 {
}

func (d *CompleteContributeDataset) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
nftID := nchain.GenerateID(d.Dataset, d.UniqueNFTID)
nftID := nchain.GenerateIDWithIndex(d.Dataset, d.UniqueNFTID)
return state.Keys{
string(storage.AssetKey(d.Dataset)): state.Read | state.Write,
string(storage.AssetNFTKey(nftID)): state.Allocate | state.Write,
Expand All @@ -49,7 +49,7 @@ func (d *CompleteContributeDataset) StateKeys(_ codec.Address, _ ids.ID) state.K
}

func (*CompleteContributeDataset) StateKeysMaxChunks() []uint16 {
return []uint16{storage.AssetChunks, storage.DatasetChunks, storage.BalanceChunks}
return []uint16{storage.AssetChunks, storage.AssetNFTChunks, storage.DatasetChunks, storage.BalanceChunks, storage.BalanceChunks, storage.BalanceChunks}
}

func (d *CompleteContributeDataset) Execute(
Expand All @@ -61,7 +61,7 @@ func (d *CompleteContributeDataset) Execute(
_ ids.ID,
) ([][]byte, error) {
// Check if the dataset exists
exists, _, description, _, _, _, _, _, _, _, _, _, _, _, _, _, owner, err := storage.GetDataset(ctx, mu, d.Dataset)
exists, _, description, _, _, _, _, _, _, saleID, _, _, _, _, _, _, owner, err := storage.GetDataset(ctx, mu, d.Dataset)
if err != nil {
return nil, err
}
Expand All @@ -72,8 +72,13 @@ func (d *CompleteContributeDataset) Execute(
return nil, ErrNotDatasetOwner
}

// Check if the dataset is already on sale
if saleID != ids.Empty {
return nil, ErrDatasetAlreadyOnSale
}

// Check if the nftID already exists
nftID := nchain.GenerateID(d.Dataset, d.UniqueNFTID)
nftID := nchain.GenerateIDWithIndex(d.Dataset, d.UniqueNFTID)
exists, _, _, _, _, _, _ = storage.GetAssetNFT(ctx, mu, nftID)
if exists {
return nil, ErrOutputNFTAlreadyExists
Expand Down
37 changes: 20 additions & 17 deletions actions/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,26 @@
package actions

const (
TransferComputeUnits = 1
CreateAssetComputeUnits = 15
UpdateAssetComputeUnits = 15
ExportAssetComputeUnits = 5
ImportAssetComputeUnits = 5
MintAssetComputeUnits = 5
MintAssetNFTComputeUnits = 5
BurnAssetComputeUnits = 1
RegisterValidatorStakeComputeUnits = 4
WithdrawValidatorStakeComputeUnits = 1
DelegateUserStakeComputeUnits = 5
UndelegateUserStakeComputeUnits = 1
ClaimStakingRewardComputeUnits = 2
CreateDatasetComputeUnits = 100
UpdateDatasetComputeUnits = 5
InitiateContributeDatasetComputeUnits = 5
CompleteContributeDatasetComputeUnits = 5
TransferComputeUnits = 1
CreateAssetComputeUnits = 15
UpdateAssetComputeUnits = 15
ExportAssetComputeUnits = 5
ImportAssetComputeUnits = 5
MintAssetComputeUnits = 5
MintAssetNFTComputeUnits = 5
BurnAssetComputeUnits = 1
RegisterValidatorStakeComputeUnits = 4
WithdrawValidatorStakeComputeUnits = 1
DelegateUserStakeComputeUnits = 5
UndelegateUserStakeComputeUnits = 1
ClaimStakingRewardComputeUnits = 2
CreateDatasetComputeUnits = 100
UpdateDatasetComputeUnits = 5
InitiateContributeDatasetComputeUnits = 5
CompleteContributeDatasetComputeUnits = 5
PublishDatasetMarketplaceComputeUnits = 5
SubscribeDatasetMarketplaceComputeUnits = 5
ClaimMarketplacePaymentComputeUnits = 5

MaxTextSize = 8
MaxMemoSize = 256
Expand Down
6 changes: 3 additions & 3 deletions actions/create_asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (*CreateAsset) GetTypeID() uint8 {
}

func (*CreateAsset) StateKeys(actor codec.Address, actionID ids.ID) state.Keys {
nftID := nchain.GenerateID(actionID, 0)
nftID := nchain.GenerateIDWithIndex(actionID, 0)
return state.Keys{
string(storage.AssetKey(actionID)): state.Allocate | state.Write,
string(storage.AssetNFTKey(nftID)): state.Allocate | state.Write,
Expand All @@ -71,7 +71,7 @@ func (*CreateAsset) StateKeys(actor codec.Address, actionID ids.ID) state.Keys {
}

func (*CreateAsset) StateKeysMaxChunks() []uint16 {
return []uint16{storage.AssetChunks, storage.AssetNFTChunks, storage.BalanceChunks}
return []uint16{storage.AssetChunks, storage.AssetNFTChunks, storage.BalanceChunks, storage.BalanceChunks}

// return []uint16{storage.AssetChunks, storage.AssetNFTChunks, storage.AssetCollectionPageChunks, storage.AssetCollectionPageCountChunks, storage.BalanceChunks}
}
Expand Down Expand Up @@ -125,7 +125,7 @@ func (c *CreateAsset) Execute(
totalSupply := uint64(0)
if c.AssetType == nconsts.AssetDatasetTokenID {
// Mint the parent NFT for the dataset(fractionalized asset)
nftID := nchain.GenerateID(actionID, 0)
nftID := nchain.GenerateIDWithIndex(actionID, 0)
if err := storage.SetAssetNFT(ctx, mu, actionID, 0, nftID, c.URI, c.Metadata, actor); err != nil {
return nil, err
}
Expand Down
11 changes: 6 additions & 5 deletions actions/create_dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (c *CreateDataset) StateKeys(actor codec.Address, actionID ids.ID) state.Ke
if c.AssetID != ids.Empty {
assetID = c.AssetID
}
nftID := nchain.GenerateID(actionID, 0)
nftID := nchain.GenerateIDWithIndex(actionID, 0)
return state.Keys{
string(storage.AssetKey(assetID)): state.Allocate | state.Write,
string(storage.DatasetKey(assetID)): state.Allocate | state.Write,
Expand All @@ -64,7 +64,7 @@ func (c *CreateDataset) StateKeys(actor codec.Address, actionID ids.ID) state.Ke
}

func (*CreateDataset) StateKeysMaxChunks() []uint16 {
return []uint16{storage.AssetChunks, storage.DatasetChunks, storage.AssetNFTChunks, storage.BalanceChunks}
return []uint16{storage.AssetChunks, storage.DatasetChunks, storage.AssetNFTChunks, storage.BalanceChunks, storage.BalanceChunks}
}

func (c *CreateDataset) Execute(
Expand Down Expand Up @@ -118,13 +118,14 @@ func (c *CreateDataset) Execute(
assetID = actionID

// Mint the parent NFT for the dataset(fractionalized asset)
nftID := nchain.GenerateID(assetID, 0)
nftID := nchain.GenerateIDWithIndex(assetID, 0)
if err := storage.SetAssetNFT(ctx, mu, assetID, 0, nftID, c.Description, c.Description, actor); err != nil {
return nil, err
}

// Create a new asset for the dataset
if err := storage.SetAsset(ctx, mu, assetID, nconsts.AssetDatasetTokenID, c.Name, c.Name, 0, c.Description, c.Description, 1, 0, actor, actor, actor, actor, actor); err != nil {
symbol := nchain.CombineWithPrefix([]byte(""), c.Name, MaxTextSize)
if err := storage.SetAsset(ctx, mu, assetID, nconsts.AssetDatasetTokenID, c.Name, symbol, 0, c.Description, c.Description, 1, 0, actor, actor, actor, actor, actor); err != nil {
return nil, err
}

Expand All @@ -151,7 +152,7 @@ func (c *CreateDataset) Execute(
// revenueModelMetadataShare = 0
// revenueModelDataOwnerCut = 10 for community datasets, 100 for sole contributor datasets
// revenueModelMetadataOwnerCut = 0
if err := storage.SetDataset(ctx, mu, assetID, c.Name, c.Description, c.Categories, c.LicenseName, c.LicenseSymbol, c.LicenseURL, c.Metadata, c.IsCommunityDataset, false, ids.Empty, 0, uint8(revenueModelDataShare), 0, uint8(revenueModelDataOwnerCut), 0, actor); err != nil {
if err := storage.SetDataset(ctx, mu, assetID, c.Name, c.Description, c.Categories, c.LicenseName, c.LicenseSymbol, c.LicenseURL, c.Metadata, c.IsCommunityDataset, ids.Empty, ids.Empty, 0, uint8(revenueModelDataShare), 0, uint8(revenueModelDataOwnerCut), 0, actor); err != nil {
return nil, err
}

Expand Down
Loading

0 comments on commit d3f8940

Please sign in to comment.