Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

[NFTs] Offchain mint #13158

Merged
merged 27 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bdf9f7d
Allow to mint with the pre-signed signatures
jsidorenko Jan 12, 2023
320582d
Another try
jsidorenko Jan 12, 2023
a92dc15
WIP: test encoder
jsidorenko Jan 13, 2023
124d872
Fix the deposits
jsidorenko Jan 17, 2023
bb91166
Refactoring + tests + benchmarks
jsidorenko Jan 17, 2023
9f7e563
Add sp-core/runtime-benchmarks
jsidorenko Jan 17, 2023
a4c7e79
Remove sp-core from dev deps
jsidorenko Jan 17, 2023
09f86aa
Enable full_crypto for benchmarks
jsidorenko Jan 17, 2023
dc9ff18
Typo
jsidorenko Jan 20, 2023
55eeb12
Fix
jsidorenko Jan 21, 2023
e8ea9ec
Update frame/nfts/src/mock.rs
jsidorenko Jan 21, 2023
5f27ced
Merge branch 'master' of https://github.com/paritytech/substrate into…
Jan 21, 2023
62edf5f
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_nfts
Jan 21, 2023
b119156
Add docs
jsidorenko Jan 22, 2023
781f834
Add attributes into the pre-signed object & track the deposit owner f…
jsidorenko Jan 31, 2023
839c37c
Update docs
jsidorenko Jan 31, 2023
4980270
Merge branch 'master' into js/offchain-mint
jsidorenko Jan 31, 2023
c2d7c99
Merge branch 'master' of https://github.com/paritytech/substrate into…
Jan 31, 2023
7120dd0
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_nfts
Jan 31, 2023
088dddd
Add the number of attributes provided to weights
jsidorenko Jan 31, 2023
ad7b0e1
Merge branch 'master' into js/offchain-mint
jsidorenko Feb 1, 2023
c53df9c
Apply suggestions
jsidorenko Feb 6, 2023
321daa5
Remove dead code
jsidorenko Feb 6, 2023
01c4d8d
Remove Copy
jsidorenko Feb 6, 2023
de9b190
Fix docs
jsidorenko Feb 6, 2023
c879e2f
Update frame/nfts/src/lib.rs
jsidorenko Feb 13, 2023
566d3c8
Update frame/nfts/src/lib.rs
jsidorenko Feb 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frame/democracy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ std = [
"sp-core/std",
]
runtime-benchmarks = [
"sp-core/full_crypto",
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
Expand Down
1 change: 0 additions & 1 deletion frame/nfts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ sp-std = { version = "5.0.0", default-features = false, path = "../../primitives

[dev-dependencies]
pallet-balances = { version = "4.0.0-dev", path = "../balances" }
sp-core = { version = "7.0.0", path = "../../primitives/core" }
sp-io = { version = "7.0.0", path = "../../primitives/io" }
sp-std = { version = "5.0.0", path = "../../primitives/std" }

Expand Down
36 changes: 36 additions & 0 deletions frame/nfts/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use frame_support::{
BoundedVec,
};
use frame_system::RawOrigin as SystemOrigin;
use sp_core::Pair;
use sp_runtime::traits::{Bounded, One};
use sp_std::prelude::*;

Expand Down Expand Up @@ -714,5 +715,40 @@ benchmarks_instance_pallet! {
}.into());
}

mint_pre_signed {
let caller_pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
let caller_signer = MultiSigner::Sr25519(caller_pair.public());
let caller = Nfts::<T, I>::signer_to_account(caller_signer.clone()).unwrap();
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
let caller_lookup = T::Lookup::unlookup(caller.clone());

let collection = T::Helper::collection(0);
let item = T::Helper::item(0);
assert_ok!(Nfts::<T, I>::force_create(
SystemOrigin::Root.into(),
caller_lookup.clone(),
default_collection_config::<T, I>()
));

let metadata = vec![0u8; T::StringLimit::get() as usize];
let mint_data = PreSignedMint {
collection,
item,
metadata: metadata.clone(),
only_account: None,
deadline: One::one(),
};
let message = Encode::encode(&mint_data);
let signature = MultiSignature::Sr25519(caller_pair.sign(&message));

let target: T::AccountId = account("target", 0, SEED);
T::Currency::make_free_balance_be(&target, DepositBalanceOf::<T, I>::max_value());
frame_system::Pallet::<T>::set_block_number(One::one());
}: _(SystemOrigin::Signed(target.clone()), mint_data, signature, caller_signer)
verify {
let metadata: BoundedVec<_, _> = metadata.try_into().unwrap();
assert_last_event::<T, I>(Event::ItemMetadataSet { collection, item, data: metadata }.into());
}

