Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add pallet-api/nonfungibles #432

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

chungquantin
Copy link
Collaborator

@chungquantin chungquantin commented Jan 16, 2025

The non-fungibles pallet offers a streamlined interface for interacting with non-fungible assets. The goal is to provide a simplified, consistent API that adheres to standards in the smart contract space. Built on top of the optimized version of the pallet-nfts (See #387).

Index Name Type
0 BalanceOf Read
1 OwnerOf Read
2 Allowance Read
3 transfer dispachable
4 approve dispatchable
5 TotalSupply Read
6 GetAttribute Read
7 mint dispatchable
8 burn dispatchable
9 Collection Read
10 NextCollectionId Read
11 ItemMetadata Read
12 create dispatchable
13 destroy dispatchable
14 set_attribute dispatchable
15 clear_attribute dispatchable
16 set_metadata dispatchable
17 clear_metadata dispatchable
18 approve_item_attributes dispatchable
19 cancel_item_attributes_approval dispatchable
20 set_max_supply dispatchable
21 clear_all_transfer_approvals dispatchable
22 clear_collection_approvals dispatchable

[sc-1242]

@codecov-commenter
Copy link

codecov-commenter commented Jan 16, 2025

Codecov Report

Attention: Patch coverage is 92.55385% with 121 lines in your changes missing coverage. Please review.

Project coverage is 73.93%. Comparing base (dd22ac2) to head (5316da1).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pallets/api/src/nonfungibles/weights.rs 41.55% 90 Missing ⚠️
pallets/api/src/mock.rs 8.69% 21 Missing ⚠️
pallets/api/src/nonfungibles/mod.rs 97.05% 4 Missing and 1 partial ⚠️
runtime/devnet/src/config/api/mod.rs 97.00% 3 Missing ⚠️
pallets/api/src/nonfungibles/tests.rs 99.82% 1 Missing and 1 partial ⚠️
@@            Coverage Diff             @@
##             main     #432      +/-   ##
==========================================
+ Coverage   71.28%   73.93%   +2.64%     
==========================================
  Files          72       76       +4     
  Lines       13626    15237    +1611     
  Branches    13626    15237    +1611     
==========================================
+ Hits         9713    11265    +1552     
- Misses       3646     3711      +65     
+ Partials      267      261       -6     
Files with missing lines Coverage Δ
pallets/api/src/nonfungibles/benchmarking.rs 100.00% <100.00%> (ø)
runtime/devnet/src/config/assets.rs 100.00% <100.00%> (ø)
runtime/devnet/src/config/proxy.rs 0.00% <ø> (ø)
runtime/devnet/src/lib.rs 5.44% <ø> (ø)
pallets/api/src/nonfungibles/tests.rs 99.82% <99.82%> (ø)
runtime/devnet/src/config/api/mod.rs 91.17% <97.00%> (+22.12%) ⬆️
pallets/api/src/nonfungibles/mod.rs 97.05% <97.05%> (ø)
pallets/api/src/mock.rs 62.50% <8.69%> (-37.50%) ⬇️
pallets/api/src/nonfungibles/weights.rs 41.55% <41.55%> (ø)

... and 8 files with indirect coverage changes

Copy link
Collaborator

@Daanvdplas Daanvdplas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking very good! Left a few comments based on a high level review. Ready to finalise everything as I didn't find anything concerning!

pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
@Daanvdplas
Copy link
Collaborator

CI also failing which would be good to resolve for final review.

@chungquantin chungquantin force-pushed the chungquantin/feat-main_pallet_nonfungibles branch from df829e7 to 13434d3 Compare January 21, 2025 02:39
@chungquantin chungquantin force-pushed the chungquantin/feat-main_pallet_nonfungibles branch from 8af4673 to ac93544 Compare January 21, 2025 04:06
// Measured: `606`
// Estimated: `68341938741594600 + a * (3168 ±44_956_152_011_428) + i * (2663 ±729_819_634_434_010)`
// Minimum execution time: 13_000_000 picoseconds.
Weight::from_parts(25_055_102, 68341938741594600)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder why this is unexpectedly high

Copy link
Collaborator

@Daanvdplas Daanvdplas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stopped reviewing after the events in mod.rs because I have found too many deviations from the spec without any explanation. Could you please give the PR a final review yourself while looking at the spec (especially found different comments) and explain any changes made to prevent confusion :)

