diff --git a/pallets/ajuna-nft-staking/benchmarking/src/lib.rs b/pallets/ajuna-nft-staking/benchmarking/src/lib.rs index c4c6e8e5..014eae76 100644 --- a/pallets/ajuna-nft-staking/benchmarking/src/lib.rs +++ b/pallets/ajuna-nft-staking/benchmarking/src/lib.rs @@ -241,23 +241,25 @@ fn contract_with( 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::::unique_saturated_from(stake_collection), T::BenchmarkHelper::contract_key(i), T::BenchmarkHelper::contract_value(ATTRIBUTE_VALUE), - ) + ), }) .collect::>() .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::::unique_saturated_from(fee_collection), T::BenchmarkHelper::contract_key(i), T::BenchmarkHelper::contract_value(ATTRIBUTE_VALUE), - ) + ), }) .collect::>() .try_into() diff --git a/pallets/ajuna-nft-staking/src/contracts.rs b/pallets/ajuna-nft-staking/src/contracts.rs index 09cb1cfc..4e0099a1 100644 --- a/pallets/ajuna-nft-staking/src/contracts.rs +++ b/pallets/ajuna-nft-staking/src/contracts.rs @@ -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(pub CollectionId, pub ItemId); @@ -30,6 +39,36 @@ pub enum Reward { Nft(NftId), } +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct ContractClause { + pub namespace: AttributeNamespace, + pub clause: Clause, +} + +impl + ContractClause +{ + pub fn evaluate_for( + &self, + address: &NftId, + ) -> bool + where + NftInspector: Inspect, + 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::(address, evaluate_fn) + } +} + #[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub enum Clause { HasAttribute(CollectionId, AttributeKey), @@ -39,9 +78,10 @@ pub enum Clause { impl Clause { - pub fn evaluate_for( + pub fn evaluate_for( &self, address: &NftId, + evaluate_fn: Fn, ) -> bool where NftInspector: Inspect, @@ -49,6 +89,7 @@ impl ItemId: PartialEq, AttributeKey: Encode, AttributeValue: Encode + Decode + PartialEq, + Fn: FnOnce(&CollectionId, &ItemId, &[u8]) -> Option>, { let clause_collection_id = match self { Clause::HasAttribute(collection_id, _) => collection_id, @@ -58,11 +99,9 @@ impl 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 @@ -72,7 +111,7 @@ impl } type BoundedClauses = - BoundedVec, ConstU32<100>>; + BoundedVec, 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. diff --git a/pallets/ajuna-nft-staking/src/tests/ext/accept.rs b/pallets/ajuna-nft-staking/src/tests/ext/accept.rs index 9071cd74..6bcbd83f 100644 --- a/pallets/ajuna-nft-staking/src/tests/ext/accept.rs +++ b/pallets/ajuna-nft-staking/src/tests/ext/accept.rs @@ -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)); @@ -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)); @@ -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)); @@ -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)); @@ -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)); diff --git a/pallets/ajuna-nft-staking/src/tests/ext/cancel.rs b/pallets/ajuna-nft-staking/src/tests/ext/cancel.rs index f447d614..11abbde1 100644 --- a/pallets/ajuna-nft-staking/src/tests/ext/cancel.rs +++ b/pallets/ajuna-nft-staking/src/tests/ext/cancel.rs @@ -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(); @@ -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(); diff --git a/pallets/ajuna-nft-staking/src/tests/ext/claim.rs b/pallets/ajuna-nft-staking/src/tests/ext/claim.rs index 83b4dc93..c14d70e1 100644 --- a/pallets/ajuna-nft-staking/src/tests/ext/claim.rs +++ b/pallets/ajuna-nft-staking/src/tests/ext/claim.rs @@ -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)); @@ -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)); diff --git a/pallets/ajuna-nft-staking/src/tests/ext/create.rs b/pallets/ajuna-nft-staking/src/tests/ext/create.rs index a1bdd18c..b909c129 100644 --- a/pallets/ajuna-nft-staking/src/tests/ext/create.rs +++ b/pallets/ajuna-nft-staking/src/tests/ext/create.rs @@ -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::::MaxStakingClauses ); @@ -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::::MaxFeeClauses ); diff --git a/pallets/ajuna-nft-staking/src/tests/ext/snipe.rs b/pallets/ajuna-nft-staking/src/tests/ext/snipe.rs index 22cc3391..48a58a9c 100644 --- a/pallets/ajuna-nft-staking/src/tests/ext/snipe.rs +++ b/pallets/ajuna-nft-staking/src/tests/ext/snipe.rs @@ -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)); @@ -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)); diff --git a/pallets/ajuna-nft-staking/src/tests/mock.rs b/pallets/ajuna-nft-staking/src/tests/mock.rs index 18bee21c..5b87f3d1 100644 --- a/pallets/ajuna-nft-staking/src/tests/mock.rs +++ b/pallets/ajuna-nft-staking/src/tests/mock.rs @@ -243,12 +243,22 @@ impl ContractOf { self.stake_duration = stake_duration; self } - pub fn stake_clauses(mut self, clauses: Vec) -> Self { - self.stake_clauses = clauses.try_into().unwrap(); + pub fn stake_clauses(mut self, ns: AttributeNamespace, clauses: Vec) -> Self { + self.stake_clauses = clauses + .into_iter() + .map(|clause| MockContractClause { namespace: ns, clause }) + .collect::>() + .try_into() + .unwrap(); self } - pub fn fee_clauses(mut self, clauses: Vec) -> Self { - self.fee_clauses = clauses.try_into().unwrap(); + pub fn fee_clauses(mut self, ns: AttributeNamespace, clauses: Vec) -> Self { + self.fee_clauses = clauses + .into_iter() + .map(|clause| MockContractClause { namespace: ns, clause }) + .collect::>() + .try_into() + .unwrap(); self } pub fn reward(mut self, reward: RewardOf) -> Self { @@ -262,6 +272,7 @@ impl ContractOf { } pub type MockClause = Clause; +pub type MockContractClause = ContractClause; pub struct MockClauses(pub Vec); pub type MockMints = Vec<(NftId, AttributeKey, AttributeValue)>;