1import { expect } from "chai";2import usingApi from "./substrate/substrate-api";3import promisifySubstrate from "./substrate/promisify-substrate";4import fs from "fs";5import privateKey from "./substrate/privateKey";6import { compactAddLength, u8aToU8a } from '@polkadot/util';7import { Hash, AccountId } from "@polkadot/types/interfaces";8import { Abi, PromiseContract } from "@polkadot/api-contract";9import { ISubmittableResult, IKeyringPair } from "@polkadot/types/types";10import BN from "bn.js";11import { alicesPublicKey, bobsPublicKey } from "./accounts";12import { ApiPromise } from "@polkadot/api";13import { GenericAccountId, u128 } from "@polkadot/types";14import getBalance from "./substrate/get-balance";151617function deployContract(api: ApiPromise, contract: Uint8Array, privateKey: IKeyringPair): Promise<Hash> {18 return promisifySubstrate(api, () => {19 return new Promise<Hash>(async (resolve, reject) => {20 const unsubscribe = api.tx.contracts.putCode(contract).signAndSend(privateKey, async result => {21 if(!result.isInBlock){22 return;23 }2425 const record = result.findRecord('contracts', 'CodeStored');2627 if(record) {28 (await unsubscribe)();29 resolve(record.event.data[0] as unknown as Hash);30 } else {31 reject('Failed to find contract hash in putCode transaction result.');32 }33 });34 })35 })();36}3738function instantiateContract(api: ApiPromise, contractHash: Hash, args: Uint8Array, privateKey: IKeyringPair): Promise<AccountId> {39 return promisifySubstrate(api, () => {40 return new Promise<AccountId>((resolve, reject) => {41 const unsubscribe = api.tx.contracts.instantiate(1000000000000000n, 1000000000000n, contractHash, args)42 .signAndSend(privateKey, async (result:ISubmittableResult) => {43 if(!result.isInBlock) {44 return;45 }46 const record = result.findRecord('contracts', 'Instantiated');47 if(record) {48 (await unsubscribe)();49 expect(alicesPublicKey).to.be.equal(record.event.data[0].toString());50 resolve(record.event.data[1] as AccountId);51 } else {52 reject('Failed to find instantiated event.');53 }54 });55 });56 })();57}5859function txContractCall(api: ApiPromise, privateKey: IKeyringPair, address: string, call: Uint8Array): Promise<void> {60 return promisifySubstrate(api, async () => {61 return new Promise<void>(async (resolve, reject) => {62 api.tx.contracts.call(address, 0, 1000000000000n, call)63 .signAndSend(privateKey, async result => {64 if(!result.isInBlock) {65 return;66 }6768 if(result.findRecord('system', 'ExtrinsicSuccess')) {69 resolve();70 }71 else {72 reject('Failed to execute tx call.');73 }74 })75 });76 })();77}7879describe('Contracts', () => {80 it(`Can deploy smart contract Flipper, instantiate it and call it's get and flip messages.`, async () => {81 await usingApi(async api => {82 const wasm = fs.readFileSync('./src/flipper/flipper.wasm');83 const contract = compactAddLength(u8aToU8a(wasm));8485 const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8'));86 const abi = new Abi(api.registry as any, metadata);8788 const alicesPrivateKey = privateKey('//Alice');8990 const contractHash = await deployContract(api, contract, alicesPrivateKey);9192 const args = abi.constructors[0](true);93 const instanceAccountId = await instantiateContract(api, contractHash, args, alicesPrivateKey);9495 const contractInstance = new PromiseContract(api, abi, instanceAccountId);96 const getFlipValue = async () => {97 return await promisifySubstrate(api, async () => {98 const result = await contractInstance.call('rpc', 'get', 0, new BN('1000000000000'))99 .send(alicesPublicKey);100 if(!result.isSuccess) {101 throw 'Failed to get flipper value';102 }103 return result.output && result.output.valueOf && result.output.valueOf();104 })();105 }106107 const initialGetResponse = await getFlipValue();108 expect(initialGetResponse).to.be.true;109110 await promisifySubstrate(api, async () => {111 return new Promise<void>(async (resolve, reject) => {112 api.tx.contracts.call(contractInstance.address.toString(), 0, 1000000000000n, contractInstance.getMessage('flip').fn())113 .signAndSend(alicesPrivateKey, async result => {114 if(!result.isInBlock) {115 return;116 }117118 if(result.findRecord('system', 'ExtrinsicSuccess')) {119 resolve();120 }121 else {122 reject('Failed to flip value.');123 }124 })125 });126 })();127128 const afterFlipGetResponse = await getFlipValue();129130 expect(afterFlipGetResponse).to.be.false;131 });132 });133134 it('Can transfer balance using smart contract.', async () => {135 await usingApi(async api => {136 const [alicesBalanceBefore, bobsBalanceBefore] = await getBalance(api, [alicesPublicKey, bobsPublicKey]);137 const wasm = fs.readFileSync('./src/balance-transfer-contract/calls.wasm');138 const contract = compactAddLength(u8aToU8a(wasm));139140 const metadata = JSON.parse(fs.readFileSync('./src/balance-transfer-contract/metadata.json').toString('utf-8'));141 const abi = new Abi(api.registry as any, metadata);142143 const alicesPrivateKey = privateKey('//Alice');144145 const contractHash = await deployContract(api, contract, alicesPrivateKey);146147 const args = abi.constructors[0]();148 const instanceAccountId = await instantiateContract(api, contractHash, args, alicesPrivateKey);149 const contractInstance = new PromiseContract(api, abi, instanceAccountId);150 const bob = new GenericAccountId(api.registry, bobsPublicKey);151 const call = contractInstance.getMessage('balance_transfer').fn(bob, new u128(api.registry, 1000000));152 await txContractCall(api, alicesPrivateKey, contractInstance.address.toString(), call);153154 const [alicesBalanceAfter, bobsBalanceAfter] = await getBalance(api, [alicesPublicKey, bobsPublicKey]);155156 expect(alicesBalanceAfter < alicesBalanceBefore).to.be.true;157 expect(bobsBalanceAfter > bobsBalanceBefore).to.be.true;158 });159 })160});