Parcourir la source

Substrate: `subxt` integration tests (#990)

Signed-off-by: Raymond Yeh <extraymond@gmail.com>
extraymond il y a 2 ans
Parent
commit
01707781ad
28 fichiers modifiés avec 4786 ajouts et 1 suppressions
  1. 36 1
      .github/workflows/test.yml
  2. 2 0
      .gitignore
  3. 5 0
      .vscode/settings.json
  4. 12 0
      integration/substrate/createpair.sol
  5. 2 0
      integration/subxt-tests/.gitignore
  6. 28 0
      integration/subxt-tests/Cargo.toml
  7. 137 0
      integration/subxt-tests/src/cases/array_struct_mapping_storage.rs
  8. 107 0
      integration/subxt-tests/src/cases/arrays.rs
  9. 81 0
      integration/subxt-tests/src/cases/asserts.rs
  10. 95 0
      integration/subxt-tests/src/cases/balances.rs
  11. 95 0
      integration/subxt-tests/src/cases/builtins.rs
  12. 136 0
      integration/subxt-tests/src/cases/builtins2.rs
  13. 85 0
      integration/subxt-tests/src/cases/create_contract.rs
  14. 74 0
      integration/subxt-tests/src/cases/destruct.rs
  15. 73 0
      integration/subxt-tests/src/cases/events.rs
  16. 202 0
      integration/subxt-tests/src/cases/external_call.rs
  17. 64 0
      integration/subxt-tests/src/cases/flipper.rs
  18. 46 0
      integration/subxt-tests/src/cases/issue666.rs
  19. 23 0
      integration/subxt-tests/src/cases/mod.rs
  20. 99 0
      integration/subxt-tests/src/cases/msg_sender.rs
  21. 697 0
      integration/subxt-tests/src/cases/primitives.rs
  22. 70 0
      integration/subxt-tests/src/cases/randomizer.rs
  23. 269 0
      integration/subxt-tests/src/cases/store.rs
  24. 232 0
      integration/subxt-tests/src/cases/structs.rs
  25. 379 0
      integration/subxt-tests/src/cases/uniswapv2_erc20.rs
  26. 264 0
      integration/subxt-tests/src/cases/uniswapv2_factory.rs
  27. 941 0
      integration/subxt-tests/src/cases/uniswapv2_pair.rs
  28. 532 0
      integration/subxt-tests/src/lib.rs

+ 36 - 1
.github/workflows/test.yml

@@ -86,7 +86,8 @@ jobs:
     - uses: actions/upload-artifact@v3.1.0
       with:
         name: solang-linux-x86-64
-        path: ./target/debug/solang
+        path: ./target/debug/solang 
+
 
   linux-arm:
     name: Linux Arm
@@ -347,6 +348,40 @@ jobs:
       if: always()
       run: docker kill ${{steps.substrate.outputs.id}}
 
+  substrate-subxt:
+    name: Substrate Integration test with subxt
+    runs-on: ubuntu-22.04
+    needs: linux-x86-64
+    steps:
+    - name: Checkout sources
+      uses: actions/checkout@v2
+      # We can't run substrate as a github actions service, since it requires
+      # command line arguments. See https://github.com/actions/runner/pull/1152
+    - name: Start substrate
+      run: echo id=$(docker run -d -p 9944:9944 ghcr.io/hyperledger/solang-substrate-ci:e41a9c0 substrate-contracts-node --dev --ws-external) >> $GITHUB_OUTPUT
+      id: substrate
+    - uses: actions/download-artifact@master
+      with:
+        name: solang-linux-x86-64
+        path: bin
+    - run: |
+        chmod 755 ./bin/solang
+        echo "$(pwd)/bin" >> $GITHUB_PATH
+    - name: Install latest rust toolchain
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: stable
+        default: true
+        override: true   
+    - run: solang compile --target substrate ../substrate/*.sol ../substrate/test/*.sol -o ./contracts/
+      working-directory: ./integration/subxt-tests
+    - name: Deploy and test contracts
+      run: cargo test -- --test-threads=1
+      working-directory: ./integration/subxt-tests
+    - name: cleanup
+      if: always()
+      run: docker kill ${{steps.substrate.outputs.id}}
+
   vscode:
     name: Visual Code Extension
     runs-on: solang-ubuntu-latest

+ 2 - 0
.gitignore

@@ -4,3 +4,5 @@ Cargo.lock
 **/*.rs.bk
 bundle.ll
 
+.helix/
+.vscode/

+ 5 - 0
.vscode/settings.json

@@ -0,0 +1,5 @@
+{
+    "rust-analyzer.linkedProjects": [
+        "./integration/subxt-tests/Cargo.toml"
+    ]
+}

+ 12 - 0
integration/substrate/createpair.sol

@@ -0,0 +1,12 @@
+pragma solidity ^0.8.0;
+import "./UniswapV2Pair.sol";
+
+contract Creator {
+    address public pair;
+
+    constructor() public {
+        pair = address(new UniswapV2Pair());
+    }
+
+}
+

+ 2 - 0
integration/subxt-tests/.gitignore

@@ -0,0 +1,2 @@
+contracts/
+target/

+ 28 - 0
integration/subxt-tests/Cargo.toml

@@ -0,0 +1,28 @@
+[package]
+edition = "2021"
+name = "subxt-tests"
+version = "0.1.0"
+license = "Apache-2.0"
+
+[dependencies]
+anyhow = "1.0.71"
+async-trait = "0.1.68"
+sp-core = "20.0.0"
+sp-runtime = "23.0.0"
+sp-weights = "19.0.0"
+pallet-contracts-primitives = "23.0.0"
+hex = "0.4.3"
+num-bigint = "0.4.3"
+once_cell = "1.17.2"
+parity-scale-codec = {  version = "3.5.0", features = ["derive"] }
+rand = "0.8.5"
+serde_json = "1.0.96"
+sp-keyring = "23.0.0"
+subxt = "0.28.0"
+tokio = {version = "1.28.2", features = ["rt-multi-thread", "macros", "time"]}
+contract-metadata = "3.0.1"
+contract-transcode = "3.0.1"
+
+[workspace]
+members = []
+

+ 137 - 0
integration/subxt-tests/src/cases/array_struct_mapping_storage.rs

@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use parity_scale_codec::{Decode, Encode};
+use sp_core::{hexdisplay::AsBytesRef, U256};
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let mut contract = Contract::new("./contracts/array_struct_mapping_storage.contract")?;
+
+    contract
+        .deploy(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("new", []).unwrap(),
+        )
+        .await?;
+
+    contract
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, _>("setNumber", ["2147483647"]).unwrap(),
+        )
+        .await?;
+
+    let b_push = |t: &ContractMessageTranscoder| t.encode::<_, String>("push", []).unwrap();
+
+    contract
+        .call(&api, sp_keyring::AccountKeyring::Alice, 0, &b_push)
+        .await?;
+
+    contract
+        .call(&api, sp_keyring::AccountKeyring::Alice, 0, &b_push)
+        .await?;
+
+    for array_no in 0..2 {
+        for i in 0..10 {
+            contract
+                .call(
+                    &api,
+                    sp_keyring::AccountKeyring::Alice,
+                    0,
+                    &|t: &ContractMessageTranscoder| {
+                        t.encode::<_, _>(
+                            "set",
+                            [
+                                format!("{}", array_no),
+                                format!("{}", 102 + i + array_no * 500),
+                                format!("{}", 300331 + i),
+                            ],
+                        )
+                        .unwrap()
+                    },
+                )
+                .await?;
+        }
+    }
+
+    for array_no in 0..2 {
+        for i in 0..10 {
+            let rs = contract
+                .try_call(
+                    &api,
+                    sp_keyring::AccountKeyring::Alice,
+                    0,
+                    &|t: &ContractMessageTranscoder| {
+                        t.encode::<_, _>(
+                            "get",
+                            [
+                                format!("{}", array_no),
+                                format!("{}", 102 + i + array_no * 500),
+                            ],
+                        )
+                        .unwrap()
+                    },
+                )
+                .await
+                .and_then(|v| <U256>::decode(&mut v.as_bytes_ref()).map_err(Into::into))?;
+
+            assert_eq!(rs, U256::from(300331_u128 + i));
+        }
+    }
+
+    contract
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, _>("rm", [format!("{}", 0), format!("{}", 104)])
+                    .unwrap()
+            },
+        )
+        .await?;
+
+    for i in 0..10 {
+        let rs = contract
+            .try_call(
+                &api,
+                sp_keyring::AccountKeyring::Alice,
+                0,
+                &|t: &ContractMessageTranscoder| {
+                    t.encode::<_, _>("get", [format!("{}", 0), format!("{}", 102 + i)])
+                        .unwrap()
+                },
+            )
+            .await
+            .and_then(|v| <U256>::decode(&mut v.as_bytes_ref()).map_err(Into::into))?;
+
+        if i != 2 {
+            assert_eq!(rs, U256::from(300331_u128 + i));
+        } else {
+            assert_eq!(rs, U256::zero());
+        }
+    }
+
+    let rs = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("number", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <i64>::decode(&mut v.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(rs, 2147483647);
+
+    Ok(())
+}

+ 107 - 0
integration/subxt-tests/src/cases/arrays.rs

@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+use contract_transcode::{ContractMessageTranscoder, Value};
+use hex::FromHex;
+
+use parity_scale_codec::{Decode, Encode};
+use rand::{seq::SliceRandom, thread_rng, Rng};
+use sp_core::{crypto::AccountId32, hexdisplay::AsBytesRef};
+
+#[ignore = "contract trapped"]
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let mut contract = Contract::new("./contracts/arrays.contract")?;
+
+    contract
+        .deploy(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("new", []).unwrap(),
+        )
+        .await?;
+
+    let mut users = Vec::new();
+
+    for i in 0..3 {
+        let rnd_addr = rand::random::<[u8; 32]>();
+
+        let name = format!("name{i}");
+
+        let id = u32::from_be_bytes(rand::random::<[u8; 4]>());
+        let mut perms = Vec::<String>::new();
+
+        let mut j: f64 = 0.0;
+        while j < rand::thread_rng().gen_range(0.0..=3.0) {
+            j += 1.0;
+
+            let p = rand::thread_rng().gen_range(0..8);
+            perms.push(format!("Perm{}", p + 1));
+        }
+
+        contract
+            .call(
+                &api,
+                sp_keyring::AccountKeyring::Alice,
+                0,
+                &|t: &ContractMessageTranscoder| {
+                    t.encode(
+                        "addUser",
+                        [
+                            id.to_string(),
+                            format!("0x{}", hex::encode(rnd_addr)),
+                            format!("\"{}\"", name.clone()),
+                            format!("[{}]", perms.join(",")),
+                        ],
+                    )
+                    .unwrap()
+                },
+            )
+            .await?;
+
+        users.push((name, rnd_addr, id, perms));
+    }
+
+    let (name, addr, id, perms) = users.choose(&mut thread_rng()).unwrap();
+
+    let output = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode("getUserById", [format!("\"{id}\"")]).unwrap()
+            },
+        )
+        .await?;
+
+    let (name, addr, id, perms) =
+        <(String, AccountId32, u64, Vec<u8>)>::decode(&mut output.as_bytes_ref())?;
+
+    if !perms.is_empty() {
+        let p = perms.choose(&mut thread_rng()).unwrap();
+
+        let output = contract
+            .try_call(
+                &api,
+                sp_keyring::AccountKeyring::Alice,
+                0,
+                &|t: &ContractMessageTranscoder| {
+                    t.encode(
+                        "hasPermission",
+                        [format!("\"{id}\""), format!("Perm{}", p + 1)],
+                    )
+                    .unwrap()
+                },
+            )
+            .await?;
+
+        let has_permission = <bool>::decode(&mut output.as_bytes_ref())?;
+        assert!(has_permission);
+    }
+
+    Ok(())
+}

+ 81 - 0
integration/subxt-tests/src/cases/asserts.rs

@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{node, Contract, WriteContract};
+use contract_transcode::ContractMessageTranscoder;
+use hex::FromHex;
+use parity_scale_codec::{Decode, Encode};
+use sp_core::hexdisplay::AsBytesRef;
+use subxt::metadata::ErrorMetadata;
+
+use crate::API;
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let mut contract = Contract::new("./contracts/asserts.contract")?;
+
+    contract
+        .deploy(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("new", []).unwrap(),
+        )
+        .await?;
+
+    let rv = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("var", []).unwrap(),
+        )
+        .await?;
+
+    let output = i64::decode(&mut rv.as_bytes_ref())?;
+    assert!(output == 1);
+
+    // read should fail
+    let res = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("test_assert_rpc", []).unwrap(),
+        )
+        .await;
+
+    if let Err(r) = res {
+        assert!(r.to_string().contains("ContractTrapped"));
+    }
+
+    // write should failed
+    let res = contract
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("test_assert_rpc", []).unwrap(),
+        )
+        .await;
+
+    if let Err(r) = res {
+        assert!(r.to_string().contains("ContractTrapped"));
+    }
+
+    // state should not change after failed operation
+    let rv = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("var", []).unwrap(),
+        )
+        .await?;
+
+    let output = i64::decode(&mut rv.as_bytes_ref())?;
+    assert!(output == 1);
+
+    Ok(())
+}

