From e8109b660feae28459c905bef1626b96308c1bc0 Mon Sep 17 00:00:00 2001 From: Raymond Yeh Date: Wed, 31 May 2023 11:40:00 +0000 Subject: [PATCH] test(subxt-tests): integration test for substrate using subxt Signed-off-by: Raymond Yeh --- .github/workflows/test.yml | 43 +- .gitignore | 1 + .vscode/settings.json | 5 + integration/substrate/createpair.sol | 12 + integration/subxt-tests/.gitignore | 2 + integration/subxt-tests/Cargo.toml | 27 + integration/subxt-tests/metadata.scale | Bin 0 -> 77765 bytes .../src/cases/array_struct_mapping_storage.rs | 135 +++ integration/subxt-tests/src/cases/arrays.rs | 105 ++ integration/subxt-tests/src/cases/asserts.rs | 79 ++ integration/subxt-tests/src/cases/balances.rs | 101 ++ integration/subxt-tests/src/cases/builtins.rs | 93 ++ .../subxt-tests/src/cases/builtins2.rs | 134 +++ .../subxt-tests/src/cases/create_contract.rs | 83 ++ integration/subxt-tests/src/cases/destruct.rs | 72 ++ integration/subxt-tests/src/cases/events.rs | 71 ++ .../subxt-tests/src/cases/external_call.rs | 207 ++++ integration/subxt-tests/src/cases/flipper.rs | 62 ++ integration/subxt-tests/src/cases/issue666.rs | 44 + integration/subxt-tests/src/cases/mod.rs | 21 + .../subxt-tests/src/cases/msg_sender.rs | 97 ++ .../subxt-tests/src/cases/primitives.rs | 695 +++++++++++++ .../subxt-tests/src/cases/randomizer.rs | 68 ++ integration/subxt-tests/src/cases/store.rs | 267 +++++ integration/subxt-tests/src/cases/structs.rs | 230 +++++ .../subxt-tests/src/cases/uniswapv2_erc20.rs | 377 +++++++ .../src/cases/uniswapv2_factory.rs | 262 +++++ .../subxt-tests/src/cases/uniswapv2_pair.rs | 970 ++++++++++++++++++ integration/subxt-tests/src/lib.rs | 548 ++++++++++ 29 files changed, 4810 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json create mode 100644 integration/substrate/createpair.sol create mode 100644 integration/subxt-tests/.gitignore create mode 100644 integration/subxt-tests/Cargo.toml create mode 100644 integration/subxt-tests/metadata.scale create mode 100644 integration/subxt-tests/src/cases/array_struct_mapping_storage.rs create mode 100644 integration/subxt-tests/src/cases/arrays.rs create mode 100644 integration/subxt-tests/src/cases/asserts.rs create mode 100644 integration/subxt-tests/src/cases/balances.rs create mode 100644 integration/subxt-tests/src/cases/builtins.rs create mode 100644 integration/subxt-tests/src/cases/builtins2.rs create mode 100644 integration/subxt-tests/src/cases/create_contract.rs create mode 100644 integration/subxt-tests/src/cases/destruct.rs create mode 100644 integration/subxt-tests/src/cases/events.rs create mode 100644 integration/subxt-tests/src/cases/external_call.rs create mode 100644 integration/subxt-tests/src/cases/flipper.rs create mode 100644 integration/subxt-tests/src/cases/issue666.rs create mode 100644 integration/subxt-tests/src/cases/mod.rs create mode 100644 integration/subxt-tests/src/cases/msg_sender.rs create mode 100644 integration/subxt-tests/src/cases/primitives.rs create mode 100644 integration/subxt-tests/src/cases/randomizer.rs create mode 100644 integration/subxt-tests/src/cases/store.rs create mode 100644 integration/subxt-tests/src/cases/structs.rs create mode 100644 integration/subxt-tests/src/cases/uniswapv2_erc20.rs create mode 100644 integration/subxt-tests/src/cases/uniswapv2_factory.rs create mode 100644 integration/subxt-tests/src/cases/uniswapv2_pair.rs create mode 100644 integration/subxt-tests/src/lib.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f4eae35c0..b95fb4eba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,10 @@ on: pull_request: workflow_dispatch: +# FIXME: currently needed to bypass a transient dependency using nightly feature +env: + RUSTC_BOOTSTRAP: 1 + jobs: repolinter: name: Repolinter @@ -86,7 +90,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 +352,42 @@ jobs: if: always() run: docker kill ${{steps.substrate.outputs.id}} + substrate-subxt: + name: Substrate Integration test with subxt + runs-on: ubuntu-20.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 paritytech/contracts-ci-linux@sha256:77a885f3c22d38385b017983c942bf7e063ac127bdc0477670d23a38711a2c38 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 -o ./contracts/ + working-directory: ./integration/subxt-tests + - run: solang compile --target substrate ../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 diff --git a/.gitignore b/.gitignore index 5ee181b8d..7f33470a8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Cargo.lock **/*.rs.bk bundle.ll +.helix/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..04a6b28fe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.linkedProjects": [ + "./integration/subxt-tests/Cargo.toml" + ] +} \ No newline at end of file diff --git a/integration/substrate/createpair.sol b/integration/substrate/createpair.sol new file mode 100644 index 000000000..37dd634e6 --- /dev/null +++ b/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()); + } + +} + diff --git a/integration/subxt-tests/.gitignore b/integration/subxt-tests/.gitignore new file mode 100644 index 000000000..9974c5ab0 --- /dev/null +++ b/integration/subxt-tests/.gitignore @@ -0,0 +1,2 @@ +contracts/ +target/ diff --git a/integration/subxt-tests/Cargo.toml b/integration/subxt-tests/Cargo.toml new file mode 100644 index 000000000..0b6584395 --- /dev/null +++ b/integration/subxt-tests/Cargo.toml @@ -0,0 +1,27 @@ +[package] +edition = "2021" +name = "subxt-tests" +version = "0.1.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 = [] + diff --git a/integration/subxt-tests/metadata.scale b/integration/subxt-tests/metadata.scale new file mode 100644 index 0000000000000000000000000000000000000000..f99acef484cc7c76f3119b8a83ec189b418088d0 GIT binary patch literal 77765 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