pallets/api/src/lib.rs Show resolved Hide resolved
pallets/api/src/mock.rs Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
/// The creator of the collection.
creator: AccountIdOf<T>,
/// The administrator of the collection.
admin: AccountIdOf<T>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for deviating from the owner parameter in pallet-nfts or is this forgotten in the copy paste of the fungibles event?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it on purpose. I don't think the owner field in the pallet-nfts makes sense because it is not the owner but the admin. And this also makes it similar to the one we have in pallet-api/fungibles

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't that be confusing with:

pub(super) owner: AccountId,

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally see that field is incorrect. Maybe it could be changed in pallet-nfts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you, we can also create an issue and adapt later as it delays finishing this PR

pallets/api/src/nonfungibles/mod.rs Show resolved Hide resolved
@chungquantin chungquantin force-pushed the chungquantin/feat-main_pallet_nonfungibles branch from df27276 to ea59ba3 Compare January 22, 2025 05:13
pop-api/integration-tests/Cargo.lock Outdated Show resolved Hide resolved
/// The events that can be emitted.
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Primary change made to the events that deviate from the spec is the introduction of collection field. The PSP34 standard events are designed for a single non-fungible token. Hence, to support multiple tokens, we need the collection field to specify the collection.

pallets/api/src/nonfungibles/mod.rs Show resolved Hide resolved
}

#[pallet::call]
impl<T: Config> Pallet<T> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the events, to support multiple tokens, we also need to accept collection as parameters to dispatchables related to a non-fungible token.

},
/// Details of a specified collection.
#[codec(index = 9)]
Collection(CollectionIdOf<T>),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following Read operations, along with the others listed below, are additional reads not specified in the standard or spec. However, I believe they are essential to the design of the Pop API's nonfungible feature. Another potential Read to consider is Item(CollectionIdOf<T>, ItemIdOf<T>), which queries the details of a collection item.