+ 95 - 0
integration/subxt-tests/src/cases/balances.rs

@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::time::Duration;
+
+use crate::{free_balance_of, node, Contract, WriteContract};
+use contract_transcode::ContractMessageTranscoder;
+use hex::FromHex;
+use parity_scale_codec::{Decode, Encode};
+use sp_core::{hexdisplay::AsBytesRef, keccak_256, KeccakHasher, H256, U256};
+
+use crate::{DeployContract, Execution, ReadContract, API};
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let mut contract = Contract::new("./contracts/balances.contract")?;
+
+    contract
+        .deploy(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            10_u128.pow(7),
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("new", []).unwrap(),
+        )
+        .await?;
+
+    let contract_balance_rpc = free_balance_of(&api, contract.address.clone().unwrap()).await?;
+
+    let rv = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("get_balance", []).unwrap(),
+        )
+        .await?;
+
+    let contract_balance = <u128>::decode(&mut rv.as_bytes_ref())?;
+    assert!(contract_balance == contract_balance_rpc);
+
+    contract
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            10_u128.pow(3),
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("pay_me", []).unwrap(),
+        )
+        .await?;
+
+    let contract_balance_after = free_balance_of(&api, contract.address.clone().unwrap()).await?;
+    assert_eq!(contract_balance + 10_u128.pow(3), contract_balance_after);
+
+    let dave = sp_keyring::AccountKeyring::Dave;
+    let dave_balance_rpc = free_balance_of(&api, dave.to_account_id()).await?;
+
+    contract
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                let mut s = keccak_256(b"transfer(address,uint128)")[..4].to_vec();
+                dave.encode_to(&mut s);
+                20000_u128.encode_to(&mut s);
+                s
+            },
+        )
+        .await?;
+
+    let dave_balance_rpc_after = free_balance_of(&api, dave.to_account_id()).await?;
+
+    assert_eq!(dave_balance_rpc_after, dave_balance_rpc + 20000_u128);
+
+    contract
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                let mut s = keccak_256(b"send(address,uint128)")[..4].to_vec();
+                dave.encode_to(&mut s);
+                10000_u128.encode_to(&mut s);
+                s
+            },
+        )
+        .await?;
+
+    let dave_balance_rpc_after2 =
+        free_balance_of(&api, sp_keyring::AccountKeyring::Dave.to_account_id()).await?;
+
+    assert_eq!(dave_balance_rpc_after + 10000_u128, dave_balance_rpc_after2);
+
+    Ok(())
+}

+ 95 - 0
integration/subxt-tests/src/cases/builtins.rs

@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::time::{Instant, SystemTime, UNIX_EPOCH};
+
+use crate::{node, Contract, DeployContract, Execution, ReadContract, API};
+use contract_transcode::ContractMessageTranscoder;
+use parity_scale_codec::{Decode, Encode};
+use sp_core::hexdisplay::AsBytesRef;
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let mut contract = Contract::new("./contracts/builtins.contract")?;
+
+    contract
+        .deploy(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("new", []).unwrap(),
+        )
+        .await?;
+
+    // check ripmed160
+    let input_str = "Call me Ishmael.";
+
+    let rv = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode("hash_ripemd160", [format!("0x{}", hex::encode(&input_str))])
+                    .unwrap()
+            },
+        )
+        .await?;
+
+    let expected = hex::decode("0c8b641c461e3c7abbdabd7f12a8905ee480dadf")?;
+    assert_eq!(rv, expected);
+
+    // check sha256
+    let rv = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode("hash_sha256", [format!("0x{}", hex::encode(&input_str))])
+                    .unwrap()
+            },
+        )
+        .await?;
+
+    let expected = hex::decode("458f3ceeeec730139693560ecf66c9c22d9c7bc7dcb0599e8e10b667dfeac043")?;
+    assert_eq!(rv, expected);
+
+    // check keccak256
+    let rv = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode(
+                    "hash_kecccak256",
+                    [format!("0x{}", hex::encode(&input_str))],
+                )
+                .unwrap()
+            },
+        )
+        .await?;
+
+    let expected = hex::decode("823ad8e1757b879aac338f9a18542928c668e479b37e4a56f024016215c5928c")?;
+    assert_eq!(rv, expected);
+
+    // check timestamp
+    let rv = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("mr_now", []).unwrap(),
+        )
+        .await?;
+
+    let now = SystemTime::now().duration_since(UNIX_EPOCH)?;
+    let decoded = u64::decode(&mut rv.as_bytes_ref())?;
+
+    assert!(now.as_secs() >= decoded);
+    assert!(now.as_secs() < decoded + 120);
+
+    Ok(())
+}

+ 136 - 0
integration/subxt-tests/src/cases/builtins2.rs

@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use parity_scale_codec::{Decode, Encode};
+use sp_core::hexdisplay::AsBytesRef;
+
+use crate::{node, Contract, DeployContract, Execution, ReadContract, API, GAS_LIMIT};
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+    let code = std::fs::read("./contracts/builtins2.wasm")?;
+
+    let c = Contract::new("./contracts/builtins2.contract")?;
+
+    let transcoder = &c.transcoder;
+
+    let selector = transcoder.encode::<_, String>("new", [])?;
+    let deployed = DeployContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        selector,
+        value: 0,
+        code,
+    }
+    .execute(&api)
+    .await?;
+
+    // check blake2_128
+    let input_str = "Call me Ishmael.";
+
+    let selector = transcoder.encode(
+        "hash_blake2_128",
+        [format!("0x{}", hex::encode(&input_str))],
+    )?;
+
+    let rv = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: deployed.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await?;
+
+    let expected = hex::decode("56691483d63cac66c38c168c703c6f13")?;
+    assert_eq!(rv.return_value, expected);
+
+    // check blake2_256
+    let selector = transcoder.encode(
+        "hash_blake2_256",
+        [format!("0x{}", hex::encode(&input_str))],
+    )?;
+
+    let rv = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: deployed.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await?;
+
+    let expected = hex::decode("1abd7330c92d835b5084219aedba821c3a599d039d5b66fb5a22ee8e813951a8")?;
+    assert_eq!(rv.return_value, expected);
+
+    // check block_height
+    let selector = transcoder.encode::<_, String>("block_height", [])?;
+
+    let rv = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: deployed.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await?;
+
+    let decoded = u64::decode(&mut rv.return_value.as_bytes_ref())? as i64;
+
+    let key = node::storage().system().number();
+
+    let rpc_block_number = api
+        .storage()
+        .at_latest()
+        .await?
+        .fetch_or_default(&key)
+        .await?;
+
+    assert!((decoded - rpc_block_number as i64).abs() <= 3);
+
+    // check gas burn
+    let selector = transcoder.encode::<_, String>("burn_gas", [format!("{}", 0_u64)])?;
+
+    let rv = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: deployed.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await?;
+
+    let gas_left = u64::decode(&mut rv.return_value.as_bytes_ref())?;
+
+    assert!(GAS_LIMIT > gas_left);
+
+    let mut previous_used = GAS_LIMIT - gas_left;
+
+    for i in 1_u64..100 {
+        // check gas burn
+        let selector = transcoder.encode::<_, String>("burn_gas", [format!("{}", i)])?;
+
+        let rv = ReadContract {
+            caller: sp_keyring::AccountKeyring::Alice,
+            contract_address: deployed.contract_address.clone(),
+            value: 0,
+            selector,
+        }
+        .execute(&api)
+        .await?;
+
+        let gas_left = u64::decode(&mut rv.return_value.as_bytes_ref())?;
+
+        assert!(GAS_LIMIT > gas_left);
+
+        let gas_used = GAS_LIMIT - gas_left;
+
+        assert!(gas_used > previous_used);
+        assert!(gas_used - previous_used < 10_u64.pow(6));
+        assert!(gas_used - previous_used > 10_u64.pow(4));
+
+        previous_used = gas_used;
+    }
+
+    Ok(())
+}

+ 85 - 0
integration/subxt-tests/src/cases/create_contract.rs

@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::str::FromStr;
+
+use contract_transcode::ContractMessageTranscoder;
+use hex::FromHex;
+use parity_scale_codec::{Decode, Encode};
+use sp_core::{crypto::AccountId32, hexdisplay::AsBytesRef, H256};
+
+use crate::{
+    free_balance_of, node, Contract, DeployContract, Execution, ReadContract, ReadLayout,
+    WriteContract, API,
+};
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let mut c_creator = Contract::new("./contracts/creator.contract")?;
+
+    c_creator
+        .deploy(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            10_u128.pow(16),
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("new", []).unwrap(),
+        )
+        .await?;
+
+    let mut c_child = Contract::new("./contracts/child_create_contract.contract")?;
+    c_child
+        .upload_code(&api, sp_keyring::AccountKeyring::Alice)
+        .await?;
+
+    c_creator
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0_u128,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("create_child", []).unwrap(),
+        )
+        .await?;
+
+    let rv = c_creator
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0_u128,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("call_child", []).unwrap(),
+        )
+        .await
+        .and_then(|v| String::decode(&mut v.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(rv, "child");
+
+    let child_addr = c_creator
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0_u128,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("c", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <AccountId32>::decode(&mut v.as_bytes_ref()).map_err(Into::into))?;
+
+    c_child.address.replace(child_addr.clone());
+    let child_balance_rpc = free_balance_of(&api, child_addr).await?;
+    assert!(child_balance_rpc != 0);
+    let creator_balance_rpc = free_balance_of(&api, c_creator.address.unwrap()).await?;
+    assert!(creator_balance_rpc < 10_u128.pow(16));
+
+    let rv = c_child
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0_u128,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("say_my_name", []).unwrap(),
+        )
+        .await
+        .and_then(|v| String::decode(&mut v.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(rv, "child");
+
+    Ok(())
+}

+ 74 - 0
integration/subxt-tests/src/cases/destruct.rs

@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use parity_scale_codec::{Decode, Encode};
+use sp_core::hexdisplay::AsBytesRef;
+
+use crate::{
+    free_balance_of, Contract, DeployContract, Execution, ReadContract, WriteContract, API,
+};
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+    let code = std::fs::read("./contracts/destruct.wasm")?;
+
+    let c = Contract::new("./contracts/destruct.contract")?;
+
+    let transcoder = &c.transcoder;
+
+    let selector = transcoder.encode::<_, String>("new", [])?;
+
+    let deployed = DeployContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        selector,
+        value: 0,
+        code,
+    }
+    .execute(&api)
+    .await?;
+
+    let selector = transcoder.encode::<_, String>("hello", [])?;
+
+    let rv = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: deployed.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| <String>::decode(&mut v.return_value.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(rv, "Hello");
+
+    let dave_before =
+        free_balance_of(&api, sp_keyring::AccountKeyring::Dave.to_account_id()).await?;
+    let contract_before = free_balance_of(&api, deployed.contract_address.clone()).await?;
+
+    let selector = transcoder.encode::<_, String>(
+        "selfterminate",
+        [format!(
+            "0x{}",
+            hex::encode(sp_keyring::AccountKeyring::Dave.to_account_id())
+        )],
+    )?;
+
+    WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: deployed.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await?;
+
+    let dave_after =
+        free_balance_of(&api, sp_keyring::AccountKeyring::Dave.to_account_id()).await?;
+    let contract_after = free_balance_of(&api, deployed.contract_address.clone()).await?;
+
+    assert_eq!(contract_after, 0);
+    assert_eq!(dave_after, dave_before + contract_before);
+
+    Ok(())
+}

+ 73 - 0
integration/subxt-tests/src/cases/events.rs

@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use parity_scale_codec::{Compact, Decode, Input};
+use sp_core::{crypto::AccountId32, hexdisplay::AsBytesRef};
+
+use crate::{Contract, DeployContract, Execution, WriteContract, API};
+use hex::FromHex;
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let mut c = Contract::new("./contracts/Events.contract")?;
+
+    c.deploy(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+        t.encode::<_, &'static str>("new", []).unwrap_or_default()
+    })
+    .await?;
+
+    let rs = c
+        .call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, &'static str>("emit_event", [])
+                .unwrap_or_default()
+        })
+        .await?;
+
+    assert_eq!(rs.len(), 4);
+
+    // TODO: currently event decoding is different than ink, as we can see in contract-transcode.
+    let e1 = &rs[0];
+
+    let e1_buffer = &mut e1.data.as_slice();
+
+    let topic = e1_buffer.read_byte()?;
+    assert_eq!(topic, 0);
+
+    // mimic the solidity struct type
+    #[derive(Decode)]
+    struct Foo1 {
+        id: i64,
+        s: String,
+    }
+
+    let Foo1 { id, s } = Foo1::decode(e1_buffer)?;
+    assert_eq!((id, s.as_str()), (254, "hello there"));
+
+    let e2 = &rs[1];
+    let e2_buffer = &mut e2.data.as_slice();
+
+    let topic = e2_buffer.read_byte()?;
+    assert_eq!(topic, 1);
+
+    // mimic the solidity struct type
+    #[derive(Decode)]
+    struct Foo2 {
+        id: i64,
+        s2: String,
+        a: AccountId32,
+    }
+
+    let Foo2 { id, s2, a } = Foo2::decode(e2_buffer)?;
+    assert_eq!(
+        (id, s2.as_str(), a),
+        (
+            i64::from_str_radix("7fffffffffffffff", 16)?,
+            "minor",
+            c.address.unwrap()
+        )
+    );
+
+    Ok(())
+}

