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.rsdiffbeforeafterboth--- a/pallets/unique/src/sponsorship.rs
+++ b/pallets/unique/src/sponsorship.rs
@@ -29,6 +29,7 @@
CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MetaUpdatePermission,
NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId,
};
+use sp_runtime::traits::Saturating;
use pallet_common::{CollectionHandle};
use pallet_evm::account::CrossAccountId;
@@ -301,3 +302,65 @@
}
}
}
+
+use crate::SponsorshipPredict;
+use up_data_structs::SponsorshipState;
+pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);
+
+impl<T> SponsorshipPredict<T> for UniqueSponsorshipPredict<T>
+where
+ T: Config,
+{
+ fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>
+ where
+ u64: From<<T as frame_system::Config>::BlockNumber>,
+ {
+ let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;
+ let _ = collection.sponsorship.sponsor()?;
+
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let limit = collection
+ .limits
+ .sponsor_transfer_timeout(match collection.mode {
+ CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+ CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ });
+
+ let last_tx_block = match collection.mode {
+ CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),
+ CollectionMode::Fungible(_) => {
+ <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
+ }
+ CollectionMode::ReFungible => {
+ <ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))
+ }
+ };
+
+ if let Some(last_tx_block) = last_tx_block {
+ return Some(
+ last_tx_block
+ .saturating_add(limit.into())
+ .saturating_sub(block_number)
+ .into(),
+ );
+ }
+
+ let token_exists = match collection.mode {
+ CollectionMode::NFT => {
+ <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))
+ }
+ CollectionMode::Fungible(_) => true,
+ CollectionMode::ReFungible => {
+ <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))
+ }
+ };
+
+ if token_exists {
+ Some(0)
+ } else {
+ None
+ }
+ }
+}
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.tsdiffbeforeafterboth1// 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/>.1617import {ApiPromise} from '@polkadot/api';18import {IKeyringPair} from '@polkadot/types/types';19import chai from 'chai';20import chaiAsPromised from 'chai-as-promised';21import privateKey from './substrate/privateKey';22import {default as usingApi} from './substrate/substrate-api';23import {24 createCollectionExpectSuccess,25 setCollectionSponsorExpectSuccess,26 confirmSponsorshipExpectSuccess,27 createItemExpectSuccess,28 transferExpectSuccess,29 normalizeAccountId,30 getNextSponsored,31} from './util/helpers';3233chai.use(chaiAsPromised);34const expect = chai.expect;35363738describe('Integration Test getNextSponsored(collection_id, owner, item_id):', () => {39 let alice: IKeyringPair;40 let bob: IKeyringPair;4142 before(async () => {43 await usingApi(async () => {44 alice = privateKey('//Alice');45 bob = privateKey('//Bob');46 });47 });4849 it('NFT', async () => {50 await usingApi(async (api: ApiPromise) => {5152 // Not existing collection 53 expect(await getNextSponsored(api, 0, normalizeAccountId(alice), 0)).to.be.equal(-1);5455 const collectionId = await createCollectionExpectSuccess();56 const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);5758 // Check with Disabled sponsoring state59 expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(-1);60 await setCollectionSponsorExpectSuccess(collectionId, bob.address);6162 // Check with Unconfirmed sponsoring state63 expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(-1);64 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');6566 // Check with Confirmed sponsoring state67 expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(0);6869 // After transfer70 await transferExpectSuccess(collectionId, itemId, alice, bob, 1);71 expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId)).to.be.equal(5);7273 // Not existing token 74 expect(await getNextSponsored(api, collectionId, normalizeAccountId(alice), itemId+1)).to.be.equal(-1);75 });76 });7778 it('Fungible', async () => {79 await usingApi(async (api: ApiPromise) => {8081 const createMode = 'Fungible';82 const funCollectionId = await createCollectionExpectSuccess({mode: {type: createMode, decimalPoints: 0}});83 await createItemExpectSuccess(alice, funCollectionId, createMode);84 await setCollectionSponsorExpectSuccess(funCollectionId, bob.address);85 await confirmSponsorshipExpectSuccess(funCollectionId, '//Bob');86 expect(await getNextSponsored(api, funCollectionId, normalizeAccountId(alice), 0)).to.be.equal(0);8788 await transferExpectSuccess(funCollectionId, 0, alice, bob, 10, 'Fungible');89 expect(await getNextSponsored(api, funCollectionId, normalizeAccountId(alice), 0)).to.be.equal(5);90 });91 });9293 it('ReFungible', async () => {94 await usingApi(async (api: ApiPromise) => {9596 const createMode = 'ReFungible';97 const refunCollectionId = await createCollectionExpectSuccess({mode: {type: createMode}});98 const refunItemId = await createItemExpectSuccess(alice, refunCollectionId, createMode);99 await setCollectionSponsorExpectSuccess(refunCollectionId, bob.address);100 await confirmSponsorshipExpectSuccess(refunCollectionId, '//Bob');101 expect(await getNextSponsored(api, refunCollectionId, normalizeAccountId(alice), refunItemId)).to.be.equal(0);102103 await transferExpectSuccess(refunCollectionId, refunItemId, alice, bob, 10, 'ReFungible');104 expect(await getNextSponsored(api, refunCollectionId, normalizeAccountId(alice), refunItemId)).to.be.equal(5);105106 // Not existing token 107 expect(await getNextSponsored(api, refunCollectionId, normalizeAccountId(alice), refunItemId+1)).to.be.equal(-1);108 });109 });110});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);