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

[Uniques V2] Atomic NFTs swap #12285

Merged
merged 34 commits into from
Oct 5, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3086c76
Atomic NFTs swap
jsidorenko Sep 16, 2022
12b756f
Fmt
jsidorenko Sep 16, 2022
8083eca
Merge branch 'js/uniques-v2-main-branch' into js/uniques-v2-swaps
jsidorenko Sep 19, 2022
5575d3f
Merge branch 'js/uniques-v2-main-branch' into js/uniques-v2-swaps
jsidorenko Sep 19, 2022
5564a52
Merge branch 'js/uniques-v2-main-branch' into js/uniques-v2-swaps
jsidorenko Sep 19, 2022
fc1024a
Fix benchmark
jsidorenko Sep 20, 2022
a2955be
Rename swap -> atomic_swap
jsidorenko Sep 20, 2022
bdc0538
Update target balance
jsidorenko Sep 20, 2022
a28cbcc
Rollback
jsidorenko Sep 20, 2022
504b7d5
Fix
jsidorenko Sep 20, 2022
1ccf996
".git/.scripts/bench-bot.sh" pallet dev pallet_nfts
Sep 20, 2022
ca2d1ec
Make desired item optional
jsidorenko Sep 29, 2022
78f4570
Apply suggestions
jsidorenko Sep 30, 2022
7c15814
Update frame/nfts/src/features/atomic_swap.rs
jsidorenko Sep 30, 2022
6232aa4
Rename fields
jsidorenko Sep 30, 2022
9acfb5c
Optimisation
jsidorenko Sep 30, 2022
8d6cbda
Add a comment
jsidorenko Sep 30, 2022
ee88eaa
Merge branch 'js/uniques-v2-main-branch' into js/uniques-v2-swaps
jsidorenko Sep 30, 2022
2ce1ea3
deadline -> maybe_deadline
jsidorenko Sep 30, 2022
99d1eb5
Add docs
jsidorenko Sep 30, 2022
b404705
Change comments
jsidorenko Sep 30, 2022
cccebd3
Add price direction field
jsidorenko Sep 30, 2022
2202f0d
".git/.scripts/bench-bot.sh" pallet dev pallet_nfts
Sep 30, 2022
2688fe5
Wrap price and direction
jsidorenko Oct 4, 2022
2dd02af
Fix benchmarks
jsidorenko Oct 4, 2022
e565989
Use ensure! instead of if {}
jsidorenko Oct 4, 2022
82841fc
Make duration param mandatory and limit it to MaxDeadlineDuration
jsidorenko Oct 4, 2022
7fbf339
Make the code safer
jsidorenko Oct 4, 2022
e956594
Fix clippy
jsidorenko Oct 4, 2022
b52a615
Chore
jsidorenko Oct 4, 2022
3b41ee1
Remove unused vars
jsidorenko Oct 5, 2022
239bf17
try
jsidorenko Oct 5, 2022
d086e81
try 2
jsidorenko Oct 5, 2022
c2afaa4
try 3
jsidorenko Oct 5, 2022
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
2 changes: 2 additions & 0 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1473,6 +1473,7 @@ parameter_types! {
pub const ValueLimit: u32 = 256;
pub const ApprovalsLimit: u32 = 20;
pub const MaxTips: u32 = 10;
pub const MaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS;
}