+ 202 - 0
integration/subxt-tests/src/cases/external_call.rs

@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use parity_scale_codec::{Decode, Encode};
+use sp_core::{crypto::AccountId32, hexdisplay::AsBytesRef};
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let caller_code = std::fs::read("./contracts/caller.wasm")?;
+    let callee_code = std::fs::read("./contracts/callee.wasm")?;
+    let callee2_code = std::fs::read("./contracts/callee2.wasm")?;
+
+    let c_caller = Contract::new("./contracts/caller.contract")?;
+    let t_caller = &c_caller.transcoder;
+
+    let c_callee = Contract::new("./contracts/callee.contract")?;
+    let t_callee = &c_callee.transcoder;
+
+    let c_callee2 = Contract::new("./contracts/callee2.contract")?;
+    let t_callee2 = &c_caller.transcoder;
+
+    let selector = t_caller.encode::<_, String>("new", [])?;
+
+    let caller = DeployContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        selector: selector.clone(),
+        value: 0,
+        code: caller_code,
+    }
+    .execute(&api)
+    .await?;
+
+    let callee = DeployContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        selector: selector.clone(),
+        value: 0,
+        code: callee_code,
+    }
+    .execute(&api)
+    .await?;
+
+    let callee2 = DeployContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        selector: selector.clone(),
+        value: 0,
+        code: callee2_code,
+    }
+    .execute(&api)
+    .await?;
+
+    // setX on callee
+    let selector = t_callee.encode::<_, String>("set_x", [format!("102")])?;
+
+    WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: callee.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await?;
+
+    // getX on callee
+    let selector = t_callee.encode::<_, String>("get_x", [])?;
+
+    let res1 = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: callee.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| <i64>::decode(&mut v.return_value.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res1, 102);
+
+    // whoAmI on caller
+    let selector = t_caller.encode::<_, String>("who_am_i", [])?;
+
+    let res2 = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: caller.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| <AccountId32>::decode(&mut v.return_value.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res2, caller.contract_address.clone());
+
+    // doCall on caller
+    let selector = t_caller.encode::<_, String>(
+        "do_call",
+        [
+            format!("0x{}", hex::encode(callee.contract_address.clone())),
+            "13123".to_string(),
+        ],
+    )?;
+
+    WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: caller.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await?;
+
+    // getX on callee
+    let selector = t_callee.encode::<_, String>("get_x", [])?;
+
+    let res3 = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: callee.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| <i64>::decode(&mut v.return_value.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res3, 13123);
+
+    // doCall2 on caller
+    let selector = t_caller.encode::<_, String>(
+        "do_call2",
+        [
+            format!("0x{}", hex::encode(callee.contract_address.clone())),
+            "20000".to_string(),
+        ],
+    )?;
+
+    let res4 = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: caller.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| <i64>::decode(&mut v.return_value.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res4, 33123);
+
+    // doCall3 on caller
+    let selector = t_caller.encode::<_, String>(
+        "do_call3",
+        [
+            format!("0x{}", hex::encode(callee.contract_address.clone())),
+            format!("0x{}", hex::encode(callee2.contract_address.clone())),
+            format!("{:?}", [3_i64, 5, 7, 9]),
+            "\"yo\"".to_string(),
+        ],
+    )?;
+
+    let res5 = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: caller.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| {
+        <(i64, String)>::decode(&mut v.return_value.as_bytes_ref()).map_err(Into::into)
+    })?;
+
+    assert_eq!(res5, (24, "my name is callee".to_string()));
+
+    // doCall4 on caller
+    let selector = t_caller.encode::<_, String>(
+        "do_call4",
+        [
+            format!("0x{}", hex::encode(callee.contract_address.clone())),
+            format!("0x{}", hex::encode(callee2.contract_address.clone())),
+            format!("{:?}", [1_i64, 2, 3, 4]),
+            "\"asda\"".to_string(),
+        ],
+    )?;
+
+    let res6 = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: caller.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| {
+        <(i64, String)>::decode(&mut v.return_value.as_bytes_ref()).map_err(Into::into)
+    })?;
+
+    assert_eq!(res6, (10, "x:asda".to_string()));
+
+    Ok(())
+}

+ 64 - 0
integration/subxt-tests/src/cases/flipper.rs

@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use parity_scale_codec::{Decode, Encode};
+use sp_core::hexdisplay::AsBytesRef;
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let mut contract = Contract::new("./contracts/flipper.contract")?;
+    contract
+        .upload_code(&api, sp_keyring::AccountKeyring::Alice)
+        .await?;
+
+    contract
+        .deploy(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            10_u128.pow(16),
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("new", ["true".into()]).unwrap(),
+        )
+        .await?;
+
+    // get value
+    let init_value = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("get", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <bool>::decode(&mut v.as_bytes_ref()).map_err(Into::into))?;
+
+    assert!(init_value);
+
+    // flip flipper
+    contract
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("flip", []).unwrap(),
+        )
+        .await?;
+
+    // get value
+    let updated = contract
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("get", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <bool>::decode(&mut v.as_bytes_ref()).map_err(Into::into))?;
+
+    assert!(!updated);
+
+    Ok(())
+}

+ 46 - 0
integration/subxt-tests/src/cases/issue666.rs

@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{Contract, API};
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+    let mut c_flipper = Contract::new("contracts/Flip.contract")?;
+    c_flipper
+        .deploy(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("new", [])
+                .expect("unable to find selector")
+        })
+        .await?;
+
+    let mut c_inc = Contract::new("./contracts/Inc.contract")?;
+
+    // flip on Flip
+    c_flipper
+        .call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("flip", [])
+                .expect("unable to find selector")
+        })
+        .await?;
+
+    c_inc
+        .deploy(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>(
+                "new",
+                [format!(
+                    "0x{}",
+                    hex::encode(c_flipper.address.clone().unwrap())
+                )],
+            )
+            .expect("unable to find selector")
+        })
+        .await?;
+    // superFlip on Inc
+    c_inc
+        .call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("superFlip", [])
+                .expect("unable to find selector")
+        })
+        .await?;
+    Ok(())
+}

+ 23 - 0
integration/subxt-tests/src/cases/mod.rs

@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: Apache-2.0
+
+mod arrays;
+
+mod array_struct_mapping_storage;
+mod asserts;
+mod balances;
+mod builtins;
+mod builtins2;
+mod create_contract;
+mod destruct;
+mod events;
+mod external_call;
+mod flipper;
+mod issue666;
+mod msg_sender;
+mod primitives;
+mod randomizer;
+mod store;
+mod structs;
+mod uniswapv2_erc20;
+mod uniswapv2_factory;
+mod uniswapv2_pair;

+ 99 - 0
integration/subxt-tests/src/cases/msg_sender.rs

@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use parity_scale_codec::{Decode, Encode, Input};
+use sp_core::{crypto::AccountId32, hexdisplay::AsBytesRef};
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    // mytoken
+    let mytoken_code = std::fs::read("./contracts/mytoken.wasm")?;
+    let mytoken_event_code = std::fs::read("./contracts/mytokenEvent.wasm")?;
+
+    let c_mytoken = Contract::new("./contracts/mytoken.contract")?;
+    let t_mytoken = &c_mytoken.transcoder;
+
+    let c_mytoken_evt = Contract::new("./contracts/mytokenEvent.contract")?;
+    let t_mytoken_evt = &c_mytoken_evt.transcoder;
+
+    let selector = t_mytoken.encode::<_, String>("new", [])?;
+
+    // let selector = build_selector("861731d5", None)?;
+
+    let mytoken = DeployContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        selector,
+        value: 0,
+        code: mytoken_code,
+    }
+    .execute(&api)
+    .await?;
+
+    // read test
+    let selector = t_mytoken.encode::<_, String>(
+        "test",
+        [
+            format!(
+                "0x{}",
+                hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id())
+            ),
+            "true".into(),
+        ],
+    )?;
+
+    let addr = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: mytoken.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| <AccountId32>::decode(&mut v.return_value.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(addr, sp_keyring::AccountKeyring::Alice.to_account_id());
+
+    // mytokenEvent
+    let selector = t_mytoken_evt.encode::<_, String>("new", [])?;
+
+    let mytoken_event = DeployContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        selector,
+        value: 0,
+        code: mytoken_event_code,
+    }
+    .execute(&api)
+    .await?;
+
+    // call test
+    let selector = t_mytoken_evt.encode::<_, String>("test", [])?;
+
+    let output = WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: mytoken_event.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await?;
+
+    assert_eq!(output.events.len(), 1);
+
+    let evt = &output.events[0];
+
+    let mut evt_buffer = evt.data.as_slice();
+
+    let topic_id = evt_buffer.read_byte()?;
+
+    assert_eq!(topic_id, 0);
+
+    let addr = <AccountId32>::decode(&mut evt_buffer)?;
+
+    assert_eq!(addr, sp_keyring::AccountKeyring::Alice.to_account_id());
+
+    Ok(())
+}

+ 697 - 0
integration/subxt-tests/src/cases/primitives.rs

