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

[NFTs] Update attributes with offchain signature #13390

Merged
merged 31 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 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
603a194
Support pre-signed attributes
jsidorenko Feb 1, 2023
30b26a7
Update docs
jsidorenko Feb 2, 2023
22c176d
Merge branch 'master' into js/offchain-attributes
jsidorenko Feb 14, 2023
2c43623
Fix merge artifacts
jsidorenko Feb 14, 2023
1a9fdee
Update docs
jsidorenko Feb 15, 2023
2056b91
Add more tests
jsidorenko Feb 15, 2023
d519049
Merge branch 'master' of https://github.com/paritytech/substrate into…
Feb 15, 2023
0032b2e
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_nfts
Feb 15, 2023
5e81316
Update frame/nfts/src/types.rs
jsidorenko Feb 22, 2023
72bcea2
Update types.rs
jsidorenko Feb 22, 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
50 changes: 50 additions & 0 deletions frame/nfts/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -773,5 +773,55 @@ benchmarks_instance_pallet! {
assert_last_event::<T, I>(Event::ItemMetadataSet { collection, item, data: metadata }.into());
}

set_attributes_pre_signed {
let n in 0 .. T::MaxAttributesPerCall::get() as u32;
let (collection, _, _) = create_collection::<T, I>();

let item_owner: T::AccountId = account("item_owner", 0, SEED);
let item_owner_lookup = T::Lookup::unlookup(item_owner.clone());

let signer_public = sr25519_generate(0.into(), None);
let signer: T::AccountId = MultiSigner::Sr25519(signer_public).into_account().into();

T::Currency::make_free_balance_be(&item_owner, DepositBalanceOf::<T, I>::max_value());

let item = T::Helper::item(0);
assert_ok!(Nfts::<T, I>::force_mint(
SystemOrigin::Root.into(),
collection,
item,
item_owner_lookup.clone(),
default_item_config(),
));

let mut attributes = vec![];
let attribute_value = vec![0u8; T::ValueLimit::get() as usize];
for i in 0..n {
let attribute_key = make_filled_vec(i as u16, T::KeyLimit::get() as usize);
attributes.push((attribute_key, attribute_value.clone()));
}
let pre_signed_data = PreSignedAttributes {
collection,
item,
attributes,
namespace: AttributeNamespace::Account(signer.clone()),
deadline: One::one(),
};
let message = Encode::encode(&pre_signed_data);
let signature = MultiSignature::Sr25519(sr25519_sign(0.into(), &signer_public, &message).unwrap());

frame_system::Pallet::<T>::set_block_number(One::one());
}: _(SystemOrigin::Signed(item_owner.clone()), pre_signed_data, signature.into(), signer.clone())
verify {
assert_last_event::<T, I>(
Event::PreSignedAttributesSet {
collection,
item,
namespace: AttributeNamespace::Account(signer.clone()),
}
.into(),
);
}

impl_benchmark_test_suite!(Nfts, crate::mock::new_test_ext(), crate::mock::Test);
}
53 changes: 53 additions & 0 deletions frame/nfts/src/features/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,59 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Ok(())
}

pub(crate) fn do_set_attributes_pre_signed(
origin: T::AccountId,
data: PreSignedAttributesOf<T, I>,
signer: T::AccountId,
) -> DispatchResult {
let PreSignedAttributes { collection, item, attributes, namespace, deadline } = data;

ensure!(
attributes.len() <= T::MaxAttributesPerCall::get() as usize,
Error::<T, I>::MaxAttributesLimitReached
);

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

let item_details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(item_details.owner == origin, Error::<T, I>::NoPermission);

// Only the CollectionOwner and Account() namespaces could be updated in this way.
// For the Account() namespace we check and set the approval if it wasn't set before.
match &namespace {
AttributeNamespace::CollectionOwner => {},
AttributeNamespace::Account(account) => {
ensure!(account == &signer, Error::<T, I>::NoPermission);
let approvals = ItemAttributesApprovalsOf::<T, I>::get(&collection, &item);
if !approvals.contains(account) {
Self::do_approve_item_attributes(
jsidorenko marked this conversation as resolved.
Show resolved Hide resolved
origin.clone(),
collection,
item,
account.clone(),
)?;
}
},
_ => return Err(Error::<T, I>::WrongNamespace.into()),
}

for (key, value) in attributes {
Self::do_set_attribute(
signer.clone(),
collection,
Some(item),
namespace.clone(),
Self::construct_attribute_key(key)?,
Self::construct_attribute_value(value)?,
origin.clone(),
)?;
}
Self::deposit_event(Event::PreSignedAttributesSet { collection, item, namespace });
Ok(())
}

pub(crate) fn do_clear_attribute(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
Expand Down
35 changes: 35 additions & 0 deletions frame/nfts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,12 @@ pub mod pallet {
price: Option<PriceWithDirection<ItemPrice<T, I>>>,
deadline: <T as SystemConfig>::BlockNumber,
},
/// New attributes have been set for an `item` of the `collection`.
PreSignedAttributesSet {
collection: T::CollectionId,
item: T::ItemId,
namespace: AttributeNamespace<T::AccountId>,
},
}

#[pallet::error]
Expand Down Expand Up @@ -614,6 +620,8 @@ pub mod pallet {
IncorrectMetadata,
/// Can't set more attributes per one call.
MaxAttributesLimitReached,
/// The provided namespace isn't supported in this call.
WrongNamespace,
}

#[pallet::call]
Expand Down Expand Up @@ -1824,6 +1832,33 @@ pub mod pallet {
ensure!(signature.verify(&*msg, &signer), Error::<T, I>::WrongSignature);
Self::do_mint_pre_signed(origin, mint_data, signer)
}

/// Set attributes for an item by providing the pre-signed approval.
///
/// Origin must be Signed and must be an owner of the `data.item`.
///
/// - `data`: The pre-signed approval that consists of the information about the item,
/// attributes to update and until what block number.
/// - `signature`: The signature of the `data` object.
/// - `signer`: The `data` object's signer. Should be an owner of the collection for the
/// `CollectionOwner` namespace.
///
/// Emits `AttributeSet` for each provided attribute.
/// Emits `ItemAttributesApprovalAdded` if the approval wasn't set before.
/// Emits `PreSignedAttributesSet` on success.
#[pallet::call_index(38)]
#[pallet::weight(T::WeightInfo::set_attributes_pre_signed(data.attributes.len() as u32))]
pub fn set_attributes_pre_signed(
origin: OriginFor<T>,
data: PreSignedAttributesOf<T, I>,
signature: T::OffchainSignature,
signer: T::AccountId,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let msg = Encode::encode(&data);
ensure!(signature.verify(&*msg, &signer), Error::<T, I>::WrongSignature);
Self::do_set_attributes_pre_signed(origin, data, signer)
}
}
}

Expand Down
Loading