git.delta.rocks / unique-network / refs/commits / ff11a705d0b0

difftreelog

Merge branch 'develop' into feature/NFTPAR-243_enableContractSponsoring

Yaroslav Bolyukin2021-02-04parents: #f8edc89 #104a9a3.patch.diff
in: master

12 files changed

added.devcontainer/Dockerfilediffbeforeafterboth
--- /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
added.devcontainer/devcontainer.jsondiffbeforeafterboth
--- /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"
+}
modifiedREADME.mddiffbeforeafterboth
--- 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
 ```
 
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -1122,8 +1122,8 @@
 
             // Check approval
             let mut approval: u128 = 0;
-            if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {
-                approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));
+            if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &sender)) {
+                approval = <Allowances<T>>::get(collection_id, (item_id, &from, &sender));
                 ensure!(approval >= value, Error::<T>::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 {
-                <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);
+                <Allowances<T>>::insert(collection_id, (item_id, &from, &sender), approval - value);
             }
             else {
-                <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));
+                <Allowances<T>>::remove(collection_id, (item_id, &from, &sender));
             }
 
             match target_collection.mode
modifiedtests/README.mddiffbeforeafterboth
--- 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`.
 
modifiedtests/package.jsondiffbeforeafterboth
--- 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",
modifiedtests/src/createMultipleItems.test.tsdiffbeforeafterboth
--- 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;
     });
   });
 });
addedtests/src/removeFromWhiteList.test.tsdiffbeforeafterboth
--- /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);
+    });
+  });
+});
addedtests/src/setContractSponsoringRateLimit.test.tsdiffbeforeafterboth
--- /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);
+    });
+  });
+});
modifiedtests/src/substrate/substrate-api.tsdiffbeforeafterboth
--- 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);
         }
       });
modifiedtests/src/util/contracthelpers.tsdiffbeforeafterboth
55
6import chai from "chai";6import chai from "chai";
7import chaiAsPromised from 'chai-as-promised';7import chaiAsPromised from 'chai-as-promised';
8import { submitTransactionAsync } from "../substrate/substrate-api";8import { submitTransactionAsync, submitTransactionExpectFailAsync } from "../substrate/substrate-api";
9import fs from "fs";9import fs from "fs";
10import { Abi, BlueprintPromise as Blueprint, CodePromise, ContractPromise as Contract } from "@polkadot/api-contract";10import { Abi, BlueprintPromise as Blueprint, CodePromise, ContractPromise as Contract } from "@polkadot/api-contract";
11import { IKeyringPair } from "@polkadot/types/types";11import { IKeyringPair } from "@polkadot/types/types";
100 expect(result.success).to.be.true;100 expect(result.success).to.be.true;
101}101}
102
103export async function toggleFlipValueExpectFailure(sender: IKeyringPair, contract: Contract) {
104 const tx = contract.tx.flip(value, gasLimit);
105 await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
106}
102107
103function instantiateTransferContract(alice: IKeyringPair, blueprint: Blueprint) : Promise<any> {108function instantiateTransferContract(alice: IKeyringPair, blueprint: Blueprint) : Promise<any> {
104 return new Promise<any>(async (resolve, reject) => {109 return new Promise<any>(async (resolve, reject) => {
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- 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<ICollectionInterface | null> => {
   return await api.query.nft.collection(collectionId) as unknown as ICollectionInterface;