From 88a8aa611cb697bc13f6e935755db2af175f0ea5 Mon Sep 17 00:00:00 2001 From: ut-akuznetsov <59873862+ut-akuznetsov@users.noreply.github.com> Date: Fri, 24 Jun 2022 09:49:43 +0000 Subject: [PATCH] Merge pull request #404 from UniqueNetwork/test/unique-refungible --- --- /dev/null +++ b/tests/src/refungible.test.ts @@ -0,0 +1,165 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {default as usingApi} from './substrate/substrate-api'; +import {IKeyringPair} from '@polkadot/types/types'; +import { + createCollectionExpectSuccess, + getBalance, + createMultipleItemsExpectSuccess, + isTokenExists, + getLastTokenId, + getAllowance, + approve, + transferFrom, + createCollection, + createRefungibleToken, + transfer, + burnItem, +} from './util/helpers'; + +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +chai.use(chaiAsPromised); +const expect = chai.expect; + +let alice: IKeyringPair; +let bob: IKeyringPair; + +describe('integration test: Refungible functionality:', () => { + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + bob = privateKeyWrapper('//Bob'); + }); + }); + + it('Create refungible collection and token', async () => { + await usingApi(async api => { + const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}}); + expect(createCollectionResult.success).to.be.true; + const collectionId = createCollectionResult.collectionId; + + const itemCountBefore = await getLastTokenId(api, collectionId); + const result = await createRefungibleToken(api, alice, collectionId, 100n); + + const itemCountAfter = await getLastTokenId(api, collectionId); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.true; + expect(itemCountAfter).to.be.equal(itemCountBefore + 1); + expect(collectionId).to.be.equal(result.collectionId); + expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString()); + }); + }); + + it('Transfer token pieces', async () => { + await usingApi(async api => { + const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; + const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; + + expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); + expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true; + + expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(40n); + expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n); + await expect(transfer(api, collectionId, tokenId, alice, bob, 41n)).to.eventually.be.rejected; + }); + }); + + it('Create multiple tokens', async () => { + const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + const args = [ + {ReFungible: {pieces: 1}}, + {ReFungible: {pieces: 2}}, + {ReFungible: {pieces: 100}}, + ]; + await createMultipleItemsExpectSuccess(alice, collectionId, args); + + await usingApi(async api => { + const tokenId = await getLastTokenId(api, collectionId); + expect(tokenId).to.be.equal(3); + expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); + }); + }); + + it('Burn some pieces', async () => { + await usingApi(async api => { + const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; + const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; + expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; + expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); + expect(await burnItem(api, alice, collectionId, tokenId, 99n)).to.be.true; + expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; + expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(1n); + }); + }); + + it('Burn all pieces', async () => { + await usingApi(async api => { + const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; + const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; + expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; + expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); + expect(await burnItem(api, alice, collectionId, tokenId, 100n)).to.be.true; + expect(await isTokenExists(api, collectionId, tokenId)).to.be.false; + }); + }); + + it('Burn some pieces for multiple users', async () => { + await usingApi(async api => { + const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; + const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; + expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; + + expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); + expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true; + + + expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(40n); + expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n); + expect(await burnItem(api, alice, collectionId, tokenId, 40n)).to.be.true; + + expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n); + expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; + expect(await burnItem(api, bob, collectionId, tokenId, 59n)).to.be.true; + + expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(1n); + expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; + expect(await burnItem(api, bob, collectionId, tokenId, 1n)).to.be.true; + + expect(await isTokenExists(api, collectionId, tokenId)).to.be.false; + }); + }); + + it('Set allowance for token', async () => { + await usingApi(async api => { + const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; + const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; + + expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); + + expect(await approve(api, collectionId, tokenId, alice, bob, 60n)).to.be.true; + expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(60n); + + expect(await transferFrom(api, collectionId, tokenId, bob, alice, bob, 20n)).to.be.true; + expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(80n); + expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(20n); + expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(40n); + }); + }); +}); --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -318,6 +318,33 @@ tokenPrefix: 'prefix', }; +export async function +createCollection( + api: ApiPromise, + sender: IKeyringPair, + params: Partial = {}, +): Promise { + const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; + + let modeprm = {}; + if (mode.type === 'NFT') { + modeprm = {nft: null}; + } else if (mode.type === 'Fungible') { + modeprm = {fungible: mode.decimalPoints}; + } else if (mode.type === 'ReFungible') { + modeprm = {refungible: null}; + } + + const tx = api.tx.unique.createCollectionEx({ + name: strToUTF16(name), + description: strToUTF16(description), + tokenPrefix: strToUTF16(tokenPrefix), + mode: modeprm as any, + }); + const events = await submitTransactionAsync(sender, tx); + return getCreateCollectionResult(events); +} + export async function createCollectionExpectSuccess(params: Partial = {}): Promise { const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; @@ -329,23 +356,7 @@ // Run the CreateCollection transaction const alicePrivateKey = privateKeyWrapper('//Alice'); - let modeprm = {}; - if (mode.type === 'NFT') { - modeprm = {nft: null}; - } else if (mode.type === 'Fungible') { - modeprm = {fungible: mode.decimalPoints}; - } else if (mode.type === 'ReFungible') { - modeprm = {refungible: null}; - } - - const tx = api.tx.unique.createCollectionEx({ - name: strToUTF16(name), - description: strToUTF16(description), - tokenPrefix: strToUTF16(tokenPrefix), - mode: modeprm as any, - }); - const events = await submitTransactionAsync(alicePrivateKey, tx); - const result = getCreateCollectionResult(events); + const result = await createCollection(api, alicePrivateKey, params); // Get number of collections after the transaction const collectionCountAfter = await getCreatedCollectionCount(api); @@ -490,7 +501,7 @@ return unused; } -export async function getAllowance(api: ApiPromise, collectionId: number, owner: CrossAccountId | string, approved: CrossAccountId | string, tokenId: number) { +export async function getAllowance(api: ApiPromise, collectionId: number, owner: CrossAccountId | string | IKeyringPair, approved: CrossAccountId | string | IKeyringPair, tokenId: number) { return (await api.rpc.unique.allowance(collectionId, normalizeAccountId(owner), normalizeAccountId(approved), tokenId)).toBigInt(); } @@ -800,16 +811,19 @@ ReFungible: CreateReFungibleData; }; -export async function burnItemExpectSuccess(sender: IKeyringPair, collectionId: number, tokenId: number, value = 1) { +export async function burnItem(api: ApiPromise, sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint) : Promise { + const tx = api.tx.unique.burnItem(collectionId, tokenId, value); + const events = await submitTransactionAsync(sender, tx); + return getGenericResult(events).success; +} + +export async function burnItemExpectSuccess(sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint = 1) { await usingApi(async (api) => { const balanceBefore = await getBalance(api, collectionId, normalizeAccountId(sender), tokenId); // if burning token by admin - use adminButnItemExpectSuccess expect(balanceBefore >= BigInt(value)).to.be.true; - const tx = api.tx.unique.burnItem(collectionId, tokenId, value); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + expect(await burnItem(api, sender, collectionId, tokenId, value)).to.be.true; const balanceAfter = await getBalance(api, collectionId, normalizeAccountId(sender), tokenId); expect(balanceAfter + BigInt(value)).to.be.equal(balanceBefore); @@ -817,17 +831,26 @@ } export async function +approve( + api: ApiPromise, + collectionId: number, + tokenId: number, owner: IKeyringPair, approved: CrossAccountId | string | IKeyringPair, amount: number | bigint, +) { + const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount); + const events = await submitTransactionAsync(owner, approveUniqueTx); + return getGenericResult(events).success; +} + +export async function approveExpectSuccess( collectionId: number, tokenId: number, owner: IKeyringPair, approved: CrossAccountId | string, amount: number | bigint = 1, ) { await usingApi(async (api: ApiPromise) => { - const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount); - const events = await submitTransactionAsync(owner, approveUniqueTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; + const result = await approve(api, collectionId, tokenId, owner, approved, amount); + expect(result).to.be.true; - expect(await getAllowance(api, collectionId, owner.address, approved, tokenId)).to.be.equal(BigInt(amount)); + expect(await getAllowance(api, collectionId, owner, approved, tokenId)).to.be.equal(BigInt(amount)); }); } @@ -846,6 +869,23 @@ } export async function +transferFrom( + api: ApiPromise, + collectionId: number, + tokenId: number, + accountApproved: IKeyringPair, + accountFrom: IKeyringPair | CrossAccountId, + accountTo: IKeyringPair | CrossAccountId, + value: number | bigint, +) { + const from = normalizeAccountId(accountFrom); + const to = normalizeAccountId(accountTo); + const transferFromTx = api.tx.unique.transferFrom(from, to, collectionId, tokenId, value); + const events = await submitTransactionAsync(accountApproved, transferFromTx); + return getGenericResult(events).success; +} + +export async function transferFromExpectSuccess( collectionId: number, tokenId: number, @@ -862,11 +902,7 @@ if (type === 'Fungible' || type === 'ReFungible') { balanceBefore = await getBalance(api, collectionId, to, tokenId); } - const transferFromTx = api.tx.unique.transferFrom(normalizeAccountId(accountFrom), to, collectionId, tokenId, value); - const events = await submitTransactionAsync(accountApproved, transferFromTx); - const result = getGenericResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; + expect(await transferFrom(api, collectionId, tokenId, accountApproved, accountFrom, accountTo, value)).to.be.true; if (type === 'NFT') { expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to); } @@ -1071,6 +1107,20 @@ } export async function +transfer( + api: ApiPromise, + collectionId: number, + tokenId: number, + sender: IKeyringPair, + recipient: IKeyringPair | CrossAccountId, + value: number | bigint, +) : Promise { + const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value); + const events = await executeTransaction(api, sender, transferTx); + return getGenericResult(events).success; +} + +export async function transferExpectSuccess( collectionId: number, tokenId: number, @@ -1084,13 +1134,14 @@ const to = normalizeAccountId(recipient); let balanceBefore = 0n; - if (type === 'Fungible') { + if (type === 'Fungible' || type === 'ReFungible') { balanceBefore = await getBalance(api, collectionId, to, tokenId); } - const transferTx = api.tx.unique.transfer(to, collectionId, tokenId, value); - const events = await executeTransaction(api, sender, transferTx); + const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value); + const events = await executeTransaction(api, sender, transferTx); const result = getTransferResult(api, events); + expect(result.collectionId).to.be.equal(collectionId); expect(result.itemId).to.be.equal(tokenId); expect(result.sender).to.be.deep.equal(normalizeAccountId(sender.address)); @@ -1100,7 +1151,7 @@ if (type === 'NFT') { expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to); } - if (type === 'Fungible') { + if (type === 'Fungible' || type === 'ReFungible') { const balanceAfter = await getBalance(api, collectionId, to, tokenId); if (JSON.stringify(to) !== JSON.stringify(from)) { expect(balanceAfter - balanceBefore).to.be.equal(BigInt(value)); @@ -1108,9 +1159,6 @@ expect(balanceAfter).to.be.equal(balanceBefore); } } - if (type === 'ReFungible') { - expect(await getBalance(api, collectionId, to, tokenId) >= value).to.be.true; - } }); } @@ -1151,7 +1199,7 @@ export async function getBalance( api: ApiPromise, collectionId: number, - owner: string | CrossAccountId, + owner: string | CrossAccountId | IKeyringPair, token: number, ): Promise { return (await api.rpc.unique.balance(collectionId, normalizeAccountId(owner), token)).toBigInt(); @@ -1226,6 +1274,16 @@ }); } +export async function createMultipleItemsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any, owner: CrossAccountId | string = sender.address) { + await usingApi(async (api) => { + const to = normalizeAccountId(owner); + const tx = api.tx.unique.createMultipleItems(collectionId, to, itemsData); + + const events = await submitTransactionAsync(sender, tx); + expect(getGenericResult(events).success).to.be.true; + }); +} + export async function createMultipleItemsWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any, owner: CrossAccountId | string = sender.address) { await usingApi(async (api) => { const to = normalizeAccountId(owner); @@ -1359,6 +1417,14 @@ return newItemId; } +export async function createRefungibleToken(api: ApiPromise, sender: IKeyringPair, collectionId: number, amount: bigint, owner: CrossAccountId | IKeyringPair | string = sender.address) : Promise { + const createData = {refungible: {pieces: amount}}; + const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createData as any); + + const events = await submitTransactionAsync(sender, tx); + return getCreateItemResult(events); +} + export async function createItemExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, owner: CrossAccountId | string = sender.address) { await usingApi(async (api) => { const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createMode); -- gitstuff