@@ -0,0 +1,697 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::str::FromStr;
+
+use contract_transcode::ContractMessageTranscoder;
+use hex::FromHex;
+use num_bigint::{BigInt, BigUint, Sign};
+use parity_scale_codec::{Decode, Encode, Input};
+use sp_core::{crypto::AccountId32, hexdisplay::AsBytesRef, keccak_256, KeccakHasher, H256, U256};
+use sp_runtime::{assert_eq_error_rate, scale_info::TypeInfo};
+use subxt::ext::sp_runtime::{traits::One, MultiAddress};
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+
+async fn query<T: Decode>(api: &API, addr: &AccountId32, selector: &[u8]) -> anyhow::Result<T> {
+    ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: addr.clone(),
+        value: 0,
+        selector: selector.to_vec(),
+    }
+    .execute(api)
+    .await
+    .and_then(|v| T::decode(&mut v.return_value.as_bytes_ref()).map_err(Into::into))
+}
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let mut c = Contract::new("contracts/primitives.contract")?;
+
+    c.deploy(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+        t.encode::<_, String>("new", [])
+            .expect("unable to find selector")
+    })
+    .await?;
+
+    // test res
+    #[derive(Encode, Decode)]
+    enum oper {
+        add,
+        sub,
+        mul,
+        div,
+        r#mod,
+        pow,
+        shl,
+        shr,
+        or,
+        and,
+        xor,
+    }
+
+    let is_mul = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("is_mul", ["mul".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| bool::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert!(is_mul);
+
+    let return_div = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("return_div", [])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| oper::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    if let oper::div = return_div {
+    } else {
+        panic!("not div");
+    }
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_i64", ["add".into(), "1000".into(), "4100".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| i64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res, 5100);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_i64", ["sub".into(), "1000".into(), "4100".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| i64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res, -3100);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_i64", ["mul".into(), "1000".into(), "4100".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| i64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, 4100000);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_i64", ["div".into(), "1000".into(), "10".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| i64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, 100);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_i64", ["mod".into(), "1000".into(), "99".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| i64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res, 10);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_i64", ["shl".into(), "-1000".into(), "8".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| i64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res, -256000);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_i64", ["shr".into(), "-1000".into(), "8".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| i64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, -4);
+
+    // op_u64
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_u64", ["add".into(), "1000".into(), "4100".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| u64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res, 5100);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_u64", ["sub".into(), "1000".into(), "4100".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| u64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res, 18446744073709548516);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>(
+                "op_u64",
+                ["mul".into(), "123456789".into(), "123456789".into()],
+            )
+            .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| u64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res, 15241578750190521);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_u64", ["div".into(), "123456789".into(), "100".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| u64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, 1234567);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_u64", ["mod".into(), "123456789".into(), "100".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| u64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, 89);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_u64", ["pow".into(), "3".into(), "7".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| u64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, 2187);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_u64", ["shl".into(), "1000".into(), "8".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| u64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, 256000);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("op_u64", ["shr".into(), "1000".into(), "8".into()])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| u64::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, 3);
+
+    // op_i256
+    // TODO: currently contract-transcode doesn't support encoding/decoding of I256 type so we'll need  to encode it manually
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("d6435f25").expect("unable to decode selector");
+
+            oper::add.encode_to(&mut sel);
+            U256::from_dec_str("1000")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("4100")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res, U256::from(5100_u128));
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("d6435f25").expect("unable to decode selector");
+
+            oper::sub.encode_to(&mut sel);
+            U256::from_dec_str("1000")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("4100")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    // use two's compliment to get negative value in
+    assert_eq!(res, !U256::from(3100_u128) + U256::one());
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("d6435f25").expect("unable to decode selector");
+
+            oper::mul.encode_to(&mut sel);
+            U256::from_dec_str("1000")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("4100")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res, U256::from_dec_str("4100000")?);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("d6435f25").expect("unable to decode selector");
+
+            oper::div.encode_to(&mut sel);
+            U256::from_dec_str("1000")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("10")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res, U256::from_dec_str("100")?);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("d6435f25").expect("unable to decode selector");
+
+            oper::r#mod.encode_to(&mut sel);
+            U256::from_dec_str("1000")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("99")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, U256::from_dec_str("10")?);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("d6435f25").expect("unable to decode selector");
+
+            oper::shl.encode_to(&mut sel);
+            (!U256::from_dec_str("10000000000000").unwrap() + U256::one()).encode_to(&mut sel);
+
+            U256::from_dec_str("8")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, !U256::from_dec_str("2560000000000000")? + U256::one());
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("d6435f25").expect("unable to decode selector");
+
+            oper::shr.encode_to(&mut sel);
+            (!U256::from_dec_str("10000000000000").unwrap() + U256::one()).encode_to(&mut sel);
+
+            U256::from_dec_str("8")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(res, !U256::from(39062500000_i64) + U256::one());
+
+    // op_u256
+    // TODO: currently U256 from string is not supported by contract-transcode, we'll need to encode it manually
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("b446eacd").expect("unable to decode selector");
+
+            oper::add.encode_to(&mut sel);
+            U256::from_dec_str("1000")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("4100")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, U256::from(5100_u128));
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("b446eacd").expect("unable to decode selector");
+
+            oper::sub.encode_to(&mut sel);
+            U256::from_dec_str("1000")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("4100")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, !U256::from(3100_u128) + U256::one());
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("b446eacd").expect("unable to decode selector");
+
+            oper::mul.encode_to(&mut sel);
+            U256::from_dec_str("123456789")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("123456789")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, U256::from(15241578750190521_u128));
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("b446eacd").expect("unable to decode selector");
+
+            oper::div.encode_to(&mut sel);
+            U256::from_dec_str("123456789")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("100")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, U256::from(1234567_u128));
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("b446eacd").expect("unable to decode selector");
+
+            oper::r#mod.encode_to(&mut sel);
+            U256::from_dec_str("123456789")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("100")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, U256::from(89_u64));
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("b446eacd").expect("unable to decode selector");
+
+            oper::pow.encode_to(&mut sel);
+            U256::from_dec_str("123456789")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("9")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(
+        res.to_string(),
+        "6662462759719942007440037531362779472290810125440036903063319585255179509"
+    );
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("b446eacd").expect("unable to decode selector");
+
+            oper::shl.encode_to(&mut sel);
+            U256::from_dec_str("10000000000000")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("8")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, U256::from(2560000000000000_u128));
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            // TODO: currently contract-transcode expects equal number of legal input args to generate the correct selector,
+            // since i256 is not supported by contract-metadata we need to manually supply the selector and encode its inputs
+            let mut sel = hex::decode("b446eacd").expect("unable to decode selector");
+
+            oper::shr.encode_to(&mut sel);
+            U256::from_dec_str("10000000000000")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            U256::from_dec_str("8")
+                .map(|o| o.encode_to(&mut sel))
+                .expect("unable to encode to selector");
+            sel
+        })
+        .await
+        .and_then(|rv| U256::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, U256::from(39062500000_u128));
+
+    // test bytesN
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("return_u8_6", [])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| <[u8; 6]>::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(hex::encode(res), "414243444546");
+
+    // test bytesS
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>("op_u8_5_shift", ["shl", "0xdeadcafe59", "8"])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| <[u8; 5]>::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_eq!(hex::encode(res), "adcafe5900");
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>("op_u8_5_shift", ["shr", "0xdeadcafe59", "8"])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| <[u8; 5]>::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(hex::encode(res), "00deadcafe");
+
+    // opU85
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>("op_u8_5", ["or", "0xdeadcafe59", "0x0000000006"])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| <[u8; 5]>::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(hex::encode(res), "deadcafe5f");
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>("op_u8_5", ["and", "0xdeadcafe59", "0x00000000ff"])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| <[u8; 5]>::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(hex::encode(res), "0000000059");
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>("op_u8_5", ["xor", "0xdeadcafe59", "0x00000000ff"])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| <[u8; 5]>::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(hex::encode(res), "deadcafea6");
+
+    // test bytes14
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>(
+                "op_u8_14_shift",
+                ["shl", "0xdeadcafe123456789abcdefbeef7", "9"],
+            )
+            .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| <[u8; 14]>::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(hex::encode(res), "5b95fc2468acf13579bdf7ddee00");
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>(
+                "op_u8_14_shift",
+                ["shr", "0xdeadcafe123456789abcdefbeef7", "9"],
+            )
+            .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| <[u8; 14]>::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(hex::encode(res), "006f56e57f091a2b3c4d5e6f7df7");
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>(
+                "op_u8_14",
+                [
+                    "or",
+                    "0xdeadcafe123456789abcdefbeef7",
+                    "0x0000060000000000000000000000",
+                ],
+            )
+            .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| <[u8; 14]>::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(hex::encode(res), "deadcefe123456789abcdefbeef7");
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>(
+                "op_u8_14",
+                [
+                    "and",
+                    "0xdeadcafe123456789abcdefbeef7",
+                    "0x000000000000000000ff00000000",
+                ],
+            )
+            .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| <[u8; 14]>::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(hex::encode(res), "000000000000000000bc00000000");
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>(
+                "op_u8_14",
+                [
+                    "xor",
+                    "0xdeadcafe123456789abcdefbeef7",
+                    "0xff00000000000000000000000000",
+                ],
+            )
+            .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| <[u8; 14]>::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(hex::encode(res), "21adcafe123456789abcdefbeef7");
+
+    // test addressPassthrough
+    let default_acc =
+        AccountId32::from_str("5GBWmgdFAMqm8ZgAHGobqDqX6tjLxJhv53ygjNtaaAn3sjeZ").unwrap();
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>(
+                "address_passthrough",
+                [format!("0x{}", hex::encode(&default_acc))],
+            )
+            .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| AccountId32::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, default_acc);
+
+    let alice = sp_keyring::AccountKeyring::Alice.to_account_id();
+
+    let dave = sp_keyring::AccountKeyring::Dave.to_account_id();
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>(
+                "address_passthrough",
+                [format!("0x{}", hex::encode(&alice))],
+            )
+            .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| AccountId32::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, alice);
+
+    let res = c
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, _>("address_passthrough", [format!("0x{}", hex::encode(&dave))])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|rv| AccountId32::decode(&mut rv.as_bytes_ref()).map_err(Into::into))?;
+    assert_eq!(res, dave);
+
+    Ok(())
+}

+ 70 - 0
integration/subxt-tests/src/cases/randomizer.rs

@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use parity_scale_codec::{Decode, Encode};
+use sp_core::{hexdisplay::AsBytesRef, keccak_256};
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+
+#[ignore = "test removed"]
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let code = std::fs::read("./contracts/randomizer.wasm")?;
+
+    let c = Contract::new("./contracts/randomizer.contract")?;
+
+    let transcoder = &c.transcoder;
+
+    let selector = transcoder.encode::<_, String>("new", [])?;
+
+    let contract = DeployContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        selector,
+        value: 0,
+        code,
+    }
+    .execute(&api)
+    .await?;
+
+    let selector =
+        transcoder.encode::<_, _>("get_random", [format!("{:?}", "01234567".as_bytes())])?;
+
+    let rs = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector: selector.clone(),
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| <[u8; 32]>::decode(&mut v.return_value.as_bytes_ref()).map_err(Into::into))?;
+
+    WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await?;
+
+    let selector = transcoder.encode::<_, String>("value", [])?;
+
+    let tx_rs = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| <[u8; 32]>::decode(&mut v.return_value.as_bytes_ref()).map_err(Into::into))?;
+
+    assert_ne!(rs, [0_u8; 32]);
+    assert_ne!(tx_rs, [0_u8; 32]);
+    assert_ne!(rs, tx_rs);
+
+    Ok(())
+}

+ 269 - 0
integration/subxt-tests/src/cases/store.rs

@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use hex::FromHex;
+use parity_scale_codec::{Decode, DecodeAll, Encode};
+use rand::Rng;
+use sp_core::{hexdisplay::AsBytesRef, keccak_256, U256};
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let code = std::fs::read("./contracts/store.wasm")?;
+
+    let c = Contract::new("./contracts/store.contract")?;
+
+    let transcoder = &c.transcoder;
+
+    let selector = transcoder.encode::<_, String>("new", [])?;
+
+    let contract = DeployContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        selector,
+        value: 0,
+        code,
+    }
+    .execute(&api)
+    .await?;
+
+    let selector = transcoder.encode::<_, String>("get_values1", [])?;
+
+    let res = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await
+    .and_then(|e| {
+        <(u64, u32, i16, U256)>::decode(&mut e.return_value.as_bytes_ref()).map_err(Into::into)
+    })?;
+
+    assert_eq!(res, (0, 0, 0, U256::zero()));
+
+    #[derive(Encode, Decode, PartialEq, Eq, Debug)]
+    enum enum_bar {
+        bar1,
+        bar2,
+        bar3,
+        bar4,
+    }
+
+    let selector = transcoder.encode::<_, String>("get_values2", [])?;
+
+    let res = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await
+    .and_then(|e| {
+        <(U256, String, Vec<u8>, [u8; 4], enum_bar)>::decode(&mut e.return_value.as_bytes_ref())
+            .map_err(Into::into)
+    })?;
+
+    assert_eq!(
+        res,
+        (
+            U256::zero(),
+            "".into(),
+            hex::decode("b00b1e")?,
+            <_>::from_hex("00000000")?,
+            enum_bar::bar1
+        )
+    );
+
+    let selector = transcoder.encode::<_, String>("set_values", [])?;
+
+    WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await?;
+
+    let selector = transcoder.encode::<_, String>("get_values1", [])?;
+
+    let res = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await
+    .and_then(|e| {
+        <(u64, u32, i16, U256)>::decode(&mut e.return_value.as_bytes_ref()).map_err(Into::into)
+    })?;
+
+    assert_eq!(
+        res,
+        (
+            u64::from_be_bytes(<[u8; 8]>::from_hex("ffffffffffffffff")?),
+            3671129839,
+            32766,
+            U256::from_big_endian(&<[u8; 32]>::from_hex(
+                "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+            )?)
+        )
+    );
+
+    let selector = transcoder.encode::<_, String>("get_values2", [])?;
+
+    let res = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await
+    .and_then(|e| {
+        <(U256, String, Vec<u8>, [u8; 4], enum_bar)>::decode(&mut e.return_value.as_bytes_ref())
+            .map_err(Into::into)
+    })?;
+
+    assert_eq!(
+        res,
+        (
+            U256::from_dec_str("102")?,
+            "the course of true love never did run smooth".into(),
+            hex::decode("b00b1e")?,
+            <_>::from_hex("41424344")?,
+            enum_bar::bar2
+        )
+    );
+
+    let selector = transcoder.encode::<_, String>("do_ops", [])?;
+
+    WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await?;
+
+    let selector = transcoder.encode::<_, String>("get_values1", [])?;
+
+    let res = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await
+    .and_then(|e| {
+        <(u64, u32, i16, U256)>::decode(&mut e.return_value.as_bytes_ref()).map_err(Into::into)
+    })?;
+
+    assert_eq!(
+        res,
+        (
+            1,
+            65263,
+            32767,
+            U256::from_big_endian(&<[u8; 32]>::from_hex(
+                "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"
+            )?)
+        )
+    );
+
+    let selector = transcoder.encode::<_, String>("get_values2", [])?;
+
+    let res = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await
+    .and_then(|e| {
+        <(U256, String, Vec<u8>, [u8; 4], enum_bar)>::decode(&mut e.return_value.as_bytes_ref())
+            .map_err(Into::into)
+    })?;
+
+    assert_eq!(
+        res,
+        (
+            U256::from_dec_str("61200")?,
+            "".into(),
+            hex::decode("b0ff1e")?,
+            <_>::from_hex("61626364")?,
+            enum_bar::bar4
+        )
+    );
+
+    let selector = transcoder.encode::<_, String>("push_zero", [])?;
+
+    WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        value: 0,
+        selector,
+    }
+    .execute(&api)
+    .await?;
+
+    let mut bs = "0xb0ff1e00".to_string();
+
+    for _ in 0..20 {
+        let selector = transcoder.encode::<_, String>("get_bs", [])?;
+
+        let res = ReadContract {
+            caller: sp_keyring::AccountKeyring::Alice,
+            contract_address: contract.contract_address.clone(),
+            value: 0,
+            selector,
+        }
+        .execute(&api)
+        .await
+        .and_then(|e| <Vec<u8>>::decode(&mut e.return_value.as_bytes_ref()).map_err(Into::into))?;
+
+        assert_eq!(res, hex::decode(&bs[2..])?);
+
+        if bs.len() <= 4 || rand::thread_rng().gen_range(0.0_f32..1.0_f32) >= 0.5 {
+            // left pad random u8 in hex
+            let val = format!("{:02x}", rand::random::<u8>());
+
+            let selector = transcoder.encode::<_, _>("push", [format!("0x{}", val)])?;
+
+            WriteContract {
+                caller: sp_keyring::AccountKeyring::Alice,
+                contract_address: contract.contract_address.clone(),
+                value: 0,
+                selector,
+            }
+            .execute(&api)
+            .await?;
+
+            bs += &val;
+        } else {
+            let selector = transcoder.encode::<_, String>("pop", [])?;
+
+            WriteContract {
+                caller: sp_keyring::AccountKeyring::Alice,
+                contract_address: contract.contract_address.clone(),
+                value: 0,
+                selector,
+            }
+            .execute(&api)
+            .await?;
+
+            bs = bs[0..bs.len() - 2].to_string();
+        }
+    }
+
+    Ok(())
+}