impl_benchmark_test_suite!(Nfts, crate::mock::new_test_ext(), crate::mock::Test);
}
10 changes: 9 additions & 1 deletion frame/nfts/src/common_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

//! Various pieces of common functionality.

use super::*;
use crate::*;
use frame_support::pallet_prelude::*;
use sp_runtime::traits::IdentifyAccount;

impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Get the owner of the item, if the item exists.
Expand All @@ -30,6 +32,12 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Collection::<T, I>::get(collection).map(|i| i.owner)
}

/// Convert signer into account id.
pub fn signer_to_account(signer: MultiSigner) -> Result<T::AccountId, DispatchError> {
Ok(T::AccountId::decode(&mut signer.into_account().as_ref())
jsidorenko marked this conversation as resolved.
Show resolved Hide resolved
.map_err(|_| Error::<T, I>::WrongPublic)?)
}

#[cfg(any(test, feature = "runtime-benchmarks"))]
pub fn set_next_id(id: T::CollectionId) {
NextCollectionId::<T, I>::set(Some(id));
Expand Down
40 changes: 40 additions & 0 deletions frame/nfts/src/features/create_delete_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,46 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Ok(())
}

pub(crate) fn do_mint_pre_signed(
mint_to: T::AccountId,
mint_data: PreSignedMintOf<T, I>,
signer: T::AccountId,
) -> DispatchResult {
let PreSignedMint { collection, item, metadata, deadline, only_account } = mint_data;
let metadata = Self::construct_metadata(metadata)?;

if let Some(account) = only_account {
ensure!(account == mint_to, Error::<T, I>::WrongOrigin);
}

let now = frame_system::Pallet::<T>::block_number();
ensure!(deadline >= now, Error::<T, I>::DeadlineExpired);

let collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
ensure!(collection_details.owner == signer, Error::<T, I>::NoPermission);

let item_config = ItemConfig { settings: Self::get_default_item_settings(&collection)? };
Self::do_mint(
collection,
item,
Some(mint_to.clone()),
mint_to.clone(),
item_config,
|_, _| Ok(()),
)?;
if !metadata.len().is_zero() {
Self::do_set_item_metadata(
Some(collection_details.owner),
collection,
item,
metadata,
Some(mint_to),
)?;
}
Ok(())
}

pub fn do_burn(
collection: T::CollectionId,
item: T::ItemId,
Expand Down
21 changes: 15 additions & 6 deletions frame/nfts/src/features/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,16 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
.saturating_add(T::MetadataDepositBase::get());
}

// the previous deposit was taken from the item's owner
if old_deposit.account.is_some() && maybe_depositor.is_none() {
T::Currency::unreserve(&old_deposit.account.unwrap(), old_deposit.amount);
T::Currency::reserve(&collection_details.owner, deposit)?;
let depositor = maybe_depositor.clone().unwrap_or(collection_details.owner.clone());
let old_depositor = old_deposit.account.unwrap_or(collection_details.owner.clone());

if depositor != old_depositor {
T::Currency::unreserve(&old_depositor, old_deposit.amount);
T::Currency::reserve(&depositor, deposit)?;
} else if deposit > old_deposit.amount {
T::Currency::reserve(&collection_details.owner, deposit - old_deposit.amount)?;
T::Currency::reserve(&depositor, deposit - old_deposit.amount)?;
} else if deposit < old_deposit.amount {
T::Currency::unreserve(&collection_details.owner, old_deposit.amount - deposit);
T::Currency::unreserve(&depositor, old_deposit.amount - deposit);
}

if maybe_depositor.is_none() {
Expand Down Expand Up @@ -191,4 +193,11 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Ok(())
})
}

