NFTs fractionalization (#12565)

* Copy Uniques into Nfts

* Connect new pallet

* Update weights

* Nfts: Multiple approvals (#12178)

* multiple approvals

* clear

* tests & clean up

* fix in logic & fmt

* fix benchmarks

* deadline

* test deadline

* current_block + deadline

* update ApprovedTransfer event

* benchmark

* docs

* Update frame/nfts/src/lib.rs

Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>

* fmt fix

* Update frame/nfts/src/lib.rs

Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>

* update tests

* anyone can cancel

* Update frame/nfts/src/tests.rs

Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>

* fmt

* fix logic

* unnecessary line

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Update frame/nfts/src/lib.rs

* Update lib.rs

* fmt

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* fmt

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* suggestion

* new line

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>
Co-authored-by: command-bot <>
Co-authored-by: Squirrel <gilescope@gmail.com>

* Fixes

* cargo fmt

* Fixes

* Fixes

* Fix CI

* Nfts: Fix Auto-Increment (#12223)

* commit

* passing benchmarks

* clean up

* sync

* runtime implementation

* fix

* fmt

* fix benchmark

* cfg

* remove try-increment-id

* remove unused error

* impl Incrementable for unsigned types

* clean up

* fix in tests

* not needed anymore

* Use OptionQuery

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* Rename Origin to RuntimeOrigin

* [Uniques V2] Tips (#12168)

* Allow to add tips when buying an NFT

* Chore

* Rework tips feature

* Add weights + benchmarks

* Convert tuple to struct

* Fix benchmark

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Update frame/nfts/src/benchmarking.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix benchmarks

* Revert the bounded_vec![] approach

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

Co-authored-by: command-bot <>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* [Uniques V2] Atomic NFTs swap (#12285)

* Atomic NFTs swap

* Fmt

* Fix benchmark

* Rename swap -> atomic_swap

* Update target balance

* Rollback

* Fix

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Make desired item optional

* Apply suggestions

* Update frame/nfts/src/features/atomic_swap.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Rename fields

* Optimisation

* Add a comment

* deadline -> maybe_deadline

* Add docs

* Change comments

* Add price direction field

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Wrap price and direction

* Fix benchmarks

* Use ensure! instead of if {}

* Make duration param mandatory and limit it to MaxDeadlineDuration

* Make the code safer

* Fix clippy

* Chore

* Remove unused vars

* try

* try 2

* try 3

Co-authored-by: command-bot <>
Co-authored-by: Squirrel <gilescope@gmail.com>

* [Uniques V2] Feature flags (#12367)

* Basics

* WIP: change the data format

* Refactor

* Remove redundant new() method

* Rename settings

* Enable tests

* Chore

* Change params order

* Delete the config on collection removal

* Chore

* Remove redundant system features

* Rename force_item_status to force_collection_status

* Update node runtime

* Chore

* Remove thaw_collection

* Chore

* Connect collection.is_frozen to config

* Allow to lock the collection in a new way

* Move free_holding into settings

* Connect collection's metadata locker to feature flags

* DRY

* Chore

* Connect pallet level feature flags

* Prepare tests for the new changes

* Implement Item settings

* Allow to lock the metadata or attributes of an item

* Common -> Settings

* Extract settings related code to a separate file

* Move feature flag checks inside the do_* methods

* Split settings.rs into parts

* Extract repeated code into macro

* Extract macros into their own file

* Chore

* Fix traits

* Fix traits

* Test SystemFeatures

* Fix benchmarks

* Add missing benchmark

* Fix node/runtime/lib.rs

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Keep item's config on burn if it's not empty

* Fix the merge artifacts

* Fmt

* Add SystemFeature::NoSwaps check

* Rename SystemFeatures to PalletFeatures

* Rename errors

* Add docs

* Change error message

* Rework pallet features

* Move macros

* Change comments

* Fmt

* Refactor Incrementable

* Use pub(crate) for do_* functions

* Update comments

* Refactor freeze and lock functions

* Rework Collection config and Item confg api

* Chore

* Make clippy happy

* Chore

* Update comment

* RequiredDeposit => DepositRequired

* Address comments

Co-authored-by: command-bot <>

* [Uniques V2] Refactor roles (#12437)

* Basics

* WIP: change the data format

* Refactor

* Remove redundant new() method

* Rename settings

* Enable tests

* Chore

* Change params order

* Delete the config on collection removal

* Chore

* Remove redundant system features

* Rename force_item_status to force_collection_status

* Update node runtime

* Chore

* Remove thaw_collection

* Chore

* Connect collection.is_frozen to config

* Allow to lock the collection in a new way

* Move free_holding into settings

* Connect collection's metadata locker to feature flags

* DRY

* Chore

* Connect pallet level feature flags

* Prepare tests for the new changes

* Implement Item settings

* Allow to lock the metadata or attributes of an item

* Common -> Settings

* Extract settings related code to a separate file

* Move feature flag checks inside the do_* methods

* Split settings.rs into parts

* Extract repeated code into macro

* Extract macros into their own file

* Chore

* Fix traits

* Fix traits

* Test SystemFeatures

* Fix benchmarks

* Add missing benchmark

* Fix node/runtime/lib.rs

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Keep item's config on burn if it's not empty

* Fix the merge artifacts

* Fmt

* Add SystemFeature::NoSwaps check

* Refactor roles structure

* Rename SystemFeatures to PalletFeatures

* Rename errors

* Add docs

* Change error message

* Rework pallet features

* Move macros

* Change comments

* Fmt

* Refactor Incrementable

* Use pub(crate) for do_* functions

* Update comments

* Refactor freeze and lock functions

* Rework Collection config and Item confg api

* Chore

* Make clippy happy

* Chore

* Fix artifacts

* Address comments

* Further refactoring

* Add comments

* Add tests for group_roles_by_account()

* Update frame/nfts/src/impl_nonfungibles.rs

* Add test

* Replace Itertools group_by with a custom implementation

* ItemsNotTransferable => ItemsNonTransferable

* Update frame/nfts/src/features/roles.rs

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Address PR comments

* Add missed comment

Co-authored-by: command-bot <>
Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Fix copy

* Remove storage_prefix

* Remove transactional

* Initial commit SFT pallet.

* Update comment

* [Uniques V2] Minting options (#12483)

* Basics

* WIP: change the data format

* Refactor

* Remove redundant new() method

* Rename settings

* Enable tests

* Chore

* Change params order

* Delete the config on collection removal

* Chore

* Remove redundant system features

* Rename force_item_status to force_collection_status

* Update node runtime

* Chore

* Remove thaw_collection

* Chore

* Connect collection.is_frozen to config

* Allow to lock the collection in a new way

* Move free_holding into settings

* Connect collection's metadata locker to feature flags

* DRY

* Chore

* Connect pallet level feature flags

* Prepare tests for the new changes

* Implement Item settings

* Allow to lock the metadata or attributes of an item

* Common -> Settings

* Extract settings related code to a separate file

* Move feature flag checks inside the do_* methods

* Split settings.rs into parts

* Extract repeated code into macro

* Extract macros into their own file

* Chore

* Fix traits

* Fix traits

* Test SystemFeatures

* Fix benchmarks

* Add missing benchmark

* Fix node/runtime/lib.rs

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Keep item's config on burn if it's not empty

* Fix the merge artifacts

* Fmt

* Add SystemFeature::NoSwaps check

* Rename SystemFeatures to PalletFeatures

* Rename errors

* Add docs

* Change error message

* Change the format of CollectionConfig to store more data

* Move max supply to the CollectionConfig and allow to change it

* Remove ItemConfig from the mint() function and use the one set in mint settings

* Add different mint options

* Allow to change the mint settings

* Add a force_mint() method

* Check mint params

* Some optimisations

* Cover with tests

* Remove merge artifacts

* Chore

* Use the new has_role() method

* Rework item deposits

* More tests

* Refactoring

* Address comments

* Refactor lock_collection()

* Update frame/nfts/src/types.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/types.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Private => Issuer

* Add more tests

* Fix benchmarks

* Add benchmarks for new methods

* [Uniques v2] Refactoring (#12570)

* Move do_set_price() and do_buy_item() to buy_sell.rs

* Move approvals to feature file

* Move metadata to feature files

* Move the rest of methods to feature files

* Remove artifacts

* Split force_collection_status into 2 methods

* Fix benchmarks

* Fix benchmarks

* Update deps

Co-authored-by: command-bot <>
Co-authored-by: Squirrel <gilescope@gmail.com>

* Rename module to NFT fractionalisation

* Loose coupling for pallet-assets

* cargo fmt

* [Uniques V2] Smart attributes (#12702)

* Basics

* WIP: change the data format

* Refactor

* Remove redundant new() method

* Rename settings

* Enable tests

* Chore

* Change params order

* Delete the config on collection removal

* Chore

* Remove redundant system features

* Rename force_item_status to force_collection_status

* Update node runtime

* Chore

* Remove thaw_collection

* Chore

* Connect collection.is_frozen to config

* Allow to lock the collection in a new way

* Move free_holding into settings

* Connect collection's metadata locker to feature flags

* DRY

* Chore

* Connect pallet level feature flags

* Prepare tests for the new changes

* Implement Item settings

* Allow to lock the metadata or attributes of an item

* Common -> Settings

* Extract settings related code to a separate file

* Move feature flag checks inside the do_* methods

* Split settings.rs into parts

* Extract repeated code into macro

* Extract macros into their own file

* Chore

* Fix traits

* Fix traits

* Test SystemFeatures

* Fix benchmarks

* Add missing benchmark

* Fix node/runtime/lib.rs

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Keep item's config on burn if it's not empty

* Fix the merge artifacts

* Fmt

* Add SystemFeature::NoSwaps check

* Rename SystemFeatures to PalletFeatures

* Rename errors

* Add docs

* Change error message

* Change the format of CollectionConfig to store more data

* Move max supply to the CollectionConfig and allow to change it

* Remove ItemConfig from the mint() function and use the one set in mint settings

* Add different mint options

* Allow to change the mint settings

* Add a force_mint() method

* Check mint params

* Some optimisations

* Cover with tests

* Remove merge artifacts

* Chore

* Use the new has_role() method

* Rework item deposits

* More tests

* Refactoring

* Address comments

* Refactor lock_collection()

* Update frame/nfts/src/types.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/types.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Private => Issuer

* Add more tests

* Fix benchmarks

* Add benchmarks for new methods

* [Uniques v2] Refactoring (#12570)

* Move do_set_price() and do_buy_item() to buy_sell.rs

* Move approvals to feature file

* Move metadata to feature files

* Move the rest of methods to feature files

* Remove artifacts

* Smart attributes

* Split force_collection_status into 2 methods

* Fix benchmarks

* Fix benchmarks

* Update deps

* Fix merge artifact

* Weights + benchmarks + docs

* Change params order

* Chore

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update docs

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Add PalletId

* Chore

* Add tests

* More tests

* Add doc

* Update errors snapshots

* Ensure we track the owner_deposit field correctly

Co-authored-by: command-bot <>
Co-authored-by: Squirrel <gilescope@gmail.com>

* [Uniques V2] Final improvements (#12736)

* Use KeyPrefixIterator instead of Box

* Change create_collection()

* Restrict from claiming NFTs twice

* Update Readme

* Remove dead code

* Refactoring

* Update readme

* Fix clippy

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Update docs

* Typo

* Fix benchmarks

* Add more docs

* Replace uniques with nfts, add minted volume storage

* DepositRequired setting should affect only the attributes within the CollectionOwner namespace

* Add unlock functionality

* [NFTs] Implement missed methods to set the attributes from other pallets (#12919)

* Implement missed methods to set the attributes from other pallets

* Revert snapshots

* Update snapshot

* Update snapshot

* Revert snapshot changes

* Update snapshots

* Yet another snapshot update..

* Asset to NFT id storage mutations

* Minor fixes

* Minor comments

* cargo fmt

* Remove benchmarking, unused clone()

* Update frame/support/src/traits/tokens/nonfungible_v2.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/support/src/traits/tokens/nonfungible_v2.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/support/src/traits/tokens/nonfungible_v2.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/support/src/traits/tokens/nonfungibles_v2.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/support/src/traits/tokens/nonfungible_v2.rs

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/support/src/traits/tokens/nonfungibles_v2.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Address comments

* [NFTs] Add the new `owner` param to mint() method (#12997)

* Add the new `owner` param to mint() method

* Fmt

* Address comments

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Fmt

* Update frame/nfts/src/common_functions.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/types.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/types.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/types.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/types.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/types.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Add call indexes

* Update snapshots

* Refactor nft fractionalisation pallet (#13008)

* Refactoring

* Make it compile

* Add tests

* Rename

* Rework nfts locking

* Update cargo.lock

* Connect the latest changes to the runtime-kitchensink

* Add benchmarks, fix other issues

* Chore

* Chore 2

* Chore 3

* Add runtime-benchmarks

* Rename

* Set metadata

* Make fields public

* Chore

* Created asset shouldn't be sufficient

* Add documentation

* minor edit to docs

* Minor corrections

Co-authored-by: lana-shanghai <svetlana.konstantinovna@gmail.com>

* fmt

* Add fee reserved before creating an asset

* Use ReservableCurrency for fee deposit

* Improvements

* Revert fmt changes

* A bit more cleanup

* Consistent naming

* Make it more generic

* Leftover

* Use Vec<u8> instead of String

* Update to the latest + improve the Locker trait

* Refactor NFTs locker

* Replace Vec with BoundedVec, add clearer errors

* cargo fmt

* Add README about unlocking NFTs

* add constant definition

* add fortitude & precision to asset related functions

* fix mock and tests

* transfer ExistentialDeposit to pallet if it's balance is below

* Refactoring

* Simplify the locking mechanism

* Use PalletAttributes enum instead of the LOCKED_NFT_KEY

* Fix benchmark

* Add missing licence details

* Update Cargo.toml

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_nft_fractionalization

* Apply suggestions from code review

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nft-fractionalization/README.md

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

---------

Co-authored-by: Jegor Sidorenko <jegor@parity.io>
Co-authored-by: Sergej Sakac <73715684+Szegoo@users.noreply.github.com>
Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>
Co-authored-by: Squirrel <gilescope@gmail.com>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>
Co-authored-by: command-bot <>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
This commit is contained in:
lanaivina
2023-05-23 09:17:48 +02:00
committed by GitHub
parent df87bae1a9
commit 41dbed0b90
23 changed files with 1483 additions and 9 deletions
@@ -0,0 +1,135 @@
// This file is part of Substrate.
// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Nft fractionalization pallet benchmarking.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::{benchmarks, whitelisted_caller};
use frame_support::{
assert_ok,
traits::{
fungible::{Inspect as InspectFungible, Mutate as MutateFungible},
tokens::nonfungibles_v2::{Create, Mutate},
Get,
},
};
use frame_system::RawOrigin as SystemOrigin;
use pallet_nfts::{CollectionConfig, CollectionSettings, ItemConfig, MintSettings};
use sp_runtime::traits::StaticLookup;
use sp_std::prelude::*;
use crate::Pallet as NftFractionalization;
type BalanceOf<T> =
<<T as Config>::Currency as InspectFungible<<T as SystemConfig>::AccountId>>::Balance;
type CollectionConfigOf<T> = CollectionConfig<
BalanceOf<T>,
<T as SystemConfig>::BlockNumber,
<T as Config>::NftCollectionId,
>;
fn default_collection_config<T: Config>() -> CollectionConfigOf<T>
where
T::Currency: InspectFungible<T::AccountId>,
{
CollectionConfig {
settings: CollectionSettings::all_enabled(),
max_supply: None,
mint_settings: MintSettings::default(),
}
}
fn mint_nft<T: Config>(nft_id: T::NftId) -> (T::AccountId, AccountIdLookupOf<T>)
where
T::Nfts: Create<T::AccountId, CollectionConfig<BalanceOf<T>, T::BlockNumber, T::NftCollectionId>>
+ Mutate<T::AccountId, ItemConfig>,
{
let caller: T::AccountId = whitelisted_caller();
let caller_lookup = T::Lookup::unlookup(caller.clone());
let ed = T::Currency::minimum_balance();
let multiplier = BalanceOf::<T>::from(100u8);
T::Currency::set_balance(&caller, ed * multiplier + T::Deposit::get() * multiplier);
assert_ok!(T::Nfts::create_collection(&caller, &caller, &default_collection_config::<T>()));
let collection = T::BenchmarkHelper::collection(0);
assert_ok!(T::Nfts::mint_into(&collection, &nft_id, &caller, &ItemConfig::default(), true));
(caller, caller_lookup)
}
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
let events = frame_system::Pallet::<T>::events();
let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
// compare to the last event record
let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
assert_eq!(event, &system_event);
}
benchmarks! {
where_clause {
where
T::Nfts: Create<T::AccountId, CollectionConfig<BalanceOf<T>, T::BlockNumber, T::NftCollectionId>>
+ Mutate<T::AccountId, ItemConfig>,
}
fractionalize {
let asset = T::BenchmarkHelper::asset(0);
let collection = T::BenchmarkHelper::collection(0);
let nft = T::BenchmarkHelper::nft(0);
let (caller, caller_lookup) = mint_nft::<T>(nft);
}: _(SystemOrigin::Signed(caller.clone()), collection, nft, asset, caller_lookup, 1000u32.into())
verify {
assert_last_event::<T>(
Event::NftFractionalized {
nft_collection: collection,
nft,
fractions: 1000u32.into(),
asset,
beneficiary: caller,
}.into()
);
}
unify {
let asset = T::BenchmarkHelper::asset(0);
let collection = T::BenchmarkHelper::collection(0);
let nft = T::BenchmarkHelper::nft(0);
let (caller, caller_lookup) = mint_nft::<T>(nft);
NftFractionalization::<T>::fractionalize(
SystemOrigin::Signed(caller.clone()).into(),
collection,
nft,
asset,
caller_lookup.clone(),
1000u32.into(),
)?;
}: _(SystemOrigin::Signed(caller.clone()), collection, nft, asset, caller_lookup)
verify {
assert_last_event::<T>(
Event::NftUnified {
nft_collection: collection,
nft,
asset,
beneficiary: caller,
}.into()
);
}
impl_benchmark_test_suite!(NftFractionalization, crate::mock::new_test_ext(), crate::mock::Test);
}
@@ -0,0 +1,389 @@
// This file is part of Substrate.
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! # NFT Fractionalization Pallet
//!
//! This pallet provides the basic functionality that should allow users
//! to leverage partial ownership, transfers, and sales, of illiquid assets,
//! whether real-world assets represented by their digital twins, or NFTs,
//! or original NFTs.
//!
//! The functionality allows a user to lock an NFT they own, create a new
//! fungible asset, and mint a set amount of tokens (`fractions`).
//!
//! It also allows the user to burn 100% of the asset and to unlock the NFT
//! into their account.
//!
//! ### Functions
//!
//! * `fractionalize`: Lock the NFT and create and mint a new fungible asset.
//! * `unify`: Return 100% of the asset and unlock the NFT.
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
mod types;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
pub mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
use frame_system::Config as SystemConfig;
pub use pallet::*;
pub use scale_info::Type;
pub use types::*;
pub use weights::WeightInfo;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{
dispatch::DispatchResult,
ensure,
pallet_prelude::*,
sp_runtime::traits::{AccountIdConversion, StaticLookup},
traits::{
fungible::{
hold::{Inspect as HoldInspectFungible, Mutate as HoldMutateFungible},
Inspect as InspectFungible, Mutate as MutateFungible,
},
fungibles::{
metadata::{MetadataDeposit, Mutate as MutateMetadata},
Create, Destroy, Inspect, Mutate,
},
tokens::{
nonfungibles_v2::{Inspect as NonFungiblesInspect, Transfer},
AssetId, Balance as AssetBalance,
Fortitude::Polite,
Precision::{BestEffort, Exact},
Preservation::Preserve,
},
},
BoundedVec, PalletId,
};
use frame_system::pallet_prelude::*;
use scale_info::prelude::{format, string::String};
use sp_runtime::traits::{One, Zero};
use sp_std::{fmt::Display, prelude::*};
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::config]
pub trait Config: frame_system::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The currency mechanism, used for paying for deposits.
type Currency: InspectFungible<Self::AccountId>
+ MutateFungible<Self::AccountId>
+ HoldInspectFungible<Self::AccountId>
+ HoldMutateFungible<Self::AccountId>;
#[pallet::constant]
type HoldReason: Get<<Self::Currency as HoldInspectFungible<Self::AccountId>>::Reason>;
/// The deposit paid by the user locking an NFT. The deposit is returned to the original NFT
/// owner when the asset is unified and the NFT is unlocked.
#[pallet::constant]
type Deposit: Get<DepositOf<Self>>;
/// Identifier for the collection of NFT.
type NftCollectionId: Member + Parameter + MaxEncodedLen + Copy + Display;
/// The type used to identify an NFT within a collection.
type NftId: Member + Parameter + MaxEncodedLen + Copy + Display;
/// The type used to describe the amount of fractions converted into assets.
type AssetBalance: AssetBalance;
/// The type used to identify the assets created during fractionalization.
type AssetId: AssetId;
/// Registry for the minted assets.
type Assets: Inspect<Self::AccountId, AssetId = Self::AssetId, Balance = Self::AssetBalance>
+ Create<Self::AccountId>
+ Destroy<Self::AccountId>
+ Mutate<Self::AccountId>
+ MutateMetadata<Self::AccountId>
+ MetadataDeposit<DepositOf<Self>>;
/// Registry for minted NFTs.
type Nfts: NonFungiblesInspect<
Self::AccountId,
ItemId = Self::NftId,
CollectionId = Self::NftCollectionId,
> + Transfer<Self::AccountId>;
/// The pallet's id, used for deriving its sovereign account ID.
#[pallet::constant]
type PalletId: Get<PalletId>;
/// The newly created asset's symbol.
#[pallet::constant]
type NewAssetSymbol: Get<BoundedVec<u8, Self::StringLimit>>;
/// The newly created asset's name.
#[pallet::constant]
type NewAssetName: Get<BoundedVec<u8, Self::StringLimit>>;
/// The maximum length of a name or symbol stored on-chain.
#[pallet::constant]
type StringLimit: Get<u32>;
/// A set of helper functions for benchmarking.
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper: BenchmarkHelper<Self::AssetId, Self::NftCollectionId, Self::NftId>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
/// Keeps track of the corresponding NFT ID, asset ID and amount minted.
#[pallet::storage]
#[pallet::getter(fn nft_to_asset)]
pub type NftToAsset<T: Config> = StorageMap<
_,
Blake2_128Concat,
(T::NftCollectionId, T::NftId),
Details<AssetIdOf<T>, AssetBalanceOf<T>, DepositOf<T>, T::AccountId>,
OptionQuery,
>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// An NFT was successfully fractionalized.
NftFractionalized {
nft_collection: T::NftCollectionId,
nft: T::NftId,
fractions: AssetBalanceOf<T>,
asset: AssetIdOf<T>,
beneficiary: T::AccountId,
},
/// An NFT was successfully returned back.
NftUnified {
nft_collection: T::NftCollectionId,
nft: T::NftId,
asset: AssetIdOf<T>,
beneficiary: T::AccountId,
},
}
#[pallet::error]
pub enum Error<T> {
/// Asset ID does not correspond to locked NFT.
IncorrectAssetId,
/// The signing account has no permission to do the operation.
NoPermission,
/// NFT doesn't exist.
NftNotFound,
/// NFT has not yet been fractionalised.
NftNotFractionalized,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Lock the NFT and mint a new fungible asset.
///
/// The dispatch origin for this call must be Signed.
/// The origin must be the owner of the NFT they are trying to lock.
///
/// `Deposit` funds of sender are reserved.
///
/// - `nft_collection_id`: The ID used to identify the collection of the NFT.
/// Is used within the context of `pallet_nfts`.
/// - `nft_id`: The ID used to identify the NFT within the given collection.
/// Is used within the context of `pallet_nfts`.
/// - `asset_id`: The ID of the new asset. It must not exist.
/// Is used within the context of `pallet_assets`.
/// - `beneficiary`: The account that will receive the newly created asset.
/// - `fractions`: The total issuance of the newly created asset class.
///
/// Emits `NftFractionalized` event when successful.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::fractionalize())]
pub fn fractionalize(
origin: OriginFor<T>,
nft_collection_id: T::NftCollectionId,
nft_id: T::NftId,
asset_id: AssetIdOf<T>,
beneficiary: AccountIdLookupOf<T>,
fractions: AssetBalanceOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let beneficiary = T::Lookup::lookup(beneficiary)?;
let nft_owner =
T::Nfts::owner(&nft_collection_id, &nft_id).ok_or(Error::<T>::NftNotFound)?;
ensure!(nft_owner == who, Error::<T>::NoPermission);
let pallet_account = Self::get_pallet_account();
let deposit = T::Deposit::get();
T::Currency::hold(&T::HoldReason::get(), &nft_owner, deposit)?;
Self::do_lock_nft(nft_collection_id, nft_id)?;
Self::do_create_asset(asset_id, pallet_account.clone())?;
Self::do_mint_asset(asset_id, &beneficiary, fractions)?;
Self::do_set_metadata(asset_id, &who, &pallet_account, &nft_collection_id, &nft_id)?;
NftToAsset::<T>::insert(
(nft_collection_id, nft_id),
Details { asset: asset_id, fractions, asset_creator: nft_owner, deposit },
);
Self::deposit_event(Event::NftFractionalized {
nft_collection: nft_collection_id,
nft: nft_id,
fractions,
asset: asset_id,
beneficiary,
});
Ok(())
}
/// Burn the total issuance of the fungible asset and return (unlock) the locked NFT.
///
/// The dispatch origin for this call must be Signed.
///
/// `Deposit` funds will be returned to `asset_creator`.
///
/// - `nft_collection_id`: The ID used to identify the collection of the NFT.
/// Is used within the context of `pallet_nfts`.
/// - `nft_id`: The ID used to identify the NFT within the given collection.
/// Is used within the context of `pallet_nfts`.
/// - `asset_id`: The ID of the asset being returned and destroyed. Must match
/// the original ID of the created asset, corresponding to the NFT.
/// Is used within the context of `pallet_assets`.
/// - `beneficiary`: The account that will receive the unified NFT.
///
/// Emits `NftUnified` event when successful.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::unify())]
pub fn unify(
origin: OriginFor<T>,
nft_collection_id: T::NftCollectionId,
nft_id: T::NftId,
asset_id: AssetIdOf<T>,
beneficiary: AccountIdLookupOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let beneficiary = T::Lookup::lookup(beneficiary)?;
NftToAsset::<T>::try_mutate_exists((nft_collection_id, nft_id), |maybe_details| {
let details = maybe_details.take().ok_or(Error::<T>::NftNotFractionalized)?;
ensure!(details.asset == asset_id, Error::<T>::IncorrectAssetId);
let deposit = details.deposit;
let asset_creator = details.asset_creator;
Self::do_burn_asset(asset_id, &who, details.fractions)?;
Self::do_unlock_nft(nft_collection_id, nft_id, &beneficiary)?;
T::Currency::release(&T::HoldReason::get(), &asset_creator, deposit, BestEffort)?;
Self::deposit_event(Event::NftUnified {
nft_collection: nft_collection_id,
nft: nft_id,
asset: asset_id,
beneficiary,
});
Ok(())
})
}
}
impl<T: Config> Pallet<T> {
/// The account ID of the pallet.
///
/// This actually does computation. If you need to keep using it, then make sure you cache
/// the value and only call this once.
fn get_pallet_account() -> T::AccountId {
T::PalletId::get().into_account_truncating()
}
/// Transfer the NFT from the account holding that NFT to the pallet's account.
fn do_lock_nft(nft_collection_id: T::NftCollectionId, nft_id: T::NftId) -> DispatchResult {
T::Nfts::disable_transfer(&nft_collection_id, &nft_id)
}
/// Transfer the NFT to the account returning the tokens.
fn do_unlock_nft(
nft_collection_id: T::NftCollectionId,
nft_id: T::NftId,
account: &T::AccountId,
) -> DispatchResult {
T::Nfts::enable_transfer(&nft_collection_id, &nft_id)?;
T::Nfts::transfer(&nft_collection_id, &nft_id, account)
}
/// Create the new asset.
fn do_create_asset(asset_id: AssetIdOf<T>, admin: T::AccountId) -> DispatchResult {
T::Assets::create(asset_id, admin, false, One::one())
}
/// Mint the `amount` of tokens with `asset_id` into the beneficiary's account.
fn do_mint_asset(
asset_id: AssetIdOf<T>,
beneficiary: &T::AccountId,
amount: AssetBalanceOf<T>,
) -> DispatchResult {
T::Assets::mint_into(asset_id, beneficiary, amount)?;
Ok(())
}
/// Burn tokens from the account.
fn do_burn_asset(
asset_id: AssetIdOf<T>,
account: &T::AccountId,
amount: AssetBalanceOf<T>,
) -> DispatchResult {
T::Assets::burn_from(asset_id, account, amount, Exact, Polite)?;
T::Assets::start_destroy(asset_id, None)
}
/// Set the metadata for the newly created asset.
fn do_set_metadata(
asset_id: AssetIdOf<T>,
depositor: &T::AccountId,
pallet_account: &T::AccountId,
nft_collection_id: &T::NftCollectionId,
nft_id: &T::NftId,
) -> DispatchResult {
let name = format!(
"{} {nft_collection_id}-{nft_id}",
String::from_utf8_lossy(&T::NewAssetName::get())
);
let symbol: &[u8] = &T::NewAssetSymbol::get();
let existential_deposit = T::Currency::minimum_balance();
let pallet_account_balance = T::Currency::balance(&pallet_account);
if pallet_account_balance < existential_deposit {
T::Currency::transfer(&depositor, &pallet_account, existential_deposit, Preserve)?;
}
let metadata_deposit = T::Assets::calc_metadata_deposit(name.as_bytes(), symbol);
if !metadata_deposit.is_zero() {
T::Currency::transfer(&depositor, &pallet_account, metadata_deposit, Preserve)?;
}
T::Assets::set(asset_id, &pallet_account, name.into(), symbol.into(), 0)
}
}
}
@@ -0,0 +1,202 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Test environment for Nft fractionalization pallet.
use super::*;
use crate as pallet_nft_fractionalization;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
construct_runtime, parameter_types,
traits::{AsEnsureOriginWithArg, ConstU32, ConstU64},
BoundedVec, PalletId,
};
use frame_system::EnsureSigned;
use pallet_nfts::PalletFeatures;
use scale_info::TypeInfo;
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify},
MultiSignature,
};
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
type Signature = MultiSignature;
type AccountPublic = <Signature as Verify>::Signer;
type AccountId = <AccountPublic as IdentifyAccount>::AccountId;
// Configure a mock runtime to test the pallet.
construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system,
NftFractionalization: pallet_nft_fractionalization,
Assets: pallet_assets,
Balances: pallet_balances,
Nfts: pallet_nfts,
}
);
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
type DbWeight = ();
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = ConstU32<16>;
}
#[derive(
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, Debug, TypeInfo,
)]
pub enum HoldIdentifier {
NftFractionalization,
}
impl pallet_balances::Config for Test {
type Balance = u64;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = ();
type MaxReserves = ConstU32<50>;
type ReserveIdentifier = [u8; 8];
type HoldIdentifier = HoldIdentifier;
type MaxHolds = ConstU32<1>;
type FreezeIdentifier = ();
type MaxFreezes = ();
}
impl pallet_assets::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Balance = u64;
type RemoveItemsLimit = ConstU32<1000>;
type AssetId = u32;
type AssetIdParameter = u32;
type Currency = Balances;
type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<Self::AccountId>>;
type ForceOrigin = frame_system::EnsureRoot<Self::AccountId>;
type AssetDeposit = ConstU64<1>;
type AssetAccountDeposit = ConstU64<10>;
type MetadataDepositBase = ConstU64<1>;
type MetadataDepositPerByte = ConstU64<1>;
type ApprovalDeposit = ConstU64<1>;
type StringLimit = ConstU32<50>;
type Freezer = ();
type Extra = ();
type CallbackHandle = ();
type WeightInfo = ();
pallet_assets::runtime_benchmarks_enabled! {
type BenchmarkHelper = ();
}
}
parameter_types! {
pub storage Features: PalletFeatures = PalletFeatures::all_enabled();
}
impl pallet_nfts::Config for Test {
type RuntimeEvent = RuntimeEvent;
type CollectionId = u32;
type ItemId = u32;
type Currency = Balances;
type CreateOrigin = AsEnsureOriginWithArg<frame_system::EnsureSigned<Self::AccountId>>;
type ForceOrigin = frame_system::EnsureRoot<Self::AccountId>;
type Locker = ();
type CollectionDeposit = ConstU64<2>;
type ItemDeposit = ConstU64<1>;
type MetadataDepositBase = ConstU64<1>;
type AttributeDepositBase = ConstU64<1>;
type DepositPerByte = ConstU64<1>;
type StringLimit = ConstU32<50>;
type KeyLimit = ConstU32<50>;
type ValueLimit = ConstU32<50>;
type ApprovalsLimit = ConstU32<10>;
type ItemAttributesApprovalsLimit = ConstU32<2>;
type MaxTips = ConstU32<10>;
type MaxDeadlineDuration = ConstU64<10000>;
type MaxAttributesPerCall = ConstU32<2>;
type Features = Features;
type OffchainSignature = Signature;
type OffchainPublic = AccountPublic;
type WeightInfo = ();
pallet_nfts::runtime_benchmarks_enabled! {
type Helper = ();
}
}
parameter_types! {
pub const StringLimit: u32 = 50;
pub const NftFractionalizationPalletId: PalletId = PalletId(*b"fraction");
pub NewAssetSymbol: BoundedVec<u8, StringLimit> = (*b"FRAC").to_vec().try_into().unwrap();
pub NewAssetName: BoundedVec<u8, StringLimit> = (*b"Frac").to_vec().try_into().unwrap();
pub const HoldReason: HoldIdentifier = HoldIdentifier::NftFractionalization;
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type Deposit = ConstU64<1>;
type Currency = Balances;
type NewAssetSymbol = NewAssetSymbol;
type NewAssetName = NewAssetName;
type NftCollectionId = <Self as pallet_nfts::Config>::CollectionId;
type NftId = <Self as pallet_nfts::Config>::ItemId;
type AssetBalance = <Self as pallet_balances::Config>::Balance;
type AssetId = <Self as pallet_assets::Config>::AssetId;
type Assets = Assets;
type Nfts = Nfts;
type PalletId = NftFractionalizationPalletId;
type WeightInfo = ();
type StringLimit = StringLimit;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = ();
type HoldReason = HoldReason;
}
// Build genesis storage according to the mock runtime.
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
@@ -0,0 +1,278 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Tests for Nft fractionalization pallet.
use crate::{mock::*, *};
use frame_support::{
assert_noop, assert_ok,
traits::{
fungible::{hold::Inspect as InspectHold, Mutate as MutateFungible},
fungibles::{metadata::Inspect, InspectEnumerable},
},
};
use pallet_nfts::CollectionConfig;
use sp_runtime::{DispatchError, ModuleError, TokenError::FundsUnavailable};
fn assets() -> Vec<u32> {
let mut s: Vec<_> = <<Test as Config>::Assets>::asset_ids().collect();
s.sort();
s
}
fn events() -> Vec<Event<Test>> {
let result = System::events()
.into_iter()
.map(|r| r.event)
.filter_map(|e| {
if let mock::RuntimeEvent::NftFractionalization(inner) = e {
Some(inner)
} else {
None
}
})
.collect();
System::reset_events();
result
}
type AccountIdOf<Test> = <Test as frame_system::Config>::AccountId;
fn account(id: u8) -> AccountIdOf<Test> {
[id; 32].into()
}
#[test]
fn fractionalize_should_work() {
new_test_ext().execute_with(|| {
let nft_collection_id = 0;
let nft_id = 0;
let asset_id = 0;
let fractions = 1000;
Balances::set_balance(&account(1), 100);
Balances::set_balance(&account(2), 100);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
CollectionConfig::default(),
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
account(1),
None,
));
assert_ok!(NftFractionalization::fractionalize(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
asset_id,
account(2),
fractions,
));
assert_eq!(assets(), vec![asset_id]);
assert_eq!(Assets::balance(asset_id, account(2)), fractions);
assert_eq!(Balances::total_balance_on_hold(&account(1)), 2);
assert_eq!(String::from_utf8(Assets::name(0)).unwrap(), "Frac 0-0");
assert_eq!(String::from_utf8(Assets::symbol(0)).unwrap(), "FRAC");
assert_eq!(Nfts::owner(nft_collection_id, nft_id), Some(account(1)));
assert_noop!(
Nfts::transfer(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
account(2),
),
DispatchError::Module(ModuleError {
index: 4,
error: [12, 0, 0, 0],
message: Some("ItemLocked")
})
);
let details = NftToAsset::<Test>::get((&nft_collection_id, &nft_id)).unwrap();
assert_eq!(details.asset, asset_id);
assert_eq!(details.fractions, fractions);
assert!(events().contains(&Event::<Test>::NftFractionalized {
nft_collection: nft_collection_id,
nft: nft_id,
fractions,
asset: asset_id,
beneficiary: account(2),
}));
let nft_id = nft_id + 1;
assert_noop!(
NftFractionalization::fractionalize(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
asset_id,
account(2),
fractions,
),
Error::<Test>::NftNotFound
);
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
account(2),
None
));
assert_noop!(
NftFractionalization::fractionalize(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
asset_id,
account(2),
fractions,
),
Error::<Test>::NoPermission
);
});
}
#[test]
fn unify_should_work() {
new_test_ext().execute_with(|| {
let nft_collection_id = 0;
let nft_id = 0;
let asset_id = 0;
let fractions = 1000;
Balances::set_balance(&account(1), 100);
Balances::set_balance(&account(2), 100);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
CollectionConfig::default(),
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
account(1),
None,
));
assert_ok!(NftFractionalization::fractionalize(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
asset_id,
account(2),
fractions,
));
assert_noop!(
NftFractionalization::unify(
RuntimeOrigin::signed(account(2)),
nft_collection_id + 1,
nft_id,
asset_id,
account(1),
),
Error::<Test>::NftNotFractionalized
);
assert_noop!(
NftFractionalization::unify(
RuntimeOrigin::signed(account(2)),
nft_collection_id,
nft_id,
asset_id + 1,
account(1),
),
Error::<Test>::IncorrectAssetId
);
// can't unify the asset a user doesn't hold
assert_noop!(
NftFractionalization::unify(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
asset_id,
account(1),
),
DispatchError::Token(FundsUnavailable)
);
assert_ok!(NftFractionalization::unify(
RuntimeOrigin::signed(account(2)),
nft_collection_id,
nft_id,
asset_id,
account(1),
));
assert_eq!(Assets::balance(asset_id, account(2)), 0);
assert_eq!(Balances::reserved_balance(&account(1)), 1);
assert_eq!(Nfts::owner(nft_collection_id, nft_id), Some(account(1)));
assert!(!NftToAsset::<Test>::contains_key((&nft_collection_id, &nft_id)));
assert!(events().contains(&Event::<Test>::NftUnified {
nft_collection: nft_collection_id,
nft: nft_id,
asset: asset_id,
beneficiary: account(1),
}));
// validate we need to hold the full balance to un-fractionalize the NFT
let asset_id = asset_id + 1;
assert_ok!(NftFractionalization::fractionalize(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
asset_id,
account(1),
fractions,
));
assert_ok!(Assets::transfer(RuntimeOrigin::signed(account(1)), asset_id, account(2), 1));
assert_eq!(Assets::balance(asset_id, account(1)), fractions - 1);
assert_eq!(Assets::balance(asset_id, account(2)), 1);
assert_noop!(
NftFractionalization::unify(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
asset_id,
account(1),
),
DispatchError::Token(FundsUnavailable)
);
assert_ok!(Assets::transfer(RuntimeOrigin::signed(account(2)), asset_id, account(1), 1));
assert_ok!(NftFractionalization::unify(
RuntimeOrigin::signed(account(1)),
nft_collection_id,
nft_id,
asset_id,
account(2),
));
assert_eq!(Nfts::owner(nft_collection_id, nft_id), Some(account(2)));
});
}
@@ -0,0 +1,76 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Various basic types for use in the Nft fractionalization pallet.
use super::*;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::traits::{fungible::Inspect as FunInspect, fungibles::Inspect};
use scale_info::TypeInfo;
use sp_runtime::traits::StaticLookup;
pub type AssetIdOf<T> = <<T as Config>::Assets as Inspect<<T as SystemConfig>::AccountId>>::AssetId;
pub type AssetBalanceOf<T> =
<<T as Config>::Assets as Inspect<<T as SystemConfig>::AccountId>>::Balance;
pub type DepositOf<T> =
<<T as Config>::Currency as FunInspect<<T as SystemConfig>::AccountId>>::Balance;
pub type AccountIdLookupOf<T> = <<T as SystemConfig>::Lookup as StaticLookup>::Source;
/// Stores the details of a fractionalized item.
#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
pub struct Details<AssetId, Fractions, Deposit, AccountId> {
/// Minted asset.
pub asset: AssetId,
/// Number of fractions minted.
pub fractions: Fractions,
/// Reserved deposit for creating a new asset.
pub deposit: Deposit,
/// Account that fractionalized an item.
pub asset_creator: AccountId,
}
/// Benchmark Helper
#[cfg(feature = "runtime-benchmarks")]
pub trait BenchmarkHelper<AssetId, CollectionId, ItemId> {
/// Returns an asset id from a given integer.
fn asset(id: u32) -> AssetId;
/// Returns a collection id from a given integer.
fn collection(id: u32) -> CollectionId;
/// Returns an nft id from a given integer.
fn nft(id: u32) -> ItemId;
}
#[cfg(feature = "runtime-benchmarks")]
impl<AssetId, CollectionId, ItemId> BenchmarkHelper<AssetId, CollectionId, ItemId> for ()
where
AssetId: From<u32>,
CollectionId: From<u32>,
ItemId: From<u32>,
{
fn asset(id: u32) -> AssetId {
id.into()
}
fn collection(id: u32) -> CollectionId {
id.into()
}
fn nft(id: u32) -> ItemId {
id.into()
}
}
+184
View File
@@ -0,0 +1,184 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Autogenerated weights for pallet_nft_fractionalization
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-05-19, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// target/production/substrate
// benchmark
// pallet
// --steps=50
// --repeat=20
// --extrinsic=*
// --execution=wasm
// --wasm-execution=compiled
// --heap-pages=4096
// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json
// --pallet=pallet_nft_fractionalization
// --chain=dev
// --header=./HEADER-APACHE2
// --output=./frame/nft-fractionalization/src/weights.rs
// --template=./.maintain/frame-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for pallet_nft_fractionalization.
pub trait WeightInfo {
fn fractionalize() -> Weight;
fn unify() -> Weight;
}
/// Weights for pallet_nft_fractionalization using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: Nfts Item (r:1 w:0)
/// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen)
/// Storage: Balances Holds (r:1 w:1)
/// Proof: Balances Holds (max_values: None, max_size: Some(83), added: 2558, mode: MaxEncodedLen)
/// Storage: Nfts Collection (r:1 w:1)
/// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen)
/// Storage: Nfts Attribute (r:1 w:1)
/// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen)
/// Storage: Assets Asset (r:1 w:1)
/// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen)
/// Storage: Assets Account (r:1 w:1)
/// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen)
/// Storage: System Account (r:1 w:1)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
/// Storage: Assets Metadata (r:1 w:1)
/// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen)
/// Storage: NftFractionalization NftToAsset (r:0 w:1)
/// Proof: NftFractionalization NftToAsset (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen)
fn fractionalize() -> Weight {
// Proof Size summary in bytes:
// Measured: `609`
// Estimated: `4326`
// Minimum execution time: 177_498_000 picoseconds.
Weight::from_parts(178_803_000, 4326)
.saturating_add(T::DbWeight::get().reads(8_u64))
.saturating_add(T::DbWeight::get().writes(8_u64))
}
/// Storage: NftFractionalization NftToAsset (r:1 w:1)
/// Proof: NftFractionalization NftToAsset (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen)
/// Storage: Assets Asset (r:1 w:1)
/// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen)
/// Storage: Assets Account (r:1 w:1)
/// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen)
/// Storage: Nfts Attribute (r:1 w:1)
/// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen)
/// Storage: Nfts Collection (r:1 w:1)
/// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen)
/// Storage: Nfts CollectionConfigOf (r:1 w:0)
/// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen)
/// Storage: Nfts ItemConfigOf (r:1 w:0)
/// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen)
/// Storage: Nfts Item (r:1 w:1)
/// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen)
/// Storage: Balances Holds (r:1 w:1)
/// Proof: Balances Holds (max_values: None, max_size: Some(83), added: 2558, mode: MaxEncodedLen)
/// Storage: Nfts Account (r:0 w:1)
/// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen)
/// Storage: Nfts ItemPriceOf (r:0 w:1)
/// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen)
/// Storage: Nfts PendingSwapOf (r:0 w:1)
/// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen)
fn unify() -> Weight {
// Proof Size summary in bytes:
// Measured: `1421`
// Estimated: `4326`
// Minimum execution time: 130_284_000 picoseconds.
Weight::from_parts(131_122_000, 4326)
.saturating_add(T::DbWeight::get().reads(9_u64))
.saturating_add(T::DbWeight::get().writes(10_u64))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
/// Storage: Nfts Item (r:1 w:0)
/// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen)
/// Storage: Balances Holds (r:1 w:1)
/// Proof: Balances Holds (max_values: None, max_size: Some(83), added: 2558, mode: MaxEncodedLen)
/// Storage: Nfts Collection (r:1 w:1)
/// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen)
/// Storage: Nfts Attribute (r:1 w:1)
/// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen)
/// Storage: Assets Asset (r:1 w:1)
/// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen)
/// Storage: Assets Account (r:1 w:1)
/// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen)
/// Storage: System Account (r:1 w:1)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
/// Storage: Assets Metadata (r:1 w:1)
/// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen)
/// Storage: NftFractionalization NftToAsset (r:0 w:1)
/// Proof: NftFractionalization NftToAsset (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen)
fn fractionalize() -> Weight {
// Proof Size summary in bytes:
// Measured: `609`
// Estimated: `4326`
// Minimum execution time: 177_498_000 picoseconds.
Weight::from_parts(178_803_000, 4326)
.saturating_add(RocksDbWeight::get().reads(8_u64))
.saturating_add(RocksDbWeight::get().writes(8_u64))
}
/// Storage: NftFractionalization NftToAsset (r:1 w:1)
/// Proof: NftFractionalization NftToAsset (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen)
/// Storage: Assets Asset (r:1 w:1)
/// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen)
/// Storage: Assets Account (r:1 w:1)
/// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen)
/// Storage: Nfts Attribute (r:1 w:1)
/// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen)
/// Storage: Nfts Collection (r:1 w:1)
/// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen)
/// Storage: Nfts CollectionConfigOf (r:1 w:0)
/// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen)
/// Storage: Nfts ItemConfigOf (r:1 w:0)
/// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen)
/// Storage: Nfts Item (r:1 w:1)
/// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen)
/// Storage: Balances Holds (r:1 w:1)
/// Proof: Balances Holds (max_values: None, max_size: Some(83), added: 2558, mode: MaxEncodedLen)
/// Storage: Nfts Account (r:0 w:1)
/// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen)
/// Storage: Nfts ItemPriceOf (r:0 w:1)
/// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen)
/// Storage: Nfts PendingSwapOf (r:0 w:1)
/// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen)
fn unify() -> Weight {
// Proof Size summary in bytes:
// Measured: `1421`
// Estimated: `4326`
// Minimum execution time: 130_284_000 picoseconds.
Weight::from_parts(131_122_000, 4326)
.saturating_add(RocksDbWeight::get().reads(9_u64))
.saturating_add(RocksDbWeight::get().writes(10_u64))
}
}