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

[NFT-Staking] Attribute namespace support #338

Merged
merged 2 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 8 additions & 6 deletions pallets/ajuna-nft-staking/benchmarking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,23 +241,25 @@ fn contract_with<T: Config>(
claim_duration: 1_u32.unique_saturated_into(),
stake_duration: 1_u32.unique_saturated_into(),
stake_clauses: (0..num_stake_clauses)
.map(|i| {
Clause::HasAttributeWithValue(
.map(|i| ContractClause {
namespace: AttributeNamespace::Pallet,
clause: Clause::HasAttributeWithValue(
CollectionIdOf::<T>::unique_saturated_from(stake_collection),
T::BenchmarkHelper::contract_key(i),
T::BenchmarkHelper::contract_value(ATTRIBUTE_VALUE),
)
),
})
.collect::<Vec<_>>()
.try_into()
.unwrap(),
fee_clauses: (num_stake_clauses..num_stake_clauses + num_fee_clauses)
.map(|i| {
Clause::HasAttributeWithValue(
.map(|i| ContractClause {
namespace: AttributeNamespace::Pallet,
clause: Clause::HasAttributeWithValue(
CollectionIdOf::<T>::unique_saturated_from(fee_collection),
T::BenchmarkHelper::contract_key(i),
T::BenchmarkHelper::contract_value(ATTRIBUTE_VALUE),
)
),
})
.collect::<Vec<_>>()
.try_into()
Expand Down
51 changes: 45 additions & 6 deletions pallets/ajuna-nft-staking/src/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ use scale_info::TypeInfo;
use sp_runtime::BoundedVec;
use sp_std::fmt::Debug;

/// Attribute namespaces for non-fungible tokens.
/// Based on the logic for
/// https://github.com/paritytech/substrate/blob/polkadot-v0.9.42/frame/nfts/src/types.rs#L326
#[derive(Debug, Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum AttributeNamespace {
Pallet,
CollectionOwner,
}

/// Type to represent the collection and item IDs of an NFT.
#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct NftId<CollectionId, ItemId>(pub CollectionId, pub ItemId);
Expand All @@ -30,6 +39,36 @@ pub enum Reward<Balance, CollectionId, ItemId> {
Nft(NftId<CollectionId, ItemId>),
}

#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct ContractClause<CollectionId, AttributeKey, AttributeValue> {
pub namespace: AttributeNamespace,
pub clause: Clause<CollectionId, AttributeKey, AttributeValue>,
}

impl<CollectionId, AttributeKey, AttributeValue>
ContractClause<CollectionId, AttributeKey, AttributeValue>
{
pub fn evaluate_for<AccountId, NftInspector, ItemId>(
&self,
address: &NftId<CollectionId, ItemId>,
) -> bool
where
NftInspector: Inspect<AccountId, CollectionId = CollectionId, ItemId = ItemId>,
CollectionId: PartialEq,
ItemId: PartialEq,
AttributeKey: Encode,
AttributeValue: Encode + Decode + PartialEq,
{
let evaluate_fn = match self.namespace {
AttributeNamespace::Pallet => NftInspector::system_attribute,
AttributeNamespace::CollectionOwner => NftInspector::attribute,
};

self.clause
.evaluate_for::<AccountId, NftInspector, ItemId, _>(address, evaluate_fn)
}
}

#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum Clause<CollectionId, AttributeKey, AttributeValue> {
HasAttribute(CollectionId, AttributeKey),
Expand All @@ -39,16 +78,18 @@ pub enum Clause<CollectionId, AttributeKey, AttributeValue> {
impl<CollectionId, AttributeKey, AttributeValue>
Clause<CollectionId, AttributeKey, AttributeValue>
{
pub fn evaluate_for<AccountId, NftInspector, ItemId>(
pub fn evaluate_for<AccountId, NftInspector, ItemId, Fn>(
&self,
address: &NftId<CollectionId, ItemId>,
evaluate_fn: Fn,
) -> bool
where
NftInspector: Inspect<AccountId, CollectionId = CollectionId, ItemId = ItemId>,
CollectionId: PartialEq,
ItemId: PartialEq,
AttributeKey: Encode,
AttributeValue: Encode + Decode + PartialEq,
Fn: FnOnce(&CollectionId, &ItemId, &[u8]) -> Option<Vec<u8>>,
{
let clause_collection_id = match self {
Clause::HasAttribute(collection_id, _) => collection_id,
Expand All @@ -58,11 +99,9 @@ impl<CollectionId, AttributeKey, AttributeValue>
clause_collection_id == collection_id &&
(match self {
Clause::HasAttribute(_, key) =>
NftInspector::system_attribute(collection_id, item_id, &key.encode()).is_some(),
evaluate_fn(collection_id, item_id, &key.encode()).is_some(),
Clause::HasAttributeWithValue(_, key, expected_value) =>
if let Some(value) =
NftInspector::system_attribute(collection_id, item_id, &key.encode())
{
if let Some(value) = evaluate_fn(collection_id, item_id, &key.encode()) {
expected_value.eq(&AttributeValue::decode(&mut value.as_slice()).unwrap())
} else {
false
Expand All @@ -72,7 +111,7 @@ impl<CollectionId, AttributeKey, AttributeValue>
}

type BoundedClauses<CollectionId, AttributeKey, AttributeValue> =
BoundedVec<Clause<CollectionId, AttributeKey, AttributeValue>, ConstU32<100>>;
BoundedVec<ContractClause<CollectionId, AttributeKey, AttributeValue>, ConstU32<100>>;

/// Specification for a staking contract, in short it's a list of criteria to be fulfilled,
/// with a given reward after the duration is complete.
Expand Down
16 changes: 8 additions & 8 deletions pallets/ajuna-nft-staking/src/tests/ext/accept.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ fn works() {
let contract = Contract::default()
.reward(Reward::Tokens(123))
.stake_duration(stake_duration)
.stake_clauses(stake_clauses.clone())
.fee_clauses(fee_clauses.clone());
.stake_clauses(AttributeNamespace::Pallet, stake_clauses.clone())
.fee_clauses(AttributeNamespace::Pallet, fee_clauses.clone());
let contract_id = H256::random();

let stakes = MockMints::from(MockClauses(stake_clauses));
Expand Down Expand Up @@ -165,8 +165,8 @@ fn rejects_when_contract_is_already_accepted() {
let contract = Contract::default()
.reward(Reward::Tokens(123))
.stake_duration(123)
.stake_clauses(stake_clauses.clone())
.fee_clauses(fee_clauses.clone());
.stake_clauses(AttributeNamespace::Pallet, stake_clauses.clone())
.fee_clauses(AttributeNamespace::Pallet, fee_clauses.clone());
let contract_id = H256::random();

let alice_stakes = MockMints::from(MockClauses(stake_clauses));
Expand Down Expand Up @@ -255,8 +255,8 @@ fn rejects_unowned_stakes() {
let contract = Contract::default()
.reward(Reward::Tokens(123))
.stake_duration(123)
.stake_clauses(stake_clauses.clone())
.fee_clauses(fee_clauses.clone());
.stake_clauses(AttributeNamespace::Pallet, stake_clauses.clone())
.fee_clauses(AttributeNamespace::Pallet, fee_clauses.clone());
let contract_id = H256::random();

let stakes = MockMints::from(MockClauses(stake_clauses));
Expand Down Expand Up @@ -315,7 +315,7 @@ fn rejects_unfulfilling_stakes() {
let contract = Contract::default()
.reward(Reward::Tokens(123))
.stake_duration(123)
.stake_clauses(stake_clauses.clone());
.stake_clauses(AttributeNamespace::Pallet, stake_clauses.clone());
let contract_id = H256::random();

let mut stakes = MockMints::from(MockClauses(stake_clauses));
Expand Down Expand Up @@ -350,7 +350,7 @@ fn rejects_unfulfilling_fees() {
let contract = Contract::default()
.reward(Reward::Tokens(123))
.stake_duration(123)
.fee_clauses(fee_clauses.clone());
.fee_clauses(AttributeNamespace::Pallet, fee_clauses.clone());
let contract_id = H256::random();

let mut fees = MockMints::from(MockClauses(fee_clauses));
Expand Down
8 changes: 4 additions & 4 deletions pallets/ajuna-nft-staking/src/tests/ext/cancel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ fn works_with_token_reward() {
let contract = Contract::default()
.reward(Reward::Tokens(reward))
.stake_duration(stake_duration)
.stake_clauses(stake_clauses.clone())
.fee_clauses(fee_clauses.clone())
.stake_clauses(AttributeNamespace::Pallet, stake_clauses.clone())
.fee_clauses(AttributeNamespace::Pallet, fee_clauses.clone())
.cancel_fee(cancellation_fee);
let contract_id = H256::random();

Expand Down Expand Up @@ -88,8 +88,8 @@ fn works_with_nft_reward() {
let contract = Contract::default()
.reward(Reward::Nft(reward_addr.clone()))
.stake_duration(stake_duration)
.stake_clauses(stake_clauses.clone())
.fee_clauses(fee_clauses.clone())
.stake_clauses(AttributeNamespace::Pallet, stake_clauses.clone())
.fee_clauses(AttributeNamespace::Pallet, fee_clauses.clone())
.cancel_fee(cancellation_fee);
let contract_id = H256::random();

Expand Down
8 changes: 4 additions & 4 deletions pallets/ajuna-nft-staking/src/tests/ext/claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ fn works_with_token_reward() {
let contract = Contract::default()
.reward(Reward::Tokens(reward_amount))
.stake_duration(stake_duration)
.stake_clauses(stake_clauses.clone())
.fee_clauses(fee_clauses.clone());
.stake_clauses(AttributeNamespace::Pallet, stake_clauses.clone())
.fee_clauses(AttributeNamespace::Pallet, fee_clauses.clone());
let contract_id = H256::random();

let stakes = MockMints::from(MockClauses(stake_clauses));
Expand Down Expand Up @@ -88,8 +88,8 @@ fn works_with_nft_reward() {
let contract = Contract::default()
.reward(Reward::Nft(reward_addr.clone()))
.stake_duration(stake_duration)
.stake_clauses(stake_clauses.clone())
.fee_clauses(fee_clauses.clone());
.stake_clauses(AttributeNamespace::Pallet, stake_clauses.clone())
.fee_clauses(AttributeNamespace::Pallet, fee_clauses.clone());
let contract_id = H256::random();

let stakes = MockMints::from(MockClauses(stake_clauses));
Expand Down
8 changes: 6 additions & 2 deletions pallets/ajuna-nft-staking/src/tests/ext/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ fn rejects_out_of_bound_staking_clauses() {
NftStake::create(
RuntimeOrigin::signed(ALICE),
H256::random(),
Contract::default().reward(Reward::Tokens(1)).stake_clauses(staking_clauses)
Contract::default()
.reward(Reward::Tokens(1))
.stake_clauses(AttributeNamespace::Pallet, staking_clauses)
),
Error::<Test>::MaxStakingClauses
);
Expand All @@ -141,7 +143,9 @@ fn rejects_out_of_bound_fee_clauses() {
NftStake::create(
RuntimeOrigin::signed(ALICE),
H256::random(),
Contract::default().reward(Reward::Tokens(1)).fee_clauses(fee_clauses)
Contract::default()
.reward(Reward::Tokens(1))
.fee_clauses(AttributeNamespace::Pallet, fee_clauses)
),
Error::<Test>::MaxFeeClauses
);
Expand Down
8 changes: 4 additions & 4 deletions pallets/ajuna-nft-staking/src/tests/ext/snipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ fn works_with_token_reward() {
.reward(Reward::Tokens(reward))
.stake_duration(stake_duration)
.claim_duration(claim_duration)
.stake_clauses(stake_clauses.clone())
.fee_clauses(fee_clauses.clone());
.stake_clauses(AttributeNamespace::Pallet, stake_clauses.clone())
.fee_clauses(AttributeNamespace::Pallet, fee_clauses.clone());
let contract_id = H256::random();

let stakes = MockMints::from(MockClauses(stake_clauses));
Expand Down Expand Up @@ -93,8 +93,8 @@ fn works_with_nft_reward() {
.reward(Reward::Nft(reward_addr.clone()))
.stake_duration(stake_duration)
.claim_duration(claim_duration)
.stake_clauses(stake_clauses.clone())
.fee_clauses(fee_clauses.clone());
.stake_clauses(AttributeNamespace::Pallet, stake_clauses.clone())
.fee_clauses(AttributeNamespace::Pallet, fee_clauses.clone());
let contract_id = H256::random();

let stakes = MockMints::from(MockClauses(stake_clauses));
Expand Down
19 changes: 15 additions & 4 deletions pallets/ajuna-nft-staking/src/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,22 @@ impl ContractOf<Test> {
self.stake_duration = stake_duration;
self
}
pub fn stake_clauses(mut self, clauses: Vec<MockClause>) -> Self {
self.stake_clauses = clauses.try_into().unwrap();
pub fn stake_clauses(mut self, ns: AttributeNamespace, clauses: Vec<MockClause>) -> Self {
self.stake_clauses = clauses
.into_iter()
.map(|clause| MockContractClause { namespace: ns, clause })
.collect::<Vec<_>>()
.try_into()
.unwrap();
self
}
pub fn fee_clauses(mut self, clauses: Vec<MockClause>) -> Self {
self.fee_clauses = clauses.try_into().unwrap();
pub fn fee_clauses(mut self, ns: AttributeNamespace, clauses: Vec<MockClause>) -> Self {
self.fee_clauses = clauses
.into_iter()
.map(|clause| MockContractClause { namespace: ns, clause })
.collect::<Vec<_>>()
.try_into()
.unwrap();
self
}
pub fn reward(mut self, reward: RewardOf<Test>) -> Self {
Expand All @@ -262,6 +272,7 @@ impl ContractOf<Test> {
}

pub type MockClause = Clause<MockCollectionId, AttributeKey, AttributeValue>;
pub type MockContractClause = ContractClause<MockCollectionId, AttributeKey, AttributeValue>;
pub struct MockClauses(pub Vec<MockClause>);
pub type MockMints = Vec<(NftId<MockCollectionId, MockItemId>, AttributeKey, AttributeValue)>;

Expand Down