impl pallet_uniques::Config for Runtime {
Expand Down Expand Up @@ -1512,6 +1513,7 @@ impl pallet_nfts::Config for Runtime {
type ValueLimit = ValueLimit;
type ApprovalsLimit = ApprovalsLimit;
type MaxTips = MaxTips;
type MaxDeadlineDuration = MaxDeadlineDuration;
type WeightInfo = pallet_nfts::weights::SubstrateWeight<Runtime>;
#[cfg(feature = "runtime-benchmarks")]
type Helper = ();
Expand Down
21 changes: 12 additions & 9 deletions frame/nfts/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,16 +483,17 @@ benchmarks_instance_pallet! {
let price = ItemPrice::<T, I>::from(100u32);
let price_direction = PriceDirection::Receive;
let price_with_direction = PriceWithDirection { amount: price, direction: price_direction };
let duration = T::BlockNumber::max_value();
}: _(SystemOrigin::Signed(caller.clone()), collection, item1, collection, Some(item2), Some(price_with_direction.clone()), Some(duration))
let duration = T::MaxDeadlineDuration::get();
}: _(SystemOrigin::Signed(caller.clone()), collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration)
verify {
let expect_deadline = duration + frame_system::Pallet::<T>::block_number();
Copy link
Member

Choose a reason for hiding this comment

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

Maybe use a saturating_add here and below in case someone uses u64::MAX as duration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

assert_last_event::<T, I>(Event::SwapCreated {
offered_collection: collection,
offered_item: item1,
desired_collection: collection,
desired_item: Some(item2),
price: Some(price_with_direction),
deadline: Some(duration),
deadline: expect_deadline,
}.into());
}

Expand All @@ -502,19 +503,20 @@ benchmarks_instance_pallet! {
let (item2, ..) = mint_item::<T, I>(1);
let price = ItemPrice::<T, I>::from(100u32);
let origin = SystemOrigin::Signed(caller.clone()).into();
let duration = T::BlockNumber::max_value();
let duration = T::MaxDeadlineDuration::get();
let price_direction = PriceDirection::Receive;
let price_with_direction = PriceWithDirection { amount: price, direction: price_direction };
Nfts::<T, I>::create_swap(origin, collection, item1, collection, Some(item2), Some(price_with_direction.clone()), Some(duration))?;
Nfts::<T, I>::create_swap(origin, collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration)?;
}: _(SystemOrigin::Signed(caller.clone()), collection, item1)
verify {
let expect_deadline = duration + frame_system::Pallet::<T>::block_number();
assert_last_event::<T, I>(Event::SwapCancelled {
offered_collection: collection,
offered_item: item1,
desired_collection: collection,
desired_item: Some(item2),
price: Some(price_with_direction),
deadline: Some(duration),
deadline: expect_deadline,
}.into());
}

Expand All @@ -525,7 +527,7 @@ benchmarks_instance_pallet! {
let price = ItemPrice::<T, I>::from(0u32);
let price_direction = PriceDirection::Receive;
let price_with_direction = PriceWithDirection { amount: price, direction: price_direction };
let duration = T::BlockNumber::max_value();
let duration = T::MaxDeadlineDuration::get();
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
let origin = SystemOrigin::Signed(caller.clone());
Expand All @@ -537,10 +539,11 @@ benchmarks_instance_pallet! {
collection,
Some(item2),
Some(price_with_direction.clone()),
Some(duration),
duration,
)?;
}: _(SystemOrigin::Signed(target.clone()), collection, item2, collection, item1, Some(price_with_direction.clone()))
verify {
let expect_deadline = duration + frame_system::Pallet::<T>::block_number();
assert_last_event::<T, I>(Event::SwapClaimed {
sent_collection: collection,
sent_item: item2,
Expand All @@ -549,7 +552,7 @@ benchmarks_instance_pallet! {
received_item: item1,
received_item_owner: caller,
price: Some(price_with_direction),
deadline: Some(duration),
deadline: expect_deadline,
}.into());
}

Expand Down
26 changes: 10 additions & 16 deletions frame/nfts/src/features/atomic_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
desired_collection_id: T::CollectionId,
maybe_desired_item_id: Option<T::ItemId>,
maybe_price: Option<PriceWithDirection<ItemPrice<T, I>>>,
maybe_duration: Option<<T as SystemConfig>::BlockNumber>,
duration: <T as SystemConfig>::BlockNumber,
) -> DispatchResult {
ensure!(duration <= T::MaxDeadlineDuration::get(), Error::<T, I>::WrongDuration);

let item = Item::<T, I>::get(&offered_collection_id, &offered_item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
ensure!(item.owner == caller, Error::<T, I>::NoPermission);
Expand All @@ -47,7 +49,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
};

let now = frame_system::Pallet::<T>::block_number();
let maybe_deadline = maybe_duration.map(|d| d.saturating_add(now));
let deadline = duration.saturating_add(now);

PendingSwapOf::<T, I>::insert(
ggwpez marked this conversation as resolved.
Show resolved Hide resolved
&offered_collection_id,
Expand All @@ -56,7 +58,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
desired_collection: desired_collection_id,
desired_item: maybe_desired_item_id,
price: maybe_price.clone(),
deadline: maybe_deadline,
deadline: deadline.clone(),
},
);

Expand All @@ -66,7 +68,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
desired_collection: desired_collection_id,
desired_item: maybe_desired_item_id,
price: maybe_price,
deadline: maybe_deadline,
deadline,
});