/// A helper method to construct metadata.
pub fn construct_metadata(
metadata: Vec<u8>,
) -> Result<BoundedVec<u8, T::StringLimit>, DispatchError> {
Ok(BoundedVec::try_from(metadata).map_err(|_| Error::<T, I>::IncorrectMetadata)?)
}
}
7 changes: 7 additions & 0 deletions frame/nfts/src/features/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Ok(config)
}

pub(crate) fn get_default_item_settings(
collection_id: &T::CollectionId,
) -> Result<ItemSettings, DispatchError> {
let collection_config = Self::get_collection_config(collection_id)?;
Ok(collection_config.mint_settings.default_item_settings)
}

pub(crate) fn is_pallet_feature_enabled(feature: PalletFeature) -> bool {
let features = T::Features::get();
return features.is_enabled(feature)
Expand Down
35 changes: 30 additions & 5 deletions frame/nfts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ use frame_support::traits::{
use frame_system::Config as SystemConfig;
use sp_runtime::{
traits::{Saturating, StaticLookup, Zero},
RuntimeDebug,
MultiSignature, MultiSigner, RuntimeDebug,
};
use sp_std::prelude::*;

Expand All @@ -67,6 +67,7 @@ pub mod pallet {
use super::*;
use frame_support::{pallet_prelude::*, traits::ExistenceRequirement};
use frame_system::pallet_prelude::*;
use sp_runtime::traits::{IdentifyAccount, Verify};

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
Expand Down Expand Up @@ -591,6 +592,14 @@ pub mod pallet {
AlreadyClaimed,
/// The provided data is incorrect.
IncorrectData,
/// The extrinsic should be sent by another origin.
jsidorenko marked this conversation as resolved.
Show resolved Hide resolved
WrongOrigin,
/// Unable to get the account id from the provided public key.
WrongPublic,
jsidorenko marked this conversation as resolved.
Show resolved Hide resolved
/// The provided signature is incorrect.
WrongSignature,
/// The provided metadata might be too long.
IncorrectMetadata,
}

#[pallet::call]
Expand Down Expand Up @@ -742,10 +751,8 @@ pub mod pallet {
) -> DispatchResult {
let caller = ensure_signed(origin)?;
let mint_to = T::Lookup::lookup(mint_to)?;

let collection_config = Self::get_collection_config(&collection)?;
let item_settings = collection_config.mint_settings.default_item_settings;
let item_config = ItemConfig { settings: item_settings };
let item_config =
ItemConfig { settings: Self::get_default_item_settings(&collection)? };

Self::do_mint(
collection,
Expand Down Expand Up @@ -1768,6 +1775,24 @@ pub mod pallet {
witness_price,
)
}

#[pallet::call_index(37)]
#[pallet::weight(T::WeightInfo::mint_pre_signed())]
pub fn mint_pre_signed(
origin: OriginFor<T>,
data: PreSignedMintOf<T, I>,
signature: MultiSignature,
signer: MultiSigner,
jsidorenko marked this conversation as resolved.
Show resolved Hide resolved
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let msg = Encode::encode(&data);
Copy link
Member

Choose a reason for hiding this comment

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

I think it should be generally Okay to sign the encoded PreSignedMint.
Sr25519 Substrate signatures have substrate as Merlin context, not sure about the other signature schemes. We could still prefix the payload with nfts, just to be sure.
@burdges

ensure!(
signature.verify(&*msg, &signer.clone().into_account()),
Error::<T, I>::WrongSignature
);
let signer_account = Self::signer_to_account(signer)?;
Self::do_mint_pre_signed(origin, data, signer_account)
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion frame/nfts/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
MultiSignature,
};

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
Expand Down Expand Up @@ -87,7 +88,7 @@ impl pallet_balances::Config for Test {
parameter_types! {
pub storage Features: PalletFeatures = PalletFeatures::all_enabled();
}

pub type Signature = MultiSignature;
jsidorenko marked this conversation as resolved.
Show resolved Hide resolved
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type CollectionId = u32;
Expand Down
Loading