difftreelog
Refactoring
in: master
3 files changed
doc/demo_milestone1-2.mddiffbeforeafterboth1# Manual Demos23Milestone 1 and 2 deliverables are marked by tag [release1](https://github.com/usetech-llc/nft_parachain/tree/release1)45## Milestone 167### A running chain89**Substrate based blockchain node to host NFT Tracking Module created with substrate-up scripts (currently substrate-up only support Substrate 1.0)**1011We have created chain based on both versions of substrate: 1 and 2. Nonetheless, the substrate 1 is not being widely used and we decided to obsolete this branch of code. The code still exists in [substrate1](https://github.com/usetech-llc/nft_parachain/tree/substrate1) branch, but this delivery document is based on substrate 2 version.1213The node can be run using docker container:14```15docker-compose up -d16```1718#### NFT Tracking Module19Open extrinsics tab of the [standard UI](https://polkadot.js.org/apps/#/extrinsics) and find "nft" in the module list. This proves that NFT module is deployed.2021#### Balances Module, Other modules included by default 22The [same UI](https://polkadot.js.org/apps/#/extrinsics) allows verification that all other modules are also installed as needed.2324#### Configuration of runtime as needed (e.g. for hot module updates)2526The `set_code` method worked out of the box and we did not need to perform any additional customizations to upload an updated WASM version of NFT palette.2728### NFT Tracking Module2930Before we continue, we need to do some preparations in the UI: Add NFT Data types so that the UI knows how to decode them. Go to [Settings-Developer Tab](https://polkadot.js.org/apps/#/settings/developer) and add following types to the JSON object in there:31```32{33 "NftItemType": {34 "Collection": "u64",35 "Owner": "AccountId",36 "Data": "Vec<u8>"37 },38 "CollectionType": {39 "Owner": "AccountId",40 "NextItemId": "u64",41 "CustomDataSize": "u32"42 },43 "Address": "AccountId",44 "LookupSource": "AccountId",45 "Weight": "u32"46}47```4849**Note:** In the future we will likely switch to substrate "2.0.0-alpha.7", in which case Weight type should be `u64`.5051#### CreateCollection5253Before running test, open [chain state tab](https://polkadot.js.org/apps/#/chainstate) and read `nft`.`nextCollectionId` state variable, which shows how many collections were created so far. If you just started the chain, this should equal 0. 5455Open extrinsics tab of the [standard UI](https://polkadot.js.org/apps/#/extrinsics) and select ALICE account. Select `nft` module and `createCollection` method. Put 1 in `custom_data_sz` and run the transaction. After it is finalized, read the `nft`.`nextCollectionId` state variable. It will be equal 1.5657Also, read the state variable `nft`.`collection` with ID = 1 (Because everything in NFT Palette is numbered from 1, not from 0). You will see something like this:5859```60nft.collection: CollectionType61{62 Owner: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,63 NextItemId: 1,64 CustomDataSize: 165}66```6768### ChangeCollectionOwner6970Open extrinsics tab of the [standard UI](https://polkadot.js.org/apps/#/extrinsics). Select `nft` module and `changeCollectionOwner` method. First, try to execute it to change owner to BOB for collection 1 from some different account than ALICE - FERDIE to see that it is not possible because ALICE owns collection 1 and FERDIE is not allowed to give it to BOB. Second, select ALICE as transaction signer and run it again. This time the collection owner changes, which will be reflected if you read the state variable `nft`.`collection` with ID = 1:7172```73nft.collection: CollectionType74{75 Owner: 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty,76 NextItemId: 1,77 CustomDataSize: 178}79```8081Change the ownership back to ALICE to continue with this demo.8283### DestroyCollection8485**Note: Before destroying collection, you can see Milestone 2 deliverables to avoid creating collection again**8687Run `nft`.`destroyCollection` with collection ID = 1, and then read collection from state. The returned fields will have default values, which indicates that collection does not exsit anymore: 88```89nft.collection: CollectionType90{91 Owner: 5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM,92 NextItemId: 0,93 CustomDataSize: 094}95```9697### End user documentation9899See [application_development.md](application_development.md)100101### Docker images for running deliverables for acceptance102103**Substrate Node (in dev mode), Unit tests for NFT Module**104105See above, the unit tests are run before the node starts within the same image.106107### Acceptance instructions108109**using Polkadot UI from https://substrate-ui.parity.io/**110111This document, except we are using Substrate v2, so we can use the newer version of the UI: https://polkadot.js.org/apps/#112113## Milestone 2114### NFT Tracking Module115**Note:** If you have destroyed collection, create it again using `nft`.`createCollection` method.116117**Note 2:** The order of items in this section is different from the original spec to make the acceptance workflow more natural.118119If you did not destroy collection while looking at Milestone 1 deliverables, use collection ID 1 in the further examples, otherwise use collection ID = 2.120121#### CreateItem122Before creating an NFT item, let's read ALICE balance for your collection, which indicates how many NFT tokens ALICE owns in this collection. Read the chain state `nft`.`balance(<Collection ID>, ALICE)`, and it will be 0.123124Execute extrinsic `nft`.`createItem` from ALICE account. Set properties to `0x01`. Now if you read the chain state `nft`.`balance(<Collection ID>, ALICE)`, it will be equal to 1. Also, you can read chain state `nft`.`itemList(<Collection ID>, 1)`, and it will return data for the token 1:125126```127nft.itemList: NftItemType128{129 Collection: 1,130 Owner: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,131 Data: 0x01132}133```134135#### GetOwner136Reading the ownership is done by reading chainstate `nft`.`itemList(<Collection ID>, 1)`. One of the returned fields is Owner:137```138nft.itemList: NftItemType139{140 Collection: 1,141 Owner: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,142 Data: 0x01143}144```145146#### Transfer147Execute `nft`.`transfer` from ALICE address to transfer token 1 to BOB and check the ownership again:148```149nft.itemList: NftItemType150{151 Collection: 1,152 Owner: 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty,153 Data: 0x01154}155```156157Transfer the token 1 back to ALICE to enable further demo actions.158159#### BalanceOf160161Read the chain state `nft`.`balance` for ALICE address and see that she owns 1 token:162```163nft.balance: u641641165```166167#### AddCollectionAdmin168Execute `nft`.`AddCollectionAdmin` from ALICE account and let CHARLIE be an admin. Now you can see that CHARLIE can transfer ALICE's token from ALICE's account to EVE and back. Also, you can read admin list from chain state and see that it is not empty:169170```171nft.adminList: Vec<AccountId>172[173 5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y174]175```176177#### RemoveCollectionAdmin178Execute `nft`.`RemoveCollectionAdmin` from ALICE account to remove CHARLIE from admins. Now you can see that CHARLIE cannot transfer ALICE's tokens anymore. If you read the chan state `nft`.`adminList`, the response will be empty:179180```181nft.adminList: Vec<AccountId>182[]183```184185#### BurnItem186Execute `nft`.`burnItem` from ALICE account to burn token 1, and then read the chain state `nft`.`itemList(<Collection ID>, 1)`. This time the chain state returns default values in fields because token does not exist anymore. You can also check ALICE balance in chain state, now it is equal 0 again.187```188nft.itemList: NftItemType189{190 Collection: 0,191 Owner: 5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM,192 Data: 0x193}194```195196### Economic Model Specification197[Economic Model Specification](economic_model.md)198199### Contracts Module Specification200https://docs.google.com/document/d/1gDtYjPR9C1VZChxEA-xAdQWQyEvMg245XrZR_MpE3cg/edit?usp=sharing201202### Bonus goal203204**Basic demo - Cryptopunks representation on the Substrate Chain.**205206See this repo, it has running and playing instructions:207https://github.com/usetech-llc/substrapunkspallets/nft/src/lib.rsdiffbeforeafterboth--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -1,14 +1,9 @@
#![cfg_attr(not(feature = "std"), no_std)]
-use codec::{Decode, Encode};
-/// A FRAME pallet template with necessary imports
-
-/// Feel free to remove or edit this file as needed.
-/// If you change the name of this file, make sure to update its references in runtime/src/lib.rs
-/// If you remove this file, you can remove those references
-
/// For more guidance on Substrate FRAME, see the example pallet
/// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs
+
+use codec::{Decode, Encode};
use frame_support::{decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure};
use frame_system::{self as system, ensure_signed};
use sp_runtime::sp_std::prelude::Vec;
@@ -19,11 +14,37 @@
#[cfg(test)]
mod tests;
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+pub enum AccessMode {
+ Normal,
+ WhiteList,
+}
+impl Default for AccessMode { fn default() -> Self { Self::Normal } }
+
+#[derive(Encode, Decode, Debug, Eq, Clone, PartialEq)]
+pub enum CollectionMode {
+ Invalid,
+ NFT,
+ Fungible,
+ ReFungible,
+}
+impl Default for CollectionMode { fn default() -> Self { Self::Invalid } }
+
#[derive(Encode, Decode, Default, Clone, PartialEq)]
#[cfg_attr(feature = "std", derive(Debug))]
+pub struct Ownership<AccountId> {
+ pub owner: AccountId,
+ pub fraction: u128
+}
+
+#[derive(Encode, Decode, Default, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Debug))]
pub struct CollectionType<AccountId> {
pub owner: AccountId,
+ pub mode: CollectionMode,
+ pub access: AccessMode,
pub next_item_id: u64,
+ pub decimal_points: u32,
pub name: Vec<u16>, // 64 include null escape char
pub description: Vec<u16>, // 256 include null escape char
pub token_prefix: Vec<u8>, // 16 include null escape char
@@ -45,55 +66,61 @@
pub data: Vec<u8>,
}
-/// The pallet's configuration trait.
-pub trait Trait: system::Trait {
- // Add other types and constants required to configure this pallet.
+#[derive(Encode, Decode, Default, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Debug))]
+pub struct FungibleItemType<AccountId> {
+ pub collection: u64,
+ pub owner: Vec<AccountId>,
+ pub data: Vec<u64>,
+}
+
+#[derive(Encode, Decode, Default, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Debug))]
+pub struct ReFungibleItemType<AccountId> {
+ pub collection: u64,
+ pub owner: Vec<Ownership<AccountId>>,
+}
- /// The overarching event type.
+pub trait Trait: system::Trait {
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
-// This pallet's storage items.
decl_storage! {
- // It is important to update your storage name so that your pallet's
- // storage items are isolated from other pallets.
trait Store for Module<T: Trait> as Nft {
- /// Next available collection ID
- pub NextCollectionID get(fn next_collection_id): u64;
+ // Next available collection ID
+ NextCollectionID get(fn next_collection_id): u64;
pub Collection get(fn collection): map hasher(identity) u64 => CollectionType<T::AccountId>;
pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;
- /// Balance owner per collection map
- pub Balance get(fn balance_count): map hasher(blake2_128_concat) (u64, T::AccountId) => u64;
- pub ApprovedList get(fn approved): map hasher(blake2_128_concat) (u64, u64) => Vec<T::AccountId>;
+ // Balance owner per collection map
+ pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;
+ pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => Vec<T::AccountId>;
- pub ItemList get(fn item_id): map hasher(blake2_128_concat) (u64, u64) => NftItemType<T::AccountId>;
- pub ItemListIndex get(fn item_index): map hasher(blake2_128_concat) u64 => u64;
+ // Item collections
+ pub NftItemList get(fn nft_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;
+ pub FungibleItemList get(fn fungible_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;
+ pub ReFungibleItemList get(fn refungible_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;
+ ItemListIndex get(fn item_index): map hasher(blake2_128_concat) u64 => u64;
- pub AddressTokens get(fn address_tokens): map hasher(blake2_128_concat) (u64, T::AccountId) => Vec<u64>;
+ pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;
}
}
-// The pallet's events
decl_event!(
pub enum Event<T>
where
AccountId = <T as system::Trait>::AccountId,
{
- Created(u64, AccountId),
+ Created(u64, CollectionMode, AccountId),
ItemCreated(u64, u64),
ItemDestroyed(u64, u64),
}
);
-// The pallet's dispatchable functions.
decl_module! {
- /// The module declaration.
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
- // Initializing events
- // this is needed only if you are using events in your pallet
fn deposit_event() = default;
// Create collection of NFT with given parameters
@@ -105,12 +132,33 @@
collection_name: Vec<u16>,
collection_description: Vec<u16>,
token_prefix: Vec<u8>,
- custom_data_sz: u32) -> DispatchResult {
+ mode: CollectionMode,
+ decimal_points: u32,
+ custom_data_size: u32) -> DispatchResult {
// Anyone can create a collection
let who = ensure_signed(origin)?;
+ // check type
+ ensure!((mode == CollectionMode::Fungible || mode == CollectionMode::NFT || mode == CollectionMode::ReFungible),
+ "Collection mode must be Fungible, NFT or ReFungible");
+
+ // NFT checks
+ if mode == CollectionMode::NFT
+ {
+ ensure!(decimal_points == 0, "Collection in NFT mode must have zero in decimal_points parameter");
+ }
+
+ // Fungible checks
+ if mode == CollectionMode::Fungible
+ {
+ ensure!(custom_data_size == 0, "Collection in Fungible mode must have zero in custom_data_size parameter");
+ ensure!(decimal_points != 0, "Collection in Fungible mode must have not zero in decimal_points parameter");
+ }
+
// check params
+ ensure!(decimal_points < 100, "decimal_points parameter must be lower than 100");
+
let mut name = collection_name.to_vec();
name.push(0);
ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");
@@ -134,17 +182,20 @@
let new_collection = CollectionType {
owner: who.clone(),
name: name,
+ mode: mode.clone(),
+ access: AccessMode::Normal,
description: description,
+ decimal_points: decimal_points,
token_prefix: prefix,
next_item_id: next_id,
- custom_data_size: custom_data_sz,
+ custom_data_size: custom_data_size,
};
// Add new collection to map
<Collection<T>>::insert(next_id, new_collection);
// call event
- Self::deposit_event(RawEvent::Created(next_id, who.clone()));
+ Self::deposit_event(RawEvent::Created(next_id, mode.clone(), who.clone()));
Ok(())
}
@@ -153,10 +204,13 @@
pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {
let sender = ensure_signed(origin)?;
- ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");
+ Self::check_owner_permissions(collection_id, sender)?;
- let owner = <Collection<T>>::get(collection_id).owner;
- ensure!(sender == owner, "You do not own this collection");
+ <AddressTokens<T>>::remove_prefix(collection_id);
+ <ApprovedList<T>>::remove_prefix(collection_id);
+ <Balance<T>>::remove_prefix(collection_id);
+ <ItemListIndex>::remove(collection_id);
+ <AdminList<T>>::remove(collection_id);
<Collection<T>>::remove(collection_id);
Ok(())
@@ -166,11 +220,8 @@
pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {
let sender = ensure_signed(origin)?;
- ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");
-
+ Self::check_owner_permissions(collection_id, sender)?;
let mut target_collection = <Collection<T>>::get(collection_id);
- ensure!(sender == target_collection.owner, "You do not own this collection");
-
target_collection.owner = new_owner;
<Collection<T>>::insert(collection_id, target_collection);
@@ -181,23 +232,11 @@
pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {
let sender = ensure_signed(origin)?;
- ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");
+ Self::check_owner_or_admin_permissions(collection_id, sender)?;
+ let mut admin_arr: Vec<T::AccountId> = Vec::new();
- let target_collection = <Collection<T>>::get(collection_id);
- let is_owner = sender == target_collection.owner;
-
- let no_perm_mes = "You do not have permissions to modify this collection";
- let exists = <AdminList<T>>::contains_key(collection_id);
-
- if !is_owner
+ if <AdminList<T>>::contains_key(collection_id)
{
- ensure!(exists, no_perm_mes);
- ensure!(<AdminList<T>>::get(collection_id).contains(&sender), no_perm_mes);
- }
-
- let mut admin_arr: Vec<T::AccountId> = Vec::new();
- if exists
- {
admin_arr = <AdminList<T>>::get(collection_id);
ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");
}
@@ -212,21 +251,9 @@
pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {
let sender = ensure_signed(origin)?;
- ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");
-
- let target_collection = <Collection<T>>::get(collection_id);
- let is_owner = sender == target_collection.owner;
-
- let no_perm_mes = "You do not have permissions to modify this collection";
- let exists = <AdminList<T>>::contains_key(collection_id);
-
- if !is_owner
- {
- ensure!(exists, no_perm_mes);
- ensure!(<AdminList<T>>::get(collection_id).contains(&sender), no_perm_mes);
- }
+ Self::check_owner_or_admin_permissions(collection_id, sender)?;
- if exists
+ if <AdminList<T>>::contains_key(collection_id)
{
let mut admin_arr = <AdminList<T>>::get(collection_id);
admin_arr.retain(|i| *i != account_id);
@@ -237,46 +264,36 @@
}
#[weight = 0]
- pub fn create_item(origin, collection_id: u64, properties: Vec<u8>) -> DispatchResult {
+ pub fn create_item(origin, collection_id: u64, properties: Vec<u8>, owner: T::AccountId) -> DispatchResult {
let sender = ensure_signed(origin)?;
- ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");
+ // check size
let target_collection = <Collection<T>>::get(collection_id);
ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");
- let is_owner = sender == target_collection.owner;
- let no_perm_mes = "You do not have permissions to modify this collection";
- let exists = <AdminList<T>>::contains_key(collection_id);
+ Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;
- if !is_owner
- {
- ensure!(exists, no_perm_mes);
- ensure!(<AdminList<T>>::get(collection_id).contains(&sender), no_perm_mes);
- }
+ let new_balance = <Balance<T>>::get(collection_id, sender.clone()) + 1;
+ <Balance<T>>::insert(collection_id, sender.clone(), new_balance);
- let new_balance = <Balance<T>>::get((collection_id, sender.clone())) + 1;
- <Balance<T>>::insert((collection_id, sender.clone()), new_balance);
+ // TODO: implement other modes
+ ensure!(target_collection.mode == CollectionMode::NFT, "Collection type not implemented");
- // Create new item
- let new_item = NftItemType {
- collection: collection_id,
- owner: sender,
- data: properties,
- };
-
-
- let current_index = <ItemListIndex>::get(collection_id)
- .checked_add(1)
- .expect("Item list index id error");
+ if target_collection.mode == CollectionMode::NFT
+ {
+ // Create nft item
+ let item = NftItemType {
+ collection: collection_id,
+ owner: owner,
+ data: properties,
+ };
- Self::add_token_index(collection_id, current_index, new_item.owner.clone())?;
-
- <ItemListIndex>::insert(collection_id, current_index);
- <ItemList<T>>::insert((collection_id, current_index), new_item);
+ Self::add_nft_item(item)?;
+ }
// call event
- Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index));
+ Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));
Ok(())
}
@@ -285,33 +302,9 @@
pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {
let sender = ensure_signed(origin)?;
- ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");
-
- let target_collection = <Collection<T>>::get(collection_id);
- let is_owner = sender == target_collection.owner;
-
- ensure!(<ItemList<T>>::contains_key((collection_id, item_id)), "Item does not exists");
- let item = <ItemList<T>>::get((collection_id, item_id));
-
- if !is_owner
- {
- // check if item owner
- if item.owner != sender
- {
- let no_perm_mes = "You do not have permissions to modify this collection";
-
- ensure!(<AdminList<T>>::contains_key(collection_id), no_perm_mes);
- ensure!(<AdminList<T>>::get(collection_id).contains(&sender), no_perm_mes);
- }
- }
- <ItemList<T>>::remove((collection_id, item_id));
+ Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;
+ Self::burn_nft_item(collection_id, item_id)?;
- Self::remove_token_index(collection_id, item_id, item.owner.clone())?;
-
- // update balance
- let new_balance = <Balance<T>>::get((collection_id, item.owner.clone())) - 1;
- <Balance<T>>::insert((collection_id, item.owner.clone()), new_balance);
-
// call event
Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));
@@ -319,49 +312,21 @@
}
#[weight = 0]
- pub fn transfer(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {
+ pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {
let sender = ensure_signed(origin)?;
- ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");
+ Self::check_owner_or_admin_permissions(collection_id, sender)?;
let target_collection = <Collection<T>>::get(collection_id);
- let is_owner = sender == target_collection.owner;
- ensure!(<ItemList<T>>::contains_key((collection_id, item_id)), "Item does not exists");
- let mut item = <ItemList<T>>::get((collection_id, item_id));
+ // TODO: implement other modes
+ ensure!(target_collection.mode == CollectionMode::NFT, "Collection type not implemented");
- if !is_owner
+ if target_collection.mode == CollectionMode::NFT
{
- // check if item owner
- if item.owner != sender
- {
- let no_perm_mes = "You do not have permissions to modify this collection";
-
- ensure!(<AdminList<T>>::contains_key(collection_id), no_perm_mes);
- ensure!(<AdminList<T>>::get(collection_id).contains(&sender), no_perm_mes);
- }
+ Self::transfer_nft(collection_id, item_id, recipient)?;
}
- <ItemList<T>>::remove((collection_id, item_id));
- // update balance
- let balance_old_owner = <Balance<T>>::get((collection_id, item.owner.clone())) - 1;
- <Balance<T>>::insert((collection_id, item.owner.clone()), balance_old_owner);
-
- let balance_new_owner = <Balance<T>>::get((collection_id, new_owner.clone())) + 1;
- <Balance<T>>::insert((collection_id, new_owner.clone()), balance_new_owner);
-
- // change owner
- let old_owner = item.owner.clone();
- item.owner = new_owner.clone();
- <ItemList<T>>::insert((collection_id, item_id), item);
-
- // update index collection
- Self::move_token_index(collection_id, item_id, old_owner, new_owner.clone())?;
-
- // reset approved list
- let itm: Vec<T::AccountId> = Vec::new();
- <ApprovedList<T>>::insert((collection_id, item_id), itm);
-
Ok(())
}
@@ -369,30 +334,17 @@
pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {
let sender = ensure_signed(origin)?;
- ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");
- let target_collection = <Collection<T>>::get(collection_id);
- let is_owner = sender == target_collection.owner;
-
- ensure!(<ItemList<T>>::contains_key((collection_id, item_id)), "Item does not exists");
- let item = <ItemList<T>>::get((collection_id, item_id));
-
- if !is_owner
+ let item_owner = Self::is_item_owner(sender.clone(), collection_id, item_id);
+ if !item_owner
{
- // check if item owner
- if item.owner != sender
- {
- let no_perm_mes = "You do not have permissions to modify this collection";
-
- ensure!(<AdminList<T>>::contains_key(collection_id), no_perm_mes);
- ensure!(<AdminList<T>>::get(collection_id).contains(&sender), no_perm_mes);
- }
+ Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;
}
- let list_exists = <ApprovedList<T>>::contains_key((collection_id, item_id));
+ let list_exists = <ApprovedList<T>>::contains_key(collection_id, item_id);
if list_exists {
- let mut list = <ApprovedList<T>>::get((collection_id, item_id));
+ let mut list = <ApprovedList<T>>::get(collection_id, item_id);
let item_contains = list.contains(&approved.clone());
if !item_contains {
@@ -402,36 +354,53 @@
let mut itm = Vec::new();
itm.push(approved.clone());
- <ApprovedList<T>>::insert((collection_id, item_id), itm);
+ <ApprovedList<T>>::insert(collection_id, item_id, itm);
}
Ok(())
}
#[weight = 0]
- pub fn transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {
+ pub fn transfer_from(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, ) -> DispatchResult {
+
+ let mut approved: bool = false;
+ let sender = ensure_signed(origin)?;
+ let approved_list_exists = <ApprovedList<T>>::contains_key(collection_id, item_id);
+ if approved_list_exists
+ {
+ let list_itm = <ApprovedList<T>>::get(collection_id, item_id);
+ approved = list_itm.contains(&recipient.clone());
+ }
- let no_perm_mes = "You do not have permissions to modify this collection";
- ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);
- let list_itm = <ApprovedList<T>>::get((collection_id, item_id));
- ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);
+ if !approved
+ {
+ Self::check_owner_or_admin_permissions(collection_id, sender)?;
+ }
+
+ let target_collection = <Collection<T>>::get(collection_id);
- Self::transfer(origin, collection_id, item_id, new_owner)?;
+ // TODO: implement other modes
+ ensure!(target_collection.mode == CollectionMode::NFT, "Collection type not implemented");
+ if target_collection.mode == CollectionMode::NFT
+ {
+ Self::transfer_nft(collection_id, item_id, recipient)?;
+ }
+
Ok(())
}
#[weight = 0]
pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {
- let no_perm_mes = "You do not have permissions to modify this collection";
- ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);
- let list_itm = <ApprovedList<T>>::get((collection_id, item_id));
- ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);
+ // let no_perm_mes = "You do not have permissions to modify this collection";
+ // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);
+ // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));
+ // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);
- // on_nft_received call
+ // // on_nft_received call
- Self::transfer(origin, collection_id, item_id, new_owner)?;
+ // Self::transfer(origin, collection_id, item_id, new_owner)?;
Ok(())
}
@@ -439,21 +408,137 @@
}
impl<T: Trait> Module<T> {
+
+ fn collection_exists(collection_id: u64) -> DispatchResult{
+ ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");
+ Ok(())
+ }
+
+ fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {
+
+ Self::collection_exists(collection_id)?;
+
+ let target_collection = <Collection<T>>::get(collection_id);
+ ensure!(subject == target_collection.owner, "You do not own this collection");
+
+ Ok(())
+ }
+
+ fn check_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {
+
+ Self::collection_exists(collection_id)?;
+
+ let target_collection = <Collection<T>>::get(collection_id);
+ let is_owner = subject == target_collection.owner;
+
+ let no_perm_mes = "You do not have permissions to modify this collection";
+ let exists = <AdminList<T>>::contains_key(collection_id);
+
+ if !is_owner
+ {
+ ensure!(exists, no_perm_mes);
+ ensure!(<AdminList<T>>::get(collection_id).contains(&subject), no_perm_mes);
+ }
+ Ok(())
+ }
+
+ fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool{
+
+ let mut result = false;
+ let target_collection = <Collection<T>>::get(collection_id);
+
+ if target_collection.mode == CollectionMode::NFT
+ {
+ let item = <NftItemList<T>>::get(collection_id, item_id);
+ result = item.owner == subject;
+ }
+
+ if target_collection.mode == CollectionMode::Fungible
+ {
+ let item = <FungibleItemList<T>>::get(collection_id, item_id);
+ result = item.owner.contains(&subject);
+ }
+
+ if target_collection.mode == CollectionMode::ReFungible
+ {
+ let item = <ReFungibleItemList<T>>::get(collection_id, item_id);
+ result = item.owner.iter().any(|i| i.owner == subject);
+ }
+
+ result
+ }
+
+ fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {
+
+ let current_index = <ItemListIndex>::get(item.collection)
+ .checked_add(1)
+ .expect("Item list index id error");
+
+ Self::add_token_index(item.collection, current_index, item.owner.clone())?;
+
+ <ItemListIndex>::insert(item.collection, current_index);
+ <NftItemList<T>>::insert(item.collection, current_index, item);
+
+ Ok(())
+ }
+
+ fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {
+
+ let item = <NftItemList<T>>::get(collection_id, item_id);
+ Self::remove_token_index(collection_id, item_id, item.owner.clone())?;
+
+ // update balance
+ let new_balance = <Balance<T>>::get(collection_id, item.owner.clone()).checked_sub(1).unwrap();
+ <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);
+ <NftItemList<T>>::remove(collection_id, item_id);
+
+ Ok(())
+ }
+
+ fn transfer_nft(collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {
+
+ let mut item = <NftItemList<T>>::get(collection_id, item_id);
+
+ // update balance
+ let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone()).checked_sub(1).unwrap();
+ <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);
+
+ let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone()).checked_add(1).unwrap();
+ <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);
+
+ // change owner
+ let old_owner = item.owner.clone();
+ item.owner = new_owner.clone();
+ <NftItemList<T>>::insert(collection_id, item_id, item);
+
+ // update index collection
+ Self::move_token_index(collection_id, item_id, old_owner, new_owner.clone())?;
+
+ // reset approved list
+ let itm: Vec<T::AccountId> = Vec::new();
+ <ApprovedList<T>>::insert(collection_id, item_id, itm);
+
+ // remove item
+ <NftItemList<T>>::remove(collection_id, item_id);
+
+ Ok(())
+ }
+
fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {
- let list_exists = <AddressTokens<T>>::contains_key((collection_id, owner.clone()));
+ let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());
if list_exists {
- let mut list = <AddressTokens<T>>::get((collection_id, owner.clone()));
+ let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());
let item_contains = list.contains(&item_index.clone());
if !item_contains {
list.push(item_index.clone());
}
- <AddressTokens<T>>::insert((collection_id, owner.clone()), list);
+ <AddressTokens<T>>::insert(collection_id, owner.clone(), list);
} else {
let mut itm = Vec::new();
itm.push(item_index.clone());
- <AddressTokens<T>>::insert((collection_id, owner), itm);
+ <AddressTokens<T>>::insert(collection_id, owner, itm);
}
Ok(())
@@ -464,14 +549,14 @@
item_index: u64,
owner: T::AccountId,
) -> DispatchResult {
- let list_exists = <AddressTokens<T>>::contains_key((collection_id, owner.clone()));
+ let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());
if list_exists {
- let mut list = <AddressTokens<T>>::get((collection_id, owner.clone()));
+ let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());
let item_contains = list.contains(&item_index.clone());
if item_contains {
list.retain(|&item| item != item_index);
- <AddressTokens<T>>::insert((collection_id, owner), list);
+ <AddressTokens<T>>::insert(collection_id, owner, list);
}
}
pallets/nft/src/mock.rsdiffbeforeafterboth--- a/pallets/nft/src/mock.rs
+++ b/pallets/nft/src/mock.rs
@@ -1,14 +1,20 @@
// Creating mock runtime here
use crate::{Module, Trait};
-use frame_support::{impl_outer_origin, parameter_types, weights::Weight};
use frame_system as system;
use sp_core::H256;
use sp_runtime::{
testing::Header,
- traits::{BlakeTwo256, IdentityLookup},
+ traits::{BlakeTwo256, IdentityLookup, Saturating},
Perbill,
};
+use frame_support::{
+ parameter_types, impl_outer_origin,
+ weights::{
+ constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight},
+ Weight,
+ },
+};
impl_outer_origin! {
pub enum Origin for Test {}
@@ -24,7 +30,10 @@
pub const MaximumBlockWeight: Weight = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
+ pub MaximumExtrinsicWeight: Weight = AvailableBlockRatio::get()
+ .saturating_sub(Perbill::from_percent(10)) * MaximumBlockWeight::get();
}
+
impl system::Trait for Test {
type Origin = Origin;
type Call = ();
@@ -40,6 +49,11 @@
type MaximumBlockWeight = MaximumBlockWeight;
type MaximumBlockLength = MaximumBlockLength;
type AvailableBlockRatio = AvailableBlockRatio;
+ type BaseCallFilter = ();
+ type DbWeight = RocksDbWeight;
+ type BlockExecutionWeight = BlockExecutionWeight;
+ type ExtrinsicBaseWeight = ExtrinsicBaseWeight;
+ type MaximumExtrinsicWeight = MaximumExtrinsicWeight;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();