From 96967bb10fdedb5aa6ab7e2aafc5a681bbded810 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 23 Dec 2020 11:01:36 +0000 Subject: [PATCH] Integration tests for setCollectionSponsor --- --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -193,7 +193,7 @@ offchain_schema: vec![], schema_version: SchemaVersion::default(), sponsor: get_account_id_from_seed::("Alice"), - unconfirmed_sponsor: get_account_id_from_seed::("Alice"), + sponsor_confirmed: true, const_on_chain_schema: vec![], variable_on_chain_schema: vec![], limits: CollectionLimits::default() --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -136,7 +136,7 @@ pub offchain_schema: Vec, pub schema_version: SchemaVersion, pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender - pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship + pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise. pub limits: CollectionLimits, // Collection private restrictions pub variable_on_chain_schema: Vec, // pub const_on_chain_schema: Vec, // @@ -591,7 +591,7 @@ offchain_schema: Vec::new(), schema_version: SchemaVersion::ImageURL, sponsor: T::AccountId::default(), - unconfirmed_sponsor: T::AccountId::default(), + sponsor_confirmed: false, variable_on_chain_schema: Vec::new(), const_on_chain_schema: Vec::new(), limits: CollectionLimits::default(), @@ -869,7 +869,8 @@ let mut target_collection = >::get(collection_id); ensure!(sender == target_collection.owner, Error::::NoPermission); - target_collection.unconfirmed_sponsor = new_sponsor; + target_collection.sponsor = new_sponsor; + target_collection.sponsor_confirmed = false; >::insert(collection_id, target_collection); Ok(()) @@ -889,10 +890,9 @@ ensure!(>::contains_key(collection_id), Error::::CollectionNotFound); let mut target_collection = >::get(collection_id); - ensure!(sender == target_collection.unconfirmed_sponsor, Error::::ConfirmUnsetSponsorFail); + ensure!(sender == target_collection.sponsor, Error::::ConfirmUnsetSponsorFail); - target_collection.sponsor = target_collection.unconfirmed_sponsor; - target_collection.unconfirmed_sponsor = T::AccountId::default(); + target_collection.sponsor_confirmed = true; >::insert(collection_id, target_collection); Ok(()) @@ -917,6 +917,7 @@ ensure!(sender == target_collection.owner, Error::::NoPermission); target_collection.sponsor = T::AccountId::default(); + target_collection.sponsor_confirmed = false; >::insert(collection_id, target_collection); Ok(()) @@ -2338,7 +2339,8 @@ Some(Call::create_item(collection_id, _owner, _properties)) => { // check free create limit - if >::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32) + if (>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) && + (>::get(collection_id).sponsor_confirmed) { >::get(collection_id).sponsor } else { @@ -2347,84 +2349,87 @@ } Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => { - let _collection_limits = >::get(collection_id).limits; - let _collection_mode = >::get(collection_id).mode; - - // sponsor timeout - let sponsor_transfer = match _collection_mode { - CollectionMode::NFT => { - - // get correct limit - let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 { - _collection_limits.sponsor_transfer_timeout - } else { - ChainLimit::get().nft_sponsor_transfer_timeout - }; - - let basket = >::get(collection_id, _item_id); - let block_number = >::block_number() as T::BlockNumber; - let limit_time = basket + limit.into(); - if block_number >= limit_time { - >::insert(collection_id, _item_id, block_number); - true - } - else { - false - } - } - CollectionMode::Fungible(_) => { - - // get correct limit - let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 { - _collection_limits.sponsor_transfer_timeout - } else { - ChainLimit::get().fungible_sponsor_transfer_timeout - }; - - let mut basket = >::get(collection_id, _item_id); - let block_number = >::block_number() as T::BlockNumber; - if basket.iter().any(|i| i.address == _new_owner.clone()) - { - let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone(); - let limit_time = item.start_block + limit.into(); + let mut sponsor_transfer = false; + if >::get(collection_id).sponsor_confirmed { + let _collection_limits = >::get(collection_id).limits; + let _collection_mode = >::get(collection_id).mode; + + // sponsor timeout + sponsor_transfer = match _collection_mode { + CollectionMode::NFT => { + + // get correct limit + let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 { + _collection_limits.sponsor_transfer_timeout + } else { + ChainLimit::get().nft_sponsor_transfer_timeout + }; + + let basket = >::get(collection_id, _item_id); + let block_number = >::block_number() as T::BlockNumber; + let limit_time = basket + limit.into(); if block_number >= limit_time { - basket.retain(|x| x.address == item.address); - basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() }); - >::insert(collection_id, _item_id, basket); + >::insert(collection_id, _item_id, block_number); true } else { false } } - else { - basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()}); - true + CollectionMode::Fungible(_) => { + + // get correct limit + let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 { + _collection_limits.sponsor_transfer_timeout + } else { + ChainLimit::get().fungible_sponsor_transfer_timeout + }; + + let mut basket = >::get(collection_id, _item_id); + let block_number = >::block_number() as T::BlockNumber; + if basket.iter().any(|i| i.address == _new_owner.clone()) + { + let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone(); + let limit_time = item.start_block + limit.into(); + if block_number >= limit_time { + basket.retain(|x| x.address == item.address); + basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() }); + >::insert(collection_id, _item_id, basket); + true + } + else { + false + } + } + else { + basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()}); + true + } + } + CollectionMode::ReFungible(_) => { + + // get correct limit + let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 { + _collection_limits.sponsor_transfer_timeout + } else { + ChainLimit::get().refungible_sponsor_transfer_timeout + }; + + let basket = >::get(collection_id, _item_id); + let block_number = >::block_number() as T::BlockNumber; + let limit_time = basket + limit.into(); + if block_number >= limit_time { + >::insert(collection_id, _item_id, block_number); + true + } else { + false + } } - } - CollectionMode::ReFungible(_) => { - - // get correct limit - let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 { - _collection_limits.sponsor_transfer_timeout - } else { - ChainLimit::get().refungible_sponsor_transfer_timeout - }; - - let basket = >::get(collection_id, _item_id); - let block_number = >::block_number() as T::BlockNumber; - let limit_time = basket + limit.into(); - if block_number >= limit_time { - >::insert(collection_id, _item_id, block_number); - true - } else { + _ => { false - } - } - _ => { - false - }, - }; + }, + }; + } if !sponsor_transfer { T::AccountId::default() --- a/runtime_types.json +++ b/runtime_types.json @@ -70,7 +70,7 @@ "OffchainSchema": "Vec", "SchemaVersion": "SchemaVersion", "Sponsor": "AccountId", - "UnconfirmedSponsor": "AccountId", + "SponsorConfirmed": "bool", "Limits": "CollectionLimits", "VariableOnChainSchema": "Vec", "ConstOnChainSchema": "Vec" --- a/tests/src/destroyCollection.test.ts +++ b/tests/src/destroyCollection.test.ts @@ -1,55 +1,12 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api"; -import { createCollectionExpectSuccess, createCollectionExpectFailure } from "./util/helpers"; +import { createCollectionExpectSuccess, createCollectionExpectFailure, destroyCollectionExpectSuccess, destroyCollectionExpectFailure } from "./util/helpers"; import type { AccountId, EventRecord } from '@polkadot/types/interfaces'; import privateKey from './substrate/privateKey'; -import { nullPublicKey } from './accounts'; chai.use(chaiAsPromised); const expect = chai.expect; - -function getDestroyResult(events: EventRecord[]): boolean { - let success: boolean = false; - events.forEach(({ phase, event: { data, method, section } }) => { - // console.log(` ${phase}: ${section}.${method}:: ${data}`); - if (method == 'ExtrinsicSuccess') { - success = true; - } - }); - return success; -} - -async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') { - await usingApi(async (api) => { - // Run the DestroyCollection transaction - const alicePrivateKey = privateKey(senderSeed); - const tx = api.tx.nft.destroyCollection(collectionId); - const events = await submitTransactionAsync(alicePrivateKey, tx); - const result = getDestroyResult(events); - - // Get the collection - const collection: any = (await api.query.nft.collection(collectionId)).toJSON(); - - // What to expect - expect(result).to.be.true; - expect(collection).to.be.not.null; - expect(collection.Owner).to.be.equal(nullPublicKey); - }); -} - -async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') { - await usingApi(async (api) => { - // Run the DestroyCollection transaction - const alicePrivateKey = privateKey(senderSeed); - const tx = api.tx.nft.destroyCollection(collectionId); - const events = await submitTransactionAsync(alicePrivateKey, tx); - const result = getDestroyResult(events); - - // What to expect - expect(result).to.be.false; - }); -} describe('integration test: ext. destroyCollection():', () => { it('NFT collection can be destroyed', async () => { --- /dev/null +++ b/tests/src/setCollectionSponsor.test.ts @@ -0,0 +1,78 @@ +// +// This file is subject to the terms and conditions defined in +// file 'LICENSE', which is part of this source code package. +// + +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { default as usingApi } from "./substrate/substrate-api"; +import { createCollectionExpectSuccess, setCollectionSponsorExpectSuccess, destroyCollectionExpectSuccess, setCollectionSponsorExpectFailure } from "./util/helpers"; +import { Keyring } from "@polkadot/api"; +import { IKeyringPair } from "@polkadot/types/types"; +import type { AccountId } from '@polkadot/types/interfaces'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +let bob: IKeyringPair; + +describe('integration test: ext. setCollectionSponsor():', () => { + + before(async () => { + await usingApi(async (api) => { + const keyring = new Keyring({ type: 'sr25519' }); + bob = keyring.addFromUri(`//Bob`); + }); + }); + + it('Set NFT collection sponsor', async () => { + const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT'); + await setCollectionSponsorExpectSuccess(collectionId, bob.address); + }); + it('Set Fungible collection sponsor', async () => { + const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible'); + await setCollectionSponsorExpectSuccess(collectionId, bob.address); + }); + it('Set ReFungible collection sponsor', async () => { + const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible'); + await setCollectionSponsorExpectSuccess(collectionId, bob.address); + }); + + it('Set the same sponsor repeatedly', async () => { + const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT'); + await setCollectionSponsorExpectSuccess(collectionId, bob.address); + await setCollectionSponsorExpectSuccess(collectionId, bob.address); + }); + it('Replace collection sponsor', async () => { + const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT'); + + const keyring = new Keyring({ type: 'sr25519' }); + const charlie = keyring.addFromUri(`//Charlie`); + await setCollectionSponsorExpectSuccess(collectionId, bob.address); + await setCollectionSponsorExpectSuccess(collectionId, charlie.address); + }); +}); + +describe('(!negative test!) integration test: ext. setCollectionSponsor():', () => { + before(async () => { + await usingApi(async (api) => { + const keyring = new Keyring({ type: 'sr25519' }); + bob = keyring.addFromUri(`//Bob`); + }); + }); + + it('(!negative test!) Add sponsor to a collection that never existed', async () => { + // Find the collection that never existed + const collectionId = 0; + await usingApi(async (api) => { + const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1; + }); + + await setCollectionSponsorExpectFailure(collectionId, bob.address); + }); + it('(!negative test!) Add sponsor to a collection that was destroyed', async () => { + const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT'); + await destroyCollectionExpectSuccess(collectionId); + await setCollectionSponsorExpectFailure(collectionId, bob.address); + }); +}); --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -9,7 +9,7 @@ import { ApiPromise, Keyring } from "@polkadot/api"; import { default as usingApi, submitTransactionAsync } from "../substrate/substrate-api"; import privateKey from '../substrate/privateKey'; -import { alicesPublicKey } from "../accounts"; +import { alicesPublicKey, nullPublicKey } from "../accounts"; import { strToUTF16, utf16ToStr, hexToStr } from '../util/util'; import { IKeyringPair } from "@polkadot/types/types"; import { BigNumber } from 'bignumber.js'; @@ -121,4 +121,79 @@ bal = new BigNumber((await api.query.system.account(unused.address)).data.free.toString()); } while (bal.toFixed() != '0'); return unused; -} \ No newline at end of file +} + +function getDestroyResult(events: EventRecord[]): boolean { + let success: boolean = false; + events.forEach(({ phase, event: { data, method, section } }) => { + // console.log(` ${phase}: ${section}.${method}:: ${data}`); + if (method == 'ExtrinsicSuccess') { + success = true; + } + }); + return success; +} + +export async function setCollectionSponsorExpectSuccess(collectionId: number, sponsor: string) { + await usingApi(async (api) => { + + // Run the transaction + const alicePrivateKey = privateKey('//Alice'); + const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor); + const events = await submitTransactionAsync(alicePrivateKey, tx); + const result = getGenericResult(events); + + // Get the collection + const collection: any = (await api.query.nft.collection(collectionId)).toJSON(); + + // What to expect + expect(result.success).to.be.true; + expect(collection.Sponsor.toString()).to.be.equal(sponsor.toString()); + expect(collection.SponsorConfirmed).to.be.false; + }); +} + +export async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') { + await usingApi(async (api) => { + // Run the DestroyCollection transaction + const alicePrivateKey = privateKey(senderSeed); + const tx = api.tx.nft.destroyCollection(collectionId); + const events = await submitTransactionAsync(alicePrivateKey, tx); + const result = getDestroyResult(events); + + // What to expect + expect(result).to.be.false; + }); +} + +export async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') { + await usingApi(async (api) => { + // Run the DestroyCollection transaction + const alicePrivateKey = privateKey(senderSeed); + const tx = api.tx.nft.destroyCollection(collectionId); + const events = await submitTransactionAsync(alicePrivateKey, tx); + const result = getDestroyResult(events); + + // Get the collection + const collection: any = (await api.query.nft.collection(collectionId)).toJSON(); + + // What to expect + expect(result).to.be.true; + expect(collection).to.be.not.null; + expect(collection.Owner).to.be.equal(nullPublicKey); + }); +} + +export async function setCollectionSponsorExpectFailure(collectionId: number, sponsor: string) { + await usingApi(async (api) => { + + // Run the transaction + const alicePrivateKey = privateKey('//Alice'); + const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor); + const events = await submitTransactionAsync(alicePrivateKey, tx); + const result = getGenericResult(events); + + // What to expect + expect(result.success).to.be.false; + }); +} -- gitstuff