+ 232 - 0
integration/subxt-tests/src/cases/structs.rs

@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use hex::FromHex;
+use parity_scale_codec::{Decode, DecodeAll, Encode};
+use rand::Rng;
+use sp_core::{hexdisplay::AsBytesRef, keccak_256, U256};
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+
+#[tokio::test]
+async fn case() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let code = std::fs::read("./contracts/structs.wasm")?;
+
+    let c = Contract::new("./contracts/structs.contract")?;
+
+    let transcoder = &c.transcoder;
+
+    let selector = transcoder.encode::<_, String>("new", [])?;
+
+    let contract = DeployContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        selector,
+        value: 0,
+        code,
+    }
+    .execute(&api)
+    .await?;
+
+    let selector = transcoder.encode::<_, String>("set_foo1", [])?;
+
+    WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await?;
+
+    let selector = transcoder.encode::<_, String>("get_both_foos", [])?;
+
+    #[derive(Encode, Decode, Eq, PartialEq, Debug)]
+    enum enum_bar {
+        bar1,
+        bar2,
+        bar3,
+        bar4,
+    }
+
+    #[derive(Encode, Decode, Eq, PartialEq, Debug)]
+    struct struct_foo {
+        f1: enum_bar,
+        f2: Vec<u8>,
+        f3: i64,
+        f4: [u8; 3],
+        f5: String,
+        f6: inner_foo,
+    }
+
+    #[derive(Encode, Decode, Eq, PartialEq, Debug)]
+    struct inner_foo {
+        in1: bool,
+        in2: String,
+    }
+
+    let rs = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| {
+        <(struct_foo, struct_foo)>::decode(&mut &v.return_value[..]).map_err(Into::into)
+    })?;
+
+    assert_eq!(rs,
+     (
+        struct_foo { f1: enum_bar::bar2, f2: hex::decode("446f6e277420636f756e7420796f757220636869636b656e73206265666f72652074686579206861746368")?, f3: -102, f4: <_>::from_hex("edaeda")?, f5: "You can't have your cake and eat it too".into(), f6: inner_foo { in1: true, in2: "There are other fish in the sea".into() } },
+        struct_foo { f1: enum_bar::bar1, f2: vec![], f3: 0, f4: <_>::from_hex("000000")?, f5: String::new(), f6:inner_foo { in1: false, in2: "".into()} } 
+     )
+    );
+
+    // TODO: find a way to generate signature with enum input
+    let mut selector = hex::decode("9c408762").unwrap();
+
+    let mut input = struct_foo {
+        f1: enum_bar::bar2,
+        f2: hex::decode("b52b073595ccb35eaebb87178227b779")?,
+        f3: -123112321,
+        f4: <_>::from_hex("123456")?,
+        f5: "Barking up the wrong tree".into(),
+        f6: inner_foo {
+            in1: true,
+            in2: "Drive someone up the wall".into(),
+        },
+    };
+
+    input.encode_to(&mut selector);
+    "nah".encode_to(&mut selector);
+
+    input.f6.in2 = "nah".into();
+
+    WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await?;
+
+    let selector = transcoder.encode::<_, _>("get_foo", ["false"])?;
+
+    let rs = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| <struct_foo>::decode(&mut &v.return_value[..]).map_err(Into::into))?;
+
+    assert_eq!(rs, input);
+
+    let selector = transcoder.encode::<_, String>("get_both_foos", [])?;
+
+    let rs = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| {
+        <(struct_foo, struct_foo)>::decode(&mut &v.return_value[..]).map_err(Into::into)
+    })?;
+
+    assert_eq!(rs,
+     (
+        struct_foo { f1: enum_bar::bar2, f2: hex::decode("446f6e277420636f756e7420796f757220636869636b656e73206265666f72652074686579206861746368")?, f3: -102, f4: <_>::from_hex("edaeda")?, f5: "You can't have your cake and eat it too".into(), f6: inner_foo { in1: true, in2: "There are other fish in the sea".into() } },
+        input
+
+     )
+    );
+
+    let selector = transcoder.encode::<_, _>("delete_foo", ["true"])?;
+
+    WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await?;
+
+    let selector = transcoder.encode::<_, _>("get_foo", ["false"])?;
+
+    let rs = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| <struct_foo>::decode(&mut &v.return_value[..]).map_err(Into::into))?;
+
+    assert_eq!(
+        rs,
+        struct_foo {
+            f1: enum_bar::bar2,
+            f2: hex::decode("b52b073595ccb35eaebb87178227b779")?,
+            f3: -123112321,
+            f4: <_>::from_hex("123456")?,
+            f5: "Barking up the wrong tree".into(),
+            f6: inner_foo {
+                in1: true,
+                in2: "nah".into()
+            }
+        }
+    );
+
+    let selector = transcoder.encode::<_, String>("struct_literal", [])?;
+
+    WriteContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await?;
+
+    let selector = transcoder.encode::<_, _>("get_foo", ["true"])?;
+
+    let rs = ReadContract {
+        caller: sp_keyring::AccountKeyring::Alice,
+        contract_address: contract.contract_address.clone(),
+        selector,
+        value: 0,
+    }
+    .execute(&api)
+    .await
+    .and_then(|v| <struct_foo>::decode(&mut &v.return_value[..]).map_err(Into::into))?;
+
+    assert_eq!(
+        rs,
+        struct_foo {
+            f1: enum_bar::bar4,
+            f2: hex::decode(
+                "537570657263616c6966726167696c697374696365787069616c69646f63696f7573"
+            )?,
+            f3: 64927,
+            f4: <_>::from_hex("e282ac")?,
+            f5: "Antidisestablishmentarianism".into(),
+            f6: inner_foo {
+                in1: true,
+                in2: "Pseudopseudohypoparathyroidism".into()
+            }
+        }
+    );
+
+    Ok(())
+}

+ 379 - 0
integration/subxt-tests/src/cases/uniswapv2_erc20.rs

@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use hex::FromHex;
+
+use parity_scale_codec::{Decode, DecodeAll, Encode, Input};
+use rand::Rng;
+use sp_core::{crypto::AccountId32, hexdisplay::AsBytesRef, keccak_256, U256};
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+
+#[tokio::test]
+async fn setup() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    let rs = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("name", [])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| String::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(rs, "Uniswap V2");
+
+    let rs = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("symbol", [])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| String::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(rs, "UNI-V2");
+
+    let rs = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("decimals", [])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| u8::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(rs, 18);
+
+    let rs = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("totalSupply", [])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| U256::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(
+        rs,
+        U256::from_dec_str("10000")? * U256::from(10).pow(18_u8.into())
+    );
+    let rs = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("balanceOf", [format!("0x{}", hex::encode(&w.alice))])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| U256::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(
+        rs,
+        U256::from_dec_str("10000")? * U256::from(10).pow(18_u8.into())
+    );
+
+    let rs = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("DOMAIN_SEPARATOR", [])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| <[u8; 32]>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    let expected = [
+        keccak_256(
+            "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
+                .as_bytes(),
+        )
+        .to_vec(),
+        keccak_256("Uniswap V2".as_bytes()).to_vec(),
+        keccak_256("1".as_bytes()).to_vec(),
+        hex::decode("0100000000000000000000000000000000000000000000000000000000000000")?,
+        AsRef::<[u8; 32]>::as_ref(&w.token_addr).to_vec(),
+    ]
+    .concat();
+
+    let expected = keccak_256(&expected[..]);
+    assert_eq!(rs, expected);
+
+    let rs = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("PERMIT_TYPEHASH", [])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| <[u8; 32]>::decode(&mut &v[..]).map_err(Into::into))?;
+    assert_eq!(
+        rs,
+        <[u8; 32]>::from_hex("6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9")?
+    );
+
+    Ok(())
+}
+
+struct MockWorld {
+    alice: AccountId32,
+    dave: AccountId32,
+    token_addr: AccountId32,
+    contract: Contract,
+}
+
+#[tokio::test]
+async fn approve() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    w.contract
+        .call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            let mut sel = keccak_256(b"approve(address,uint256)")[..4].to_vec();
+            w.dave.encode_to(&mut sel);
+            U256::from(10_u128.pow(18)).encode_to(&mut sel);
+            sel
+        })
+        .await?;
+
+    let rs = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>(
+                "allowance",
+                [
+                    format!("0x{}", hex::encode(&w.alice)),
+                    format!("0x{}", hex::encode(&w.dave)),
+                ],
+            )
+            .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| U256::decode(&mut &v[..]).map_err(Into::into))?;
+    assert_eq!(rs, U256::from(10_u128.pow(18)));
+
+    Ok(())
+}
+
+#[tokio::test]
+async fn transfer() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    w.contract
+        .call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            let mut sel = keccak_256(b"transfer(address,uint256)")[..4].to_vec();
+            w.dave.encode_to(&mut sel);
+            U256::from(10_u128.pow(18)).encode_to(&mut sel);
+            sel
+        })
+        .await?;
+
+    let alice_balance = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("balanceOf", [format!("0x{}", hex::encode(&w.alice))])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| U256::decode(&mut &v[..]).map_err(Into::into))?;
+
+    let dave_balance = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("balanceOf", [format!("0x{}", hex::encode(&w.dave))])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| U256::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(
+        alice_balance,
+        U256::from_dec_str("10000")? * U256::from(10).pow(18_u8.into())
+            - U256::from(10_u128.pow(18))
+    );
+
+    assert_eq!(dave_balance, U256::from(10_u128.pow(18)));
+
+    Ok(())
+}
+
+#[tokio::test]
+async fn transfer_from() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    w.contract
+        .call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            let mut sel = keccak_256(b"approve(address,uint256)")[..4].to_vec();
+            w.dave.encode_to(&mut sel);
+            U256::from(10_u128.pow(18)).encode_to(&mut sel);
+            sel
+        })
+        .await?;
+
+    w.contract
+        .call(&api, sp_keyring::AccountKeyring::Dave, 0, |_| {
+            let mut sel = keccak_256(b"transferFrom(address,address,uint256)")[..4].to_vec();
+            w.alice.encode_to(&mut sel);
+            w.dave.encode_to(&mut sel);
+            U256::from(10_u128.pow(18)).encode_to(&mut sel);
+
+            sel
+        })
+        .await?;
+
+    let rs = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>(
+                "allowance",
+                [
+                    format!("0x{}", hex::encode(&w.alice)),
+                    format!("0x{}", hex::encode(&w.dave)),
+                ],
+            )
+            .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| U256::decode(&mut &v[..]).map_err(Into::into))?;
+    assert_eq!(rs, 0_u8.into());
+
+    let alice_balance = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("balanceOf", [format!("0x{}", hex::encode(&w.alice))])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| U256::decode(&mut &v[..]).map_err(Into::into))?;
+    assert_eq!(
+        alice_balance,
+        U256::from_dec_str("10000")? * U256::from(10).pow(18_u8.into())
+            - U256::from(10_u128.pow(18))
+    );
+
+    let dave_balance = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("balanceOf", [format!("0x{}", hex::encode(&w.dave))])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| U256::decode(&mut &v[..]).map_err(Into::into))?;
+    assert_eq!(
+        alice_balance,
+        U256::from_dec_str("10000")? * U256::from(10).pow(18_u8.into())
+            - U256::from(10_u128.pow(18))
+    );
+
+    assert_eq!(dave_balance, U256::from(10_u128.pow(18)));
+
+    Ok(())
+}
+
+#[tokio::test]
+async fn transfer_from_max() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    w.contract
+        .call(&api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            let mut sel = keccak_256(b"approve(address,uint256)")[..4].to_vec();
+            w.dave.encode_to(&mut sel);
+            U256::MAX.encode_to(&mut sel);
+            sel
+        })
+        .await?;
+
+    w.contract
+        .call(&api, sp_keyring::AccountKeyring::Dave, 0, |_| {
+            let mut sel = keccak_256(b"transferFrom(address,address,uint256)")[..4].to_vec();
+            w.alice.encode_to(&mut sel);
+            w.dave.encode_to(&mut sel);
+            U256::from(10_u128.pow(18)).encode_to(&mut sel);
+
+            sel
+        })
+        .await?;
+
+    let rs = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>(
+                "allowance",
+                [
+                    format!("0x{}", hex::encode(&w.alice)),
+                    format!("0x{}", hex::encode(&w.dave)),
+                ],
+            )
+            .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| U256::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(rs, U256::MAX);
+
+    let alice_balance = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("balanceOf", [format!("0x{}", hex::encode(&w.alice))])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| U256::decode(&mut &v[..]).map_err(Into::into))?;
+    assert_eq!(
+        alice_balance,
+        U256::from_dec_str("10000")? * U256::from(10).pow(18_u8.into())
+            - U256::from(10_u128.pow(18))
+    );
+
+    let dave_balance = w
+        .contract
+        .try_call(&api, sp_keyring::AccountKeyring::Alice, 0, |t| {
+            t.encode::<_, String>("balanceOf", [format!("0x{}", hex::encode(&w.dave))])
+                .expect("unable to find selector")
+        })
+        .await
+        .and_then(|v| U256::decode(&mut &v[..]).map_err(Into::into))?;
+    assert_eq!(
+        alice_balance,
+        U256::from_dec_str("10000")? * U256::from(10).pow(18_u8.into())
+            - U256::from(10_u128.pow(18))
+    );
+
+    assert_eq!(dave_balance, U256::from(10_u128.pow(18)));
+
+    Ok(())
+}
+
+impl MockWorld {
+    async fn init(api: &API) -> anyhow::Result<Self> {
+        let alice: AccountId32 = sp_keyring::AccountKeyring::Alice.to_account_id();
+        let dave: AccountId32 = sp_keyring::AccountKeyring::Dave.to_account_id();
+
+        let mut c = Contract::new("./contracts/ERC20.contract")?;
+
+        c.deploy(api, sp_keyring::AccountKeyring::Alice, 0, |_| {
+            let mut sel = hex::decode("5816c425").expect("unable to decode");
+            U256::from_dec_str("10000")
+                .map(|t| t * U256::from(10).pow(18_u8.into()))
+                .unwrap()
+                .encode_to(&mut sel);
+            sel
+        })
+        .await?;
+
+        Ok(Self {
+            alice,
+            dave,
+            token_addr: c.address.clone().unwrap(),
+            contract: c,
+        })
+    }
+}

