git.delta.rocks / unique-network / refs/commits / 861b9092fa33

difftreelog

NFTPAR-94: SIT: Balances can be transferred using smart contract.

sotmorskiy2020-10-02parent: #154381a.patch.diff
in: master

18 files changed

addedtests/ink-types-node-runtime/.gitignorediffbeforeafterboth
--- /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
addedtests/ink-types-node-runtime/Cargo.tomldiffbeforeafterboth
--- /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 <admin@parity.io>"]
+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",
+]
addedtests/ink-types-node-runtime/LICENSEdiffbeforeafterboth

no changes

addedtests/ink-types-node-runtime/README.mddiffbeforeafterboth
--- /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
addedtests/ink-types-node-runtime/examples/calls/.gitignorediffbeforeafterboth
--- /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
addedtests/ink-types-node-runtime/examples/calls/.ink/abi_gen/Cargo.tomldiffbeforeafterboth
--- /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 <admin@parity.io>"]
+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"
addedtests/ink-types-node-runtime/examples/calls/.ink/abi_gen/main.rsdiffbeforeafterboth
--- /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 = <contract::Calls as ink_lang::GenerateAbi>::generate_abi();
+    let contents = serde_json::to_string_pretty(&abi)?;
+    std::fs::create_dir("target").ok();
+    std::fs::write("target/metadata.json", contents)?;
+    Ok(())
+}
addedtests/ink-types-node-runtime/examples/calls/Cargo.tomldiffbeforeafterboth
--- /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 <admin@parity.io>"]
+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"
+]
addedtests/ink-types-node-runtime/examples/calls/lib.rsdiffbeforeafterboth
--- /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);
+        }
+    }
+}
addedtests/ink-types-node-runtime/src/calls.rsdiffbeforeafterboth
--- /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 <http://www.gnu.org/licenses/>.
+
+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<NodeRuntimeTypes, AccountIndex>),
+}
+
+impl From<Balances<NodeRuntimeTypes, AccountIndex>> for Call {
+    fn from(balances_call: Balances<NodeRuntimeTypes, AccountIndex>) -> Call {
+        Call::Balances(balances_call)
+    }
+}
+/// Generic Balance Call, could be used with other runtimes
+#[derive(Encode, Decode, Clone, PartialEq, Eq)]
+pub enum Balances<T, AccountIndex>
+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<T::AccountId, AccountIndex>,
+        #[codec(compact)] T::Balance,
+        #[codec(compact)] T::Balance,
+    ),
+}
+
+/// Construct a `Balances::transfer` call
+pub fn transfer_balance(account: AccountId, balance: Balance) -> Call {
+    Balances::<NodeRuntimeTypes, AccountIndex>::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::<NodeRuntimeTypes, AccountIndex>::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::<Runtime>::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);
+    }
+}
addedtests/ink-types-node-runtime/src/lib.rsdiffbeforeafterboth
--- /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 <http://www.gnu.org/licenses/>.
+
+//! 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<AccountId32> 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::<u8>()).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<Hash, TryFromSliceError> {
+        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;
+}
addedtests/src/balance-transfer-contract/calls.wasmdiffbeforeafterboth

binary blob — no preview

addedtests/src/balance-transfer-contract/metadata.jsondiffbeforeafterboth
--- /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
addedtests/src/contracts.test.tsdiffbeforeafterboth
--- /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<Hash> {
+  return promisifySubstrate(api, () => {
+    return new Promise<Hash>(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<AccountId> {
+  return promisifySubstrate(api, () => {
+    return new Promise<AccountId>((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<void> {
+  return promisifySubstrate(api, async () => {
+    return new Promise<void>(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<void>(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;
+    });
+  })
+});
deletedtests/src/deploy-flipper.test.tsdiffbeforeafterboth
--- 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<Hash>(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<AccountId>((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<void>(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;
-    });
-  });
-});
addedtests/src/substrate/get-balance.tsdiffbeforeafterboth
--- /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<bigint[]> {
+  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());
+}
modifiedtests/src/substrate/privateKey.tsdiffbeforeafterboth
--- 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);
modifiedtests/src/transfer.test.tsdiffbeforeafterboth
--- 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<bigint[]> {
-  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;