Skip to content

Commit cb3cb2a

Browse files
committed
Refactor UniqueAssets Trait
Breaking change. Use generic traits and associated types correctly in order to create a usable interface.
1 parent 2355876 commit cb3cb2a

File tree

5 files changed

+50
-114
lines changed

5 files changed

+50
-114
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
edition = '2018'
33
name = 'pallet-commodities'
4-
version = '1.0.0-rc6'
4+
version = '1.0.0-rc7'
55
authors = ['Dan Forbes <dan@parity.io>']
66
license = 'Unlicense'
77
description = 'A unique asset (NFT) interface and a Substrate FRAME implementation optimized for commodity assets.'

README.md

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,24 @@
22

33
# Commodities FRAME Pallet: NFTs for Substrate
44

5-
This is a [FRAME](https://substrate.dev/docs/en/knowledgebase/runtime/frame) pallet that defines and implements a
6-
[non-fungible token (NFT)](https://en.wikipedia.org/wiki/Non-fungible_token) interface as well as an interface for
7-
managing a set of such assets, including asset ownership, creation, destruction and transfer.
5+
This is a [FRAME](https://substrate.dev/docs/en/knowledgebase/runtime/frame) pallet that defines and implements an
6+
interface for managing a set of [non-fungible tokens (NFTs)](https://en.wikipedia.org/wiki/Non-fungible_token). Assets
7+
have an owner and can be created, destroyed and transferred.
88

99
## Interface
1010

11-
This package defines [two public traits](src/nft.rs) (Rust interfaces) for working with NFTs: the `NFT` trait and the
12-
`UniqueAssets` trait.
13-
14-
## `NFT` Trait
15-
16-
The `NFT` trait uses two types to define a unique asset:
17-
18-
- `ID`: a URI for the asset
19-
- `Info`: a set of attributes that uniquely describe the asset
20-
21-
Assets with equivalent attributes (as defined by the `Info` type) **must** have an equal `ID` and assets with different
22-
`ID`s **must not** have equivalent attributes.
11+
This package defines [a public trait](src/nft.rs) (Rust interface) for working with NFTs: the `UniqueAssets` trait.
2312

2413
## `UniqueAssets` Trait
2514

26-
This trait is generic with respect to a type that implements the `NFT` trait; it defines the type abstractions and
27-
public functions needed to manage a set of unique assets.
15+
This trait is generic with respect to a type that is used to identify asset owners - the `AccountId` type. Assets with
16+
equivalent attributes (as defined by the `AssetInfo` type) **must** have equal `AssetId`s and assets with different
17+
`AssetId`s **must not** have equivalent attributes.
2818

2919
### Types
3020

31-
- `AccountId`: the type used to identify asset owners
21+
- `AssetId`: a URI for an asset
22+
- `AssetInfo`: a set of attributes that uniquely describes an asset
3223
- `AssetLimit`: the maximum number of assets, expressed as an unsigned 128-bit integer, that may exist in this set at
3324
once
3425
- `UserAssetLimit`: the maximum number of assets, expressed as an unsigned 64-bit integer, that any single account may
@@ -40,15 +31,15 @@ public functions needed to manage a set of unique assets.
4031
- `burned() -> u128`: returns the total number of assets from this set that have been burned
4132
- `total_for_account(AccountId) -> u64`: returns the total number of asset from this set that are owned by a given
4233
account
43-
- `assets_for_account(AccountId) -> Vec<NFT>`: returns the list of assets from this set that are owned by a given
44-
account
45-
- `owner_of(NFT::Id) -> AccountId`: returns the ID of the account that owns the given asset from this set
46-
- `mint(AccountId, NFT::Info) -> Result<AssetID, DispatchError>`: use the given attributes to create a new unique asset
34+
- `assets_for_account(AccountId) -> Vec<(AssetId, AssetInfo)>`: returns the list of assets from this set that are owned
35+
by a given account
36+
- `owner_of(AssetId) -> AccountId`: returns the ID of the account that owns the given asset from this set
37+
- `mint(AccountId, AssetInfo) -> Result<AssetID, DispatchError>`: use the given attributes to create a new unique asset
4738
that belongs to this set and assign ownership of it to the given account
4839
- Failure cases: asset duplication, asset limit reached for set, asset limit for this set reached for account
49-
- `burn(NFT::Id) -> DispatchResult`: destroy the given asset
40+
- `burn(AssetId) -> DispatchResult`: destroy the given asset
5041
- Failure cases: asset doesn't exist
51-
- `transfer(AccountId, NFT::Id) -> DispatchResult`: transfer ownership of the given asset from this set from its current
42+
- `transfer(AccountId, AssetId) -> DispatchResult`: transfer ownership of the given asset from this set from its current
5243
owner to a given target account
5344
- Failure cases: asset doesn't exist, asset limit for this set reached for target account
5445

src/lib.rs

Lines changed: 16 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,18 @@
3636
3737
#![cfg_attr(not(feature = "std"), no_std)]
3838

39-
use codec::{Decode, Encode, FullCodec};
39+
use codec::FullCodec;
4040
use frame_support::{
4141
decl_error, decl_event, decl_module, decl_storage, dispatch, ensure,
4242
traits::{EnsureOrigin, Get},
4343
Hashable,
4444
};
4545
use frame_system::ensure_signed;
46-
use sp_runtime::{
47-
traits::{Hash, Member},
48-
RuntimeDebug,
49-
};
50-
use sp_std::{
51-
cmp::{Eq, Ordering},
52-
fmt::Debug,
53-
vec::Vec,
54-
};
46+
use sp_runtime::traits::{Hash, Member};
47+
use sp_std::{cmp::Eq, fmt::Debug, vec::Vec};
5548

5649
pub mod nft;
57-
pub use crate::nft::{UniqueAssets, NFT};
50+
pub use crate::nft::UniqueAssets;
5851

5952
#[cfg(test)]
6053
mod mock;
@@ -66,7 +59,7 @@ pub trait Trait<I = DefaultInstance>: frame_system::Trait {
6659
/// The dispatch origin that is able to mint new instances of this type of commodity.
6760
type CommodityAdmin: EnsureOrigin<Self::Origin>;
6861
/// The data type that is used to describe this type of commodity.
69-
type CommodityInfo: Hashable + Member + Debug + Default + FullCodec;
62+
type CommodityInfo: Hashable + Member + Debug + Default + FullCodec + Ord;
7063
/// The maximum number of this type of commodity that may exist (minted - burned).
7164
type CommodityLimit: Get<u128>;
7265
/// The maximum number of this type of commodity that any single account may own.
@@ -77,39 +70,8 @@ pub trait Trait<I = DefaultInstance>: frame_system::Trait {
7770
/// The runtime system's hashing algorithm is used to uniquely identify commodities.
7871
pub type CommodityId<T> = <T as frame_system::Trait>::Hash;
7972

80-
/// A generic definition of an NFT that will be used by this pallet.
81-
#[derive(Encode, Decode, Clone, Eq, RuntimeDebug)]
82-
pub struct Commodity<Hash, CommodityInfo> {
83-
pub id: Hash,
84-
pub commodity: CommodityInfo,
85-
}
86-
87-
/// An alias for this pallet's NFT implementation.
88-
pub type CommodityFor<T, I> = Commodity<CommodityId<T>, <T as Trait<I>>::CommodityInfo>;
89-
90-
impl<CommodityId, CommodityInfo> NFT for Commodity<CommodityId, CommodityInfo> {
91-
type Id = CommodityId;
92-
type Info = CommodityInfo;
93-
}
94-
95-
// Needed to maintain a sorted list.
96-
impl<CommodityId: Ord, CommodityInfo: Eq> Ord for Commodity<CommodityId, CommodityInfo> {
97-
fn cmp(&self, other: &Self) -> Ordering {
98-
self.id.cmp(&other.id)
99-
}
100-
}
101-
102-
impl<CommodityId: Ord, CommodityInfo> PartialOrd for Commodity<CommodityId, CommodityInfo> {
103-
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
104-
Some(self.id.cmp(&other.id))
105-
}
106-
}
107-
108-
impl<CommodityId: Eq, CommodityInfo> PartialEq for Commodity<CommodityId, CommodityInfo> {
109-
fn eq(&self, other: &Self) -> bool {
110-
self.id == other.id
111-
}
112-
}
73+
/// Associates a commodity with its ID.
74+
pub type Commodity<T, I> = (CommodityId<T>, <T as Trait<I>>::CommodityInfo);
11375

11476
decl_storage! {
11577
trait Store for Module<T: Trait<I>, I: Instance = DefaultInstance> as Commodity {
@@ -120,7 +82,7 @@ decl_storage! {
12082
/// The total number of this type of commodity owned by an account.
12183
TotalForAccount get(fn total_for_account): map hasher(blake2_128_concat) T::AccountId => u64 = 0;
12284
/// A mapping from an account to a list of all of the commodities of this type that are owned by it.
123-
CommoditiesForAccount get(fn commodities_for_account): map hasher(blake2_128_concat) T::AccountId => Vec<CommodityFor<T, I>>;
85+
CommoditiesForAccount get(fn commodities_for_account): map hasher(blake2_128_concat) T::AccountId => Vec<Commodity<T, I>>;
12486
/// A mapping from a commodity ID to the account that owns it.
12587
AccountForCommodity get(fn account_for_commodity): map hasher(identity) CommodityId<T> => T::AccountId;
12688
}
@@ -130,7 +92,7 @@ decl_storage! {
13092
build(|config: &GenesisConfig<T, I>| {
13193
for (who, assets) in config.balances.iter() {
13294
for asset in assets {
133-
match <Module::<T, I> as UniqueAssets::<Commodity<CommodityId<T>, <T as Trait<I>>::CommodityInfo>>>::mint(who, asset.clone()) {
95+
match <Module::<T, I> as UniqueAssets::<T::AccountId>>::mint(who, asset.clone()) {
13496
Ok(_) => {}
13597
Err(err) => { panic!(err) },
13698
}
@@ -237,10 +199,9 @@ decl_module! {
237199
}
238200
}
239201

240-
impl<T: Trait<I>, I: Instance>
241-
UniqueAssets<Commodity<CommodityId<T>, <T as Trait<I>>::CommodityInfo>> for Module<T, I>
242-
{
243-
type AccountId = <T as frame_system::Trait>::AccountId;
202+
impl<T: Trait<I>, I: Instance> UniqueAssets<T::AccountId> for Module<T, I> {
203+
type AssetId = CommodityId<T>;
204+
type AssetInfo = T::CommodityInfo;
244205
type AssetLimit = T::CommodityLimit;
245206
type UserAssetLimit = T::UserCommodityLimit;
246207

@@ -256,9 +217,7 @@ impl<T: Trait<I>, I: Instance>
256217
Self::total_for_account(account)
257218
}
258219

259-
fn assets_for_account(
260-
account: &T::AccountId,
261-
) -> Vec<Commodity<CommodityId<T>, <T as Trait<I>>::CommodityInfo>> {
220+
fn assets_for_account(account: &T::AccountId) -> Vec<Commodity<T, I>> {
262221
Self::commodities_for_account(account)
263222
}
264223

@@ -287,10 +246,7 @@ impl<T: Trait<I>, I: Instance>
287246
Error::<T, I>::TooManyCommodities
288247
);
289248

290-
let new_commodity = Commodity {
291-
id: commodity_id,
292-
commodity: commodity_info,
293-
};
249+
let new_commodity = (commodity_id, commodity_info);
294250

295251
Total::<I>::mutate(|total| *total += 1);
296252
TotalForAccount::<T, I>::mutate(owner_account, |total| *total += 1);
@@ -312,10 +268,7 @@ impl<T: Trait<I>, I: Instance>
312268
Error::<T, I>::NonexistentCommodity
313269
);
314270

315-
let burn_commodity = Commodity::<CommodityId<T>, <T as Trait<I>>::CommodityInfo> {
316-
id: *commodity_id,
317-
commodity: <T as Trait<I>>::CommodityInfo::default(),
318-
};
271+
let burn_commodity = (*commodity_id, <T as Trait<I>>::CommodityInfo::default());
319272

320273
Total::<I>::mutate(|total| *total -= 1);
321274
Burned::<I>::mutate(|total| *total += 1);
@@ -346,10 +299,7 @@ impl<T: Trait<I>, I: Instance>
346299
Error::<T, I>::TooManyCommoditiesForAccount
347300
);
348301

349-
let xfer_commodity = Commodity::<CommodityId<T>, <T as Trait<I>>::CommodityInfo> {
350-
id: *commodity_id,
351-
commodity: <T as Trait<I>>::CommodityInfo::default(),
352-
};
302+
let xfer_commodity = (*commodity_id, <T as Trait<I>>::CommodityInfo::default());
353303

354304
TotalForAccount::<T, I>::mutate(&owner, |total| *total -= 1);
355305
TotalForAccount::<T, I>::mutate(dest_account, |total| *total += 1);

src/nft.rs

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,14 @@ use frame_support::{
1717
};
1818
use sp_std::vec::Vec;
1919

20-
/// A unique asset; assets with equivalent attributes (as defined by the Info type) **must** have an
21-
/// equal ID and assets with different IDs **must not** have equivalent attributes.
22-
pub trait NFT {
20+
/// An interface over a set of unique assets.
21+
/// Assets with equivalent attributes (as defined by the AssetInfo type) **must** have an equal ID
22+
/// and assets with different IDs **must not** have equivalent attributes.
23+
pub trait UniqueAssets<AccountId> {
2324
/// The type used to identify unique assets.
24-
type Id;
25+
type AssetId;
2526
/// The attributes that distinguish unique assets.
26-
type Info;
27-
}
28-
29-
/// An interface over a set of unique assets.
30-
pub trait UniqueAssets<Asset: NFT> {
31-
/// The type used to identify asset owners.
32-
type AccountId;
27+
type AssetInfo;
3328
/// The maximum number of this type of asset that may exist (minted - burned).
3429
type AssetLimit: Get<u128>;
3530
/// The maximum number of this type of asset that any single account may own.
@@ -40,28 +35,28 @@ pub trait UniqueAssets<Asset: NFT> {
4035
/// The total number of this type of asset that has been burned (may overflow).
4136
fn burned() -> u128;
4237
/// The total number of this type of asset owned by an account.
43-
fn total_for_account(account: &Self::AccountId) -> u64;
38+
fn total_for_account(account: &AccountId) -> u64;
4439
/// The set of unique assets owned by an account.
45-
fn assets_for_account(account: &Self::AccountId) -> Vec<Asset>;
40+
fn assets_for_account(account: &AccountId) -> Vec<(Self::AssetId, Self::AssetInfo)>;
4641
/// The ID of the account that owns an asset.
47-
fn owner_of(asset_id: &Asset::Id) -> Self::AccountId;
42+
fn owner_of(asset_id: &Self::AssetId) -> AccountId;
4843

4944
/// Use the provided asset info to create a new unique asset for the specified user.
5045
/// This method **must** return an error in the following cases:
5146
/// - The asset, as identified by the asset info, already exists.
5247
/// - The specified owner account has already reached the user asset limit.
5348
/// - The total asset limit has already been reached.
5449
fn mint(
55-
owner_account: &Self::AccountId,
56-
asset_info: Asset::Info,
57-
) -> Result<Asset::Id, DispatchError>;
50+
owner_account: &AccountId,
51+
asset_info: Self::AssetInfo,
52+
) -> Result<Self::AssetId, DispatchError>;
5853
/// Destroy an asset.
5954
/// This method **must** return an error in the following case:
6055
/// - The asset with the specified ID does not exist.
61-
fn burn(asset_id: &Asset::Id) -> DispatchResult;
56+
fn burn(asset_id: &Self::AssetId) -> DispatchResult;
6257
/// Transfer ownership of an asset to another account.
6358
/// This method **must** return an error in the following cases:
6459
/// - The asset with the specified ID does not exist.
6560
/// - The destination account has already reached the user asset limit.
66-
fn transfer(dest_account: &Self::AccountId, asset_id: &Asset::Id) -> DispatchResult;
61+
fn transfer(dest_account: &AccountId, asset_id: &Self::AssetId) -> DispatchResult;
6762
}

src/tests.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ fn mint() {
2929
let commodities_for_account = SUT::commodities_for_account::<u64>(1);
3030
assert_eq!(commodities_for_account.len(), 1);
3131
assert_eq!(
32-
commodities_for_account[0].id,
32+
commodities_for_account[0].0,
3333
Vec::<u8>::default().blake2_256().into()
3434
);
35-
assert_eq!(commodities_for_account[0].commodity, Vec::<u8>::default());
35+
assert_eq!(commodities_for_account[0].1, Vec::<u8>::default());
3636
assert_eq!(
3737
SUT::account_for_commodity::<H256>(Vec::<u8>::default().blake2_256().into()),
3838
1
@@ -151,10 +151,10 @@ fn transfer() {
151151
let commodities_for_account = SUT::commodities_for_account::<u64>(2);
152152
assert_eq!(commodities_for_account.len(), 1);
153153
assert_eq!(
154-
commodities_for_account[0].id,
154+
commodities_for_account[0].0,
155155
Vec::<u8>::default().blake2_256().into()
156156
);
157-
assert_eq!(commodities_for_account[0].commodity, Vec::<u8>::default());
157+
assert_eq!(commodities_for_account[0].1, Vec::<u8>::default());
158158
assert_eq!(
159159
SUT::account_for_commodity::<H256>(Vec::<u8>::default().blake2_256().into()),
160160
2

0 commit comments

Comments
 (0)