+ 264 - 0
integration/subxt-tests/src/cases/uniswapv2_factory.rs

@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+use hex::FromHex;
+use parity_scale_codec::{Decode, DecodeAll, Encode, Input};
+use rand::Rng;
+use sp_core::{
+    crypto::{AccountId32, Ss58Codec},
+    hexdisplay::AsBytesRef,
+    keccak_256, U256,
+};
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+
+#[tokio::test]
+async fn setup() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    let rs = w
+        .factory
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("feeTo", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <AccountId32>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(
+        rs,
+        AccountId32::from_string("5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM")?
+    );
+
+    let rs = w
+        .factory
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("feeToSetter", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <AccountId32>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(rs, sp_keyring::AccountKeyring::Alice.to_account_id());
+
+    let rs = w
+        .factory
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("allPairsLength", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <u8>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(rs, 0);
+
+    Ok(())
+}
+
+#[tokio::test]
+async fn test_pair() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    w.factory
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "createPair",
+                    [
+                        format!(
+                            "0x{}",
+                            hex::encode(
+                                AccountId32::from_string(
+                                    "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUv7BA"
+                                )
+                                .unwrap()
+                            )
+                        ),
+                        format!(
+                            "0x{}",
+                            hex::encode(
+                                AccountId32::from_string(
+                                    "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyV1W6M"
+                                )
+                                .unwrap()
+                            )
+                        ),
+                    ],
+                )
+                .unwrap()
+            },
+        )
+        .await?;
+
+    Ok(())
+}
+
+#[tokio::test]
+async fn test_pair_reverse() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    w.factory
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "createPair",
+                    [
+                        format!(
+                            "0x{}",
+                            hex::encode(
+                                AccountId32::from_string(
+                                    "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyV1W6M"
+                                )
+                                .unwrap()
+                            )
+                        ),
+                        format!(
+                            "0x{}",
+                            hex::encode(
+                                AccountId32::from_string(
+                                    "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUv7BA"
+                                )
+                                .unwrap()
+                            )
+                        ),
+                    ],
+                )
+                .unwrap()
+            },
+        )
+        .await?;
+
+    Ok(())
+}
+
+#[tokio::test]
+async fn set_fee_to() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    w.factory
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "setFeeTo",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Dave.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await?;
+
+    let rs = w
+        .factory
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("feeTo", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <AccountId32>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(rs, sp_keyring::AccountKeyring::Dave.to_account_id());
+
+    Ok(())
+}
+
+#[tokio::test]
+async fn set_fee_to_setter() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    w.factory
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "setFeeToSetter",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Dave.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await?;
+
+    let rs = w
+        .factory
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("feeToSetter", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <AccountId32>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(rs, sp_keyring::AccountKeyring::Dave.to_account_id());
+
+    Ok(())
+}
+
+struct MockWorld {
+    factory: Contract,
+}
+
+impl MockWorld {
+    async fn init(api: &API) -> anyhow::Result<Self> {
+        let mut contract = Contract::new("./contracts/UniswapV2Factory.contract")?;
+
+        Contract::new("./contracts/UniswapV2Pair.contract")?
+            .upload_code(api, sp_keyring::AccountKeyring::Alice)
+            .await?;
+
+        contract
+            .deploy(
+                api,
+                sp_keyring::AccountKeyring::Alice,
+                10_u128.pow(16),
+                &|t: &ContractMessageTranscoder| {
+                    t.encode::<_, String>(
+                        "new",
+                        [format!(
+                            "0x{}",
+                            hex::encode(&sp_keyring::AccountKeyring::Alice.to_account_id())
+                        )],
+                    )
+                    .unwrap()
+                },
+            )
+            .await?;
+
+        Ok(Self { factory: contract })
+    }
+}

+ 941 - 0
integration/subxt-tests/src/cases/uniswapv2_pair.rs

