From f2d9edacc0b2cbdd3bb686f1df957bf8bd2c7af2 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 14 Dec 2022 15:17:02 +0000 Subject: [PATCH] Add end-to-end tests for multitoken transfers --- .../testing/886-vp-user-multitoken-auth .md | 2 + Cargo.lock | 1 + tests/Cargo.toml | 1 + tests/src/e2e.rs | 1 + tests/src/e2e/helpers.rs | 49 ++- tests/src/e2e/multitoken_tests.rs | 369 ++++++++++++++++++ tests/src/e2e/multitoken_tests/helpers.rs | 189 +++++++++ wasm/Cargo.lock | 1 + wasm_for_tests/wasm_source/Cargo.lock | 1 + 9 files changed, 612 insertions(+), 2 deletions(-) create mode 100644 .changelog/unreleased/testing/886-vp-user-multitoken-auth .md create mode 100644 tests/src/e2e/multitoken_tests.rs create mode 100644 tests/src/e2e/multitoken_tests/helpers.rs diff --git a/.changelog/unreleased/testing/886-vp-user-multitoken-auth .md b/.changelog/unreleased/testing/886-vp-user-multitoken-auth .md new file mode 100644 index 0000000000..d0e4c8f967 --- /dev/null +++ b/.changelog/unreleased/testing/886-vp-user-multitoken-auth .md @@ -0,0 +1,2 @@ +- Add e2e tests for multitoken transfers + ([#886](https://github.com/anoma/namada/pull/886)) \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e0510b2423..d13d4fb0b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3902,6 +3902,7 @@ dependencies = [ "proptest", "prost", "rand 0.8.5", + "regex", "rust_decimal", "rust_decimal_macros", "serde_json", diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 79c7c46cca..3cc68f574a 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -32,6 +32,7 @@ ibc = {version = "0.14.0", default-features = false} ibc-proto = {version = "0.17.1", default-features = false} ibc-relayer = {version = "0.14.0", default-features = false} prost = "0.9.0" +regex = "1.7.0" serde_json = {version = "1.0.65"} sha2 = "0.9.3" test-log = {version = "0.2.7", default-features = false, features = ["trace"]} diff --git a/tests/src/e2e.rs b/tests/src/e2e.rs index 521c4d3db4..0441532c95 100644 --- a/tests/src/e2e.rs +++ b/tests/src/e2e.rs @@ -15,5 +15,6 @@ pub mod eth_bridge_tests; pub mod helpers; pub mod ibc_tests; pub mod ledger_tests; +pub mod multitoken_tests; pub mod setup; pub mod wallet_tests; diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 590b92602b..5792dd1afe 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -16,9 +16,54 @@ use namada::types::storage::Epoch; use namada_apps::config::genesis::genesis_config; use namada_apps::config::{Config, TendermintMode}; -use super::setup::{sleep, Test, ENV_VAR_DEBUG, ENV_VAR_USE_PREBUILT_BINARIES}; +use super::setup::{ + self, sleep, NamadaBgCmd, Test, ENV_VAR_DEBUG, + ENV_VAR_USE_PREBUILT_BINARIES, +}; use crate::e2e::setup::{Bin, Who, APPS_PACKAGE}; -use crate::run; +use crate::{run, run_as}; + +/// Sets up a test chain with a single validator node running in the background, +/// and returns the [`Test`] handle and [`NamadaBgCmd`] for the validator node. +/// It blocks until the node is ready to receive RPC requests from +/// `namadac`. +pub fn setup_single_node_test() -> Result<(Test, NamadaBgCmd)> { + let test = setup::single_node_net()?; + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + ledger.exp_string("Namada ledger node started")?; + // TODO(namada#867): we only need to wait until the RPC server is available, + // not necessarily for a block to be committed + // ledger.exp_string("Starting RPC HTTP server on")?; + ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + Ok((test, ledger.background())) +} + +pub fn init_established_account( + test: &Test, + rpc_addr: &str, + source_alias: &str, + key_alias: &str, + established_alias: &str, +) -> Result<()> { + let init_account_args = vec![ + "init-account", + "--source", + source_alias, + "--public-key", + key_alias, + "--alias", + established_alias, + "--ledger-address", + rpc_addr, + ]; + let mut client_init_account = + run!(test, Bin::Client, init_account_args, Some(40))?; + client_init_account.exp_string("Transaction is valid.")?; + client_init_account.exp_string("Transaction applied")?; + client_init_account.assert_success(); + Ok(()) +} /// Find the address of an account by its alias from the wallet pub fn find_address(test: &Test, alias: impl AsRef) -> Result
{ diff --git a/tests/src/e2e/multitoken_tests.rs b/tests/src/e2e/multitoken_tests.rs new file mode 100644 index 0000000000..460395cc59 --- /dev/null +++ b/tests/src/e2e/multitoken_tests.rs @@ -0,0 +1,369 @@ +//! Tests for multitoken functionality +use color_eyre::eyre::Result; +use namada_core::types::token; + +use super::helpers::get_actor_rpc; +use super::setup::constants::{ALBERT, BERTHA, CHRISTEL}; +use super::setup::{self, Who}; +use crate::e2e; +use crate::e2e::setup::constants::{ALBERT_KEY, BERTHA_KEY}; + +mod helpers; + +#[test] +fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + // establish a multitoken VP with the following balances + // - #atest5blah/tokens/red/balance/$albert_established = 100 + // - #atest5blah/tokens/red/balance/$bertha = 0 + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + println!("Fake multitoken VP established at {}", multitoken_vp_addr); + + let albert_addr = e2e::helpers::find_address(&test, ALBERT)?; + let albert_starting_red_balance = token::Amount::from(100_000_000); + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &albert_addr, + &albert_starting_red_balance, + )?; + + let transfer_amount = token::Amount::from(10_000_000); + + // make a transfer from Albert to Bertha, signed by Christel - this should + // be rejected + let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + BERTHA, + CHRISTEL, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer.exp_string(&format!("Rejected: {albert_addr}"))?; + unauthorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!(albert_balance, albert_starting_red_balance); + + // make a transfer from Albert to Bertha, signed by Albert - this should + // be accepted + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + BERTHA, + ALBERT, + &token::Amount::from(10_000_000), + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!( + albert_balance, + albert_starting_red_balance - transfer_amount + ); + Ok(()) +} + +#[test] +fn test_multitoken_transfer_established_to_implicit() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + println!("Fake multitoken VP established at {}", multitoken_vp_addr); + + // create an established account that Albert controls + let established_alias = "established"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + ALBERT, + ALBERT_KEY, + established_alias, + )?; + + let established_starting_red_balance = token::Amount::from(100_000_000); + // mint some red tokens for the established account + let established_addr = + e2e::helpers::find_address(&test, established_alias)?; + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &established_addr, + &established_starting_red_balance, + )?; + + let transfer_amount = token::Amount::from(10_000_000); + // attempt an unauthorized transfer to Albert from the established account + let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + BERTHA, + CHRISTEL, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer + .exp_string(&format!("Rejected: {established_addr}"))?; + unauthorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!(established_balance, established_starting_red_balance); + + // attempt an authorized transfer to Albert from the established account + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + BERTHA, + ALBERT, + &transfer_amount, + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!( + established_balance, + established_starting_red_balance - transfer_amount + ); + + Ok(()) +} + +#[test] +fn test_multitoken_transfer_implicit_to_established() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + println!("Fake multitoken VP established at {}", multitoken_vp_addr); + + // create an established account controlled by Bertha + let established_alias = "established"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + BERTHA, + BERTHA_KEY, + established_alias, + )?; + + let albert_addr = e2e::helpers::find_address(&test, ALBERT)?; + let albert_starting_red_balance = token::Amount::from(100_000_000); + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &albert_addr, + &albert_starting_red_balance, + )?; + + let transfer_amount = token::Amount::from(10_000_000); + + // attempt an unauthorized transfer from Albert to the established account + let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + established_alias, + CHRISTEL, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer.exp_string(&format!("Rejected: {albert_addr}"))?; + unauthorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!(albert_balance, albert_starting_red_balance); + + // attempt an authorized transfer to Albert from the established account + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + established_alias, + ALBERT, + &transfer_amount, + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!( + albert_balance, + albert_starting_red_balance - transfer_amount + ); + + Ok(()) +} + +#[test] +fn test_multitoken_transfer_established_to_established() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + println!("Fake multitoken VP established at {}", multitoken_vp_addr); + + // create an established account that Albert controls + let established_alias = "established"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + ALBERT, + ALBERT_KEY, + established_alias, + )?; + + let established_starting_red_balance = token::Amount::from(100_000_000); + // mint some red tokens for the established account + let established_addr = + e2e::helpers::find_address(&test, established_alias)?; + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &established_addr, + &established_starting_red_balance, + )?; + + // create another established account to receive transfers + let receiver_alias = "receiver"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + BERTHA, + BERTHA_KEY, + receiver_alias, + )?; + + let established_starting_red_balance = token::Amount::from(100_000_000); + // mint some red tokens for the established account + let established_addr = + e2e::helpers::find_address(&test, established_alias)?; + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &established_addr, + &established_starting_red_balance, + )?; + + let transfer_amount = token::Amount::from(10_000_000); + + // attempt an unauthorized transfer + let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + receiver_alias, + CHRISTEL, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer + .exp_string(&format!("Rejected: {established_addr}"))?; + unauthorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!(established_balance, established_starting_red_balance); + + // attempt an authorized transfer which should succeed + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + receiver_alias, + ALBERT, + &transfer_amount, + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!( + established_balance, + established_starting_red_balance - transfer_amount + ); + + Ok(()) +} diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs new file mode 100644 index 0000000000..fb1138ca82 --- /dev/null +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -0,0 +1,189 @@ +//! Helpers for use in multitoken tests. +use std::path::PathBuf; +use std::str::FromStr; + +use borsh::BorshSerialize; +use color_eyre::eyre::Result; +use eyre::Context; +use namada_core::types::address::Address; +use namada_core::types::{storage, token}; +use namada_test_utils::tx_data::TxWriteData; +use namada_tx_prelude::storage::KeySeg; +use rand::Rng; +use regex::Regex; + +use super::setup::constants::{wasm_abs_path, NAM, VP_ALWAYS_TRUE_WASM}; +use super::setup::{Bin, NamadaCmd, Test}; +use crate::e2e::setup::constants::{ALBERT, TX_WRITE_WASM}; +use crate::run; + +const MULTITOKEN_KEY_SEGMENT: &str = "tokens"; +const BALANCE_KEY_SEGMENT: &str = "balance"; +const RED_TOKEN_KEY_SEGMENT: &str = "red"; +const MULTITOKEN_RED_TOKEN_SUB_PREFIX: &str = "tokens/red"; + +const ARBITRARY_SIGNER: &str = ALBERT; + +/// Initializes a VP to represent a multitoken account. +pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { + // we use a VP that always returns true for the multitoken VP here, as we + // are testing out the VPs of the sender and receiver of multitoken + // transactions here - not any multitoken VP itself + let multitoken_vp_wasm_path = wasm_abs_path(VP_ALWAYS_TRUE_WASM) + .to_string_lossy() + .to_string(); + let multitoken_alias = "multitoken"; + + let init_account_args = vec![ + "init-account", + "--source", + ARBITRARY_SIGNER, + "--public-key", + // Value obtained from + // `namada::types::key::ed25519::tests::gen_keypair` + "001be519a321e29020fa3cbfbfd01bd5e92db134305609270b71dace25b5a21168", + "--code-path", + &multitoken_vp_wasm_path, + "--alias", + multitoken_alias, + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + rpc_addr, + ]; + let mut client_init_account = + run!(test, Bin::Client, init_account_args, Some(40))?; + client_init_account.exp_string("Transaction is valid.")?; + client_init_account.exp_string("Transaction applied")?; + client_init_account.assert_success(); + Ok(multitoken_alias.to_string()) +} + +/// Generates a random path within the `test` directory. +fn generate_random_test_dir_path(test: &Test) -> PathBuf { + let rng = rand::thread_rng(); + let random_string: String = rng + .sample_iter(&rand::distributions::Alphanumeric) + .take(24) + .map(char::from) + .collect(); + test.test_dir.path().join(random_string) +} + +/// Writes `contents` to a random path within the `test` directory, and return +/// the path. +pub fn write_test_file( + test: &Test, + contents: impl AsRef<[u8]>, +) -> Result { + let path = generate_random_test_dir_path(test); + std::fs::write(&path, contents)?; + Ok(path) +} + +/// Mint red tokens to the given address. +pub fn mint_red_tokens( + test: &Test, + rpc_addr: &str, + multitoken: &Address, + owner: &Address, + amount: &token::Amount, +) -> Result<()> { + let red_balance_key = storage::Key::from(multitoken.to_db_key()) + .push(&MULTITOKEN_KEY_SEGMENT.to_owned())? + .push(&RED_TOKEN_KEY_SEGMENT.to_owned())? + .push(&BALANCE_KEY_SEGMENT.to_owned())? + .push(owner)?; + + let tx_code_path = wasm_abs_path(TX_WRITE_WASM); + let tx_data_path = write_test_file( + test, + TxWriteData { + key: red_balance_key, + value: amount.try_to_vec()?, + } + .try_to_vec()?, + )?; + + let tx_data_path = tx_data_path.to_string_lossy().to_string(); + let tx_code_path = tx_code_path.to_string_lossy().to_string(); + let tx_args = vec![ + "tx", + "--signer", + ARBITRARY_SIGNER, + "--code-path", + &tx_code_path, + "--data-path", + &tx_data_path, + "--ledger-address", + rpc_addr, + ]; + let mut client_tx = run!(test, Bin::Client, tx_args, Some(40))?; + client_tx.exp_string("Transaction is valid.")?; + client_tx.exp_string("Transaction applied")?; + client_tx.assert_success(); + Ok(()) +} + +pub fn attempt_red_tokens_transfer( + test: &Test, + rpc_addr: &str, + multitoken: &str, + from: &str, + to: &str, + signer: &str, + amount: &token::Amount, +) -> Result { + let amount = amount.to_string(); + let transfer_args = vec![ + "transfer", + "--token", + multitoken, + "--sub-prefix", + MULTITOKEN_RED_TOKEN_SUB_PREFIX, + "--source", + from, + "--target", + to, + "--signer", + signer, + "--amount", + &amount, + "--ledger-address", + rpc_addr, + ]; + run!(test, Bin::Client, transfer_args, Some(40)) +} + +pub fn fetch_red_token_balance( + test: &Test, + rpc_addr: &str, + multitoken_alias: &str, + owner_alias: &str, +) -> Result { + let balance_args = vec![ + "balance", + "--owner", + owner_alias, + "--token", + multitoken_alias, + "--sub-prefix", + MULTITOKEN_RED_TOKEN_SUB_PREFIX, + "--ledger-address", + rpc_addr, + ]; + let mut client_balance = run!(test, Bin::Client, balance_args, Some(40))?; + let (_, matched) = client_balance.exp_regex(&format!( + r"{MULTITOKEN_RED_TOKEN_SUB_PREFIX}: (\d*\.?\d+)" + ))?; + let decimal_regex = Regex::new(r"(\d*\.?\d+)").unwrap(); + println!("Got balance for {}: {}", owner_alias, matched); + let decimal = decimal_regex.find(&matched).unwrap().as_str(); + client_balance.assert_success(); + token::Amount::from_str(decimal) + .wrap_err(format!("Failed to parse {}", matched)) +} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index bd0cd40691..e8ca8c26ab 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2587,6 +2587,7 @@ dependencies = [ "namada_tx_prelude", "namada_vp_prelude", "prost", + "regex", "rust_decimal", "rust_decimal_macros", "serde_json", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 90a8552a4b..bcd244d110 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2587,6 +2587,7 @@ dependencies = [ "namada_tx_prelude", "namada_vp_prelude", "prost", + "regex", "rust_decimal", "rust_decimal_macros", "serde_json",