difftreelog
Merge pull request #337 from UniqueNetwork/CORE-178
in: master
feature/core-178
9 files changed
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -119,6 +119,15 @@
) -> Result<Option<Collection<AccountId>>>;
#[rpc(name = "unique_collectionStats")]
fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;
+
+ #[rpc(name = "unique_nextSponsored")]
+ fn next_sponsored(
+ &self,
+ collection: CollectionId,
+ account: CrossAccountId,
+ token: TokenId,
+ at: Option<BlockHash>,
+ ) -> Result<Option<u64>>;
#[rpc(name = "unique_effectiveCollectionLimits")]
fn effective_collection_limits(
&self,
@@ -228,5 +237,6 @@
pass_method!(last_token_id(collection: CollectionId) -> TokenId);
pass_method!(collection_by_id(collection: CollectionId) -> Option<Collection<AccountId>>);
pass_method!(collection_stats() -> CollectionStats);
+ pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option<u64>);
pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>);
}
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -67,7 +67,7 @@
mod eth;
mod sponsorship;
-pub use sponsorship::UniqueSponsorshipHandler;
+pub use sponsorship::{UniqueSponsorshipHandler, UniqueSponsorshipPredict};
pub use eth::sponsoring::UniqueEthSponsorshipHandler;
pub use eth::UniqueErcSupport;
@@ -82,6 +82,12 @@
pub mod weights;
use weights::WeightInfo;
+pub trait SponsorshipPredict<T: Config> {
+ fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>
+ where
+ u64: From<<T as frame_system::Config>::BlockNumber>;
+}
+
decl_error! {
/// Error for non-fungible-token module.
pub enum Error for Module<T: Config> {
pallets/unique/src/sponsorship.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617use crate::{18 Config, Call, CreateItemBasket, VariableMetaDataBasket, ReFungibleTransferBasket,19 FungibleTransferBasket, NftTransferBasket, CreateItemData, CollectionMode, NftApproveBasket,20 FungibleApproveBasket, RefungibleApproveBasket,21};22use core::marker::PhantomData;23use up_sponsorship::SponsorshipHandler;24use frame_support::{25 traits::{IsSubType},26 storage::{StorageMap, StorageDoubleMap, StorageNMap},27};28use up_data_structs::{29 CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MetaUpdatePermission,30 NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId,31};32use pallet_common::{CollectionHandle};33use pallet_evm::account::CrossAccountId;3435pub fn withdraw_transfer<T: Config>(36 collection: &CollectionHandle<T>,37 who: &T::CrossAccountId,38 item_id: &TokenId,39) -> Option<()> {40 // preliminary sponsoring correctness check41 match collection.mode {42 CollectionMode::NFT => {43 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;44 if !owner.conv_eq(who) {45 return None;46 }47 }48 CollectionMode::Fungible(_) => {49 if item_id != &TokenId::default() {50 return None;51 }52 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {53 return None;54 }55 }56 CollectionMode::ReFungible => {57 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {58 return None;59 }60 }61 }6263 // sponsor timeout64 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;65 let limit = collection66 .limits67 .sponsor_transfer_timeout(match collection.mode {68 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,69 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,70 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,71 });7273 let last_tx_block = match collection.mode {74 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),75 CollectionMode::Fungible(_) => {76 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())77 }78 CollectionMode::ReFungible => {79 <ReFungibleTransferBasket<T>>::get((collection.id, item_id, who.as_sub()))80 }81 };8283 if let Some(last_tx_block) = last_tx_block {84 let timeout = last_tx_block + limit.into();85 if block_number < timeout {86 return None;87 }88 }8990 match collection.mode {91 CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),92 CollectionMode::Fungible(_) => {93 <FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)94 }95 CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(96 (collection.id, item_id, who.as_sub()),97 block_number,98 ),99 };100101 Some(())102}103104pub fn withdraw_create_item<T: Config>(105 collection: &CollectionHandle<T>,106 who: &T::CrossAccountId,107 _properties: &CreateItemData,108) -> Option<()> {109 if _properties.data_size() as u32 > collection.limits.sponsored_data_size() {110 return None;111 }112113 // sponsor timeout114 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;115 let limit = collection116 .limits117 .sponsor_transfer_timeout(match _properties {118 CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,119 CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,120 CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,121 });122123 if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {124 let timeout = last_tx_block + limit.into();125 if block_number < timeout {126 return None;127 }128 }129130 CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);131132 Some(())133}134135pub fn withdraw_set_variable_meta_data<T: Config>(136 who: &T::CrossAccountId,137 collection: &CollectionHandle<T>,138 item_id: &TokenId,139 data: &[u8],140) -> Option<()> {141 // TODO: make it work for admins142 if collection.meta_update_permission != MetaUpdatePermission::ItemOwner {143 return None;144 }145 // preliminary sponsoring correctness check146 match collection.mode {147 CollectionMode::NFT => {148 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;149 if !owner.conv_eq(who) {150 return None;151 }152 }153 CollectionMode::Fungible(_) => {154 if item_id != &TokenId::default() {155 return None;156 }157 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {158 return None;159 }160 }161 CollectionMode::ReFungible => {162 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {163 return None;164 }165 }166 }167168 // Can't sponsor fungible collection, this tx will be rejected169 // as invalid170 if matches!(collection.mode, CollectionMode::Fungible(_)) {171 return None;172 }173 if data.len() > collection.limits.sponsored_data_size() as usize {174 return None;175 }176177 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;178 let limit = collection.limits.sponsored_data_rate_limit()?;179180 if let Some(last_tx_block) = VariableMetaDataBasket::<T>::get(collection.id, item_id) {181 let timeout = last_tx_block + limit.into();182 if block_number < timeout {183 return None;184 }185 }186187 <VariableMetaDataBasket<T>>::insert(collection.id, item_id, block_number);188189 Some(())190}191192pub fn withdraw_approve<T: Config>(193 collection: &CollectionHandle<T>,194 who: &T::AccountId,195 item_id: &TokenId,196) -> Option<()> {197 // sponsor timeout198 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;199 let limit = collection.limits.sponsor_approve_timeout();200201 let last_tx_block = match collection.mode {202 CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),203 CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),204 CollectionMode::ReFungible => {205 <RefungibleApproveBasket<T>>::get((collection.id, item_id, who))206 }207 };208209 if let Some(last_tx_block) = last_tx_block {210 let timeout = last_tx_block + limit.into();211 if block_number < timeout {212 return None;213 }214 }215216 match collection.mode {217 CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),218 CollectionMode::Fungible(_) => {219 <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)220 }221 CollectionMode::ReFungible => {222 <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)223 }224 };225226 Some(())227}228229fn load<T: Config>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {230 let collection = CollectionHandle::new(id)?;231 let sponsor = collection.sponsorship.sponsor().cloned()?;232 Some((sponsor, collection))233}234235pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);236impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>237where238 T: Config,239 C: IsSubType<Call<T>>,240{241 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {242 match IsSubType::<Call<T>>::is_sub_type(call)? {243 Call::create_item {244 collection_id,245 data,246 ..247 } => {248 let (sponsor, collection) = load(*collection_id)?;249 withdraw_create_item::<T>(250 &collection,251 &T::CrossAccountId::from_sub(who.clone()),252 data,253 )254 .map(|()| sponsor)255 }256 Call::transfer {257 collection_id,258 item_id,259 ..260 } => {261 let (sponsor, collection) = load(*collection_id)?;262 withdraw_transfer::<T>(263 &collection,264 &T::CrossAccountId::from_sub(who.clone()),265 item_id,266 )267 .map(|()| sponsor)268 }269 Call::transfer_from {270 collection_id,271 item_id,272 from,273 ..274 } => {275 let (sponsor, collection) = load(*collection_id)?;276 withdraw_transfer::<T>(&collection, from, item_id).map(|()| sponsor)277 }278 Call::approve {279 collection_id,280 item_id,281 ..282 } => {283 let (sponsor, collection) = load(*collection_id)?;284 withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)285 }286 Call::set_variable_meta_data {287 collection_id,288 item_id,289 data,290 } => {291 let (sponsor, collection) = load(*collection_id)?;292 withdraw_set_variable_meta_data::<T>(293 &T::CrossAccountId::from_sub(who.clone()),294 &collection,295 item_id,296 data,297 )298 .map(|()| sponsor)299 }300 _ => None,301 }302 }303}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617use crate::{18 Config, Call, CreateItemBasket, VariableMetaDataBasket, ReFungibleTransferBasket,19 FungibleTransferBasket, NftTransferBasket, CreateItemData, CollectionMode, NftApproveBasket,20 FungibleApproveBasket, RefungibleApproveBasket,21};22use core::marker::PhantomData;23use up_sponsorship::SponsorshipHandler;24use frame_support::{25 traits::{IsSubType},26 storage::{StorageMap, StorageDoubleMap, StorageNMap},27};28use up_data_structs::{29 CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MetaUpdatePermission,30 NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId,31};32use sp_runtime::traits::Saturating;33use pallet_common::{CollectionHandle};34use pallet_evm::account::CrossAccountId;3536pub fn withdraw_transfer<T: Config>(37 collection: &CollectionHandle<T>,38 who: &T::CrossAccountId,39 item_id: &TokenId,40) -> Option<()> {41 // preliminary sponsoring correctness check42 match collection.mode {43 CollectionMode::NFT => {44 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;45 if !owner.conv_eq(who) {46 return None;47 }48 }49 CollectionMode::Fungible(_) => {50 if item_id != &TokenId::default() {51 return None;52 }53 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {54 return None;55 }56 }57 CollectionMode::ReFungible => {58 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {59 return None;60 }61 }62 }6364 // sponsor timeout65 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;66 let limit = collection67 .limits68 .sponsor_transfer_timeout(match collection.mode {69 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,70 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,71 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,72 });7374 let last_tx_block = match collection.mode {75 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),76 CollectionMode::Fungible(_) => {77 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())78 }79 CollectionMode::ReFungible => {80 <ReFungibleTransferBasket<T>>::get((collection.id, item_id, who.as_sub()))81 }82 };8384 if let Some(last_tx_block) = last_tx_block {85 let timeout = last_tx_block + limit.into();86 if block_number < timeout {87 return None;88 }89 }9091 match collection.mode {92 CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),93 CollectionMode::Fungible(_) => {94 <FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)95 }96 CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(97 (collection.id, item_id, who.as_sub()),98 block_number,99 ),100 };101102 Some(())103}104105pub fn withdraw_create_item<T: Config>(106 collection: &CollectionHandle<T>,107 who: &T::CrossAccountId,108 _properties: &CreateItemData,109) -> Option<()> {110 if _properties.data_size() as u32 > collection.limits.sponsored_data_size() {111 return None;112 }113114 // sponsor timeout115 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;116 let limit = collection117 .limits118 .sponsor_transfer_timeout(match _properties {119 CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,120 CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,121 CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,122 });123124 if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {125 let timeout = last_tx_block + limit.into();126 if block_number < timeout {127 return None;128 }129 }130131 CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);132133 Some(())134}135136pub fn withdraw_set_variable_meta_data<T: Config>(137 who: &T::CrossAccountId,138 collection: &CollectionHandle<T>,139 item_id: &TokenId,140 data: &[u8],141) -> Option<()> {142 // TODO: make it work for admins143 if collection.meta_update_permission != MetaUpdatePermission::ItemOwner {144 return None;145 }146 // preliminary sponsoring correctness check147 match collection.mode {148 CollectionMode::NFT => {149 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;150 if !owner.conv_eq(who) {151 return None;152 }153 }154 CollectionMode::Fungible(_) => {155 if item_id != &TokenId::default() {156 return None;157 }158 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {159 return None;160 }161 }162 CollectionMode::ReFungible => {163 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {164 return None;165 }166 }167 }168169 // Can't sponsor fungible collection, this tx will be rejected170 // as invalid171 if matches!(collection.mode, CollectionMode::Fungible(_)) {172 return None;173 }174 if data.len() > collection.limits.sponsored_data_size() as usize {175 return None;176 }177178 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;179 let limit = collection.limits.sponsored_data_rate_limit()?;180181 if let Some(last_tx_block) = VariableMetaDataBasket::<T>::get(collection.id, item_id) {182 let timeout = last_tx_block + limit.into();183 if block_number < timeout {184 return None;185 }186 }187188 <VariableMetaDataBasket<T>>::insert(collection.id, item_id, block_number);189190 Some(())191}192193pub fn withdraw_approve<T: Config>(194 collection: &CollectionHandle<T>,195 who: &T::AccountId,196 item_id: &TokenId,197) -> Option<()> {198 // sponsor timeout199 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;200 let limit = collection.limits.sponsor_approve_timeout();201202 let last_tx_block = match collection.mode {203 CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),204 CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),205 CollectionMode::ReFungible => {206 <RefungibleApproveBasket<T>>::get((collection.id, item_id, who))207 }208 };209210 if let Some(last_tx_block) = last_tx_block {211 let timeout = last_tx_block + limit.into();212 if block_number < timeout {213 return None;214 }215 }216217 match collection.mode {218 CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),219 CollectionMode::Fungible(_) => {220 <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)221 }222 CollectionMode::ReFungible => {223 <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)224 }225 };226227 Some(())228}229230fn load<T: Config>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {231 let collection = CollectionHandle::new(id)?;232 let sponsor = collection.sponsorship.sponsor().cloned()?;233 Some((sponsor, collection))234}235236pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);237impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>238where239 T: Config,240 C: IsSubType<Call<T>>,241{242 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {243 match IsSubType::<Call<T>>::is_sub_type(call)? {244 Call::create_item {245 collection_id,246 data,247 ..248 } => {249 let (sponsor, collection) = load(*collection_id)?;250 withdraw_create_item::<T>(251 &collection,252 &T::CrossAccountId::from_sub(who.clone()),253 data,254 )255 .map(|()| sponsor)256 }257 Call::transfer {258 collection_id,259 item_id,260 ..261 } => {262 let (sponsor, collection) = load(*collection_id)?;263 withdraw_transfer::<T>(264 &collection,265 &T::CrossAccountId::from_sub(who.clone()),266 item_id,267 )268 .map(|()| sponsor)269 }270 Call::transfer_from {271 collection_id,272 item_id,273 from,274 ..275 } => {276 let (sponsor, collection) = load(*collection_id)?;277 withdraw_transfer::<T>(&collection, from, item_id).map(|()| sponsor)278 }279 Call::approve {280 collection_id,281 item_id,282 ..283 } => {284 let (sponsor, collection) = load(*collection_id)?;285 withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)286 }287 Call::set_variable_meta_data {288 collection_id,289 item_id,290 data,291 } => {292 let (sponsor, collection) = load(*collection_id)?;293 withdraw_set_variable_meta_data::<T>(294 &T::CrossAccountId::from_sub(who.clone()),295 &collection,296 item_id,297 data,298 )299 .map(|()| sponsor)300 }301 _ => None,302 }303 }304}305306use crate::SponsorshipPredict;307use up_data_structs::SponsorshipState;308pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);309310impl<T> SponsorshipPredict<T> for UniqueSponsorshipPredict<T>311where312 T: Config,313{314 fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>315 where316 u64: From<<T as frame_system::Config>::BlockNumber>,317 {318 let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;319 let _ = collection.sponsorship.sponsor()?;320321 // sponsor timeout322 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;323 let limit = collection324 .limits325 .sponsor_transfer_timeout(match collection.mode {326 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,327 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,328 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,329 });330331 let last_tx_block = match collection.mode {332 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),333 CollectionMode::Fungible(_) => {334 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())335 }336 CollectionMode::ReFungible => {337 <ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))338 }339 };340341 if let Some(last_tx_block) = last_tx_block {342 return Some(343 last_tx_block344 .saturating_add(limit.into())345 .saturating_sub(block_number)346 .into(),347 );348 }349350 let token_exists = match collection.mode {351 CollectionMode::NFT => {352 <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))353 }354 CollectionMode::Fungible(_) => true,355 CollectionMode::ReFungible => {356 <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))357 }358 };359360 if token_exists {361 Some(0)362 } else {363 None364 }365 }366}primitives/rpc/src/lib.rsdiffbeforeafterboth--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -59,6 +59,7 @@
fn last_token_id(collection: CollectionId) -> Result<TokenId>;
fn collection_by_id(collection: CollectionId) -> Result<Option<Collection<AccountId>>>;
fn collection_stats() -> Result<CollectionStats>;
+ fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>>;
fn effective_collection_limits(collection_id: CollectionId) -> Result<Option<CollectionLimits>>;
}
}
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -69,6 +69,13 @@
fn collection_stats() -> Result<CollectionStats, DispatchError> {
Ok(<pallet_common::Pallet<Runtime>>::collection_stats())
}
+ fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>, DispatchError> {
+ Ok(<pallet_unique::UniqueSponsorshipPredict<Runtime> as
+ pallet_unique::SponsorshipPredict<Runtime>>::predict(
+ collection,
+ account,
+ token))
+ }
fn effective_collection_limits(collection: CollectionId) -> Result<Option<CollectionLimits>, DispatchError> {
Ok(<pallet_common::Pallet<Runtime>>::effective_collection_limits(collection))
tests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -623,6 +623,10 @@
* Get token variable metadata
**/
variableMetadata: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Bytes>>;
+ /**
+ * nextSponsored transaction
+ **/
+ nextSponsored: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, account: AccountId | string | Uint8Array | { Substrate: any } | { Ethereum: any } | string | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<u64>>>;
};
web3: {
/**
tests/src/interfaces/unique/definitions.tsdiffbeforeafterboth--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -55,6 +55,7 @@
collectionById: fun('Get collection by specified id', [collectionParam], 'Option<UpDataStructsCollection>'),
collectionStats: fun('Get collection stats', [], 'UpDataStructsCollectionStats'),
allowed: fun('Check if user is allowed to use collection', [collectionParam, crossAccountParam()], 'bool'),
+ nextSponsored: fun('Get number of blocks when sponsored transaction is available', [collectionParam, crossAccountParam(), tokenParam], 'Option<u64>'),
effectiveCollectionLimits: fun('Get effective collection limits', [collectionParam], 'Option<UpDataStructsCollectionLimits>'),
},
};
tests/src/nextSponsoring.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/nextSponsoring.test.ts
@@ -0,0 +1,110 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {ApiPromise} from '@polkadot/api';
+import {IKeyringPair} from '@polkadot/types/types';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import {default as usingApi} from './substrate/substrate-api';
+import {
+ createCollectionExpectSuccess,
+ setCollectionSponsorExpectSuccess,
+ confirmSponsorshipExpectSuccess,
+ createItemExpectSuccess,
+ transferExpectSuccess,
+ normalizeAccountId,
+ getNextSponsored,
+} from './util/helpers';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+
+
+describe('Integration Test getNextSponsored(collection_id, owner, item_id):', () => {
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
+ });
+ });
+
+ it('NFT', async () => {
+ await usingApi(async (api: ApiPromise) => {
+
+ // Not existing collection
+ expect(await getNextSponsored(api, 0, normalizeAccountId(alice), 0)).to.be.equal(-1);
+
+ const collectionId = await createCollectionExpectSuccess();
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
+
+ // Check with Disabled sponsoring state
+ expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(-1);
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+
+ // Check with Unconfirmed sponsoring state
+ expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(-1);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ // Check with Confirmed sponsoring state
+ expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(0);
+
+ // After transfer
+ await transferExpectSuccess(collectionId, itemId, alice, bob, 1);
+ expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(5);
+
+ // Not existing token
+ expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId+1)).to.be.equal(-1);
+ });
+ });
+
+ it('Fungible', async () => {
+ await usingApi(async (api: ApiPromise) => {
+
+ const createMode = 'Fungible';
+ const funCollectionId = await createCollectionExpectSuccess({mode: {type: createMode, decimalPoints: 0}});
+ await createItemExpectSuccess(alice, funCollectionId, createMode);
+ await setCollectionSponsorExpectSuccess(funCollectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(funCollectionId, '//Bob');
+ expect(await getNextSponsored(api, funCollectionId, normalizeAccountId(alice), 0)).to.be.equal(0);
+
+ await transferExpectSuccess(funCollectionId, 0, alice, bob, 10, 'Fungible');
+ expect(await getNextSponsored(api, funCollectionId, normalizeAccountId(alice), 0)).to.be.equal(5);
+ });
+ });
+
+ it('ReFungible', async () => {
+ await usingApi(async (api: ApiPromise) => {
+
+ const createMode = 'ReFungible';
+ const refunCollectionId = await createCollectionExpectSuccess({mode: {type: createMode}});
+ const refunItemId = await createItemExpectSuccess(alice, refunCollectionId, createMode);
+ await setCollectionSponsorExpectSuccess(refunCollectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(refunCollectionId, '//Bob');
+ expect(await getNextSponsored(api, refunCollectionId, normalizeAccountId(alice), refunItemId)).to.be.equal(0);
+
+ await transferExpectSuccess(refunCollectionId, refunItemId, alice, bob, 10, 'ReFungible');
+ expect(await getNextSponsored(api, refunCollectionId, normalizeAccountId(alice), refunItemId)).to.be.equal(5);
+
+ // Not existing token
+ expect(await getNextSponsored(api, refunCollectionId, normalizeAccountId(alice), refunItemId+1)).to.be.equal(-1);
+ });
+ });
+});
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -608,6 +608,15 @@
});
}
+export async function getNextSponsored(
+ api: ApiPromise,
+ collectionId: number,
+ account: string | CrossAccountId,
+ tokenId: number,
+): Promise<number> {
+ return Number((await api.rpc.unique.nextSponsored(collectionId, account, tokenId)).unwrapOr(-1));
+}
+
export async function toggleContractAllowlistExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, value = true) {
await usingApi(async (api) => {
const tx = api.tx.unique.toggleContractAllowList(contractAddress, value);