From eb6665e1cce9ab631e742b3a4ba5c987766a4549 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Mon, 12 Oct 2020 13:18:20 +0000 Subject: [PATCH] Merge pull request #8 from usetech-llc/feature/NFTPAR-94 Feature/nftpar-94 SIT: Balances can be transferred using smart contract. --- --- /dev/null +++ b/tests/ink-types-node-runtime/.gitignore @@ -0,0 +1,16 @@ +# Ignore build artifacts +/target/ +/examples/**/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock + +# Ignore VS Code artifacts. +**/.vscode/** + +# Ignore history files. +**/.history/** \ No newline at end of file --- /dev/null +++ b/tests/ink-types-node-runtime/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "ink_types_node_runtime" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +license = "GPL-3.0" +readme = "README.md" + +repository = "https://github.com/paritytech/ink" +documentation = "https://github.com/paritytech/ink/wiki" +homepage = "https://www.parity.io/" + +description = "[ink!] Rust based eDSL for writing smart contracts for Substrate" +keywords = ["wasm", "parity", "webassembly", "blockchain", "edsl"] +categories = ["no-std", "embedded"] + +include = ["/Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"] + +[dependencies] +ink_core = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_core", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate/", package = "frame-system", default-features = false } +pallet-indices = { git = "https://github.com/paritytech/substrate/", package = "pallet-indices", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate/", package = "sp-core", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate/", package = "sp-io", default-features = false, features = ["disable_panic_handler", "disable_oom", "disable_allocator"] } +sp-runtime = { git = "https://github.com/paritytech/substrate/", package = "sp-runtime", default-features = false } +scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] } + +[dependencies.type-metadata] +git = "https://github.com/type-metadata/type-metadata.git" +rev = "02eae9f35c40c943b56af5b60616219f2b72b47d" +default-features = false +features = ["derive"] +optional = true + + +[dev-dependencies] +node-runtime = { git = "https://github.com/paritytech/substrate/", package = "node-runtime", features = ["std"] } + +[features] +default = ["std"] +std = [ + "ink_core/std", + "frame-system/std", + "pallet-indices/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", +] +ink-generate-abi = [ + "std", + "type-metadata", + "ink_core/ink-generate-abi", +] --- /dev/null +++ b/tests/ink-types-node-runtime/README.md @@ -0,0 +1,33 @@ +# Node runtime types for `ink!` + +Defines types for [ink!](https://github.com/paritytech/ink) smart contracts targeting [Substrate's `node-runtime`](https://github.com/paritytech/substrate/blob/master/bin/node/runtime/src/lib.rs). + +Supplies an implementation of the [ink! `EnvTypes` trait](https://github.com/paritytech/ink/blob/master/core/src/env/types.rs#L128). + +See `ink!` [examples](./examples) for usage. + +## Requirements +``` +rustup component add rust-src --toolchain nightly +rustup target add wasm32-unknown-unknown --toolchain nightly +rustup target add wasm32-unknown-unknown --toolchain stable +cargo install cargo-contract --vers 0.6.1 --force +``` + +## Build + +### Runtime Dependencies +``` +cargo +nightly build --release +``` + +### ink! Smart Contract +``` +cargo +nightly contract build +cargo +nightly contract generate-metadata +``` + +## Test +``` +cargo +nightly test +``` \ No newline at end of file --- /dev/null +++ b/tests/ink-types-node-runtime/examples/calls/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file --- /dev/null +++ b/tests/ink-types-node-runtime/examples/calls/.ink/abi_gen/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "abi-gen" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" +publish = false + +[[bin]] +name = "abi-gen" +path = "main.rs" + +[dependencies] +contract = { path = "../..", package = "calls", default-features = false, features = ["ink-generate-abi"] } +ink_lang = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_lang", default-features = false, features = ["ink-generate-abi"] } +serde = "1.0.101" +serde_json = "1.0.55" --- /dev/null +++ b/tests/ink-types-node-runtime/examples/calls/.ink/abi_gen/main.rs @@ -0,0 +1,7 @@ +fn main() -> Result<(), std::io::Error> { + let abi = ::generate_abi(); + let contents = serde_json::to_string_pretty(&abi)?; + std::fs::create_dir("target").ok(); + std::fs::write("target/metadata.json", contents)?; + Ok(()) +} --- /dev/null +++ b/tests/ink-types-node-runtime/examples/calls/Cargo.toml @@ -0,0 +1,73 @@ +[package] +name = "calls" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +ink_abi = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_abi", default-features = false, features = ["derive"], optional = true } +ink_primitives = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_primitives", default-features = false } +ink_core = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_core", default-features = false } +ink_lang = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_lang", default-features = false } +ink_prelude = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_prelude", default-features = false } + +scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] } +sp-keyring = { git = "https://github.com/paritytech/substrate/", package = "sp-keyring", optional = true } +ink_types_node_runtime = { path = "../../", default-features = false } + +[dependencies.type-metadata] +git = "https://github.com/type-metadata/type-metadata.git" +rev = "02eae9f35c40c943b56af5b60616219f2b72b47d" +default-features = false +features = ["derive"] +optional = true + +[lib] +name = "calls" +path = "lib.rs" +crate-type = [ + # Used for normal contract Wasm blobs. + "cdylib", + # Used for ABI generation. + "rlib", +] + +[features] +default = ["test-env"] +std = [ + "ink_abi/std", + "ink_core/std", + "ink_primitives/std", + "scale/std", + "type-metadata/std", + "ink_types_node_runtime/std", + "sp-keyring", +] +test-env = [ + "std", + "ink_lang/test-env", +] +ink-generate-abi = [ + "std", + "ink_abi", + "type-metadata", + "ink_core/ink-generate-abi", + "ink_lang/ink-generate-abi", + "ink_types_node_runtime/ink-generate-abi", +] +ink-as-dependency = [] + +[profile.release] +panic = "abort" +lto = true +opt-level = "z" +overflow-checks = true +codegen-units = 1 + +[workspace] +members = [ + ".ink/abi_gen" +] +exclude = [ + ".ink" +] --- /dev/null +++ b/tests/ink-types-node-runtime/examples/calls/lib.rs @@ -0,0 +1,48 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use ink_lang as ink; + +#[ink::contract(version = "0.1.0", env = NodeRuntimeTypes)] +mod calls { + use ink_core::env; + use ink_prelude::*; + use ink_types_node_runtime::{calls as runtime_calls, NodeRuntimeTypes}; + + /// This simple dummy contract dispatches substrate runtime calls + #[ink(storage)] + struct Calls {} + + impl Calls { + #[ink(constructor)] + fn new(&mut self) {} + + /// Dispatches a `transfer` call to the Balances srml module + #[ink(message)] + fn balance_transfer(&self, dest: AccountId, value: Balance) { + // create the Balances::transfer Call + let transfer_call = runtime_calls::transfer_balance(dest, value); + + // dispatch the call to the runtime + let result = self.env().invoke_runtime(&transfer_call); + + // report result to console + // NOTE: println should only be used on a development chain) + env::println(&format!("Balance transfer invoke_runtime result {:?}", result)); + } + } + + #[cfg(test)] + mod tests { + use super::*; + use sp_keyring::AccountKeyring; + + #[test] + fn dispatches_balances_call() { + let calls = Calls::new(); + let alice = AccountId::from(AccountKeyring::Alice.to_account_id()); + // assert_eq!(calls.env().dispatched_calls().into_iter().count(), 0); + calls.balance_transfer(alice, 10000); + // assert_eq!(calls.env().dispatched_calls().into_iter().count(), 1); + } + } +} --- /dev/null +++ b/tests/ink-types-node-runtime/src/calls.rs @@ -0,0 +1,100 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of ink!. +// +// ink! 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. +// +// ink! 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 ink!. If not, see . + +use ink_core::env::EnvTypes; +use scale::{Codec, Decode, Encode}; +use pallet_indices::address::Address; +use sp_runtime::traits::Member; +use crate::{AccountId, AccountIndex, Balance, NodeRuntimeTypes}; + +/// Default runtime Call type, a subset of the runtime Call module variants +/// +/// The codec indices of the modules *MUST* match those in the concrete runtime. +#[derive(Encode, Decode)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))] +pub enum Call { + #[codec(index = "5")] + Balances(Balances), +} + +impl From> for Call { + fn from(balances_call: Balances) -> Call { + Call::Balances(balances_call) + } +} +/// Generic Balance Call, could be used with other runtimes +#[derive(Encode, Decode, Clone, PartialEq, Eq)] +pub enum Balances +where + T: EnvTypes, + T::AccountId: Member + Codec, + AccountIndex: Member + Codec, +{ + #[allow(non_camel_case_types)] + #[codec(index = "3")] + transfer(T::AccountId, #[codec(compact)] T::Balance), + #[allow(non_camel_case_types)] + #[codec(index = "6")] + set_balance( + Address, + #[codec(compact)] T::Balance, + #[codec(compact)] T::Balance, + ), +} + +/// Construct a `Balances::transfer` call +pub fn transfer_balance(account: AccountId, balance: Balance) -> Call { + Balances::::transfer(account, balance).into() +} + +#[cfg(test)] +mod tests { + use crate::{calls, AccountIndex, NodeRuntimeTypes}; + use super::Call; + + use node_runtime::{self, Runtime}; + use pallet_indices::address; + use scale::{Decode, Encode}; + + + #[test] + fn call_balance_transfer() { + let balance = 10_000; + let account_index = 0; + + let contract_address = calls::Address::Index(account_index); + let contract_transfer = + calls::Balances::::transfer(contract_address, balance); + let contract_call = Call::Balances(contract_transfer); + + let srml_address = address::Address::Index(account_index); + let srml_transfer = node_runtime::BalancesCall::::transfer(srml_address, balance); + let srml_call = node_runtime::Call::Balances(srml_transfer); + + let contract_call_encoded = contract_call.encode(); + let srml_call_encoded = srml_call.encode(); + + assert_eq!(srml_call_encoded, contract_call_encoded); + + let srml_call_decoded: node_runtime::Call = + Decode::decode(&mut contract_call_encoded.as_slice()) + .expect("Balances transfer call decodes to srml type"); + let srml_call_encoded = srml_call_decoded.encode(); + let contract_call_decoded: Call = Decode::decode(&mut srml_call_encoded.as_slice()) + .expect("Balances transfer call decodes back to contract type"); + assert!(contract_call == contract_call_decoded); + } +} --- /dev/null +++ b/tests/ink-types-node-runtime/src/lib.rs @@ -0,0 +1,122 @@ +// Copyright 2018-2019 Parity Technologies (UK) Ltd. +// This file is part of ink!. +// +// ink! 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. +// +// ink! 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 ink!. If not, see . + +//! Definitions for environment types for contracts targeted at a +//! substrate chain with the default `node-runtime` configuration. + +#![cfg_attr(not(feature = "std"), no_std)] + +use core::{array::TryFromSliceError, convert::TryFrom}; +use ink_core::env::Clear; +use scale::{Decode, Encode}; +use sp_core::crypto::AccountId32; +#[cfg(feature = "ink-generate-abi")] +use type_metadata::{HasTypeId, HasTypeDef, Metadata, MetaType, TypeId, TypeDef, TypeIdArray}; + +pub mod calls; + +/// Contract environment types defined in substrate node-runtime +#[cfg_attr(feature = "ink-generate-abi", derive(Metadata))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum NodeRuntimeTypes {} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] +pub struct AccountId (AccountId32); + +impl From for AccountId { + fn from(account: AccountId32) -> Self { + AccountId(account) + } +} + +#[cfg(feature = "ink-generate-abi")] +impl HasTypeId for AccountId { + fn type_id() -> TypeId { + TypeIdArray::new(32, MetaType::new::()).into() + } +} + +#[cfg(feature = "ink-generate-abi")] +impl HasTypeDef for AccountId { + fn type_def() -> TypeDef { + TypeDef::builtin() + } +} + +/// The default SRML balance type. +pub type Balance = u128; + +/// The default SRML hash type. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Encode, Decode)] +pub struct Hash([u8; 32]); + +impl From<[u8; 32]> for Hash { + fn from(hash: [u8; 32]) -> Hash { + Hash(hash) + } +} + +impl<'a> TryFrom<&'a [u8]> for Hash { + type Error = TryFromSliceError; + + fn try_from(bytes: &'a [u8]) -> Result { + let hash = <[u8; 32]>::try_from(bytes)?; + Ok(Hash(hash)) + } +} + +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsMut<[u8]> for Hash { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[..] + } +} + +impl Clear for Hash { + fn is_clear(&self) -> bool { + self.as_ref().iter().all(|&byte| byte == 0x00) + } + + fn clear() -> Self { + Self([0x00; 32]) + } +} + +/// The default SRML moment type. +pub type Moment = u64; + +/// The default SRML blocknumber type. +pub type BlockNumber = u64; + +/// The default SRML AccountIndex type. +pub type AccountIndex = u32; + +/// The default timestamp type. +pub type Timestamp = u64; + +impl ink_core::env::EnvTypes for NodeRuntimeTypes { + type AccountId = AccountId; + type Balance = Balance; + type Hash = Hash; + type Timestamp = Timestamp; + type BlockNumber = BlockNumber; + type Call = calls::Call; +} --- /dev/null +++ b/tests/src/balance-transfer-contract/metadata.json @@ -0,0 +1,96 @@ +{ + "registry": { + "strings": [ + "Storage", + "calls", + "__ink_private", + "__ink_storage", + "new", + "balance_transfer", + "dest", + "AccountId", + "value", + "Balance" + ], + "types": [ + { + "id": { + "custom.name": 1, + "custom.namespace": [ + 2, + 2, + 3, + 4 + ], + "custom.params": [] + }, + "def": { + "struct.fields": [] + } + }, + { + "id": { + "array.len": 32, + "array.type": 3 + }, + "def": "builtin" + }, + { + "id": "u8", + "def": "builtin" + }, + { + "id": "u128", + "def": "builtin" + } + ] + }, + "storage": { + "struct.type": 1, + "struct.fields": [] + }, + "contract": { + "name": 2, + "constructors": [ + { + "name": 5, + "selector": "[\"0x5E\",\"0xBD\",\"0x88\",\"0xD6\"]", + "args": [], + "docs": [] + } + ], + "messages": [ + { + "name": 6, + "selector": "[\"0xEC\",\"0x9F\",\"0xA4\",\"0xF0\"]", + "mutates": false, + "args": [ + { + "name": 7, + "type": { + "ty": 2, + "display_name": [ + 8 + ] + } + }, + { + "name": 9, + "type": { + "ty": 4, + "display_name": [ + 10 + ] + } + } + ], + "return_type": null, + "docs": [ + "Dispatches a `transfer` call to the Balances srml module" + ] + } + ], + "events": [], + "docs": [] + } +} \ No newline at end of file --- /dev/null +++ b/tests/src/contracts.test.ts @@ -0,0 +1,160 @@ +import { expect } from "chai"; +import usingApi from "./substrate/substrate-api"; +import promisifySubstrate from "./substrate/promisify-substrate"; +import fs from "fs"; +import privateKey from "./substrate/privateKey"; +import { compactAddLength, u8aToU8a } from '@polkadot/util'; +import { Hash, AccountId } from "@polkadot/types/interfaces"; +import { Abi, PromiseContract } from "@polkadot/api-contract"; +import { ISubmittableResult, IKeyringPair } from "@polkadot/types/types"; +import BN from "bn.js"; +import { alicesPublicKey, bobsPublicKey } from "./accounts"; +import { ApiPromise } from "@polkadot/api"; +import { GenericAccountId, u128 } from "@polkadot/types"; +import getBalance from "./substrate/get-balance"; + + +function deployContract(api: ApiPromise, contract: Uint8Array, privateKey: IKeyringPair): Promise { + return promisifySubstrate(api, () => { + return new Promise(async (resolve, reject) => { + const unsubscribe = api.tx.contracts.putCode(contract).signAndSend(privateKey, async result => { + if(!result.isInBlock){ + return; + } + + const record = result.findRecord('contracts', 'CodeStored'); + + if(record) { + (await unsubscribe)(); + resolve(record.event.data[0] as unknown as Hash); + } else { + reject('Failed to find contract hash in putCode transaction result.'); + } + }); + }) + })(); +} + +function instantiateContract(api: ApiPromise, contractHash: Hash, args: Uint8Array, privateKey: IKeyringPair): Promise { + return promisifySubstrate(api, () => { + return new Promise((resolve, reject) => { + const unsubscribe = api.tx.contracts.instantiate(1000000000000000n, 1000000000000n, contractHash, args) + .signAndSend(privateKey, async (result:ISubmittableResult) => { + if(!result.isInBlock) { + return; + } + const record = result.findRecord('contracts', 'Instantiated'); + if(record) { + (await unsubscribe)(); + expect(alicesPublicKey).to.be.equal(record.event.data[0].toString()); + resolve(record.event.data[1] as AccountId); + } else { + reject('Failed to find instantiated event.'); + } + }); + }); + })(); +} + +function txContractCall(api: ApiPromise, privateKey: IKeyringPair, address: string, call: Uint8Array): Promise { + return promisifySubstrate(api, async () => { + return new Promise(async (resolve, reject) => { + api.tx.contracts.call(address, 0, 1000000000000n, call) + .signAndSend(privateKey, async result => { + if(!result.isInBlock) { + return; + } + + if(result.findRecord('system', 'ExtrinsicSuccess')) { + resolve(); + } + else { + reject('Failed to execute tx call.'); + } + }) + }); + })(); +} + +describe('Contracts', () => { + it(`Can deploy smart contract Flipper, instantiate it and call it's get and flip messages.`, async () => { + await usingApi(async api => { + const wasm = fs.readFileSync('./src/flipper/flipper.wasm'); + const contract = compactAddLength(u8aToU8a(wasm)); + + const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8')); + const abi = new Abi(api.registry as any, metadata); + + const alicesPrivateKey = privateKey('//Alice'); + + const contractHash = await deployContract(api, contract, alicesPrivateKey); + + const args = abi.constructors[0](true); + const instanceAccountId = await instantiateContract(api, contractHash, args, alicesPrivateKey); + + const contractInstance = new PromiseContract(api, abi, instanceAccountId); + const getFlipValue = async () => { + return await promisifySubstrate(api, async () => { + const result = await contractInstance.call('rpc', 'get', 0, new BN('1000000000000')) + .send(alicesPublicKey); + if(!result.isSuccess) { + throw 'Failed to get flipper value'; + } + return result.output && result.output.valueOf && result.output.valueOf(); + })(); + } + + const initialGetResponse = await getFlipValue(); + expect(initialGetResponse).to.be.true; + + await promisifySubstrate(api, async () => { + return new Promise(async (resolve, reject) => { + api.tx.contracts.call(contractInstance.address.toString(), 0, 1000000000000n, contractInstance.getMessage('flip').fn()) + .signAndSend(alicesPrivateKey, async result => { + if(!result.isInBlock) { + return; + } + + if(result.findRecord('system', 'ExtrinsicSuccess')) { + resolve(); + } + else { + reject('Failed to flip value.'); + } + }) + }); + })(); + + const afterFlipGetResponse = await getFlipValue(); + + expect(afterFlipGetResponse).to.be.false; + }); + }); + + it('Can transfer balance using smart contract.', async () => { + await usingApi(async api => { + const [alicesBalanceBefore, bobsBalanceBefore] = await getBalance(api, [alicesPublicKey, bobsPublicKey]); + const wasm = fs.readFileSync('./src/balance-transfer-contract/calls.wasm'); + const contract = compactAddLength(u8aToU8a(wasm)); + + const metadata = JSON.parse(fs.readFileSync('./src/balance-transfer-contract/metadata.json').toString('utf-8')); + const abi = new Abi(api.registry as any, metadata); + + const alicesPrivateKey = privateKey('//Alice'); + + const contractHash = await deployContract(api, contract, alicesPrivateKey); + + const args = abi.constructors[0](); + const instanceAccountId = await instantiateContract(api, contractHash, args, alicesPrivateKey); + const contractInstance = new PromiseContract(api, abi, instanceAccountId); + const bob = new GenericAccountId(api.registry, bobsPublicKey); + const call = contractInstance.getMessage('balance_transfer').fn(bob, new u128(api.registry, 1000000)); + await txContractCall(api, alicesPrivateKey, contractInstance.address.toString(), call); + + const [alicesBalanceAfter, bobsBalanceAfter] = await getBalance(api, [alicesPublicKey, bobsPublicKey]); + + expect(alicesBalanceAfter < alicesBalanceBefore).to.be.true; + expect(bobsBalanceAfter > bobsBalanceBefore).to.be.true; + }); + }) +}); --- a/tests/src/deploy-flipper.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { expect } from "chai"; -import usingApi from "./substrate/substrate-api"; -import promisifySubstrate from "./substrate/promisify-substrate"; -import fs from "fs"; -import privateKey from "./substrate/privateKey"; -import { compactAddLength, u8aToU8a } from '@polkadot/util'; -import { Hash, AccountId } from "@polkadot/types/interfaces"; -import { Abi, PromiseContract } from "@polkadot/api-contract"; -import { ISubmittableResult } from "@polkadot/types/types"; -import BN from "bn.js"; -import { alicesPublicKey } from "./accounts"; - -describe('Flipper', () => { - it(`Can deploy smart contract Flipper, instantiate it and call it's get and flip messages.`, async () => { - await usingApi(async api => { - const wasm = fs.readFileSync('./src/flipper/flipper.wasm'); - const contract = compactAddLength(u8aToU8a(wasm)); - - const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8')); - const abi = new Abi(api.registry as any, metadata); - - const alicesPrivateKey = privateKey('//Alice'); - - const contractHash = await promisifySubstrate(api, () => { - return new Promise(async (resolve, reject) => { - const unsubscribe = api.tx.contracts.putCode(contract).signAndSend(alicesPrivateKey, async result => { - if(!result.isInBlock){ - return; - } - - const record = result.findRecord('contracts', 'CodeStored'); - - if(record) { - (await unsubscribe)(); - resolve(record.event.data[0] as unknown as Hash); - } else { - reject('Failed to find contract hash in putCode transaction result.'); - } - }); - }) - })(); - - - const instanceAccountId = await promisifySubstrate(api, () => { - return new Promise((resolve, reject) => { - const args = abi.constructors[0](true); - const unsubscribe = api.tx.contracts.instantiate(1000000000000000n, 1000000000000n, contractHash, args) - .signAndSend(alicesPrivateKey, async (result:ISubmittableResult) => { - if(!result.isInBlock) { - return; - } - const record = result.findRecord('contracts', 'Instantiated'); - if(record) { - (await unsubscribe)(); - expect(alicesPublicKey).to.be.equal(record.event.data[0].toString()); - resolve(record.event.data[1] as AccountId); - } else { - reject('Failed to find instantiated event,'); - } - }); - }); - })(); - - const contractInstance = new PromiseContract(api, abi, instanceAccountId); - const getFlipValue = async () => { - return await promisifySubstrate(api, async () => { - const result = await contractInstance.call('rpc', 'get', 0, new BN('1000000000000')) - .send(alicesPublicKey); - if(!result.isSuccess) { - throw 'Failed to get flipper value'; - } - return result.output && result.output.valueOf && result.output.valueOf(); - })(); - } - - const initialGetResponse = await getFlipValue(); - expect(initialGetResponse).to.be.true; - - await promisifySubstrate(api, async () => { - return new Promise(async (resolve, reject) => { - api.tx.contracts.call(contractInstance.address.toString(), 0, 1000000000000n, contractInstance.getMessage('flip').fn()) - .signAndSend(alicesPrivateKey, async result => { - if(!result.isInBlock) { - return; - } - - if(result.findRecord('system', 'ExtrinsicSuccess')) { - resolve(); - } - else { - reject('Failed to flip value.'); - } - }) - }); - })(); - - const afterFlipGetResponse = await getFlipValue(); - - expect(afterFlipGetResponse).to.be.false; - }); - }); -}); --- /dev/null +++ b/tests/src/substrate/get-balance.ts @@ -0,0 +1,9 @@ +import { ApiPromise } from "@polkadot/api"; +import promisifySubstrate from "./promisify-substrate"; +import {AccountInfo} from "@polkadot/types/interfaces/system"; + +export default async function getBalance(api: ApiPromise, accounts: string[]): Promise { + const balance = promisifySubstrate(api, (accounts: string[]) => api.query.system.account.multi(accounts)); + const responce = await balance(accounts) as unknown as AccountInfo[]; + return responce.map(r => r.data.free.toBigInt().valueOf()); +} --- a/tests/src/substrate/privateKey.ts +++ b/tests/src/substrate/privateKey.ts @@ -1,6 +1,7 @@ import { Keyring } from "@polkadot/api"; +import { IKeyringPair } from "@polkadot/types/types"; -export default function privateKey(account: string) { +export default function privateKey(account: string): IKeyringPair { const keyring = new Keyring({ type: 'sr25519' }); return keyring.addFromUri(account); --- a/tests/src/transfer.test.ts +++ b/tests/src/transfer.test.ts @@ -1,18 +1,10 @@ -import createSubstrateApi from "./substrate/substrate-api"; -import { Keyring, ApiPromise } from "@polkadot/api"; -import {AccountInfo} from "@polkadot/types/interfaces/system"; import { expect } from "chai"; import usingApi from "./substrate/substrate-api"; import promisifySubstrate from "./substrate/promisify-substrate"; import waitNewBlocks from "./substrate/wait-new-blocks"; import { alicesPublicKey, bobsPublicKey } from "./accounts"; import privateKey from "./substrate/privateKey"; - -async function getBalance(api: ApiPromise, accounts: string[]): Promise { - const balance = promisifySubstrate(api, (accounts: string[]) => api.query.system.account.multi(accounts)); - const responce = await balance(accounts) as unknown as AccountInfo[]; - return responce.map(r => r.data.free.toBigInt().valueOf()); -} +import getBalance from "./substrate/get-balance"; describe('Transfer', () => { it('Balance transfers', async () => { @@ -27,7 +19,7 @@ await waitNewBlocks(api); - const [alicesBalanceAfter, bobsBalanceAfter] = await getBalance(api, [alicesPublicKey, bobsPublicKey]); + const [alicesBalanceAfter, bobsBalanceAfter] = await getBalance(api, [alicesPublicKey, bobsPublicKey]); expect(alicesBalanceAfter < alicesBalanceBefore).to.be.true; expect(bobsBalanceAfter > bobsBalanceBefore).to.be.true; -- gitstuff