@@ -0,0 +1,941 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::ops::Mul;
+
+use contract_transcode::ContractMessageTranscoder;
+use hex::FromHex;
+
+use parity_scale_codec::{Decode, DecodeAll, Encode, Input};
+use rand::Rng;
+use sp_core::{
+    crypto::{AccountId32, Ss58Codec},
+    hexdisplay::AsBytesRef,
+    keccak_256, U256,
+};
+
+use crate::{Contract, DeployContract, Execution, ReadContract, WriteContract, API};
+
+#[ignore = "trapped when transfer liquidity"]
+#[tokio::test]
+async fn mint() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    let min_liquidity: U256 = U256::from(1000_u32);
+
+    w.token_0
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                let mut s = keccak_256(b"transfer(address,uint)")[..4].to_vec();
+                w.pair.address.clone().unwrap().encode_to(&mut s);
+                U256::from(10_u8).pow(18_u8.into()).encode_to(&mut s);
+
+                s
+            },
+        )
+        .await?;
+
+    w.token_1
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                let mut s = keccak_256(b"transfer(address,uint)")[..4].to_vec();
+                w.pair.address.clone().unwrap().encode_to(&mut s);
+                U256::from(10_u8)
+                    .pow(18_u8.into())
+                    .mul(U256::from(4_u8))
+                    .encode_to(&mut s);
+
+                s
+            },
+        )
+        .await?;
+
+    let expected_liquidity = U256::from(10_u8).pow(18_u8.into()).mul(U256::from(2_u8));
+
+    w.pair
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "mint",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await?;
+
+    let total_supply = w
+        .pair
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("totalSupply", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(expected_liquidity, total_supply);
+
+    let balance = w
+        .pair
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(balance, expected_liquidity - min_liquidity);
+
+    let balance_0 = w
+        .token_0
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(w.pair.address.as_ref().unwrap())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(balance_0, U256::from(10_u8).pow(18_u8.into()));
+
+    let balance_1 = w
+        .token_1
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(w.pair.address.as_ref().unwrap())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(
+        balance_1,
+        U256::from(10_u8).pow(18_u8.into()).mul(U256::from(4_u8))
+    );
+
+    let (r0, r1, block) = w
+        .pair
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("getReserves", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <(u128, u128, u32)>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(U256::from(r0), U256::from(10_u8).pow(18_u8.into()));
+    assert_eq!(
+        U256::from(r1),
+        U256::from(10_u8).pow(18_u8.into()).mul(U256::from(4_u8))
+    );
+
+    Ok(())
+}
+
+#[ignore = "trapped when adding liquidity"]
+#[tokio::test]
+async fn swap_token0() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let token0_amount = U256::from(10_u8)
+        .pow(U256::from(18_u8))
+        .mul(U256::from(5_u8));
+
+    let token1_amount = U256::from(10_u8).pow(U256::from(19_u8));
+
+    let w = MockWorld::init(&api).await?;
+
+    let min_liquidity: U256 = U256::from(1000_u32);
+
+    w.add_liquitity(&api, &token0_amount, &token1_amount)
+        .await?;
+
+    let swap_amount = U256::from(10_u8).pow(18_u8.into());
+    let expected_output = U256::from_dec_str("1662497915624478906")?;
+
+    w.token_0
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                let mut s = t
+                    .encode::<_, String>(
+                        "transfer",
+                        [format!(
+                            "0x{}",
+                            hex::encode(w.pair.address.as_ref().unwrap())
+                        )],
+                    )
+                    .unwrap();
+
+                swap_amount.encode_to(&mut s);
+                s
+            },
+        )
+        .await?;
+
+    w.pair
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                let mut s = t.encode::<_, String>("swap", []).unwrap();
+
+                (
+                    U256::zero(),
+                    expected_output,
+                    sp_keyring::AccountKeyring::Alice.to_account_id(),
+                    "",
+                )
+                    .encode_to(&mut s);
+
+                s
+            },
+        )
+        .await?;
+
+    let out = w
+        .pair
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("getReserves", []).unwrap(),
+        )
+        .await
+        .and_then(|v| {
+            let t = &w.pair.transcoder;
+
+            <(u128, u128, u32)>::decode(&mut &v[..]).map_err(Into::into)
+        })?;
+
+    assert_eq!(U256::from(out.0), token0_amount + swap_amount);
+    assert_eq!(U256::from(out.1), token1_amount - expected_output);
+    let bal_0 = w
+        .token_0
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(w.pair.address.as_ref().unwrap())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(bal_0, token0_amount + swap_amount);
+
+    let bal_1 = w
+        .token_1
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(w.pair.address.as_ref().unwrap())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+    assert_eq!(bal_1, token1_amount - expected_output);
+
+    let supply_0 = w
+        .token_0
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("totalSupply", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    let supply_1 = w
+        .token_1
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("totalSupply", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    let wallet_balance_0 = w
+        .token_0
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    let wallet_balance_1 = w
+        .token_1
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(wallet_balance_0, supply_0 - token0_amount - swap_amount,);
+    assert_eq!(wallet_balance_1, supply_1 - token1_amount + expected_output);
+
+    Ok(())
+}
+
+#[ignore = "trapped when adding liquidity"]
+#[tokio::test]
+async fn swap_token1() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let token0_amount = U256::from(10_u8)
+        .pow(U256::from(18_u8))
+        .mul(U256::from(5_u8));
+
+    let token1_amount = U256::from(10_u8).pow(U256::from(19_u8));
+
+    let w = MockWorld::init(&api).await?;
+
+    let min_liquidity: U256 = U256::from(1000_u32);
+
+    w.add_liquitity(&api, &token0_amount, &token1_amount)
+        .await?;
+
+    let swap_amount = U256::from(10_u8).pow(18_u8.into());
+    let expected_output = U256::from_dec_str("453305446940074565")?;
+
+    w.token_1
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                let mut s = t
+                    .encode::<_, String>(
+                        "transfer",
+                        [format!(
+                            "0x{}",
+                            hex::encode(w.pair.address.as_ref().unwrap())
+                        )],
+                    )
+                    .unwrap();
+
+                swap_amount.encode_to(&mut s);
+                s
+            },
+        )
+        .await?;
+
+    w.pair
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                let mut s = t.encode::<_, String>("swap", []).unwrap();
+
+                (
+                    expected_output,
+                    U256::zero(),
+                    sp_keyring::AccountKeyring::Alice.to_account_id(),
+                    "",
+                )
+                    .encode_to(&mut s);
+
+                s
+            },
+        )
+        .await?;
+
+    let out = w
+        .pair
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("getReserves", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <(u128, u128, u32)>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(U256::from(out.0), token0_amount - expected_output);
+    assert_eq!(U256::from(out.1), token1_amount + swap_amount);
+    let bal_0 = w
+        .token_0
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(w.pair.address.as_ref().unwrap())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(bal_0, token0_amount - expected_output);
+
+    let bal_1 = w
+        .token_1
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(w.pair.address.as_ref().unwrap())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+    assert_eq!(bal_1, token1_amount + swap_amount);
+
+    let supply_0 = w
+        .token_0
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("totalSupply", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    let supply_1 = w
+        .token_1
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("totalSupply", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    let wallet_balance_0 = w
+        .token_0
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    let wallet_balance_1 = w
+        .token_1
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(wallet_balance_0, supply_0 - token0_amount + expected_output);
+    assert_eq!(wallet_balance_1, supply_1 - token1_amount - swap_amount);
+
+    Ok(())
+}
+
+#[ignore = "trapped when adding liquidity"]
+#[tokio::test]
+async fn burn() -> anyhow::Result<()> {
+    let api = API::new().await?;
+
+    let w = MockWorld::init(&api).await?;
+
+    let token_amount = U256::from(10_u8).pow(18_u8.into()).mul(U256::from(3_u8));
+
+    w.add_liquitity(&api, &token_amount, &token_amount).await?;
+
+    w.pair
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                let mut s = t
+                    .encode::<_, String>(
+                        "transfer",
+                        [format!(
+                            "0x{}",
+                            hex::encode(w.pair.address.as_ref().unwrap())
+                        )],
+                    )
+                    .unwrap();
+
+                (token_amount - U256::from(1000_u32)).encode_to(&mut s);
+                s
+            },
+        )
+        .await?;
+
+    w.pair
+        .call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "burn",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await?;
+
+    let wallet_balance_0 = w
+        .pair
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(wallet_balance_0, U256::zero());
+
+    let pair_supply = w
+        .pair
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("totalSupply", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(pair_supply, U256::from(1000_u32));
+
+    let pair_balance_0 = w
+        .token_0
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(w.pair.address.as_ref().unwrap())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(pair_balance_0, U256::from(1000_u32));
+
+    let pair_balance_1 = w
+        .token_1
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(w.pair.address.as_ref().unwrap())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(pair_balance_1, U256::from(1000_u32));
+
+    let supply_0 = w
+        .token_0
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("totalSupply", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    let supply_1 = w
+        .token_1
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| t.encode::<_, String>("totalSupply", []).unwrap(),
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    let wallet_balance_0 = w
+        .token_0
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    let wallet_balance_1 = w
+        .token_1
+        .try_call(
+            &api,
+            sp_keyring::AccountKeyring::Alice,
+            0,
+            &|t: &ContractMessageTranscoder| {
+                t.encode::<_, String>(
+                    "balanceOf",
+                    [format!(
+                        "0x{}",
+                        hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id())
+                    )],
+                )
+                .unwrap()
+            },
+        )
+        .await
+        .and_then(|v| <U256>::decode(&mut &v[..]).map_err(Into::into))?;
+
+    assert_eq!(wallet_balance_0, supply_0 - U256::from(1000_u32));
+    assert_eq!(wallet_balance_1, supply_1 - U256::from(1000_u32));
+
+    Ok(())
+}
+struct MockWorld {
+    factory: Contract,
+    pair: Contract,
+    token_0: Contract,
+    token_1: Contract,
+}
+
+impl MockWorld {
+    async fn init(api: &API) -> anyhow::Result<Self> {
+        let mut factory = Contract::new("./contracts/UniswapV2Factory.contract")?;
+
+        factory
+            .deploy(
+                api,
+                sp_keyring::AccountKeyring::Alice,
+                10_u128.pow(16),
+                &|t: &ContractMessageTranscoder| {
+                    t.encode::<_, String>(
+                        "new",
+                        [format!(
+                            "0x{}",
+                            hex::encode(&sp_keyring::AccountKeyring::Alice.to_account_id())
+                        )],
+                    )
+                    .unwrap()
+                },
+            )
+            .await?;
+
+        let mut pair = Contract::new("./contracts/UniswapV2Pair.contract")?;
+
+        factory
+            .upload_code(api, sp_keyring::AccountKeyring::Alice)
+            .await?;
+
+        let mut token_a = Contract::new("./contracts/UniswapV2ERC20.contract")?;
+        token_a
+            .deploy(
+                api,
+                sp_keyring::AccountKeyring::Alice,
+                0,
+                &|t: &ContractMessageTranscoder| {
+                    let mut selector = t.encode::<_, String>("new", []).unwrap();
+
+                    U256::from(10_u8).pow(22_u8.into()).encode_to(&mut selector);
+
+                    selector
+                },
+            )
+            .await?;
+
+        let mut token_b = Contract::new("./contracts/UniswapV2ERC20.contract")?;
+        token_b
+            .deploy(
+                api,
+                sp_keyring::AccountKeyring::Alice,
+                0,
+                &|t: &ContractMessageTranscoder| {
+                    let mut selector = t.encode::<_, String>("new", []).unwrap();
+
+                    U256::from(10_u8).pow(22_u8.into()).encode_to(&mut selector);
+
+                    selector
+                },
+            )
+            .await?;
+
+        factory
+            .call(
+                api,
+                sp_keyring::AccountKeyring::Alice,
+                0,
+                &|t: &ContractMessageTranscoder| {
+                    t.encode::<_, String>(
+                        "createPair",
+                        [
+                            format!("0x{}", hex::encode(token_a.address.as_ref().unwrap())),
+                            format!("0x{}", hex::encode(token_b.address.as_ref().unwrap())),
+                        ],
+                    )
+                    .unwrap()
+                },
+            )
+            .await?;
+
+        let pair_addr = factory
+            .try_call(
+                api,
+                sp_keyring::AccountKeyring::Alice,
+                0,
+                &|t: &ContractMessageTranscoder| {
+                    t.encode::<_, String>(
+                        "getPair",
+                        [
+                            format!("0x{}", hex::encode(token_a.address.as_ref().unwrap())),
+                            format!("0x{}", hex::encode(token_b.address.as_ref().unwrap())),
+                        ],
+                    )
+                    .unwrap()
+                },
+            )
+            .await
+            .and_then(|v| <AccountId32>::decode(&mut &v[..]).map_err(Into::into))?;
+
+        pair = pair.from_addr(pair_addr)?;
+
+        let token_0_addr = pair
+            .try_call(
+                api,
+                sp_keyring::AccountKeyring::Alice,
+                0,
+                &|t: &ContractMessageTranscoder| t.encode::<_, String>("token0", []).unwrap(),
+            )
+            .await
+            .and_then(|v| <AccountId32>::decode(&mut &v[..]).map_err(Into::into))?;
+
+        let (token_0, token_1) = if *token_a.address.as_ref().unwrap() == token_0_addr {
+            (token_a, token_b)
+        } else {
+            (token_b, token_a)
+        };
+
+        Ok(Self {
+            factory,
+            pair,
+            token_0,
+            token_1,
+        })
+    }
+
+    async fn add_liquitity(
+        &self,
+        api: &API,
+        amount_a: &U256,
+        amount_b: &U256,
+    ) -> anyhow::Result<()> {
+        self.token_0
+            .call(
+                api,
+                sp_keyring::AccountKeyring::Alice,
+                0,
+                &|t: &ContractMessageTranscoder| {
+                    let mut s = keccak_256(b"transfer(address,uint)")[..4].to_vec();
+                    self.pair.address.clone().unwrap().encode_to(&mut s);
+                    amount_a.encode_to(&mut s);
+                    s
+                },
+            )
+            .await?;
+
+        self.token_1
+            .call(
+                api,
+                sp_keyring::AccountKeyring::Alice,
+                0,
+                &|t: &ContractMessageTranscoder| {
+                    let mut s = keccak_256(b"transfer(address,uint)")[..4].to_vec();
+                    self.pair.address.clone().unwrap().encode_to(&mut s);
+                    amount_b.encode_to(&mut s);
+                    s
+                },
+            )
+            .await?;
+
+        self.pair
+            .call(
+                api,
+                sp_keyring::AccountKeyring::Alice,
+                0,
+                &|t: &ContractMessageTranscoder| {
+                    let mut s = keccak_256(b"mint(address)")[..4].to_vec();
+                    sp_keyring::AccountKeyring::Alice
+                        .to_account_id()
+                        .encode_to(&mut s);
+
+                    s
+                },
+            )
+            .await?;
+
+        Ok(())
+    }
+}

+ 532 - 0
integration/subxt-tests/src/lib.rs

@@ -0,0 +1,532 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use contract_transcode::ContractMessageTranscoder;
+
+use node::runtime_types::pallet_contracts::wasm::Determinism;
+use pallet_contracts_primitives::{
+    ContractExecResult, ContractResult, ExecReturnValue, GetStorageResult,
+};
+use parity_scale_codec::{Decode, Encode};
+
+use sp_core::{crypto::AccountId32, hexdisplay::AsBytesRef, Bytes};
+use sp_weights::Weight;
+use subxt::{
+    blocks::ExtrinsicEvents as TxEvents,
+    ext::sp_runtime::DispatchError,
+    tx::PairSigner,
+    utils::{MultiAddress, Static},
+    Config, OnlineClient, PolkadotConfig,
+};
+
+use contract_metadata::ContractMetadata;
+use sp_keyring::AccountKeyring;
+use tokio::time::timeout;
+
+mod cases;
+
+// metadata file obtained from the latest substrate-contracts-node
+#[subxt::subxt(
+    runtime_metadata_url = "ws://localhost:9944",
+    substitute_type(
+        type = "sp_weights::weight_v2::Weight",
+        with = "::subxt::utils::Static<::sp_weights::Weight>"
+    )
+)]
+pub mod node {}
+
+pub type API = OnlineClient<PolkadotConfig>;
+
+pub struct DeployContract {
+    pub caller: AccountKeyring,
+    pub selector: Vec<u8>,
+    pub value: u128,
+    pub code: Vec<u8>,
+}
+pub struct WriteContract {
+    pub caller: AccountKeyring,
+    pub contract_address: AccountId32,
+    pub selector: Vec<u8>,
+    pub value: u128,
+}
+pub struct ReadContract {
+    pub caller: AccountKeyring,
+    pub contract_address: AccountId32,
+    pub value: u128,
+    pub selector: Vec<u8>,
+}
+
+pub struct ReadLayout {
+    pub contract_address: AccountId32,
+    pub key: Vec<u8>,
+}
+
+#[async_trait::async_trait]
+trait Execution {
+    type Output;
+
+    async fn execute(self, api: &API) -> Result<Self::Output, anyhow::Error>;
+}
+
+pub mod output {
+    use super::*;
+    pub struct Deployed {
+        pub contract_address: AccountId32,
+        pub events: Vec<node::contracts::events::ContractEmitted>,
+    }
+    pub struct WriteSuccess {
+        pub events: Vec<node::contracts::events::ContractEmitted>,
+    }
+    pub struct ReadSuccess {
+        pub return_value: Vec<u8>,
+    }
+}
+
+const GAS_LIMIT: u64 = 2 * 10_u64.pow(11);
+
+fn random_salt() -> Vec<u8> {
+    let random_u8 = rand::random::<[u8; 32]>();
+    Bytes::from(random_u8.to_vec()).encode()
+}
+
+#[async_trait::async_trait]
+impl Execution for DeployContract {
+    type Output = output::Deployed;
+
+    async fn execute(self, api: &API) -> Result<Self::Output, anyhow::Error> {
+        let Self {
+            caller,
+            selector,
+            code,
+            value,
+        } = self;
+
+        let evts = raw_instantiate_and_upload(
+            api,
+            caller,
+            value,
+            GAS_LIMIT,
+            None,
+            code,
+            selector,
+            random_salt(),
+        )
+        .await?;
+
+        let contract_address = evts
+            .iter()
+            .find_map(|e| {
+                e.ok()
+                    .and_then(|i| i.as_event::<node::contracts::events::Instantiated>().ok())
+                    .flatten()
+                    .map(|i| i.contract)
+                    .and_then(|c| <_ as Decode>::decode(&mut c.encode().as_bytes_ref()).ok())
+            })
+            .ok_or_else(|| anyhow::anyhow!("unable to find deployed"))?;
+
+        let events = evts
+            .iter()
+            .filter_map(|e| {
+                e.ok()
+                    .and_then(|v| {
+                        v.as_event::<node::contracts::events::ContractEmitted>()
+                            .ok()
+                    })
+                    .flatten()
+            })
+            .collect::<Vec<_>>();
+
+        Ok(output::Deployed {
+            contract_address,
+            events,
+        })
+    }
+}
+
+#[async_trait::async_trait]
+impl Execution for WriteContract {
+    type Output = output::WriteSuccess;
+
+    async fn execute(self, api: &API) -> Result<Self::Output, anyhow::Error> {
+        let Self {
+            caller,
+            contract_address,
+            selector,
+            value,
+        } = self;
+
+        let evts = raw_call(
+            api,
+            contract_address,
+            caller,
+            value,
+            GAS_LIMIT,
+            None,
+            selector,
+        )
+        .await?;
+
+        if let Some(e) = evts.iter().filter_map(|e| e.ok()).find_map(|e| {
+            e.as_event::<node::system::events::ExtrinsicFailed>()
+                .ok()
+                .flatten()
+        }) {
+            if let node::runtime_types::sp_runtime::DispatchError::Module(e) = &e.dispatch_error {
+                if let Ok(details) = api.metadata().error(e.index, e.error[0]) {
+                    return Err(anyhow::anyhow!("{details:?}"));
+                }
+            }
+
+            return Err(anyhow::anyhow!("{e:?}"));
+        }
+
+        let events = evts
+            .iter()
+            .filter_map(|e| {
+                e.ok()
+                    .and_then(|v| {
+                        v.as_event::<node::contracts::events::ContractEmitted>()
+                            .ok()
+                    })
+                    .flatten()
+            })
+            .collect::<Vec<_>>();
+
+        Ok(output::WriteSuccess { events })
+    }
+}
+
+#[async_trait::async_trait]
+impl Execution for ReadContract {
+    type Output = output::ReadSuccess;
+
+    async fn execute(self, api: &API) -> Result<Self::Output, anyhow::Error> {
+        let Self {
+            caller,
+            contract_address,
+            selector,
+            value,
+        } = self;
+
+        let rv = read_call(api, caller, contract_address, value, selector).await?;
+
+        if rv
+            .result
+            .as_ref()
+            .map(|v| v.did_revert())
+            .unwrap_or_else(|_| false)
+        {
+            Err(anyhow::anyhow!("{:?}", rv.debug_message))
+        } else {
+            Ok(output::ReadSuccess {
+                return_value: rv.result.map(|v| v.data.to_vec()).unwrap_or_default(),
+            })
+        }
+    }
+}
+
+#[async_trait::async_trait]
+impl Execution for ReadLayout {
+    type Output = GetStorageResult;
+
+    async fn execute(self, api: &API) -> Result<Self::Output, anyhow::Error> {
+        let ReadLayout {
+            contract_address,
+            key,
+        } = self;
+
+        query_call(api, contract_address, key).await
+    }
+}
+
+#[derive(Encode)]
+pub struct CallRequest {
+    origin: <PolkadotConfig as Config>::AccountId,
+    dest: <PolkadotConfig as Config>::AccountId,
+    value: u128,
+    gas_limit: Option<Weight>,
+    storage_deposit_limit: Option<u128>,
+    input_data: Vec<u8>,
+}
+
+async fn raw_instantiate_and_upload(
+    api: &API,
+    builtin_keyring: sp_keyring::AccountKeyring,
+    value: u128,
+    gas_limit: u64,
+    storage_deposit_limit: Option<u128>,
+    code: Vec<u8>,
+    data: Vec<u8>,
+    salt: Vec<u8>,
+) -> anyhow::Result<TxEvents<PolkadotConfig>> {
+    let signer = PairSigner::new(builtin_keyring.pair());
+
+    let payload = node::tx().contracts().instantiate_with_code(
+        value,
+        Static::from(sp_weights::Weight::from(gas_limit)),
+        storage_deposit_limit.map(Into::into),
+        code,
+        data,
+        salt,
+    );
+
+    let evt = api
+        .tx()
+        .sign_and_submit_then_watch_default(&payload, &signer)
+        .await?
+        .wait_for_in_block()
+        .await?
+        .fetch_events()
+        .await?;
+
+    Ok(evt)
+}
+
+async fn raw_upload(
+    api: &API,
+    builtin_keyring: sp_keyring::AccountKeyring,
+    storage_deposit_limit: Option<u128>,
+    code: Vec<u8>,
+) -> anyhow::Result<TxEvents<PolkadotConfig>> {
+    let signer = PairSigner::new(builtin_keyring.pair());
+
+    let payload = node::tx().contracts().upload_code(
+        code,
+        storage_deposit_limit.map(Into::into),
+        Determinism::Enforced,
+    );
+
+    let evt = api
+        .tx()
+        .sign_and_submit_then_watch_default(&payload, &signer)
+        .await?
+        .wait_for_in_block()
+        .await?
+        .fetch_events()
+        .await?;
+
+    Ok(evt)
+}
+
+const TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
+
+async fn raw_call(
+    api: &API,
+    dest: AccountId32,
+    builtin_keyring: sp_keyring::AccountKeyring,
+    value: u128,
+    gas_limit: u64,
+    storage_deposit_limit: Option<u128>,
+    data: Vec<u8>,
+) -> anyhow::Result<TxEvents<PolkadotConfig>> {
+    let signer = PairSigner::new(builtin_keyring.pair());
+
+    let payload = node::tx().contracts().call(
+        MultiAddress::Id(<_ as Decode>::decode(&mut dest.encode().as_bytes_ref())?),
+        value,
+        Static::from(sp_weights::Weight::from(gas_limit)),
+        storage_deposit_limit.map(Into::into),
+        data,
+    );
+
+    let evt = timeout(
+        TIMEOUT,
+        api.tx()
+            .sign_and_submit_then_watch_default(&payload, &signer)
+            .await?
+            .wait_for_in_block()
+            .await?
+            .fetch_events(),
+    )
+    .await??;
+
+    Ok(evt)
+}
+
+async fn query_call(
+    api: &API,
+    contract_address: AccountId32,
+    key: Vec<u8>,
+) -> anyhow::Result<GetStorageResult> {
+    let rv = api
+        .rpc()
+        .state_call(
+            "ContractsApi_get_storage",
+            Some((contract_address, key).encode().as_bytes_ref()),
+            None,
+        )
+        .await?;
+
+    <GetStorageResult>::decode(&mut rv.as_bytes_ref()).map_err(|e| anyhow::anyhow!("{e:?}"))
+}
+
+async fn read_call(
+    api: &API,
+    caller: AccountKeyring,
+    contract_address: AccountId32,
+    value: u128,
+    selector: Vec<u8>,
+) -> anyhow::Result<ContractExecResult<u128>> {
+    let req = CallRequest {
+        origin: <subxt::utils::AccountId32 as Decode>::decode(&mut caller.encode().as_bytes_ref())?,
+        dest: <_ as Decode>::decode(&mut contract_address.encode().as_bytes_ref())?,
+        value,
+        gas_limit: Some(Weight::from(GAS_LIMIT)),
+        storage_deposit_limit: None,
+        input_data: selector,
+    };
+
+    let rv = api
+        .rpc()
+        .state_call("ContractsApi_call", Some(req.encode().as_bytes_ref()), None)
+        .await?;
+
+    let rv = ContractResult::<Result<ExecReturnValue, DispatchError>, u128>::decode(
+        &mut rv.as_bytes_ref(),
+    )?;
+
+    Ok(rv)
+}
+
+pub async fn free_balance_of(api: &API, addr: AccountId32) -> anyhow::Result<u128> {
+    let key = node::storage()
+        .system()
+        .account(<subxt::utils::AccountId32 as Decode>::decode(
+            &mut addr.encode().as_bytes_ref(),
+        )?);
+
+    let val = api
+        .storage()
+        .at_latest()
+        .await?
+        .fetch_or_default(&key)
+        .await?;
+
+    Ok(val.data.free)
+}
+
+struct Contract {
+    path: &'static str,
+    transcoder: ContractMessageTranscoder,
+    blob: Vec<u8>,
+    address: Option<AccountId32>,
+}
+
+impl Contract {
+    pub fn new(path: &'static str) -> anyhow::Result<Self> {
+        let contract = ContractMetadata::load(path)?;
+        let transcoder = ContractMessageTranscoder::load(path)?;
+        let blob = contract
+            .source
+            .wasm
+            .map(|v| v.0)
+            .expect("unable to find wasm blob");
+
+        Ok(Self {
+            path,
+            transcoder,
+            blob,
+            address: None,
+        })
+    }
+
+    pub fn from_addr(&self, address: AccountId32) -> anyhow::Result<Self> {
+        let mut out = Contract::new(self.path)?;
+
+        out.address.replace(address);
+
+        Ok(out)
+    }
+
+    pub async fn upload_code(
+        &self,
+        api: &API,
+        caller: sp_keyring::AccountKeyring,
+    ) -> anyhow::Result<()> {
+        raw_upload(api, caller, None, self.blob.clone()).await?;
+
+        Ok(())
+    }
+
+    pub async fn deploy(
+        &mut self,
+        api: &API,
+        caller: sp_keyring::AccountKeyring,
+        value: u128,
+        build_selector: impl Fn(&ContractMessageTranscoder) -> Vec<u8>,
+    ) -> anyhow::Result<Vec<node::contracts::events::ContractEmitted>> {
+        let transcoder = &self.transcoder;
+
+        let selector = build_selector(transcoder);
+
+        let deployed = DeployContract {
+            caller,
+            selector,
+            value,
+            code: self.blob.clone(),
+        }
+        .execute(api)
+        .await?;
+        let addr = deployed.contract_address;
+
+        self.address.replace(addr.clone());
+
+        Ok(deployed.events)
+    }
+
+    pub async fn call(
+        &self,
+        api: &API,
+        caller: sp_keyring::AccountKeyring,
+        value: u128,
+        build_selector: impl Fn(&ContractMessageTranscoder) -> Vec<u8>,
+    ) -> anyhow::Result<Vec<node::contracts::events::ContractEmitted>> {
+        let transcoder = &self.transcoder;
+
+        let selector = build_selector(transcoder);
+
+        let out = WriteContract {
+            caller,
+            selector,
+            value,
+            contract_address: self.address.clone().unwrap(),
+        }
+        .execute(api)
+        .await?;
+
+        Ok(out.events)
+    }
+
+    pub async fn try_call(
+        &self,
+        api: &API,
+        caller: sp_keyring::AccountKeyring,
+        value: u128,
+        build_selector: impl Fn(&ContractMessageTranscoder) -> Vec<u8>,
+    ) -> anyhow::Result<Vec<u8>> {
+        let transcoder = &self.transcoder;
+        let selector = build_selector(transcoder);
+
+        let out = ReadContract {
+            caller,
+            selector,
+            value,
+            contract_address: self.address.clone().unwrap(),
+        }
+        .execute(api)
+        .await?;
+
+        Ok(out.return_value)
+    }
+
+    pub async fn read_storage(&self, api: &API, key: Vec<u8>) -> anyhow::Result<Option<Vec<u8>>> {
+        let out = ReadLayout {
+            contract_address: self.address.clone().unwrap(),
+            key,
+        }
+        .execute(api)
+        .await?
+        .map_err(|e| anyhow::anyhow!("{e:?}"))?;
+
+        Ok(out)
+    }
+}