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
3// file 'LICENSE', which is part of this source code package.3// file 'LICENSE', which is part of this source code package.
4//4//
5
6import { assert } from 'chai';5import { ApiPromise } from '@polkadot/api';
6import BN from 'bn.js';
7import chai from 'chai';
7import { alicesPublicKey } from './accounts';8import chaiAsPromised from 'chai-as-promised';
8import privateKey from './substrate/privateKey';9import privateKey from './substrate/privateKey';
9import usingApi from './substrate/substrate-api';10import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';
10import waitNewBlocks from './substrate/wait-new-blocks';11import {
1112 createCollectionExpectSuccess,
13 destroyCollectionExpectSuccess,
14 IReFungibleTokenDataType,
15} from './util/helpers';
16
17chai.use(chaiAsPromised);
12const idCollection = 12;18const expect = chai.expect;
19
20interface ITokenDataType {
21 Owner: number[];
22 ConstData: number[];
23 VariableData: number[];
24}
25
26describe('Integration Test createMultipleItems(collection_id, owner, items_data):', () => {
27 it('Create 0x31, 0x32, 0x33 items in active NFT collection and verify tokens data in chain', async () => {
28 await usingApi(async (api: ApiPromise) => {
29 const collectionId = await createCollectionExpectSuccess();
30 const itemsListIndexBefore = await api.query.nft.itemListIndex(collectionId) as unknown as BN;
31 expect(itemsListIndexBefore.toNumber()).to.be.equal(0);
32 const Alice = privateKey('//Alice');
33 const args = [{ nft: ['0x31', '0x31'] }, { nft: ['0x32', '0x32'] }, { nft: ['0x33', '0x33'] }];
34 const createMultipleItemsTx = await api.tx.nft
35 .createMultipleItems(collectionId, Alice.address, args);
36 await submitTransactionAsync(Alice, createMultipleItemsTx);
37 const itemsListIndexAfter = await api.query.nft.itemListIndex(collectionId) as unknown as BN;
38 expect(itemsListIndexAfter.toNumber()).to.be.equal(3);
39 const token1Data = await api.query.nft.nftItemList(collectionId, 1) as unknown as ITokenDataType;
40 const token2Data = await api.query.nft.nftItemList(collectionId, 2) as unknown as ITokenDataType;
41 const token3Data = await api.query.nft.nftItemList(collectionId, 3) as unknown as ITokenDataType;
42
43 expect(token1Data.Owner.toString()).to.be.equal(Alice.address);
44 expect(token2Data.Owner.toString()).to.be.equal(Alice.address);
45 expect(token3Data.Owner.toString()).to.be.equal(Alice.address);
46
47 expect(token1Data.ConstData.toString()).to.be.equal('0x31');
48 expect(token2Data.ConstData.toString()).to.be.equal('0x32');
49 expect(token3Data.ConstData.toString()).to.be.equal('0x33');
50
51 expect(token1Data.VariableData.toString()).to.be.equal('0x31');
52 expect(token2Data.VariableData.toString()).to.be.equal('0x32');
53 expect(token3Data.VariableData.toString()).to.be.equal('0x33');
54 });
55 });
56
57 it('Create 0x31, 0x32, 0x33 items in active ReFungible collection and verify tokens data in chain', async () => {
58 await usingApi(async (api: ApiPromise) => {
59 const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
60 const itemsListIndexBefore = await api.query.nft.itemListIndex(collectionId) as unknown as BN;
61 expect(itemsListIndexBefore.toNumber()).to.be.equal(0);
62 const Alice = privateKey('//Alice');
63 const args = [
64 { Refungible: ['0x31', '0x31'] },
65 { Refungible: ['0x32', '0x32'] },
66 { Refungible: ['0x33', '0x33'] },
67 ];
68 const createMultipleItemsTx = await api.tx.nft
69 .createMultipleItems(collectionId, Alice.address, args);
70 await submitTransactionAsync(Alice, createMultipleItemsTx);
71 const itemsListIndexAfter = await api.query.nft.itemListIndex(collectionId) as unknown as BN;
72 expect(itemsListIndexAfter.toNumber()).to.be.equal(3);
73 const token1Data = await api.query.nft.reFungibleItemList(collectionId, 1) as unknown as IReFungibleTokenDataType;
74 const token2Data = await api.query.nft.reFungibleItemList(collectionId, 2) as unknown as IReFungibleTokenDataType;
75 const token3Data = await api.query.nft.reFungibleItemList(collectionId, 3) as unknown as IReFungibleTokenDataType;
76
77 expect(token1Data.Owner[0].Owner.toString()).to.be.equal(Alice.address);
78 expect(token1Data.Owner[0].Fraction.toNumber()).to.be.equal(1);
79
80 expect(token2Data.Owner[0].Owner.toString()).to.be.equal(Alice.address);
81 expect(token2Data.Owner[0].Fraction.toNumber()).to.be.equal(1);
82
83 expect(token3Data.Owner[0].Owner.toString()).to.be.equal(Alice.address);
84 expect(token3Data.Owner[0].Fraction.toNumber()).to.be.equal(1);
85
86 expect(token1Data.ConstData.toString()).to.be.equal('0x31');
87 expect(token2Data.ConstData.toString()).to.be.equal('0x32');
88 expect(token3Data.ConstData.toString()).to.be.equal('0x33');
89
90 expect(token1Data.VariableData.toString()).to.be.equal('0x31');
91 expect(token2Data.VariableData.toString()).to.be.equal('0x32');
92 expect(token3Data.VariableData.toString()).to.be.equal('0x33');
93 });
94 });
95});
1396
14describe.skip('integration test: ext. createMultipleItems():', () => {97describe('Negative Integration Test createMultipleItems(collection_id, owner, items_data):', () => {
15 it('Create two NFT tokens in active NFT collection', async () => {98 it('Create token with not existing type', async () => {
16 await usingApi(async (api) => {99 await usingApi(async (api: ApiPromise) => {
17 const AitemListIndex = await api.query.nft.itemListIndex(idCollection);100 const collectionId = await createCollectionExpectSuccess();
18 console.log(`itemListIndex count (before): ${AitemListIndex}`);101 const Alice = privateKey('//Alice');
102 try {
19 const args = ['NFT', 'NFT'];103 const args = [{ invalid: null }, { invalid: null }, { invalid: null }];
20 const alicePrivateKey = privateKey('//Alice');
21 const createMultipleItems = await api.tx.nft104 const createMultipleItemsTx = await api.tx.nft
22 .createMultipleItems(idCollection, alicesPublicKey, args)105 .createMultipleItems(collectionId, Alice.address, args);
23 .signAndSend(alicePrivateKey);
24 // tslint:disable-next-line: no-unused-expression
25 assert.exists(createMultipleItems, 'createMultipleItems is neither `null` or `undefined`');
26 console.log(`Ext. createMultipleItems submitted with hash: ${createMultipleItems}`);
27 await waitNewBlocks(api);
28 const BitemListIndex = await api.query.nft.itemListIndex(idCollection);106 await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected;
29 console.log(`itemListIndex count (after): ${BitemListIndex}`);
30 if (BitemListIndex === AitemListIndex) { assert.fail('Corret token not added in collection!'); }107 } catch (e) {
108 // tslint:disable-next-line:no-unused-expression
109 expect(e).to.be.exist;
110 }
31 });111 });
32 });112 });
113
33 it('(!negative test!) Create two Fungible tokens in active NFT collection', async () => {114 it('Create token in not existing collection', async () => {
34 await usingApi(async (api) => {115 await usingApi(async (api: ApiPromise) => {
35 const AitemListIndex = await api.query.nft.itemListIndex(idCollection);116 const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
36 console.log(`itemListIndex count (before): ${AitemListIndex}`);
37 const args = ['Fungible', 'Fungible'];
38 const alicePrivateKey = privateKey('//Alice');117 const Alice = privateKey('//Alice');
39 const createMultipleItems = await api.tx.nft118 const createMultipleItemsTx = await api.tx.nft
40 .createMultipleItems(idCollection, alicesPublicKey, args)119 .createMultipleItems(collectionId, Alice.address, ['NFT', 'NFT', 'NFT']);
41 .signAndSend(alicePrivateKey);
42 // tslint:disable-next-line: no-unused-expression
43 assert.exists(createMultipleItems, 'createMultipleItems is neither `null` or `undefined`');
44 console.log(`Ext. createMultipleItems submitted with hash: ${createMultipleItems}`);
45 await waitNewBlocks(api);
46 const BitemListIndex = await api.query.nft.itemListIndex(idCollection);120 await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected;
47 console.log(`itemListIndex count (after): ${BitemListIndex}`);
48 if (BitemListIndex > AitemListIndex) { assert.fail('Incorrect token added in collection!'); }
49 });121 });
50 });122 });
123
51 it('(!negative test!) Create two ReFungible tokens in active NFT collection', async () => {124 it('Create NFT and Re-fungible tokens that has reached the maximum data limit', async () => {
52 await usingApi(async (api) => {125 await usingApi(async (api: ApiPromise) => {
126 // NFT
53 const AitemListIndex = await api.query.nft.itemListIndex(idCollection);127 const collectionId = await createCollectionExpectSuccess();
54 console.log(`itemListIndex count (before): ${AitemListIndex}`);
55 const args = ['ReFungible', 'ReFungible'];128 const Alice = privateKey('//Alice');
56 const alicePrivateKey = privateKey('//Alice');129 const args = [
130 { nft: ['A'.repeat(2049), 'A'.repeat(2049)] },
131 { nft: ['B'.repeat(2049), 'B'.repeat(2049)] },
132 { nft: ['C'.repeat(2049), 'C'.repeat(2049)] },
133 ];
57 const createMultipleItems = await api.tx.nft134 const createMultipleItemsTx = await api.tx.nft
58 .createMultipleItems(idCollection, alicesPublicKey, args)135 .createMultipleItems(collectionId, Alice.address, args);
59 .signAndSend(alicePrivateKey);
60 // tslint:disable-next-line: no-unused-expression
61 assert.exists(createMultipleItems, 'createMultipleItems is neither `null` or `undefined`');136 await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected;
137
138 // ReFungible
139 const collectionIdReFungible =
62 console.log(`Ext. createMultipleItems submitted with hash: ${createMultipleItems}`);140 await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
141 const argsReFungible = [
63 await waitNewBlocks(api);142 { ReFungible: ['1'.repeat(2049), '1'.repeat(2049)] },
143 { ReFungible: ['2'.repeat(2049), '2'.repeat(2049)] },
144 { ReFungible: ['3'.repeat(2049), '3'.repeat(2049)] },
145 ];
64 const BitemListIndex = await api.query.nft.itemListIndex(idCollection);146 const createMultipleItemsTxFungible = await api.tx.nft
65 console.log(`itemListIndex count (after): ${BitemListIndex}`);147 .createMultipleItems(collectionIdReFungible, Alice.address, argsReFungible);
66 if (BitemListIndex > AitemListIndex) { assert.fail('Incorrect token added in collection!'); }148 await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTxFungible)).to.be.rejected;
67 });149 });
68 });150 });
151
152 it('Create tokens with different types', async () => {
153 await usingApi(async (api: ApiPromise) => {
154 const collectionId = await createCollectionExpectSuccess();
155 const Alice = privateKey('//Alice');
156 const createMultipleItemsTx = await api.tx.nft
157 .createMultipleItems(collectionId, Alice.address, ['NFT', 'Fungible', 'ReFungible']);
158 await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected;
159 // garbage collection :-D
160 await destroyCollectionExpectSuccess(collectionId);
161 });
162 });
163
164 it('Create tokens with different data limits <> maximum data limit', async () => {
165 await usingApi(async (api: ApiPromise) => {
166 const collectionId = await createCollectionExpectSuccess();
167 const Alice = privateKey('//Alice');
168 const args = [
169 { nft: ['A', 'A'] },
170 { nft: ['B', 'B'.repeat(2049)] },
171 { nft: ['C'.repeat(2049), 'C'] },
172 ];
173 const createMultipleItemsTx = await api.tx.nft
174 .createMultipleItems(collectionId, Alice.address, args);
175 await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected;
176 });
177 });
69});178});
70179
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
--- 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<any> {
   return new Promise<any>(async (resolve, reject) => {
     const unsub = await blueprint.tx
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;