Ok(())
Expand All @@ -80,14 +82,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
let swap = PendingSwapOf::<T, I>::get(&offered_collection_id, &offered_item_id)
.ok_or(Error::<T, I>::UnknownSwap)?;

let is_past_deadline = if let Some(deadline) = swap.deadline {
let now = frame_system::Pallet::<T>::block_number();
now > deadline
} else {
false
};

if !is_past_deadline {
let now = frame_system::Pallet::<T>::block_number();
if swap.deadline > now {
let item = Item::<T, I>::get(&offered_collection_id, &offered_item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
ensure!(item.owner == caller, Error::<T, I>::NoPermission);
Expand Down Expand Up @@ -132,10 +128,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
ensure!(desired_item == send_item_id, Error::<T, I>::UnknownSwap);
}

if let Some(deadline) = swap.deadline {
let now = frame_system::Pallet::<T>::block_number();
ensure!(now <= deadline, Error::<T, I>::DeadlineExpired);
}
let now = frame_system::Pallet::<T>::block_number();
ensure!(now <= swap.deadline, Error::<T, I>::DeadlineExpired);

if let Some(ref price) = swap.price {
match price.direction {
Expand Down
16 changes: 11 additions & 5 deletions frame/nfts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ pub mod pallet {
#[pallet::constant]
type MaxTips: Get<u32>;

/// The max duration in blocks for deadlines.
#[pallet::constant]
type MaxDeadlineDuration: Get<<Self as SystemConfig>::BlockNumber>;

#[cfg(feature = "runtime-benchmarks")]
/// A set of helper functions for benchmarking.
type Helper: BenchmarkHelper<Self::CollectionId, Self::ItemId>;
Expand Down Expand Up @@ -457,7 +461,7 @@ pub mod pallet {
desired_collection: T::CollectionId,
desired_item: Option<T::ItemId>,
price: Option<PriceWithDirection<ItemPrice<T, I>>>,
deadline: Option<<T as SystemConfig>::BlockNumber>,
deadline: <T as SystemConfig>::BlockNumber,
},
/// The swap was cancelled.
SwapCancelled {
Expand All @@ -466,7 +470,7 @@ pub mod pallet {
desired_collection: T::CollectionId,
desired_item: Option<T::ItemId>,
price: Option<PriceWithDirection<ItemPrice<T, I>>>,
deadline: Option<<T as SystemConfig>::BlockNumber>,
deadline: <T as SystemConfig>::BlockNumber,
},
/// The swap has been claimed.
SwapClaimed {
Expand All @@ -477,7 +481,7 @@ pub mod pallet {
received_item: T::ItemId,
received_item_owner: T::AccountId,
price: Option<PriceWithDirection<ItemPrice<T, I>>>,
deadline: Option<<T as SystemConfig>::BlockNumber>,
deadline: <T as SystemConfig>::BlockNumber,
},
}

Expand Down Expand Up @@ -527,6 +531,8 @@ pub mod pallet {
ReachedApprovalLimit,
/// The deadline has already expired.
DeadlineExpired,
/// The duration provided should be less or equal to MaxDeadlineDuration.
WrongDuration,
}

impl<T: Config<I>, I: 'static> Pallet<T, I> {
Expand Down Expand Up @@ -1713,7 +1719,7 @@ pub mod pallet {
desired_collection: T::CollectionId,
maybe_desired_item: Option<T::ItemId>,
maybe_price: Option<PriceWithDirection<ItemPrice<T, I>>>,
maybe_duration: Option<<T as SystemConfig>::BlockNumber>,
duration: <T as SystemConfig>::BlockNumber,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
Self::do_create_swap(
Expand All @@ -1723,7 +1729,7 @@ pub mod pallet {
desired_collection,
maybe_desired_item,
maybe_price,
maybe_duration,
duration,
)
}

Expand Down
1 change: 1 addition & 0 deletions frame/nfts/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ impl Config for Test {
type ValueLimit = ConstU32<50>;
type ApprovalsLimit = ConstU32<10>;
type MaxTips = ConstU32<10>;
type MaxDeadlineDuration = ConstU64<10000>;
type WeightInfo = ();
#[cfg(feature = "runtime-benchmarks")]
type Helper = ();
Expand Down
42 changes: 30 additions & 12 deletions frame/nfts/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
//! Tests for Nfts pallet.

use crate::{mock::*, Event, *};
use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::Currency};
use frame_support::{
assert_noop, assert_ok,
dispatch::Dispatchable,
traits::{Currency, Get},
};
use pallet_balances::Error as BalancesError;
use sp_std::prelude::*;

Expand Down Expand Up @@ -1134,7 +1138,7 @@ fn create_cancel_swap_should_work() {
collection_id,
Some(item_2 + 1),
Some(price_with_direction.clone()),
Some(duration),
duration,
),
Error::<Test>::UnknownItem
);
Expand All @@ -1146,34 +1150,48 @@ fn create_cancel_swap_should_work() {
collection_id + 1,
None,
Some(price_with_direction.clone()),
Some(duration),
duration,
),
Error::<Test>::UnknownCollection
);

let max_duration: u64 = <Test as Config>::MaxDeadlineDuration::get();
assert_noop!(
Nfts::create_swap(
RuntimeOrigin::signed(user_id),
collection_id,
item_1,
collection_id,
Some(item_2),
Some(price_with_direction.clone()),
max_duration.saturating_add(1),
),
Error::<Test>::WrongDuration
);

assert_ok!(Nfts::create_swap(
RuntimeOrigin::signed(user_id),
collection_id,
item_1,
collection_id,
Some(item_2),
Some(price_with_direction.clone()),
Some(duration),
duration,
));

let swap = PendingSwapOf::<Test>::get(collection_id, item_1).unwrap();
assert_eq!(swap.desired_collection, collection_id);
assert_eq!(swap.desired_item, Some(item_2));
assert_eq!(swap.price, Some(price_with_direction.clone()));
assert_eq!(swap.deadline, Some(expect_deadline));
assert_eq!(swap.deadline, expect_deadline);

assert!(events().contains(&Event::<Test>::SwapCreated {
offered_collection: collection_id,
offered_item: item_1,
desired_collection: collection_id,
desired_item: Some(item_2),
price: Some(price_with_direction.clone()),
deadline: Some(expect_deadline),
deadline: expect_deadline,
}));

// validate we can cancel the swap
Expand All @@ -1184,7 +1202,7 @@ fn create_cancel_swap_should_work() {
desired_collection: collection_id,
desired_item: Some(item_2),
price: Some(price_with_direction.clone()),
deadline: Some(expect_deadline),
deadline: expect_deadline,
}));
assert!(!PendingSwapOf::<Test>::contains_key(collection_id, item_1));

Expand All @@ -1196,7 +1214,7 @@ fn create_cancel_swap_should_work() {
collection_id,
Some(item_2),
Some(price_with_direction.clone()),
Some(duration),
duration,
));
assert_noop!(
Nfts::cancel_swap(RuntimeOrigin::signed(user_id + 1), collection_id, item_1),
Expand All @@ -1213,7 +1231,7 @@ fn create_cancel_swap_should_work() {
collection_id,
None,
Some(price_with_direction),
Some(duration),
duration,
));

let swap = PendingSwapOf::<Test>::get(collection_id, item_1).unwrap();
Expand Down Expand Up @@ -1259,7 +1277,7 @@ fn claim_swap_should_work() {
collection_id,
Some(item_2),
Some(price_with_direction.clone()),
Some(duration),
duration,
));

// validate the deadline
Expand Down Expand Up @@ -1365,7 +1383,7 @@ fn claim_swap_should_work() {
received_item: item_1,
received_item_owner: user_1,
price: Some(price_with_direction.clone()),
deadline: Some(deadline),
deadline,
}));

// validate the optional desired_item param and another price direction
Expand All @@ -1381,7 +1399,7 @@ fn claim_swap_should_work() {
collection_id,
None,
Some(price_with_direction.clone()),
Some(duration),
duration,
));
assert_ok!(Nfts::claim_swap(
RuntimeOrigin::signed(user_2),
Expand Down
2 changes: 1 addition & 1 deletion frame/nfts/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ pub struct PendingSwap<CollectionId, ItemId, ItemPriceWithDirection, Deadline> {
/// A price for the desired `item` with the direction.
pub(super) price: Option<ItemPriceWithDirection>,
/// An optional deadline for the swap.
pub(super) deadline: Option<Deadline>,
pub(super) deadline: Deadline,
}

#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
Expand Down