nISok_k3QSu1q$VuKR+ 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 K%Y`tx82$k{f8!1S literal 0 HcmV?d00001 diff --git a/integration/subxt-tests/src/cases/array_struct_mapping_storage.rs b/integration/subxt-tests/src/cases/array_struct_mapping_storage.rs new file mode 100644 index 000000000..769c32a6d --- /dev/null +++ b/integration/subxt-tests/src/cases/array_struct_mapping_storage.rs @@ -0,0 +1,135 @@ +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| ::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| ::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| ::decode(&mut v.as_bytes_ref()).map_err(Into::into))?; + + assert_eq!(rs, 2147483647); + + Ok(()) +} diff --git a/integration/subxt-tests/src/cases/arrays.rs b/integration/subxt-tests/src/cases/arrays.rs new file mode 100644 index 000000000..6d001e34a --- /dev/null +++ b/integration/subxt-tests/src/cases/arrays.rs @@ -0,0 +1,105 @@ +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] +#[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::::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)>::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 = ::decode(&mut output.as_bytes_ref())?; + assert!(has_permission); + } + + Ok(()) +} diff --git a/integration/subxt-tests/src/cases/asserts.rs b/integration/subxt-tests/src/cases/asserts.rs new file mode 100644 index 000000000..43ef78443 --- /dev/null +++ b/integration/subxt-tests/src/cases/asserts.rs @@ -0,0 +1,79 @@ +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(()) +} diff --git a/integration/subxt-tests/src/cases/balances.rs b/integration/subxt-tests/src/cases/balances.rs new file mode 100644 index 000000000..bb5229eba --- /dev/null +++ b/integration/subxt-tests/src/cases/balances.rs @@ -0,0 +1,101 @@ +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}; + +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 = ::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| { + t.encode::<_, String>( + "transfer", + [ + format!("{:?}", dave.to_raw_public()), + format!("{}", 20000_u128), + ], + ) + .unwrap() + }, + ) + .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| { + t.encode::<_, String>( + "send", + [ + format!("{:?}", dave.to_raw_public()), + format!("{}", 10000_u128), + ], + ) + .unwrap() + }, + ) + .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(()) +} diff --git a/integration/subxt-tests/src/cases/builtins.rs b/integration/subxt-tests/src/cases/builtins.rs new file mode 100644 index 000000000..b16ea5172 --- /dev/null +++ b/integration/subxt-tests/src/cases/builtins.rs @@ -0,0 +1,93 @@ +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(()) +} diff --git a/integration/subxt-tests/src/cases/builtins2.rs b/integration/subxt-tests/src/cases/builtins2.rs new file mode 100644 index 000000000..c786b4594 --- /dev/null +++ b/integration/subxt-tests/src/cases/builtins2.rs @@ -0,0 +1,134 @@ +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(()) +} diff --git a/integration/subxt-tests/src/cases/create_contract.rs b/integration/subxt-tests/src/cases/create_contract.rs new file mode 100644 index 000000000..2463688f2 --- /dev/null +++ b/integration/subxt-tests/src/cases/create_contract.rs @@ -0,0 +1,83 @@ +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| ::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(()) +} diff --git a/integration/subxt-tests/src/cases/destruct.rs b/integration/subxt-tests/src/cases/destruct.rs new file mode 100644 index 000000000..d59cedeb1 --- /dev/null +++ b/integration/subxt-tests/src/cases/destruct.rs @@ -0,0 +1,72 @@ +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| ::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(()) +} diff --git a/integration/subxt-tests/src/cases/events.rs b/integration/subxt-tests/src/cases/events.rs new file mode 100644 index 000000000..ae2ab05b1 --- /dev/null +++ b/integration/subxt-tests/src/cases/events.rs @@ -0,0 +1,71 @@ +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(()) +} diff --git a/integration/subxt-tests/src/cases/external_call.rs b/integration/subxt-tests/src/cases/external_call.rs new file mode 100644 index 000000000..b06e557f8 --- /dev/null +++ b/integration/subxt-tests/src/cases/external_call.rs @@ -0,0 +1,207 @@ +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")])?; + + // let selector = build_selector( + // "250c2025", + // Some(&mut |s| { + // 102_i64.encode_to(s); + // }), + // )?; + + 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| ::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| ::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| ::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| ::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(()) +} diff --git a/integration/subxt-tests/src/cases/flipper.rs b/integration/subxt-tests/src/cases/flipper.rs new file mode 100644 index 000000000..f4029a7f5 --- /dev/null +++ b/integration/subxt-tests/src/cases/flipper.rs @@ -0,0 +1,62 @@ +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| ::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| ::decode(&mut v.as_bytes_ref()).map_err(Into::into))?; + + assert!(!updated); + + Ok(()) +} diff --git a/integration/subxt-tests/src/cases/issue666.rs b/integration/subxt-tests/src/cases/issue666.rs new file mode 100644 index 000000000..50c46c9d4 --- /dev/null +++ b/integration/subxt-tests/src/cases/issue666.rs @@ -0,0 +1,44 @@ +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(()) +} diff --git a/integration/subxt-tests/src/cases/mod.rs b/integration/subxt-tests/src/cases/mod.rs new file mode 100644 index 000000000..a3e28aee4 --- /dev/null +++ b/integration/subxt-tests/src/cases/mod.rs @@ -0,0 +1,21 @@ +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; diff --git a/integration/subxt-tests/src/cases/msg_sender.rs b/integration/subxt-tests/src/cases/msg_sender.rs new file mode 100644 index 000000000..6fd1ef1b7 --- /dev/null +++ b/integration/subxt-tests/src/cases/msg_sender.rs @@ -0,0 +1,97 @@ +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| ::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 = ::decode(&mut evt_buffer)?; + + assert_eq!(addr, sp_keyring::AccountKeyring::Alice.to_account_id()); + + Ok(()) +} diff --git a/integration/subxt-tests/src/cases/primitives.rs b/integration/subxt-tests/src/cases/primitives.rs new file mode 100644 index 000000000..99a7339a9 --- /dev/null +++ b/integration/subxt-tests/src/cases/primitives.rs @@ -0,0 +1,695 @@ +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(api: &API, addr: &AccountId32, selector: &[u8]) -> anyhow::Result { + 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 don'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(()) +} diff --git a/integration/subxt-tests/src/cases/randomizer.rs b/integration/subxt-tests/src/cases/randomizer.rs new file mode 100644 index 000000000..37a6a3c08 --- /dev/null +++ b/integration/subxt-tests/src/cases/randomizer.rs @@ -0,0 +1,68 @@ +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] +#[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(()) +} diff --git a/integration/subxt-tests/src/cases/store.rs b/integration/subxt-tests/src/cases/store.rs new file mode 100644 index 000000000..425ff4c22 --- /dev/null +++ b/integration/subxt-tests/src/cases/store.rs @@ -0,0 +1,267 @@ +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; 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; 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; 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| >::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::()); + + 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(()) +} diff --git a/integration/subxt-tests/src/cases/structs.rs b/integration/subxt-tests/src/cases/structs.rs new file mode 100644 index 000000000..2776cc90a --- /dev/null +++ b/integration/subxt-tests/src/cases/structs.rs @@ -0,0 +1,230 @@ +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, + 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| ::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| ::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| ::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(()) +} diff --git a/integration/subxt-tests/src/cases/uniswapv2_erc20.rs b/integration/subxt-tests/src/cases/uniswapv2_erc20.rs new file mode 100644 index 000000000..86078df2a --- /dev/null +++ b/integration/subxt-tests/src/cases/uniswapv2_erc20.rs @@ -0,0 +1,377 @@ +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 { + 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, + }) + } +} diff --git a/integration/subxt-tests/src/cases/uniswapv2_factory.rs b/integration/subxt-tests/src/cases/uniswapv2_factory.rs new file mode 100644 index 000000000..cde2f4c49 --- /dev/null +++ b/integration/subxt-tests/src/cases/uniswapv2_factory.rs @@ -0,0 +1,262 @@ +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| ::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| ::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| ::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| ::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| ::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 { + 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 }) + } +} diff --git a/integration/subxt-tests/src/cases/uniswapv2_pair.rs b/integration/subxt-tests/src/cases/uniswapv2_pair.rs new file mode 100644 index 000000000..65f2f46cb --- /dev/null +++ b/integration/subxt-tests/src/cases/uniswapv2_pair.rs @@ -0,0 +1,970 @@ +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] +#[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 = t + .encode::<_, String>( + "transfer", + [format!( + "0x{}", + hex::encode(w.pair.address.as_ref().unwrap()) + )], + ) + .unwrap(); + + 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 = t + .encode::<_, String>( + "transfer", + [format!( + "0x{}", + hex::encode(w.pair.address.as_ref().unwrap()) + )], + ) + .unwrap(); + + 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| ::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| ::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| ::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| ::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] +#[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| ::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| ::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| ::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| ::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| ::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| ::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] +#[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| ::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| ::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| ::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| ::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| ::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| ::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] +#[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| ::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| ::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| ::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| ::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| ::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| ::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| ::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| ::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 { + 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| ::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| ::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.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.encode_to(&mut s); + amount_b.encode_to(&mut s); + s + + // let mut s = t + // .encode( + // "transfer", + // [format!( + // "0x{}", + // hex::encode(self.pair.address.as_ref().unwrap()) + // )], + // ) + // .unwrap(); + + // amount_b.encode_to(&mut s); + // s + }, + ) + .await?; + + self.pair + .call( + api, + sp_keyring::AccountKeyring::Alice, + 0, + &|t: &ContractMessageTranscoder| { + t.encode( + "mint", + [format!( + "0x{}", + hex::encode(sp_keyring::AccountKeyring::Alice.to_account_id()) + )], + ) + .unwrap() + }, + ) + .await?; + + Ok(()) + } +} diff --git a/integration/subxt-tests/src/lib.rs b/integration/subxt-tests/src/lib.rs new file mode 100644 index 000000000..5b53e1b98 --- /dev/null +++ b/integration/subxt-tests/src/lib.rs @@ -0,0 +1,548 @@ +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_path = "./metadata.scale", + substitute_type( + type = "sp_weights::weight_v2::Weight", + with = "::subxt::utils::Static<::sp_weights::Weight>" + ) +)] +pub mod node {} + +pub type API = OnlineClient; + +pub struct DeployContract { + pub caller: AccountKeyring, + pub selector: Vec, + pub value: u128, + pub code: Vec, +} +pub struct WriteContract { + pub caller: AccountKeyring, + pub contract_address: AccountId32, + pub selector: Vec, + pub value: u128, +} +pub struct ReadContract { + pub caller: AccountKeyring, + pub contract_address: AccountId32, + pub value: u128, + pub selector: Vec, +} + +pub struct ReadLayout { + pub contract_address: AccountId32, + pub key: Vec, +} + +#[async_trait::async_trait] +trait Execution { + type Output; + + async fn execute(self, api: &API) -> Result; +} + +pub mod output { + use super::*; + pub struct Deployed { + pub contract_address: AccountId32, + pub events: Vec, + } + pub struct WriteSuccess { + pub events: Vec, + } + pub struct ReadSuccess { + pub return_value: Vec, + } +} + +const GAS_LIMIT: u64 = 2 * 10_u64.pow(11); + +fn random_salt() -> Vec { + 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 { + 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::().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::() + .ok() + }) + .flatten() + }) + .collect::>(); + + 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 { + 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::() + .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::() + .ok() + }) + .flatten() + }) + .collect::>(); + + Ok(output::WriteSuccess { events }) + } +} + +#[async_trait::async_trait] +impl Execution for ReadContract { + type Output = output::ReadSuccess; + + async fn execute(self, api: &API) -> Result { + 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(), + }) + } + + // if rv.did_revert() { + // Err(anyhow::anyhow!("reverted")) + // } else { + // Ok(output::ReadSuccess { + // return_value: rv.data.to_vec(), + // }) + // } + } +} + +#[async_trait::async_trait] +impl Execution for ReadLayout { + type Output = GetStorageResult; + + async fn execute(self, api: &API) -> Result { + let ReadLayout { + contract_address, + key, + } = self; + + query_call(api, contract_address, key).await + } +} + +#[derive(Encode)] +pub struct CallRequest { + origin: ::AccountId, + dest: ::AccountId, + value: u128, + gas_limit: Option, + storage_deposit_limit: Option, + input_data: Vec, +} + +async fn raw_instantiate_and_upload( + api: &API, + builtin_keyring: sp_keyring::AccountKeyring, + value: u128, + gas_limit: u64, + storage_deposit_limit: Option, + code: Vec, + data: Vec, + salt: Vec, +) -> anyhow::Result> { + 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, + code: Vec, +) -> anyhow::Result> { + 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, + data: Vec, +) -> anyhow::Result> { + 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, +) -> anyhow::Result { + let rv = api + .rpc() + .state_call( + "ContractsApi_get_storage", + Some((contract_address, key).encode().as_bytes_ref()), + None, + ) + .await?; + + ::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, +) -> anyhow::Result> { + let req = CallRequest { + origin: ::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::, u128>::decode( + &mut rv.as_bytes_ref(), + )?; + + // rv.result.map_err(|e| { + // if let DispatchError::Module(m) = e { + // if let Ok(d) = api.metadata().error(m.index, m.error[0]) { + // return anyhow::anyhow!("{d:?}"); + // } + // } + + // anyhow::anyhow!("{e:?}") + // }) + + Ok(rv) +} + +pub async fn free_balance_of(api: &API, addr: AccountId32) -> anyhow::Result { + let key = node::storage() + .system() + .account(::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, + address: Option, +} + +impl Contract { + pub fn new(path: &'static str) -> anyhow::Result { + 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 { + 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, + ) -> anyhow::Result> { + 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, + ) -> anyhow::Result> { + 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, + ) -> anyhow::Result> { + 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) -> anyhow::Result>> { + let out = ReadLayout { + contract_address: self.address.clone().unwrap(), + key, + } + .execute(api) + .await? + .map_err(|e| anyhow::anyhow!("{e:?}"))?; + + Ok(out) + } +}