--- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,14 @@ +FROM mcr.microsoft.com/vscode/devcontainers/rust:0-1 + +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && \ + apt-get -y install --no-install-recommends libssl-dev pkg-config libclang-dev clang + +USER vscode + +RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash && \ + export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + nvm install v12.20.1 && \ + rustup toolchain install nightly-2020-10-01 && \ + rustup default nightly-2020-10-01 && \ + rustup target add wasm32-unknown-unknown \ No newline at end of file --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,18 @@ +{ + "name": "Rust", + "build": { + "dockerfile": "Dockerfile" + }, + "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "lldb.executable": "/usr/bin/lldb", + "files.watcherExclude": { + "**/target/**": true + } + }, + "extensions": [ + "matklad.rust-analyzer" + ], + "remoteUser": "vscode" +} --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ ```bash rustup toolchain install 1.49.0 -rustup toolchain install nightly-2020-10-01 +rustup toolchain install nightly-2020-01-27 rustup default nightly-2021-01-27 ``` --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -1122,8 +1122,8 @@ // Check approval let mut approval: u128 = 0; - if >::contains_key(collection_id, (item_id, &from, &recipient)) { - approval = >::get(collection_id, (item_id, &from, &recipient)); + if >::contains_key(collection_id, (item_id, &from, &sender)) { + approval = >::get(collection_id, (item_id, &from, &sender)); ensure!(approval >= value, Error::::TokenValueNotEnough); appoved_transfer = true; } @@ -1144,10 +1144,10 @@ // Reduce approval by transferred amount or remove if remaining approval drops to 0 if approval.checked_sub(value).unwrap_or(0) > 0 { - >::insert(collection_id, (item_id, &from, &recipient), approval - value); + >::insert(collection_id, (item_id, &from, &sender), approval - value); } else { - >::remove(collection_id, (item_id, &from, &recipient)); + >::remove(collection_id, (item_id, &from, &sender)); } match target_collection.mode --- a/tests/README.md +++ b/tests/README.md @@ -2,8 +2,8 @@ ## How to run -1. Run `npm install`. +1. Run `yarn install`. 2. Setup a test node. You can do it using `docker-compose up -d` in parent directory. 3. Optional step - configure tests with env variables or by editing [configuration file](src/config.ts). -4. Run `npm test`. +4. Run `yarn test`. --- a/tests/package.json +++ b/tests/package.json @@ -25,8 +25,10 @@ "testSetVariableMetaData": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetaData.test.ts", "testSetCollectionLimits": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionLimits.test.ts", "testRemoveCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/removeCollectionAdmin.test.ts", + "testRemoveFromWhiteList": "mocha --timeout 9999999 -r ts-node/register ./**/removeFromWhiteList.test.ts", "testConnection": "mocha --timeout 9999999 -r ts-node/register ./**/connection.test.ts", "testCollection": "mocha --timeout 9999999 -r ts-node/register ./**/createCollection.test.ts", + "testCreateMultipleItems": "mocha --timeout 9999999 -r ts-node/register ./**/createMultipleItems.test.ts", "testApprove": "mocha --timeout 9999999 -r ts-node/register ./**/approve.test.ts", "testTransferFrom": "mocha --timeout 9999999 -r ts-node/register ./**/transferFrom.test.ts", "testCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/createCollection.test.ts", @@ -35,7 +37,8 @@ "testTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/transfer.test.ts", "testBurnItem": "mocha --timeout 9999999 -r ts-node/register ./**/burnItem.test.ts", "testCreditFeesToTreasury": "mocha --timeout 9999999 -r ts-node/register ./**/creditFeesToTreasury.test.ts", - "testEnableContractSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/enableContractSponsoring.test.ts" + "testEnableContractSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/enableContractSponsoring.test.ts", + "testSetContractSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setContractSponsoringRateLimit.test.ts" }, "author": "", "license": "SEE LICENSE IN ../LICENSE", --- a/tests/src/createMultipleItems.test.ts +++ b/tests/src/createMultipleItems.test.ts @@ -2,68 +2,177 @@ // This file is subject to the terms and conditions defined in // file 'LICENSE', which is part of this source code package. // +import { ApiPromise } from '@polkadot/api'; +import BN from 'bn.js'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import privateKey from './substrate/privateKey'; +import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api'; +import { + createCollectionExpectSuccess, + destroyCollectionExpectSuccess, + IReFungibleTokenDataType, +} from './util/helpers'; -import { assert } from 'chai'; -import { alicesPublicKey } from './accounts'; -import privateKey from './substrate/privateKey'; -import usingApi from './substrate/substrate-api'; -import waitNewBlocks from './substrate/wait-new-blocks'; +chai.use(chaiAsPromised); +const expect = chai.expect; -const idCollection = 12; +interface ITokenDataType { + Owner: number[]; + ConstData: number[]; + VariableData: number[]; +} -describe.skip('integration test: ext. createMultipleItems():', () => { - it('Create two NFT tokens in active NFT collection', async () => { - await usingApi(async (api) => { - const AitemListIndex = await api.query.nft.itemListIndex(idCollection); - console.log(`itemListIndex count (before): ${AitemListIndex}`); - const args = ['NFT', 'NFT']; - const alicePrivateKey = privateKey('//Alice'); - const createMultipleItems = await api.tx.nft - .createMultipleItems(idCollection, alicesPublicKey, args) - .signAndSend(alicePrivateKey); - // tslint:disable-next-line: no-unused-expression - assert.exists(createMultipleItems, 'createMultipleItems is neither `null` or `undefined`'); - console.log(`Ext. createMultipleItems submitted with hash: ${createMultipleItems}`); - await waitNewBlocks(api); - const BitemListIndex = await api.query.nft.itemListIndex(idCollection); - console.log(`itemListIndex count (after): ${BitemListIndex}`); - if (BitemListIndex === AitemListIndex) { assert.fail('Corret token not added in collection!'); } +describe('Integration Test createMultipleItems(collection_id, owner, items_data):', () => { + it('Create 0x31, 0x32, 0x33 items in active NFT collection and verify tokens data in chain', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionExpectSuccess(); + const itemsListIndexBefore = await api.query.nft.itemListIndex(collectionId) as unknown as BN; + expect(itemsListIndexBefore.toNumber()).to.be.equal(0); + const Alice = privateKey('//Alice'); + const args = [{ nft: ['0x31', '0x31'] }, { nft: ['0x32', '0x32'] }, { nft: ['0x33', '0x33'] }]; + const createMultipleItemsTx = await api.tx.nft + .createMultipleItems(collectionId, Alice.address, args); + await submitTransactionAsync(Alice, createMultipleItemsTx); + const itemsListIndexAfter = await api.query.nft.itemListIndex(collectionId) as unknown as BN; + expect(itemsListIndexAfter.toNumber()).to.be.equal(3); + const token1Data = await api.query.nft.nftItemList(collectionId, 1) as unknown as ITokenDataType; + const token2Data = await api.query.nft.nftItemList(collectionId, 2) as unknown as ITokenDataType; + const token3Data = await api.query.nft.nftItemList(collectionId, 3) as unknown as ITokenDataType; + + expect(token1Data.Owner.toString()).to.be.equal(Alice.address); + expect(token2Data.Owner.toString()).to.be.equal(Alice.address); + expect(token3Data.Owner.toString()).to.be.equal(Alice.address); + + expect(token1Data.ConstData.toString()).to.be.equal('0x31'); + expect(token2Data.ConstData.toString()).to.be.equal('0x32'); + expect(token3Data.ConstData.toString()).to.be.equal('0x33'); + + expect(token1Data.VariableData.toString()).to.be.equal('0x31'); + expect(token2Data.VariableData.toString()).to.be.equal('0x32'); + expect(token3Data.VariableData.toString()).to.be.equal('0x33'); }); }); - it('(!negative test!) Create two Fungible tokens in active NFT collection', async () => { - await usingApi(async (api) => { - const AitemListIndex = await api.query.nft.itemListIndex(idCollection); - console.log(`itemListIndex count (before): ${AitemListIndex}`); - const args = ['Fungible', 'Fungible']; - const alicePrivateKey = privateKey('//Alice'); - const createMultipleItems = await api.tx.nft - .createMultipleItems(idCollection, alicesPublicKey, args) - .signAndSend(alicePrivateKey); - // tslint:disable-next-line: no-unused-expression - assert.exists(createMultipleItems, 'createMultipleItems is neither `null` or `undefined`'); - console.log(`Ext. createMultipleItems submitted with hash: ${createMultipleItems}`); - await waitNewBlocks(api); - const BitemListIndex = await api.query.nft.itemListIndex(idCollection); - console.log(`itemListIndex count (after): ${BitemListIndex}`); - if (BitemListIndex > AitemListIndex) { assert.fail('Incorrect token added in collection!'); } + + it('Create 0x31, 0x32, 0x33 items in active ReFungible collection and verify tokens data in chain', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}}); + const itemsListIndexBefore = await api.query.nft.itemListIndex(collectionId) as unknown as BN; + expect(itemsListIndexBefore.toNumber()).to.be.equal(0); + const Alice = privateKey('//Alice'); + const args = [ + { Refungible: ['0x31', '0x31'] }, + { Refungible: ['0x32', '0x32'] }, + { Refungible: ['0x33', '0x33'] }, + ]; + const createMultipleItemsTx = await api.tx.nft + .createMultipleItems(collectionId, Alice.address, args); + await submitTransactionAsync(Alice, createMultipleItemsTx); + const itemsListIndexAfter = await api.query.nft.itemListIndex(collectionId) as unknown as BN; + expect(itemsListIndexAfter.toNumber()).to.be.equal(3); + const token1Data = await api.query.nft.reFungibleItemList(collectionId, 1) as unknown as IReFungibleTokenDataType; + const token2Data = await api.query.nft.reFungibleItemList(collectionId, 2) as unknown as IReFungibleTokenDataType; + const token3Data = await api.query.nft.reFungibleItemList(collectionId, 3) as unknown as IReFungibleTokenDataType; + + expect(token1Data.Owner[0].Owner.toString()).to.be.equal(Alice.address); + expect(token1Data.Owner[0].Fraction.toNumber()).to.be.equal(1); + + expect(token2Data.Owner[0].Owner.toString()).to.be.equal(Alice.address); + expect(token2Data.Owner[0].Fraction.toNumber()).to.be.equal(1); + + expect(token3Data.Owner[0].Owner.toString()).to.be.equal(Alice.address); + expect(token3Data.Owner[0].Fraction.toNumber()).to.be.equal(1); + + expect(token1Data.ConstData.toString()).to.be.equal('0x31'); + expect(token2Data.ConstData.toString()).to.be.equal('0x32'); + expect(token3Data.ConstData.toString()).to.be.equal('0x33'); + + expect(token1Data.VariableData.toString()).to.be.equal('0x31'); + expect(token2Data.VariableData.toString()).to.be.equal('0x32'); + expect(token3Data.VariableData.toString()).to.be.equal('0x33'); }); }); - it('(!negative test!) Create two ReFungible tokens in active NFT collection', async () => { - await usingApi(async (api) => { - const AitemListIndex = await api.query.nft.itemListIndex(idCollection); - console.log(`itemListIndex count (before): ${AitemListIndex}`); - const args = ['ReFungible', 'ReFungible']; - const alicePrivateKey = privateKey('//Alice'); - const createMultipleItems = await api.tx.nft - .createMultipleItems(idCollection, alicesPublicKey, args) - .signAndSend(alicePrivateKey); - // tslint:disable-next-line: no-unused-expression - assert.exists(createMultipleItems, 'createMultipleItems is neither `null` or `undefined`'); - console.log(`Ext. createMultipleItems submitted with hash: ${createMultipleItems}`); - await waitNewBlocks(api); - const BitemListIndex = await api.query.nft.itemListIndex(idCollection); - console.log(`itemListIndex count (after): ${BitemListIndex}`); - if (BitemListIndex > AitemListIndex) { assert.fail('Incorrect token added in collection!'); } +}); + +describe('Negative Integration Test createMultipleItems(collection_id, owner, items_data):', () => { + it('Create token with not existing type', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionExpectSuccess(); + const Alice = privateKey('//Alice'); + try { + const args = [{ invalid: null }, { invalid: null }, { invalid: null }]; + const createMultipleItemsTx = await api.tx.nft + .createMultipleItems(collectionId, Alice.address, args); + await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected; + } catch (e) { + // tslint:disable-next-line:no-unused-expression + expect(e).to.be.exist; + } + }); + }); + + it('Create token in not existing collection', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1; + const Alice = privateKey('//Alice'); + const createMultipleItemsTx = await api.tx.nft + .createMultipleItems(collectionId, Alice.address, ['NFT', 'NFT', 'NFT']); + await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected; + }); + }); + + it('Create NFT and Re-fungible tokens that has reached the maximum data limit', async () => { + await usingApi(async (api: ApiPromise) => { + // NFT + const collectionId = await createCollectionExpectSuccess(); + const Alice = privateKey('//Alice'); + const args = [ + { nft: ['A'.repeat(2049), 'A'.repeat(2049)] }, + { nft: ['B'.repeat(2049), 'B'.repeat(2049)] }, + { nft: ['C'.repeat(2049), 'C'.repeat(2049)] }, + ]; + const createMultipleItemsTx = await api.tx.nft + .createMultipleItems(collectionId, Alice.address, args); + await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected; + + // ReFungible + const collectionIdReFungible = + await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}}); + const argsReFungible = [ + { ReFungible: ['1'.repeat(2049), '1'.repeat(2049)] }, + { ReFungible: ['2'.repeat(2049), '2'.repeat(2049)] }, + { ReFungible: ['3'.repeat(2049), '3'.repeat(2049)] }, + ]; + const createMultipleItemsTxFungible = await api.tx.nft + .createMultipleItems(collectionIdReFungible, Alice.address, argsReFungible); + await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTxFungible)).to.be.rejected; + }); + }); + + it('Create tokens with different types', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionExpectSuccess(); + const Alice = privateKey('//Alice'); + const createMultipleItemsTx = await api.tx.nft + .createMultipleItems(collectionId, Alice.address, ['NFT', 'Fungible', 'ReFungible']); + await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected; + // garbage collection :-D + await destroyCollectionExpectSuccess(collectionId); + }); + }); + + it('Create tokens with different data limits <> maximum data limit', async () => { + await usingApi(async (api: ApiPromise) => { + const collectionId = await createCollectionExpectSuccess(); + const Alice = privateKey('//Alice'); + const args = [ + { nft: ['A', 'A'] }, + { nft: ['B', 'B'.repeat(2049)] }, + { nft: ['C'.repeat(2049), 'C'] }, + ]; + const createMultipleItemsTx = await api.tx.nft + .createMultipleItems(collectionId, Alice.address, args); + await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected; }); }); }); --- /dev/null +++ b/tests/src/removeFromWhiteList.test.ts @@ -0,0 +1,84 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { default as usingApi } from './substrate/substrate-api'; +import { + createCollectionExpectSuccess, + destroyCollectionExpectSuccess, + enableWhiteListExpectSuccess, + addToWhiteListExpectSuccess, + removeFromWhiteListExpectSuccess, + isWhitelisted, + findNotExistingCollection, + removeFromWhiteListExpectFailure, + disableWhiteListExpectSuccess, +} from './util/helpers'; +import { IKeyringPair } from '@polkadot/types/types'; +import privateKey from './substrate/privateKey'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +describe('Integration Test removeFromWhiteList', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + + before(async () => { + await usingApi(async (api) => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + }); + }); + + it('ensure bob is not in whitelist after removal', async () => { + await usingApi(async () => { + const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } }); + await enableWhiteListExpectSuccess(alice, collectionId); + await addToWhiteListExpectSuccess(alice, collectionId, bob.address); + + await removeFromWhiteListExpectSuccess(alice, collectionId, bob.address); + expect(await isWhitelisted(collectionId, bob.address)).to.be.false; + }); + }); + + it('allows removal from collection with unset whitelist status', async () => { + await usingApi(async () => { + const collectionWithoutWhitelistId = await createCollectionExpectSuccess(); + await enableWhiteListExpectSuccess(alice, collectionWithoutWhitelistId); + await addToWhiteListExpectSuccess(alice, collectionWithoutWhitelistId, bob.address); + await disableWhiteListExpectSuccess(alice, collectionWithoutWhitelistId); + + await removeFromWhiteListExpectSuccess(alice, collectionWithoutWhitelistId, bob.address); + }); + }); +}); + +describe('Negative Integration Test removeFromWhiteList', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + + before(async () => { + await usingApi(async (api) => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + }); + }); + + it('fails on removal from not existing collection', async () => { + await usingApi(async (api) => { + const collectionId = await findNotExistingCollection(api); + + await removeFromWhiteListExpectFailure(alice, collectionId, bob.address); + }); + }); + + it('fails on removal from removed collection', async () => { + await usingApi(async () => { + const collectionId = await createCollectionExpectSuccess(); + await enableWhiteListExpectSuccess(alice, collectionId); + await addToWhiteListExpectSuccess(alice, collectionId, bob.address); + await destroyCollectionExpectSuccess(collectionId); + + await removeFromWhiteListExpectFailure(alice, collectionId, bob.address); + }); + }); +}); --- /dev/null +++ b/tests/src/setContractSponsoringRateLimit.test.ts @@ -0,0 +1,62 @@ +import { IKeyringPair } from '@polkadot/types/types'; +import privateKey from './substrate/privateKey'; +import usingApi from './substrate/substrate-api'; +import waitNewBlocks from './substrate/wait-new-blocks'; +import { deployFlipper, toggleFlipValueExpectFailure, toggleFlipValueExpectSuccess } from './util/contracthelpers'; +import { + enableContractSponsoringExpectSuccess, + findUnusedAddress, + setContractSponsoringRateLimitExpectFailure, + setContractSponsoringRateLimitExpectSuccess, +} from './util/helpers'; + +describe('Integration Test setContractSponsoringRateLimit', () => { + it('ensure sponsored contract can\'t be called twice without pause for free', async () => { + await usingApi(async (api) => { + const user = await findUnusedAddress(api); + + const [flipper, deployer] = await deployFlipper(api); + await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); + await setContractSponsoringRateLimitExpectSuccess(deployer, flipper.address, 10); + await toggleFlipValueExpectSuccess(user, flipper); + await toggleFlipValueExpectFailure(user, flipper); + }); + }); + + it('ensure sponsored contract can be called twice with pause for free', async () => { + await usingApi(async (api) => { + const user = await findUnusedAddress(api); + + const [flipper, deployer] = await deployFlipper(api); + await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); + await setContractSponsoringRateLimitExpectSuccess(deployer, flipper.address, 1); + await toggleFlipValueExpectSuccess(user, flipper); + await waitNewBlocks(api, 1); + await toggleFlipValueExpectSuccess(user, flipper); + }); + }); +}); + +describe('Negative Integration Test setContractSponsoringRateLimit', () => { + let alice: IKeyringPair; + + before(async () => { + alice = privateKey('//Alice'); + }); + + it('fails when called for non-contract address', async () => { + await usingApi(async (api) => { + const user = await findUnusedAddress(api); + + await setContractSponsoringRateLimitExpectFailure(alice, user.address, 1); + }); + }); + + it('fails when called by non-owning user', async () => { + await usingApi(async (api) => { + const [flipper, _] = await deployFlipper(api); + + await setContractSponsoringRateLimitExpectFailure(alice, flipper.address, 1); + }); + }); +}); --- a/tests/src/substrate/substrate-api.ts +++ b/tests/src/substrate/substrate-api.ts @@ -123,9 +123,9 @@ // console.log('transactionStatus', transactionStatus, 'events', events); - if (transactionStatus == TransactionStatus.Success) { + if (transactionStatus === TransactionStatus.Success) { resolve(events); - } else if (transactionStatus == TransactionStatus.Fail) { + } else if (transactionStatus === TransactionStatus.Fail) { reject(events); } }); --- a/tests/src/util/contracthelpers.ts +++ b/tests/src/util/contracthelpers.ts @@ -5,7 +5,7 @@ import chai from "chai"; import chaiAsPromised from 'chai-as-promised'; -import { submitTransactionAsync } from "../substrate/substrate-api"; +import { submitTransactionAsync, submitTransactionExpectFailAsync } from "../substrate/substrate-api"; import fs from "fs"; import { Abi, BlueprintPromise as Blueprint, CodePromise, ContractPromise as Contract } from "@polkadot/api-contract"; import { IKeyringPair } from "@polkadot/types/types"; @@ -100,6 +100,11 @@ expect(result.success).to.be.true; } +export async function toggleFlipValueExpectFailure(sender: IKeyringPair, contract: Contract) { + const tx = contract.tx.flip(value, gasLimit); + await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; +} + function instantiateTransferContract(alice: IKeyringPair, blueprint: Blueprint) : Promise { return new Promise(async (resolve, reject) => { const unsub = await blueprint.tx --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -51,7 +51,7 @@ Value: BN; } -interface IReFungibleTokenDataType { +export interface IReFungibleTokenDataType { Owner: IReFungibleOwner[]; ConstData: number[]; VariableData: number[]; @@ -414,6 +414,16 @@ }); } +export async function setContractSponsoringRateLimitExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) { + await usingApi(async (api) => { + const tx = api.tx.nft.setContractSponsoringRateLimit(contractAddress, rateLimit); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getGenericResult(events); + + expect(result.success).to.be.false; + }); +} + export async function setVariableMetaDataExpectSuccess(sender: IKeyringPair, collectionId: number, itemId: number, data: number[]) { await usingApi(async (api) => { const tx = api.tx.nft.setVariableMetaData(collectionId, itemId, '0x' + Buffer.from(data).toString('hex')); @@ -492,7 +502,7 @@ } const transferFromTx = await api.tx.nft.transferFrom( accountFrom.address, accountTo.address, collectionId, tokenId, value); - const events = await submitTransactionAsync(accountFrom, transferFromTx); + const events = await submitTransactionAsync(accountApproved, transferFromTx); const result = getCreateItemResult(events); // tslint:disable-next-line:no-unused-expression expect(result.success).to.be.true; @@ -635,11 +645,14 @@ return newItemId; } -export async function enableWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number) { +export async function setPublicAccessModeExpectSuccess( + sender: IKeyringPair, collectionId: number, + accessMode: 'Normal' | 'WhiteList', +) { await usingApi(async (api) => { // Run the transaction - const tx = api.tx.nft.setPublicAccessMode(collectionId, 'WhiteList'); + const tx = api.tx.nft.setPublicAccessMode(collectionId, accessMode); const events = await submitTransactionAsync(sender, tx); const result = getGenericResult(events); @@ -649,10 +662,18 @@ // What to expect // tslint:disable-next-line:no-unused-expression expect(result.success).to.be.true; - expect(collection.Access).to.be.equal('WhiteList'); + expect(collection.Access).to.be.equal(accessMode); }); } +export async function enableWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number) { + await setPublicAccessModeExpectSuccess(sender, collectionId, 'WhiteList'); +} + +export async function disableWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number) { + await setPublicAccessModeExpectSuccess(sender, collectionId, 'Normal'); +} + export async function enablePublicMintingExpectSuccess(sender: IKeyringPair, collectionId: number) { await usingApi(async (api) => { @@ -670,6 +691,14 @@ }); } +export async function isWhitelisted(collectionId: number, address: string) { + let whitelisted: boolean = false; + await usingApi(async (api) => { + whitelisted = (await api.query.nft.whiteList(collectionId, address)).toJSON() as unknown as boolean; + }); + return whitelisted; +} + export async function addToWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string) { await usingApi(async (api) => { @@ -692,6 +721,32 @@ }); } +export async function removeFromWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string) { + await usingApi(async (api) => { + // Run the transaction + const tx = api.tx.nft.removeFromWhiteList(collectionId, address); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.true; + }); +} + +export async function removeFromWhiteListExpectFailure(sender: IKeyringPair, collectionId: number, address: string) { + await usingApi(async (api) => { + // Run the transaction + const tx = api.tx.nft.removeFromWhiteList(collectionId, address); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getGenericResult(events); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.false; + }); +} + export const getDetailedCollectionInfo = async (api: ApiPromise, collectionId: number) : Promise => { return await api.query.nft.collection(collectionId) as unknown as ICollectionInterface;