/// - `item` - The item of the collection of whose approvals will be cleared.
#[pallet::call_index(5)]
#[pallet::weight(NftsWeightInfoOf::<T>::clear_all_transfer_approvals())]
pub fn clear_all_transfer_approvals(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional dispatchables added that are not mentioned in the spec.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to hear why

Also, can we follow the order of the dispatchables defined in the spec. Makes it easier to review. The more unknown / not explained changes in a PR the more question marks arise for the reviewer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are implemented for the pallet while the spec mainly stick to the PSP34 standard. And we did agree that we should not limit the capabilities of the pallet. Hence, as we already provided this in the fork pallet nfts, implement it for the nonfungibles pallet will enrich the pallet features

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does however make the PR bigger, more to review, etc.

pallets/api/src/nonfungibles/tests.rs Outdated Show resolved Hide resolved
@r0gue-io r0gue-io deleted a comment from chungquantin Jan 22, 2025
Copy link
Collaborator

@Daanvdplas Daanvdplas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid PR! Most of the functionality is correct so well done!

Most of the comments are related to differences with the spec which caused confusion as deviating from it means that it needs to be reviewed again which takes time. In addition, having the order of functions in the same order makes the review process streamlined, same counts for the order of tests with the code (i.e. dispatachables order, etc).

Moreover, the additional functions that have been added create review overload (because it is already a very big PR) because this requires much more than reviewing the logic, tests and benchmarks. This is also why I haven't reviewed those functions.

pallets/api/src/nonfungibles/mod.rs Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
/// The creator of the collection.
creator: AccountIdOf<T>,
/// The administrator of the collection.
admin: AccountIdOf<T>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't that be confusing with:

pub(super) owner: AccountId,

pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/mod.rs Outdated Show resolved Hide resolved
runtime/devnet/src/config/api/mod.rs Show resolved Hide resolved
runtime/devnet/src/config/assets.rs Show resolved Hide resolved
runtime/devnet/src/config/assets.rs Show resolved Hide resolved
runtime/devnet/src/lib.rs Show resolved Hide resolved
@@ -46,7 +52,7 @@ impl frame_system::Config for Test {
type BlockHashCount = BlockHashCount;
type BlockLength = ();
type BlockWeights = ();
type DbWeight = ();
type DbWeight = RocksDbWeight;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requires for testing with <Test as frame_system::Config>::DbWeight::get()::reads(1)

Copy link
Collaborator

@Daanvdplas Daanvdplas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just skimmed over and found some improvements in the impl.rs. I will do a proper review monday.

pallets/api/src/nonfungibles/impls.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/impls.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/impls.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/impls.rs Outdated Show resolved Hide resolved
pallets/api/src/nonfungibles/impls.rs Outdated Show resolved Hide resolved
@chungquantin chungquantin requested a review from peterwht January 24, 2025 17:14
Copy link
Collaborator

@peterwht peterwht left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to see how clean this and simple the mapping is to the pallet.

I haven't reviewed everything, but there are several comments I left.

The biggest one is that in several of these functions you are doing reads before calling into pallet-nfts. However, these values are only needed for the event. It also has the side-effect of double charging weight. By moving these reads to AFTER the pallet-nft call we get a few things:

  1. pallet-nfts does the error handling for us
  2. pallet-nfts pulls the storage items into the memory overlay -- hence allowing us to remove the double charged weight

item: ItemIdOf<T>,
to: AccountIdOf<T>,
) -> DispatchResultWithPostInfo {
let owner = NftsOf::<T>::owner(collection, item)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend we move this let owner ... to AFTER the pallet-nfts::transfer and do NOT charge for the read. The reason we don't charge for the read is because the queried item is already in the memory overlay and won't be read from storage. Additionally, we can simplify to use a .expect(...) because pallet-nfts will handle the error checking for us.

/// - `item` - The item.
/// - `to` - The recipient account.
#[pallet::call_index(3)]
#[pallet::weight(NftsWeightInfoOf::<T>::transfer() + T::DbWeight::get().reads(1))]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly, this extra read should not be needed as the Item is already in the memory overlay (hence we are double charging for the item).

If you ran a benchmark with and without the let owner = ... you should see the number of reads stays the same.

to: AccountIdOf<T>,
collection: CollectionIdOf<T>,
item: ItemIdOf<T>,
witness: MintWitness<ItemIdOf<T>, ItemPriceOf<T>>,
Copy link
Collaborator

@peterwht peterwht Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we be able to modify this parameter to something like:

  • Change name to price
  • Set type to ItemPriceOf only
  • If the witness is optional (as is in the actual pallet-nfts::mint), we should do maybe_price

The item id is already a parameter, so we can create the MintWitness instance ourselves in this function.

/// - `collection` - The collection identifier.
/// - `item` - The item to burn.
#[pallet::call_index(8)]
#[pallet::weight(NftsWeightInfoOf::<T>::burn() + T::DbWeight::get().reads(1))]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to transfer, we are double charging a read here due to the memory overlay.

collection: CollectionIdOf<T>,
item: ItemIdOf<T>,
) -> DispatchResultWithPostInfo {
let owner = NftsOf::<T>::owner(collection, item)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, would recommend simplifying this by moving it after pallet-nfts::burn, use .expect(), and remove the error mapping with weight read.

/// - `admin` - The admin of this collection.
/// - `config` - The configuration of the collection.
#[pallet::call_index(12)]
#[pallet::weight(NftsWeightInfoOf::<T>::create() + T::DbWeight::get().reads(1))]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double-charging

config: CollectionConfigOf<T>,
) -> DispatchResultWithPostInfo {
let creator = ensure_signed(origin.clone())?;
let id = NextCollectionIdOf::<T>::get()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would recommend moving after nfts::create, use .expect.

It's cleaner to let pallet-nfts handle the errors for us and pull the storage into the memory overlay.

witness.item_configs,
witness.attributes,
))]
pub fn destroy(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Preface: I haven't reviewed the destroy witness PR). One thought, why don't we expand the standard a little bit and have functionality similar to:

  • destroy_without_witness // max weight until post-dispatch
  • destroy_with_witness // optional choice to provide the witness for accurate weight

Note: I am not saying to use those names, but just representing the functionality.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see here for more context: #445 (comment)

collection,
item,
key: key.to_vec(),
data: value.to_vec(),
Copy link
Collaborator

@peterwht peterwht Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are currently cloning each of these twice. Once when you do key.clone(), and once when you do key.to_vec()

I recommend defining two vars at the top of the fn: key_vec = key.to_vec(); and data_vec = data.to_vec(). This then allows you to do only one clone (which occurs with the .to_vec()).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants