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
before · tests/src/createMultipleItems.test.ts
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56import { assert } from 'chai';7import { alicesPublicKey } from './accounts';8import privateKey from './substrate/privateKey';9import usingApi from './substrate/substrate-api';10import waitNewBlocks from './substrate/wait-new-blocks';1112const idCollection = 12;1314describe.skip('integration test: ext. createMultipleItems():', () => {15  it('Create two NFT tokens in active NFT collection', async () => {16    await usingApi(async (api) => {17      const AitemListIndex = await api.query.nft.itemListIndex(idCollection);18      console.log(`itemListIndex count (before): ${AitemListIndex}`);19      const args = ['NFT', 'NFT'];20      const alicePrivateKey = privateKey('//Alice');21      const createMultipleItems = await api.tx.nft22      .createMultipleItems(idCollection, alicesPublicKey, args)23      .signAndSend(alicePrivateKey);24      // tslint:disable-next-line: no-unused-expression25      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);29      console.log(`itemListIndex count (after): ${BitemListIndex}`);30      if (BitemListIndex === AitemListIndex) { assert.fail('Corret token not added in collection!'); }31    });32  });33  it('(!negative test!) Create two Fungible tokens in active NFT collection', async () => {34    await usingApi(async (api) => {35      const AitemListIndex = await api.query.nft.itemListIndex(idCollection);36      console.log(`itemListIndex count (before): ${AitemListIndex}`);37      const args = ['Fungible', 'Fungible'];38      const alicePrivateKey = privateKey('//Alice');39      const createMultipleItems = await api.tx.nft40      .createMultipleItems(idCollection, alicesPublicKey, args)41      .signAndSend(alicePrivateKey);42      // tslint:disable-next-line: no-unused-expression43      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);47      console.log(`itemListIndex count (after): ${BitemListIndex}`);48      if (BitemListIndex > AitemListIndex) { assert.fail('Incorrect token added in collection!'); }49    });50  });51  it('(!negative test!) Create two ReFungible tokens in active NFT collection', async () => {52    await usingApi(async (api) => {53      const AitemListIndex = await api.query.nft.itemListIndex(idCollection);54      console.log(`itemListIndex count (before): ${AitemListIndex}`);55      const args = ['ReFungible', 'ReFungible'];56      const alicePrivateKey = privateKey('//Alice');57      const createMultipleItems = await api.tx.nft58      .createMultipleItems(idCollection, alicesPublicKey, args)59      .signAndSend(alicePrivateKey);60      // tslint:disable-next-line: no-unused-expression61      assert.exists(createMultipleItems, 'createMultipleItems is neither `null` or `undefined`');62      console.log(`Ext. createMultipleItems submitted with hash: ${createMultipleItems}`);63      await waitNewBlocks(api);64      const BitemListIndex = await api.query.nft.itemListIndex(idCollection);65      console.log(`itemListIndex count (after): ${BitemListIndex}`);66      if (BitemListIndex > AitemListIndex) { assert.fail('Incorrect token added in collection!'); }67    });68  });69});
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;