From e41911b4699bf0862cef5182b7ceabec519f1889 Mon Sep 17 00:00:00 2001 From: Nurullah Giray Kuru Date: Fri, 4 Aug 2023 16:07:19 -0700 Subject: [PATCH] [api] Add support for strictness level for consistency on API tests (#9444) Description This commit builds threads and run ID on top of the consistent API testing introduced here. See proposal for more high level information. One problem we saw in the dashboard is that the problems we observed were regarding eventual consistency. We added support for adjusting the strictness level for such errors. Changes: Refactor of tests into tests module and decomposing into steps (setting up for next PR, individual step timing). Persistent checks for information retrieval. Added token support for Testnet faucet. --- crates/aptos-api-tester/src/counters.rs | 29 +- crates/aptos-api-tester/src/fail_message.rs | 27 + crates/aptos-api-tester/src/main.rs | 32 +- .../aptos-api-tester/src/persistent_check.rs | 178 +++++++ crates/aptos-api-tester/src/tests.rs | 489 ------------------ .../src/tests/coin_transfer.rs | 205 ++++++++ crates/aptos-api-tester/src/tests/mod.rs | 6 + .../aptos-api-tester/src/tests/new_account.rs | 141 +++++ .../src/tests/nft_transfer.rs | 472 +++++++++++++++++ .../src/tests/publish_module.rs | 314 +++++++++++ crates/aptos-api-tester/src/testsetups.rs | 70 --- crates/aptos-api-tester/src/utils.rs | 11 +- crates/aptos-rest-client/src/faucet.rs | 40 +- sdk/src/token_client.rs | 4 +- 14 files changed, 1404 insertions(+), 614 deletions(-) create mode 100644 crates/aptos-api-tester/src/fail_message.rs create mode 100644 crates/aptos-api-tester/src/persistent_check.rs delete mode 100644 crates/aptos-api-tester/src/tests.rs create mode 100644 crates/aptos-api-tester/src/tests/coin_transfer.rs create mode 100644 crates/aptos-api-tester/src/tests/mod.rs create mode 100644 crates/aptos-api-tester/src/tests/new_account.rs create mode 100644 crates/aptos-api-tester/src/tests/nft_transfer.rs create mode 100644 crates/aptos-api-tester/src/tests/publish_module.rs delete mode 100644 crates/aptos-api-tester/src/testsetups.rs diff --git a/crates/aptos-api-tester/src/counters.rs b/crates/aptos-api-tester/src/counters.rs index 79798807d85e7..62441749d83a9 100644 --- a/crates/aptos-api-tester/src/counters.rs +++ b/crates/aptos-api-tester/src/counters.rs @@ -7,55 +7,50 @@ pub static API_TEST_SUCCESS: Lazy = Lazy::new(|| { register_histogram_vec!( "api_test_success", "Number of user flows which succesfully passed", - &["test_name", "network_name", "start_time"], + &["test_name", "network_name", "run_id"], ) .unwrap() }); -pub fn test_success(test_name: &str, network_name: &str, start_time: &str) -> Histogram { - API_TEST_SUCCESS.with_label_values(&[test_name, network_name, start_time]) +pub fn test_success(test_name: &str, network_name: &str, run_id: &str) -> Histogram { + API_TEST_SUCCESS.with_label_values(&[test_name, network_name, run_id]) } pub static API_TEST_FAIL: Lazy = Lazy::new(|| { register_histogram_vec!( "api_test_fail", "Number of user flows which failed checks", - &["test_name", "network_name", "start_time"], + &["test_name", "network_name", "run_id"], ) .unwrap() }); -pub fn test_fail(test_name: &str, network_name: &str, start_time: &str) -> Histogram { - API_TEST_FAIL.with_label_values(&[test_name, network_name, start_time]) +pub fn test_fail(test_name: &str, network_name: &str, run_id: &str) -> Histogram { + API_TEST_FAIL.with_label_values(&[test_name, network_name, run_id]) } pub static API_TEST_ERROR: Lazy = Lazy::new(|| { register_histogram_vec!("api_test_error", "Number of user flows which crashed", &[ "test_name", "network_name", - "start_time" + "run_id" ],) .unwrap() }); -pub fn test_error(test_name: &str, network_name: &str, start_time: &str) -> Histogram { - API_TEST_ERROR.with_label_values(&[test_name, network_name, start_time]) +pub fn test_error(test_name: &str, network_name: &str, run_id: &str) -> Histogram { + API_TEST_ERROR.with_label_values(&[test_name, network_name, run_id]) } pub static API_TEST_LATENCY: Lazy = Lazy::new(|| { register_histogram_vec!( "api_test_latency", "Time it takes to complete a user flow", - &["test_name", "network_name", "start_time", "result"], + &["test_name", "network_name", "run_id", "result"], ) .unwrap() }); -pub fn test_latency( - test_name: &str, - network_name: &str, - start_time: &str, - result: &str, -) -> Histogram { - API_TEST_LATENCY.with_label_values(&[test_name, network_name, start_time, result]) +pub fn test_latency(test_name: &str, network_name: &str, run_id: &str, result: &str) -> Histogram { + API_TEST_LATENCY.with_label_values(&[test_name, network_name, run_id, result]) } diff --git a/crates/aptos-api-tester/src/fail_message.rs b/crates/aptos-api-tester/src/fail_message.rs new file mode 100644 index 0000000000000..5e271cc2147ca --- /dev/null +++ b/crates/aptos-api-tester/src/fail_message.rs @@ -0,0 +1,27 @@ +// Copyright © Aptos Foundation + +pub static FAIL_WRONG_ACCOUNT_DATA: &str = "wrong account data"; +pub static FAIL_WRONG_BALANCE: &str = "wrong balance"; +pub static FAIL_WRONG_BALANCE_AT_VERSION: &str = "wrong balance at version"; +pub static FAIL_WRONG_COLLECTION_DATA: &str = "wrong collection data"; +pub static FAIL_WRONG_MESSAGE: &str = "wrong message"; +pub static FAIL_WRONG_MODULE: &str = "wrong module"; +pub static FAIL_WRONG_TOKEN_BALANCE: &str = "wrong token balance"; +pub static FAIL_WRONG_TOKEN_DATA: &str = "wrong token data"; +pub static ERROR_COULD_NOT_BUILD_PACKAGE: &str = "failed to build package"; +pub static ERROR_COULD_NOT_CHECK: &str = "persistency check never started"; +pub static ERROR_COULD_NOT_CREATE_ACCOUNT: &str = "failed to create account"; +pub static ERROR_COULD_NOT_CREATE_TRANSACTION: &str = "failed to create transaction"; +pub static ERROR_COULD_NOT_FINISH_TRANSACTION: &str = "failed to finish transaction"; +pub static ERROR_COULD_NOT_FUND_ACCOUNT: &str = "failed to fund account"; +pub static ERROR_COULD_NOT_SERIALIZE: &str = "failed to serialize"; +pub static ERROR_NO_ACCOUNT_DATA: &str = "can't find account data"; +pub static ERROR_NO_BALANCE: &str = "can't find account balance"; +pub static ERROR_NO_BYTECODE: &str = "can't find bytecode"; +pub static ERROR_NO_COLLECTION_DATA: &str = "can't find collection data"; +pub static ERROR_NO_MESSAGE: &str = "can't find message"; +pub static ERROR_NO_METADATA: &str = "can't find metadata"; +pub static ERROR_NO_MODULE: &str = "can't find module"; +pub static ERROR_NO_TOKEN_BALANCE: &str = "can't find token balance"; +pub static ERROR_NO_TOKEN_DATA: &str = "can't find token data"; +pub static ERROR_NO_VERSION: &str = "can't find transaction version"; diff --git a/crates/aptos-api-tester/src/main.rs b/crates/aptos-api-tester/src/main.rs index 06a13713ffb57..8a555f112d916 100644 --- a/crates/aptos-api-tester/src/main.rs +++ b/crates/aptos-api-tester/src/main.rs @@ -4,15 +4,13 @@ #![forbid(unsafe_code)] mod counters; +mod fail_message; +mod persistent_check; mod tests; -mod testsetups; mod utils; use crate::{ - testsetups::{ - setup_and_run_cointransfer, setup_and_run_newaccount, setup_and_run_nfttransfer, - setup_and_run_publishmodule, - }, + tests::{coin_transfer, new_account, nft_transfer, publish_module}, utils::{set_metrics, NetworkName, TestFailure, TestName, TestResult}, }; use anyhow::Result; @@ -28,7 +26,7 @@ use std::{ async fn process_result>>( test_name: TestName, network_name: NetworkName, - start_time: &str, + run_id: &str, fut: Fut, ) { // start timer @@ -51,7 +49,7 @@ async fn process_result>>( &output, &test_name.to_string(), &network_name.to_string(), - start_time, + run_id, time, ); info!( @@ -64,55 +62,55 @@ async fn process_result>>( } async fn test_flows(network_name: NetworkName) -> Result<()> { - let start_time = SystemTime::now() + let run_id = SystemTime::now() .duration_since(UNIX_EPOCH)? .as_secs() .to_string(); - info!("testing {} at {}", network_name.to_string(), start_time); + info!("testing {} at {}", network_name.to_string(), run_id); // Test new account creation and funding - let test_time = start_time.clone(); + let test_time = run_id.clone(); let handle_newaccount = tokio::spawn(async move { process_result( TestName::NewAccount, network_name, &test_time, - setup_and_run_newaccount(network_name), + new_account::test(network_name), ) .await; }); // Flow 1: Coin transfer - let test_time = start_time.clone(); + let test_time = run_id.clone(); let handle_cointransfer = tokio::spawn(async move { process_result( TestName::CoinTransfer, network_name, &test_time, - setup_and_run_cointransfer(network_name), + coin_transfer::test(network_name), ) .await; }); // Flow 2: NFT transfer - let test_time = start_time.clone(); + let test_time = run_id.clone(); let handle_nfttransfer = tokio::spawn(async move { process_result( TestName::NftTransfer, network_name, &test_time, - setup_and_run_nfttransfer(network_name), + nft_transfer::test(network_name), ) .await; }); // Flow 3: Publishing module - let test_time = start_time.clone(); + let test_time = run_id.clone(); process_result( TestName::PublishModule, network_name, &test_time, - setup_and_run_publishmodule(network_name), + publish_module::test(network_name), ) .await; diff --git a/crates/aptos-api-tester/src/persistent_check.rs b/crates/aptos-api-tester/src/persistent_check.rs new file mode 100644 index 0000000000000..9e1873af2cc27 --- /dev/null +++ b/crates/aptos-api-tester/src/persistent_check.rs @@ -0,0 +1,178 @@ +// Copyright © Aptos Foundation + +use crate::{fail_message::ERROR_COULD_NOT_CHECK, utils::TestFailure}; +use anyhow::anyhow; +use aptos_api_types::HexEncodedBytes; +use aptos_rest_client::Client; +use aptos_sdk::{token_client::TokenClient, types::LocalAccount}; +use aptos_types::account_address::AccountAddress; +use futures::Future; +use std::time::Duration; +use tokio::time::Instant; + +static PERSISTENCY_TIMEOUT: Duration = Duration::from_secs(30); + +pub async fn account<'a, 'b, F, Fut>( + step: &str, + f: F, + client: &'a Client, + account: &'b LocalAccount, +) -> Result<(), TestFailure> +where + F: Fn(&'a Client, &'b LocalAccount) -> Fut, + Fut: Future>, +{ + // set a default error in case checks never start + let mut result: Result<(), TestFailure> = Err(could_not_check(step)); + let timer = Instant::now(); + + // try to get a good result + while Instant::now().duration_since(timer) < PERSISTENCY_TIMEOUT { + result = f(client, account).await; + if result.is_ok() { + break; + } + } + + // return last failure if no good result occurs + result +} + +pub async fn address<'a, F, Fut>( + step: &str, + f: F, + client: &'a Client, + address: AccountAddress, +) -> Result<(), TestFailure> +where + F: Fn(&'a Client, AccountAddress) -> Fut, + Fut: Future>, +{ + // set a default error in case checks never start + let mut result: Result<(), TestFailure> = Err(could_not_check(step)); + let timer = Instant::now(); + + // try to get a good result + while Instant::now().duration_since(timer) < PERSISTENCY_TIMEOUT { + result = f(client, address).await; + if result.is_ok() { + break; + } + } + + // return last failure if no good result occurs + result +} + +pub async fn address_bytes<'a, 'b, F, Fut>( + step: &str, + f: F, + client: &'a Client, + address: AccountAddress, + bytes: &'b HexEncodedBytes, +) -> Result<(), TestFailure> +where + F: Fn(&'a Client, AccountAddress, &'b HexEncodedBytes) -> Fut, + Fut: Future>, +{ + // set a default error in case checks never start + let mut result: Result<(), TestFailure> = Err(could_not_check(step)); + let timer = Instant::now(); + + // try to get a good result + while Instant::now().duration_since(timer) < PERSISTENCY_TIMEOUT { + result = f(client, address, bytes).await; + if result.is_ok() { + break; + } + } + + // return last failure if no good result occurs + result +} + +pub async fn address_version<'a, F, Fut>( + step: &str, + f: F, + client: &'a Client, + address: AccountAddress, + version: u64, +) -> Result<(), TestFailure> +where + F: Fn(&'a Client, AccountAddress, u64) -> Fut, + Fut: Future>, +{ + // set a default error in case checks never start + let mut result: Result<(), TestFailure> = Err(could_not_check(step)); + let timer = Instant::now(); + + // try to get a good result + while Instant::now().duration_since(timer) < PERSISTENCY_TIMEOUT { + result = f(client, address, version).await; + if result.is_ok() { + break; + } + } + + // return last failure if no good result occurs + result +} + +pub async fn token_address<'a, F, Fut>( + step: &str, + f: F, + token_client: &'a TokenClient<'a>, + address: AccountAddress, +) -> Result<(), TestFailure> +where + F: Fn(&'a TokenClient<'a>, AccountAddress) -> Fut, + Fut: Future>, +{ + // set a default error in case checks never start + let mut result: Result<(), TestFailure> = Err(could_not_check(step)); + let timer = Instant::now(); + + // try to get a good result + while Instant::now().duration_since(timer) < PERSISTENCY_TIMEOUT { + result = f(token_client, address).await; + if result.is_ok() { + break; + } + } + + // return last failure if no good result occurs + result +} + +pub async fn token_address_address<'a, F, Fut>( + step: &str, + f: F, + token_client: &'a TokenClient<'a>, + address: AccountAddress, + address2: AccountAddress, +) -> Result<(), TestFailure> +where + F: Fn(&'a TokenClient<'a>, AccountAddress, AccountAddress) -> Fut, + Fut: Future>, +{ + // set a default error in case checks never start + let mut result: Result<(), TestFailure> = Err(could_not_check(step)); + let timer = Instant::now(); + + // try to get a good result + while Instant::now().duration_since(timer) < PERSISTENCY_TIMEOUT { + result = f(token_client, address, address2).await; + if result.is_ok() { + break; + } + } + + // return last failure if no good result occurs + result +} + +// Utils + +fn could_not_check(step: &str) -> TestFailure { + anyhow!(format!("{} in step: {}", ERROR_COULD_NOT_CHECK, step)).into() +} diff --git a/crates/aptos-api-tester/src/tests.rs b/crates/aptos-api-tester/src/tests.rs deleted file mode 100644 index d7071f441ad24..0000000000000 --- a/crates/aptos-api-tester/src/tests.rs +++ /dev/null @@ -1,489 +0,0 @@ -// Copyright © Aptos Foundation - -use crate::utils::TestFailure; -use anyhow::{anyhow, Result}; -use aptos_api_types::{HexEncodedBytes, U64}; -use aptos_cached_packages::aptos_stdlib::EntryFunctionCall; -use aptos_framework::{BuildOptions, BuiltPackage}; -use aptos_logger::info; -use aptos_rest_client::{Account, Client}; -use aptos_sdk::{ - bcs, - coin_client::CoinClient, - token_client::{ - build_and_submit_transaction, CollectionData, CollectionMutabilityConfig, RoyaltyOptions, - TokenClient, TokenData, TokenMutabilityConfig, TransactionOptions, - }, - types::LocalAccount, -}; -use aptos_types::{ - account_address::AccountAddress, - transaction::{EntryFunction, TransactionPayload}, -}; -use move_core_types::{ident_str, language_storage::ModuleId}; -use std::{collections::BTreeMap, path::PathBuf}; - -// fail messages -static FAIL_ACCOUNT_DATA: &str = "wrong account data"; -static FAIL_BALANCE: &str = "wrong balance"; -static FAIL_BALANCE_AFTER_TRANSACTION: &str = "wrong balance after transaction"; -static FAIL_BALANCE_BEFORE_TRANSACTION: &str = "wrong balance before transaction"; -static FAIL_COLLECTION_DATA: &str = "wrong collection data"; -static FAIL_TOKEN_DATA: &str = "wrong token data"; -static FAIL_TOKEN_BALANCE: &str = "wrong token balance"; -static FAIL_TOKENS_BEFORE_CLAIM: &str = "found tokens for receiver when shouldn't"; -static FAIL_TOKEN_BALANCE_AFTER_TRANSACTION: &str = "wrong token balance after transaction"; -static FAIL_BYTECODE: &str = "wrong bytecode"; -static FAIL_MODULE_INTERACTION: &str = "module interaction isn't reflected correctly"; -static ERROR_NO_VERSION: &str = "transaction did not return version"; -static ERROR_NO_BYTECODE: &str = "error while getting bytecode from blobs"; -static ERROR_MODULE_INTERACTION: &str = "module interaction isn't reflected"; - -/// Tests new account creation. Checks that: -/// - account data exists -/// - account balance reflects funded amount -pub async fn test_newaccount( - client: &Client, - account: &LocalAccount, - amount_funded: u64, -) -> Result<(), TestFailure> { - // ask for account data - let response = client.get_account(account.address()).await?; - - // check account data - let expected_account = Account { - authentication_key: account.authentication_key(), - sequence_number: account.sequence_number(), - }; - let actual_account = response.inner(); - - if &expected_account != actual_account { - info!( - "fail: {}, expected {:?}, got {:?}", - FAIL_ACCOUNT_DATA, expected_account, actual_account - ); - return Err(TestFailure::Fail(FAIL_ACCOUNT_DATA)); - } - - // check account balance - let expected_balance = U64(amount_funded); - let actual_balance = client - .get_account_balance(account.address()) - .await? - .inner() - .coin - .value; - - if expected_balance != actual_balance { - info!( - "fail: {}, expected {:?}, got {:?}", - FAIL_BALANCE, expected_balance, actual_balance - ); - return Err(TestFailure::Fail(FAIL_BALANCE)); - } - - Ok(()) -} - -/// Tests coin transfer. Checks that: -/// - receiver balance reflects transferred amount -/// - receiver balance shows correct amount at the previous version -pub async fn test_cointransfer( - client: &Client, - coin_client: &CoinClient<'_>, - account: &mut LocalAccount, - receiver: AccountAddress, - amount: u64, -) -> Result<(), TestFailure> { - // get starting balance - let starting_receiver_balance = u64::from( - client - .get_account_balance(receiver) - .await? - .inner() - .coin - .value, - ); - - // transfer coins to second account - let pending_txn = coin_client - .transfer(account, receiver, amount, None) - .await?; - let response = client.wait_for_transaction(&pending_txn).await?; - - // check receiver balance - let expected_receiver_balance = U64(starting_receiver_balance + amount); - let actual_receiver_balance = client - .get_account_balance(receiver) - .await? - .inner() - .coin - .value; - - if expected_receiver_balance != actual_receiver_balance { - info!( - "fail: {}, expected {:?}, got {:?}", - FAIL_BALANCE_AFTER_TRANSACTION, expected_receiver_balance, actual_receiver_balance - ); - return Err(TestFailure::Fail(FAIL_BALANCE_AFTER_TRANSACTION)); - } - - // check account balance with a lower version number - let version = match response.inner().version() { - Some(version) => version, - _ => { - info!("error: {}", ERROR_MODULE_INTERACTION); - return Err(TestFailure::Error(anyhow!(ERROR_NO_VERSION))); - }, - }; - - let expected_balance_at_version = U64(starting_receiver_balance); - let actual_balance_at_version = client - .get_account_balance_at_version(receiver, version - 1) - .await? - .inner() - .coin - .value; - - if expected_balance_at_version != actual_balance_at_version { - info!( - "fail: {}, expected {:?}, got {:?}", - FAIL_BALANCE_BEFORE_TRANSACTION, expected_balance_at_version, actual_balance_at_version - ); - return Err(TestFailure::Fail(FAIL_BALANCE_BEFORE_TRANSACTION)); - } - - Ok(()) -} - -/// Tests nft transfer. Checks that: -/// - collection data exists -/// - token data exists -/// - token balance reflects transferred amount -pub async fn test_nfttransfer( - client: &Client, - token_client: &TokenClient<'_>, - account: &mut LocalAccount, - receiver: &mut LocalAccount, -) -> Result<(), TestFailure> { - // create collection - let collection_name = "test collection".to_string(); - let collection_description = "collection description".to_string(); - let collection_uri = "collection uri".to_string(); - let collection_maximum = 1000; - - let pending_txn = token_client - .create_collection( - account, - &collection_name, - &collection_description, - &collection_uri, - collection_maximum, - None, - ) - .await?; - client.wait_for_transaction(&pending_txn).await?; - - // create token - let token_name = "test token".to_string(); - let token_description = "token description".to_string(); - let token_uri = "token uri".to_string(); - let token_maximum = 1000; - let token_supply = 10; - - let pending_txn = token_client - .create_token( - account, - &collection_name, - &token_name, - &token_description, - token_supply, - &token_uri, - token_maximum, - None, - None, - ) - .await?; - client.wait_for_transaction(&pending_txn).await?; - - // check collection metadata - let expected_collection_data = CollectionData { - name: collection_name.clone(), - description: collection_description, - uri: collection_uri, - maximum: U64(collection_maximum), - mutability_config: CollectionMutabilityConfig { - description: false, - maximum: false, - uri: false, - }, - }; - let actual_collection_data = token_client - .get_collection_data(account.address(), &collection_name) - .await?; - - if expected_collection_data != actual_collection_data { - info!( - "fail: {}, expected {:?}, got {:?}", - FAIL_COLLECTION_DATA, expected_collection_data, actual_collection_data - ); - return Err(TestFailure::Fail(FAIL_COLLECTION_DATA)); - } - - // check token metadata - let expected_token_data = TokenData { - name: token_name.clone(), - description: token_description, - uri: token_uri, - maximum: U64(token_maximum), - mutability_config: TokenMutabilityConfig { - description: false, - maximum: false, - properties: false, - royalty: false, - uri: false, - }, - supply: U64(token_supply), - royalty: RoyaltyOptions { - payee_address: account.address(), - royalty_points_denominator: U64(0), - royalty_points_numerator: U64(0), - }, - largest_property_version: U64(0), - }; - let actual_token_data = token_client - .get_token_data(account.address(), &collection_name, &token_name) - .await?; - - if expected_token_data != actual_token_data { - info!( - "fail: {}, expected {:?}, got {:?}", - FAIL_TOKEN_DATA, expected_token_data, actual_token_data - ); - return Err(TestFailure::Fail(FAIL_TOKEN_DATA)); - } - - // offer token - let pending_txn = token_client - .offer_token( - account, - receiver.address(), - account.address(), - &collection_name, - &token_name, - 2, - None, - None, - ) - .await?; - client.wait_for_transaction(&pending_txn).await?; - - // check token balance for the sender - let expected_sender_token_balance = U64(8); - let actual_sender_token_balance = token_client - .get_token( - account.address(), - account.address(), - &collection_name, - &token_name, - ) - .await? - .amount; - - if expected_sender_token_balance != actual_sender_token_balance { - info!( - "fail: {}, expected {:?}, got {:?}", - FAIL_TOKEN_BALANCE, expected_sender_token_balance, actual_sender_token_balance - ); - return Err(TestFailure::Fail(FAIL_TOKEN_BALANCE)); - } - - // check that token store isn't initialized for the receiver - if token_client - .get_token( - receiver.address(), - account.address(), - &collection_name, - &token_name, - ) - .await - .is_ok() - { - info!( - "fail: {}, expected no token client resource for the receiver", - FAIL_TOKENS_BEFORE_CLAIM - ); - return Err(TestFailure::Fail(FAIL_TOKENS_BEFORE_CLAIM)); - } - - // claim token - let pending_txn = token_client - .claim_token( - receiver, - account.address(), - account.address(), - &collection_name, - &token_name, - None, - None, - ) - .await?; - client.wait_for_transaction(&pending_txn).await?; - - // check token balance for the receiver - let expected_receiver_token_balance = U64(2); - let actual_receiver_token_balance = token_client - .get_token( - receiver.address(), - account.address(), - &collection_name, - &token_name, - ) - .await? - .amount; - - if expected_receiver_token_balance != actual_receiver_token_balance { - info!( - "{}, expected {:?}, got {:?}", - FAIL_TOKEN_BALANCE_AFTER_TRANSACTION, - expected_receiver_token_balance, - actual_receiver_token_balance - ); - return Err(TestFailure::Fail(FAIL_TOKEN_BALANCE_AFTER_TRANSACTION)); - } - - Ok(()) -} - -/// Helper function that publishes module and returns the bytecode. -async fn publish_module(client: &Client, account: &mut LocalAccount) -> Result { - // get file to compile - let move_dir = PathBuf::from("./aptos-move/move-examples/hello_blockchain"); - - // insert address - let mut named_addresses: BTreeMap = BTreeMap::new(); - named_addresses.insert("hello_blockchain".to_string(), account.address()); - - // build options - let options = BuildOptions { - named_addresses, - ..BuildOptions::default() - }; - - // build module - let package = BuiltPackage::build(move_dir, options)?; - let blobs = package.extract_code(); - let metadata = package.extract_metadata()?; - - // create payload - let payload: aptos_types::transaction::TransactionPayload = - EntryFunctionCall::CodePublishPackageTxn { - metadata_serialized: bcs::to_bytes(&metadata) - .expect("PackageMetadata should deserialize"), - code: blobs.clone(), - } - .encode(); - - // create and submit transaction - let pending_txn = - build_and_submit_transaction(client, account, payload, TransactionOptions::default()) - .await?; - client.wait_for_transaction(&pending_txn).await?; - - let blob = match blobs.get(0) { - Some(bytecode) => bytecode.clone(), - None => { - info!("error: {}", ERROR_NO_BYTECODE); - return Err(anyhow!(ERROR_NO_BYTECODE)); - }, - }; - - Ok(HexEncodedBytes::from(blob)) -} - -/// Helper function that interacts with the message module. -async fn set_message(client: &Client, account: &mut LocalAccount, message: &str) -> Result<()> { - // create payload - let payload = TransactionPayload::EntryFunction(EntryFunction::new( - ModuleId::new(account.address(), ident_str!("message").to_owned()), - ident_str!("set_message").to_owned(), - vec![], - vec![bcs::to_bytes(message)?], - )); - - // create and submit transaction - let pending_txn = - build_and_submit_transaction(client, account, payload, TransactionOptions::default()) - .await?; - client.wait_for_transaction(&pending_txn).await?; - - Ok(()) -} - -/// Helper function that gets back the result of the interaction. -async fn get_message(client: &Client, address: AccountAddress) -> Option { - let resource = match client - .get_account_resource( - address, - format!("{}::message::MessageHolder", address.to_hex_literal()).as_str(), - ) - .await - { - Ok(response) => response.into_inner()?, - Err(_) => return None, - }; - - Some(resource.data.get("message")?.as_str()?.to_owned()) -} - -/// Tests module publishing and interaction. Checks that: -/// - module data exists -/// - can interact with module -/// - resources reflect interaction -pub async fn test_publishmodule( - client: &Client, - account: &mut LocalAccount, -) -> Result<(), TestFailure> { - // publish module - let blob = publish_module(client, account).await?; - - // check module data - let response = client - .get_account_module(account.address(), "message") - .await?; - - let expected_bytecode = &blob; - let actual_bytecode = &response.inner().bytecode; - - if expected_bytecode != actual_bytecode { - info!( - "fail: {}, expected {:?}, got {:?}", - FAIL_BYTECODE, expected_bytecode, actual_bytecode - ); - return Err(TestFailure::Fail(FAIL_BYTECODE)); - } - - // interact with module - let message = "test message"; - set_message(client, account, message).await?; - - // check that the message is sent - let expected_message = message.to_string(); - let actual_message = match get_message(client, account.address()).await { - Some(message) => message, - None => { - info!("error: {}", ERROR_MODULE_INTERACTION); - return Err(TestFailure::Error(anyhow!(ERROR_MODULE_INTERACTION))); - }, - }; - - if expected_message != actual_message { - info!( - "fail: {}, expected {:?}, got {:?}", - FAIL_MODULE_INTERACTION, expected_message, actual_message - ); - return Err(TestFailure::Fail(FAIL_MODULE_INTERACTION)); - } - - Ok(()) -} diff --git a/crates/aptos-api-tester/src/tests/coin_transfer.rs b/crates/aptos-api-tester/src/tests/coin_transfer.rs new file mode 100644 index 0000000000000..4a23fa331215b --- /dev/null +++ b/crates/aptos-api-tester/src/tests/coin_transfer.rs @@ -0,0 +1,205 @@ +// Copyright © Aptos Foundation + +use crate::{ + fail_message::{ + ERROR_COULD_NOT_CREATE_ACCOUNT, ERROR_COULD_NOT_CREATE_TRANSACTION, + ERROR_COULD_NOT_FINISH_TRANSACTION, ERROR_COULD_NOT_FUND_ACCOUNT, ERROR_NO_BALANCE, + ERROR_NO_VERSION, FAIL_WRONG_BALANCE, FAIL_WRONG_BALANCE_AT_VERSION, + }, + persistent_check, + utils::{ + create_account, create_and_fund_account, get_client, get_faucet_client, NetworkName, + TestFailure, + }, +}; +use anyhow::{anyhow, Result}; +use aptos_api_types::U64; +use aptos_logger::info; +use aptos_rest_client::Client; +use aptos_sdk::{coin_client::CoinClient, types::LocalAccount}; +use aptos_types::account_address::AccountAddress; + +static TRANSFER_AMOUNT: u64 = 1_000; + +/// Tests coin transfer. Checks that: +/// - receiver balance reflects transferred amount +/// - receiver balance shows correct amount at the previous version +pub async fn test(network_name: NetworkName) -> Result<(), TestFailure> { + // setup + let (client, mut account, receiver) = setup(network_name).await?; + let coin_client = CoinClient::new(&client); + + // transfer coins to the receiver + let version = transfer_coins(&client, &coin_client, &mut account, receiver).await?; + + // check receiver balance persistently + persistent_check::address( + "check_account_balance", + check_account_balance, + &client, + receiver, + ) + .await?; + + // check receiver balance at previous version persistently + persistent_check::address_version( + "check_account_balance_at_version", + check_account_balance_at_version, + &client, + receiver, + version, + ) + .await?; + + Ok(()) +} + +// Steps + +async fn setup( + network_name: NetworkName, +) -> Result<(Client, LocalAccount, AccountAddress), TestFailure> { + // spin up clients + let client = get_client(network_name); + let faucet_client = get_faucet_client(network_name); + + // create account + let account = match create_and_fund_account(&faucet_client).await { + Ok(account) => account, + Err(e) => { + info!( + "test: coin_transfer part: setup ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FUND_ACCOUNT, e + ); + return Err(e.into()); + }, + }; + + // create receiver + let receiver = match create_account(&faucet_client).await { + Ok(account) => account.address(), + Err(e) => { + info!( + "test: coin_transfer part: setup ERROR: {}, with error {:?}", + ERROR_COULD_NOT_CREATE_ACCOUNT, e + ); + return Err(e.into()); + }, + }; + + Ok((client, account, receiver)) +} + +async fn transfer_coins( + client: &Client, + coin_client: &CoinClient<'_>, + account: &mut LocalAccount, + receiver: AccountAddress, +) -> Result { + // create transaction + let pending_txn = match coin_client + .transfer(account, receiver, TRANSFER_AMOUNT, None) + .await + { + Ok(pending_txn) => pending_txn, + Err(e) => { + info!( + "test: coin_transfer part: transfer_coins ERROR: {}, with error {:?}", + ERROR_COULD_NOT_CREATE_TRANSACTION, e + ); + return Err(e.into()); + }, + }; + + // wait and get version + let response = match client.wait_for_transaction(&pending_txn).await { + Ok(response) => response, + Err(e) => { + info!( + "test: coin_transfer part: transfer_coins ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FINISH_TRANSACTION, e + ); + return Err(e.into()); + }, + }; + + let version = match response.inner().version() { + Some(version) => version, + None => { + info!( + "test: coin_transfer part: transfer_coins ERROR: {}", + ERROR_NO_VERSION + ); + return Err(anyhow!(ERROR_NO_VERSION).into()); + }, + }; + + // return version + Ok(version) +} + +async fn check_account_balance( + client: &Client, + address: AccountAddress, +) -> Result<(), TestFailure> { + // expected + let expected = U64(TRANSFER_AMOUNT); + + // actual + let actual = match client.get_account_balance(address).await { + Ok(response) => response.into_inner().coin.value, + Err(e) => { + info!( + "test: coin_transfer part: check_account_balance ERROR: {}, with error {:?}", + ERROR_NO_BALANCE, e + ); + return Err(e.into()); + }, + }; + + // compare + if expected != actual { + info!( + "test: coin_transfer part: check_account_balance FAIL: {}, expected {:?}, got {:?}", + FAIL_WRONG_BALANCE, expected, actual + ); + return Err(TestFailure::Fail(FAIL_WRONG_BALANCE)); + } + + Ok(()) +} + +async fn check_account_balance_at_version( + client: &Client, + address: AccountAddress, + transaction_version: u64, +) -> Result<(), TestFailure> { + // expected + let expected = U64(0); + + // actual + let actual = match client + .get_account_balance_at_version(address, transaction_version - 1) + .await + { + Ok(response) => response.into_inner().coin.value, + Err(e) => { + info!( + "test: coin_transfer part: check_account_balance_at_version ERROR: {}, with error {:?}", + ERROR_NO_BALANCE, e + ); + return Err(e.into()); + }, + }; + + // compare + if expected != actual { + info!( + "test: coin_transfer part: check_account_balance_at_version FAIL: {}, expected {:?}, got {:?}", + FAIL_WRONG_BALANCE_AT_VERSION, expected, actual + ); + return Err(TestFailure::Fail(FAIL_WRONG_BALANCE_AT_VERSION)); + } + + Ok(()) +} diff --git a/crates/aptos-api-tester/src/tests/mod.rs b/crates/aptos-api-tester/src/tests/mod.rs new file mode 100644 index 0000000000000..e3251b0aeda8c --- /dev/null +++ b/crates/aptos-api-tester/src/tests/mod.rs @@ -0,0 +1,6 @@ +// Copyright © Aptos Foundation + +pub mod coin_transfer; +pub mod new_account; +pub mod nft_transfer; +pub mod publish_module; diff --git a/crates/aptos-api-tester/src/tests/new_account.rs b/crates/aptos-api-tester/src/tests/new_account.rs new file mode 100644 index 0000000000000..4c1a557f00b4d --- /dev/null +++ b/crates/aptos-api-tester/src/tests/new_account.rs @@ -0,0 +1,141 @@ +// Copyright © Aptos Foundation + +use crate::{ + fail_message::{ + ERROR_COULD_NOT_CREATE_ACCOUNT, ERROR_COULD_NOT_FUND_ACCOUNT, ERROR_NO_ACCOUNT_DATA, + ERROR_NO_BALANCE, FAIL_WRONG_ACCOUNT_DATA, FAIL_WRONG_BALANCE, + }, + persistent_check, + utils::{create_account, get_client, get_faucet_client, NetworkName, TestFailure}, +}; +use aptos_api_types::U64; +use aptos_logger::info; +use aptos_rest_client::{Account, Client, FaucetClient}; +use aptos_sdk::types::LocalAccount; +use aptos_types::account_address::AccountAddress; + +static FUND_AMOUNT: u64 = 1_000_000; + +/// Tests new account creation. Checks that: +/// - account data exists +/// - account balance reflects funded amount +pub async fn test(network_name: NetworkName) -> Result<(), TestFailure> { + // setup + let (client, faucet_client, account) = setup(network_name).await?; + + // check account data persistently + persistent_check::account("check_account_data", check_account_data, &client, &account).await?; + + // fund account + fund(&faucet_client, account.address()).await?; + + // check account balance persistently + persistent_check::address( + "check_account_balance", + check_account_balance, + &client, + account.address(), + ) + .await?; + + Ok(()) +} + +// Steps + +async fn setup( + network_name: NetworkName, +) -> Result<(Client, FaucetClient, LocalAccount), TestFailure> { + // spin up clients + let client = get_client(network_name); + let faucet_client = get_faucet_client(network_name); + + // create account + let account = match create_account(&faucet_client).await { + Ok(account) => account, + Err(e) => { + info!( + "test: new_account part: setup ERROR: {}, with error {:?}", + ERROR_COULD_NOT_CREATE_ACCOUNT, e + ); + return Err(e.into()); + }, + }; + + Ok((client, faucet_client, account)) +} + +async fn fund(faucet_client: &FaucetClient, address: AccountAddress) -> Result<(), TestFailure> { + // fund account + if let Err(e) = faucet_client.fund(address, FUND_AMOUNT).await { + info!( + "test: new_account part: fund ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FUND_ACCOUNT, e + ); + return Err(e.into()); + } + + Ok(()) +} + +async fn check_account_data(client: &Client, account: &LocalAccount) -> Result<(), TestFailure> { + // expected + let expected = Account { + authentication_key: account.authentication_key(), + sequence_number: account.sequence_number(), + }; + + // actual + let actual = match client.get_account(account.address()).await { + Ok(response) => response.into_inner(), + Err(e) => { + info!( + "test: new_account part: check_account_data ERROR: {}, with error {:?}", + ERROR_NO_ACCOUNT_DATA, e + ); + return Err(e.into()); + }, + }; + + // compare + if expected != actual { + info!( + "test: new_account part: check_account_data FAIL: {}, expected {:?}, got {:?}", + FAIL_WRONG_ACCOUNT_DATA, expected, actual + ); + return Err(TestFailure::Fail(FAIL_WRONG_ACCOUNT_DATA)); + } + + Ok(()) +} + +async fn check_account_balance( + client: &Client, + address: AccountAddress, +) -> Result<(), TestFailure> { + // expected + let expected = U64(FUND_AMOUNT); + + // actual + let actual = match client.get_account_balance(address).await { + Ok(response) => response.into_inner().coin.value, + Err(e) => { + info!( + "test: new_account part: check_account_balance ERROR: {}, with error {:?}", + ERROR_NO_BALANCE, e + ); + return Err(e.into()); + }, + }; + + // compare + if expected != actual { + info!( + "test: new_account part: check_account_balance FAIL: {}, expected {:?}, got {:?}", + FAIL_WRONG_BALANCE, expected, actual + ); + return Err(TestFailure::Fail(FAIL_WRONG_BALANCE)); + } + + Ok(()) +} diff --git a/crates/aptos-api-tester/src/tests/nft_transfer.rs b/crates/aptos-api-tester/src/tests/nft_transfer.rs new file mode 100644 index 0000000000000..541966d703d42 --- /dev/null +++ b/crates/aptos-api-tester/src/tests/nft_transfer.rs @@ -0,0 +1,472 @@ +// Copyright © Aptos Foundation + +use crate::{ + fail_message::{ + ERROR_COULD_NOT_CREATE_TRANSACTION, ERROR_COULD_NOT_FINISH_TRANSACTION, + ERROR_COULD_NOT_FUND_ACCOUNT, ERROR_NO_COLLECTION_DATA, ERROR_NO_TOKEN_BALANCE, + ERROR_NO_TOKEN_DATA, FAIL_WRONG_COLLECTION_DATA, FAIL_WRONG_TOKEN_BALANCE, + FAIL_WRONG_TOKEN_DATA, + }, + persistent_check, + utils::{create_and_fund_account, get_client, get_faucet_client, NetworkName, TestFailure}, +}; +use aptos_api_types::U64; +use aptos_logger::info; +use aptos_rest_client::Client; +use aptos_sdk::{ + token_client::{ + CollectionData, CollectionMutabilityConfig, RoyaltyOptions, TokenClient, TokenData, + TokenMutabilityConfig, + }, + types::LocalAccount, +}; +use aptos_types::account_address::AccountAddress; + +static COLLECTION_NAME: &str = "test collection"; +static TOKEN_NAME: &str = "test token"; +static TOKEN_SUPPLY: u64 = 10; +static OFFER_AMOUNT: u64 = 2; + +/// Tests nft transfer. Checks that: +/// - collection data exists +/// - token data exists +/// - token balance reflects transferred amount +pub async fn test(network_name: NetworkName) -> Result<(), TestFailure> { + // setup + let (client, mut account, mut receiver) = setup(network_name).await?; + let token_client = TokenClient::new(&client); + + // create collection + create_collection(&client, &token_client, &mut account).await?; + + // check collection metadata persistently + persistent_check::token_address( + "check_collection_metadata", + check_collection_metadata, + &token_client, + account.address(), + ) + .await?; + + // create token + create_token(&client, &token_client, &mut account).await?; + + // check token metadata persistently + persistent_check::token_address( + "check_token_metadata", + check_token_metadata, + &token_client, + account.address(), + ) + .await?; + + // offer token + offer_token(&client, &token_client, &mut account, receiver.address()).await?; + + // check senders balance persistently + persistent_check::token_address( + "check_sender_balance", + check_sender_balance, + &token_client, + account.address(), + ) + .await?; + + // claim token + claim_token(&client, &token_client, &mut receiver, account.address()).await?; + + // check receivers balance persistently + persistent_check::token_address_address( + "check_receiver_balance", + check_receiver_balance, + &token_client, + receiver.address(), + account.address(), + ) + .await?; + + Ok(()) +} + +// Steps + +async fn setup( + network_name: NetworkName, +) -> Result<(Client, LocalAccount, LocalAccount), TestFailure> { + // spin up clients + let client = get_client(network_name); + let faucet_client = get_faucet_client(network_name); + + // create account + let account = match create_and_fund_account(&faucet_client).await { + Ok(account) => account, + Err(e) => { + info!( + "test: nft_transfer part: setup ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FUND_ACCOUNT, e + ); + return Err(e.into()); + }, + }; + + // create receiver + let receiver = match create_and_fund_account(&faucet_client).await { + Ok(receiver) => receiver, + Err(e) => { + info!( + "test: nft_transfer part: setup ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FUND_ACCOUNT, e + ); + return Err(e.into()); + }, + }; + + Ok((client, account, receiver)) +} + +async fn create_collection( + client: &Client, + token_client: &TokenClient<'_>, + account: &mut LocalAccount, +) -> Result<(), TestFailure> { + // set up collection data + let collection_data = collection_data(); + + // create transaction + let pending_txn = match token_client + .create_collection( + account, + &collection_data.name, + &collection_data.description, + &collection_data.uri, + collection_data.maximum.into(), + None, + ) + .await + { + Ok(txn) => txn, + Err(e) => { + info!( + "test: nft_transfer part: create_collection ERROR: {}, with error {:?}", + ERROR_COULD_NOT_CREATE_TRANSACTION, e + ); + return Err(e.into()); + }, + }; + + // wait for transaction to finish + if let Err(e) = client.wait_for_transaction(&pending_txn).await { + info!( + "test: nft_transfer part: create_collection ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FINISH_TRANSACTION, e + ); + return Err(e.into()); + }; + + Ok(()) +} + +async fn check_collection_metadata( + token_client: &TokenClient<'_>, + address: AccountAddress, +) -> Result<(), TestFailure> { + // set up collection data + let collection_data = collection_data(); + + // expected + let expected = collection_data.clone(); + + // actual + let actual = match token_client + .get_collection_data(address, &collection_data.name) + .await + { + Ok(data) => data, + Err(e) => { + info!( + "test: nft_transfer part: check_collection_metadata ERROR: {}, with error {:?}", + ERROR_NO_COLLECTION_DATA, e + ); + return Err(e.into()); + }, + }; + + // compare + if expected != actual { + info!( + "test: nft_transfer part: check_collection_metadata FAIL: {}, expected {:?}, got {:?}", + FAIL_WRONG_COLLECTION_DATA, expected, actual + ); + return Err(TestFailure::Fail(FAIL_WRONG_COLLECTION_DATA)); + } + + Ok(()) +} + +async fn create_token( + client: &Client, + token_client: &TokenClient<'_>, + account: &mut LocalAccount, +) -> Result<(), TestFailure> { + // set up token data + let token_data = token_data(account.address()); + + // create transaction + let pending_txn = match token_client + .create_token( + account, + COLLECTION_NAME, + &token_data.name, + &token_data.description, + token_data.supply.into(), + &token_data.uri, + token_data.maximum.into(), + None, + None, + ) + .await + { + Ok(txn) => txn, + Err(e) => { + info!( + "test: nft_transfer part: create_token ERROR: {}, with error {:?}", + ERROR_COULD_NOT_CREATE_TRANSACTION, e + ); + return Err(e.into()); + }, + }; + + // wait for transaction to finish + if let Err(e) = client.wait_for_transaction(&pending_txn).await { + info!( + "test: nft_transfer part: create_token ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FINISH_TRANSACTION, e + ); + return Err(e.into()); + }; + + Ok(()) +} + +async fn check_token_metadata( + token_client: &TokenClient<'_>, + address: AccountAddress, +) -> Result<(), TestFailure> { + // set up token data + let token_data = token_data(address); + + // expected + let expected = token_data; + + // actual + let actual = match token_client + .get_token_data(address, COLLECTION_NAME, TOKEN_NAME) + .await + { + Ok(data) => data, + Err(e) => { + info!( + "test: nft_transfer part: check_token_metadata ERROR: {}, with error {:?}", + ERROR_NO_TOKEN_DATA, e + ); + return Err(e.into()); + }, + }; + + // compare + if expected != actual { + info!( + "test: nft_transfer part: check_token_metadata FAIL: {}, expected {:?}, got {:?}", + FAIL_WRONG_TOKEN_DATA, expected, actual + ); + return Err(TestFailure::Fail(FAIL_WRONG_TOKEN_DATA)); + } + + Ok(()) +} + +async fn offer_token( + client: &Client, + token_client: &TokenClient<'_>, + account: &mut LocalAccount, + receiver: AccountAddress, +) -> Result<(), TestFailure> { + // create transaction + let pending_txn = match token_client + .offer_token( + account, + receiver, + account.address(), + COLLECTION_NAME, + TOKEN_NAME, + OFFER_AMOUNT, + None, + None, + ) + .await + { + Ok(txn) => txn, + Err(e) => { + info!( + "test: nft_transfer part: offer_token ERROR: {}, with error {:?}", + ERROR_COULD_NOT_CREATE_TRANSACTION, e + ); + return Err(e.into()); + }, + }; + + // wait for transaction to finish + if let Err(e) = client.wait_for_transaction(&pending_txn).await { + info!( + "test: nft_transfer part: offer_token ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FINISH_TRANSACTION, e + ); + return Err(e.into()); + }; + + Ok(()) +} + +async fn check_sender_balance( + token_client: &TokenClient<'_>, + address: AccountAddress, +) -> Result<(), TestFailure> { + check_token_balance( + token_client, + address, + address, + U64(TOKEN_SUPPLY - OFFER_AMOUNT), + "check_sender_balance", + ) + .await +} + +async fn claim_token( + client: &Client, + token_client: &TokenClient<'_>, + receiver: &mut LocalAccount, + sender: AccountAddress, +) -> Result<(), TestFailure> { + // create transaction + let pending_txn = match token_client + .claim_token( + receiver, + sender, + sender, + COLLECTION_NAME, + TOKEN_NAME, + None, + None, + ) + .await + { + Ok(txn) => txn, + Err(e) => { + info!( + "test: nft_transfer part: claim_token ERROR: {}, with error {:?}", + ERROR_COULD_NOT_CREATE_TRANSACTION, e + ); + return Err(e.into()); + }, + }; + + // wait for transaction to finish + if let Err(e) = client.wait_for_transaction(&pending_txn).await { + info!( + "test: nft_transfer part: claim_token ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FINISH_TRANSACTION, e + ); + return Err(e.into()); + }; + + Ok(()) +} + +async fn check_receiver_balance( + token_client: &TokenClient<'_>, + address: AccountAddress, + creator: AccountAddress, +) -> Result<(), TestFailure> { + check_token_balance( + token_client, + address, + creator, + U64(OFFER_AMOUNT), + "check_receiver_balance", + ) + .await +} + +// Utils + +fn collection_data() -> CollectionData { + CollectionData { + name: COLLECTION_NAME.to_string(), + description: "collection description".to_string(), + uri: "collection uri".to_string(), + maximum: U64(1000), + mutability_config: CollectionMutabilityConfig { + description: false, + maximum: false, + uri: false, + }, + } +} + +fn token_data(address: AccountAddress) -> TokenData { + TokenData { + name: TOKEN_NAME.to_string(), + description: "token description".to_string(), + uri: "token uri".to_string(), + maximum: U64(1000), + mutability_config: TokenMutabilityConfig { + description: false, + maximum: false, + properties: false, + royalty: false, + uri: false, + }, + supply: U64(TOKEN_SUPPLY), + royalty: RoyaltyOptions { + // change this when you use! + payee_address: address, + royalty_points_denominator: U64(0), + royalty_points_numerator: U64(0), + }, + largest_property_version: U64(0), + } +} + +async fn check_token_balance( + token_client: &TokenClient<'_>, + address: AccountAddress, + creator: AccountAddress, + expected: U64, + part: &str, +) -> Result<(), TestFailure> { + // actual + let actual = match token_client + .get_token(address, creator, COLLECTION_NAME, TOKEN_NAME) + .await + { + Ok(data) => data.amount, + Err(e) => { + info!( + "test: nft_transfer part: {} ERROR: {}, with error {:?}", + part, ERROR_NO_TOKEN_BALANCE, e + ); + return Err(e.into()); + }, + }; + + // compare + if expected != actual { + info!( + "test: nft_transfer part: {} FAIL: {}, expected {:?}, got {:?}", + part, FAIL_WRONG_TOKEN_BALANCE, expected, actual + ); + return Err(TestFailure::Fail(FAIL_WRONG_TOKEN_BALANCE)); + } + + Ok(()) +} diff --git a/crates/aptos-api-tester/src/tests/publish_module.rs b/crates/aptos-api-tester/src/tests/publish_module.rs new file mode 100644 index 0000000000000..08e4653e357e7 --- /dev/null +++ b/crates/aptos-api-tester/src/tests/publish_module.rs @@ -0,0 +1,314 @@ +// Copyright © Aptos Foundation + +use crate::{ + fail_message::{ + ERROR_COULD_NOT_BUILD_PACKAGE, ERROR_COULD_NOT_CREATE_TRANSACTION, + ERROR_COULD_NOT_FINISH_TRANSACTION, ERROR_COULD_NOT_FUND_ACCOUNT, + ERROR_COULD_NOT_SERIALIZE, ERROR_NO_BYTECODE, ERROR_NO_MESSAGE, ERROR_NO_METADATA, + ERROR_NO_MODULE, FAIL_WRONG_MESSAGE, FAIL_WRONG_MODULE, + }, + persistent_check, + utils::{create_and_fund_account, get_client, get_faucet_client, NetworkName, TestFailure}, +}; +use anyhow::{anyhow, Result}; +use aptos_api_types::HexEncodedBytes; +use aptos_cached_packages::aptos_stdlib::EntryFunctionCall; +use aptos_framework::{BuildOptions, BuiltPackage}; +use aptos_logger::info; +use aptos_rest_client::Client; +use aptos_sdk::{ + bcs, + token_client::{build_and_submit_transaction, TransactionOptions}, + types::LocalAccount, +}; +use aptos_types::{ + account_address::AccountAddress, + transaction::{EntryFunction, TransactionPayload}, +}; +use move_core_types::{ident_str, language_storage::ModuleId}; +use std::{collections::BTreeMap, path::PathBuf}; + +static MODULE_NAME: &str = "message"; +static MESSAGE: &str = "test message"; + +pub async fn test(network_name: NetworkName) -> Result<(), TestFailure> { + // setup + let (client, mut account) = setup(network_name).await?; + + // build module + let package = build_module(account.address()).await?; + + // publish module + let blob = publish_module(&client, &mut account, package).await?; + + // check module data persistently + persistent_check::address_bytes( + "check_module_data", + check_module_data, + &client, + account.address(), + &blob, + ) + .await?; + + // set message + set_message(&client, &mut account).await?; + + // check message persistently + persistent_check::address("check_message", check_message, &client, account.address()).await?; + + Ok(()) +} + +// Steps + +async fn setup(network_name: NetworkName) -> Result<(Client, LocalAccount), TestFailure> { + // spin up clients + let client = get_client(network_name); + let faucet_client = get_faucet_client(network_name); + + // create account + let account = match create_and_fund_account(&faucet_client).await { + Ok(account) => account, + Err(e) => { + info!( + "test: publish_module part: setup ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FUND_ACCOUNT, e + ); + return Err(e.into()); + }, + }; + + Ok((client, account)) +} + +async fn build_module(address: AccountAddress) -> Result { + // get file to compile + let move_dir = PathBuf::from("./aptos-move/move-examples/hello_blockchain"); + + // insert address + let mut named_addresses: BTreeMap = BTreeMap::new(); + named_addresses.insert("hello_blockchain".to_string(), address); + + // build options + let options = BuildOptions { + named_addresses, + ..BuildOptions::default() + }; + + // build module + let package = match BuiltPackage::build(move_dir, options) { + Ok(package) => package, + Err(e) => { + info!( + "test: publish_module part: publish_module ERROR: {}, with error {:?}", + ERROR_COULD_NOT_BUILD_PACKAGE, e + ); + return Err(e.into()); + }, + }; + + Ok(package) +} + +async fn publish_module( + client: &Client, + account: &mut LocalAccount, + package: BuiltPackage, +) -> Result { + // get bytecode + let blobs = package.extract_code(); + + // get metadata + let metadata = match package.extract_metadata() { + Ok(data) => data, + Err(e) => { + info!( + "test: publish_module part: publish_module ERROR: {}, with error {:?}", + ERROR_NO_METADATA, e + ); + return Err(e.into()); + }, + }; + + // serialize metadata + let metadata_serialized = match bcs::to_bytes(&metadata) { + Ok(data) => data, + Err(e) => { + info!( + "test: publish_module part: publish_module ERROR: {}, with error {:?}", + ERROR_COULD_NOT_SERIALIZE, e + ); + return Err(anyhow!(e).into()); + }, + }; + + // create payload + let payload: aptos_types::transaction::TransactionPayload = + EntryFunctionCall::CodePublishPackageTxn { + metadata_serialized, + code: blobs.clone(), + } + .encode(); + + // create transaction + let pending_txn = + match build_and_submit_transaction(client, account, payload, TransactionOptions::default()) + .await + { + Ok(txn) => txn, + Err(e) => { + info!( + "test: publish_module part: publish_module ERROR: {}, with error {:?}", + ERROR_COULD_NOT_CREATE_TRANSACTION, e + ); + return Err(e.into()); + }, + }; + + // wait for transaction to finish + if let Err(e) = client.wait_for_transaction(&pending_txn).await { + info!( + "test: publish_module part: publish_module ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FINISH_TRANSACTION, e + ); + return Err(e.into()); + }; + + // get blob for later comparison + let blob = match blobs.get(0) { + Some(bytecode) => HexEncodedBytes::from(bytecode.clone()), + None => { + info!( + "test: publish_module part: publish_module ERROR: {}", + ERROR_NO_BYTECODE + ); + return Err(anyhow!(ERROR_NO_BYTECODE).into()); + }, + }; + + Ok(blob) +} + +async fn check_module_data( + client: &Client, + address: AccountAddress, + expected: &HexEncodedBytes, +) -> Result<(), TestFailure> { + // actual + let response = match client.get_account_module(address, MODULE_NAME).await { + Ok(response) => response, + Err(e) => { + info!( + "test: publish_module part: check_module_data ERROR: {}, with error {:?}", + ERROR_NO_MODULE, e + ); + return Err(e.into()); + }, + }; + let actual = &response.inner().bytecode; + + // compare + if expected != actual { + info!( + "test: publish_module part: check_module_data FAIL: {}, expected {:?}, got {:?}", + FAIL_WRONG_MODULE, expected, actual + ); + return Err(TestFailure::Fail(FAIL_WRONG_MODULE)); + } + + Ok(()) +} + +async fn set_message(client: &Client, account: &mut LocalAccount) -> Result<(), TestFailure> { + // set up message + let message = match bcs::to_bytes(MESSAGE) { + Ok(data) => data, + Err(e) => { + info!( + "test: publish_module part: set_message ERROR: {}, with error {:?}", + ERROR_COULD_NOT_SERIALIZE, e + ); + return Err(anyhow!(e).into()); + }, + }; + + // create payload + let payload = TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new(account.address(), ident_str!(MODULE_NAME).to_owned()), + ident_str!("set_message").to_owned(), + vec![], + vec![message], + )); + + // create transaction + let pending_txn = + match build_and_submit_transaction(client, account, payload, TransactionOptions::default()) + .await + { + Ok(txn) => txn, + Err(e) => { + info!( + "test: publish_module part: set_message ERROR: {}, with error {:?}", + ERROR_COULD_NOT_CREATE_TRANSACTION, e + ); + return Err(e.into()); + }, + }; + + // wait for transaction to finish + if let Err(e) = client.wait_for_transaction(&pending_txn).await { + info!( + "test: publish_module part: set_message ERROR: {}, with error {:?}", + ERROR_COULD_NOT_FINISH_TRANSACTION, e + ); + return Err(e.into()); + }; + + Ok(()) +} + +async fn check_message(client: &Client, address: AccountAddress) -> Result<(), TestFailure> { + // expected + let expected = MESSAGE.to_string(); + + // actual + let actual = match get_message(client, address).await { + Some(message) => message, + None => { + info!( + "test: publish_module part: check_message ERROR: {}", + ERROR_NO_MESSAGE + ); + return Err(anyhow!(ERROR_NO_MESSAGE).into()); + }, + }; + + // compare + if expected != actual { + info!( + "test: publish_module part: check_message FAIL: {}, expected {:?}, got {:?}", + FAIL_WRONG_MESSAGE, expected, actual + ); + return Err(TestFailure::Fail(FAIL_WRONG_MESSAGE)); + } + + Ok(()) +} + +// Utils + +async fn get_message(client: &Client, address: AccountAddress) -> Option { + let resource = match client + .get_account_resource( + address, + format!("{}::message::MessageHolder", address.to_hex_literal()).as_str(), + ) + .await + { + Ok(response) => response.into_inner()?, + Err(_) => return None, + }; + + Some(resource.data.get("message")?.as_str()?.to_owned()) +} diff --git a/crates/aptos-api-tester/src/testsetups.rs b/crates/aptos-api-tester/src/testsetups.rs deleted file mode 100644 index 78ff4f5cb5153..0000000000000 --- a/crates/aptos-api-tester/src/testsetups.rs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright © Aptos Foundation - -use crate::{ - tests::{test_cointransfer, test_newaccount, test_nfttransfer, test_publishmodule}, - utils::{ - create_account, create_and_fund_account, get_client, get_faucet_client, NetworkName, - TestFailure, - }, -}; -use anyhow::Result; -use aptos_sdk::{coin_client::CoinClient, token_client::TokenClient}; - -pub async fn setup_and_run_newaccount(network_name: NetworkName) -> Result<(), TestFailure> { - // spin up clients - let client = get_client(network_name); - let faucet_client = get_faucet_client(network_name); - - // create and fund account - let account = create_and_fund_account(&faucet_client).await?; - - // run test - test_newaccount(&client, &account, 100_000_000).await -} - -pub async fn setup_and_run_cointransfer(network_name: NetworkName) -> Result<(), TestFailure> { - // spin up clients - let client = get_client(network_name); - let faucet_client = get_faucet_client(network_name); - let coin_client = CoinClient::new(&client); - - // create and fund accounts - let mut account = create_and_fund_account(&faucet_client).await?; - let receiver = create_account(&faucet_client).await?; - - // run test - test_cointransfer( - &client, - &coin_client, - &mut account, - receiver.address(), - 1_000, - ) - .await -} - -pub async fn setup_and_run_nfttransfer(network_name: NetworkName) -> Result<(), TestFailure> { - // spin up clients - let client = get_client(network_name); - let faucet_client = get_faucet_client(network_name); - let token_client = TokenClient::new(&client); - - // create and fund accounts - let mut account = create_and_fund_account(&faucet_client).await?; - let mut receiver = create_and_fund_account(&faucet_client).await?; - - // run test - test_nfttransfer(&client, &token_client, &mut account, &mut receiver).await -} - -pub async fn setup_and_run_publishmodule(network_name: NetworkName) -> Result<(), TestFailure> { - // spin up clients - let client = get_client(network_name); - let faucet_client = get_faucet_client(network_name); - - // create and fund accounts - let mut account = create_and_fund_account(&faucet_client).await?; - - // run test - test_publishmodule(&client, &mut account).await -} diff --git a/crates/aptos-api-tester/src/utils.rs b/crates/aptos-api-tester/src/utils.rs index d0c482d26eff2..eb85d94578002 100644 --- a/crates/aptos-api-tester/src/utils.rs +++ b/crates/aptos-api-tester/src/utils.rs @@ -2,10 +2,10 @@ use crate::counters::{test_error, test_fail, test_latency, test_success}; use anyhow::Result; -use aptos_logger::info; use aptos_rest_client::{error::RestError, Client, FaucetClient}; use aptos_sdk::types::LocalAccount; use once_cell::sync::Lazy; +use std::env; use url::Url; // network urls @@ -127,7 +127,12 @@ pub fn get_client(network_name: NetworkName) -> Client { pub fn get_faucet_client(network_name: NetworkName) -> FaucetClient { match network_name { NetworkName::Testnet => { - FaucetClient::new(TESTNET_FAUCET_URL.clone(), TESTNET_NODE_URL.clone()) + let faucet_client = + FaucetClient::new(TESTNET_FAUCET_URL.clone(), TESTNET_NODE_URL.clone()); + match env::var("TESTNET_FAUCET_CLIENT_TOKEN") { + Ok(token) => faucet_client.with_auth_token(token), + Err(_) => faucet_client, + } }, NetworkName::Devnet => { FaucetClient::new(DEVNET_FAUCET_URL.clone(), DEVNET_NODE_URL.clone()) @@ -139,7 +144,6 @@ pub fn get_faucet_client(network_name: NetworkName) -> FaucetClient { pub async fn create_account(faucet_client: &FaucetClient) -> Result { let account = LocalAccount::generate(&mut rand::rngs::OsRng); faucet_client.create_account(account.address()).await?; - info!("{:?}", account.address()); Ok(account) } @@ -148,7 +152,6 @@ pub async fn create_account(faucet_client: &FaucetClient) -> Result Result { let account = LocalAccount::generate(&mut rand::rngs::OsRng); faucet_client.fund(account.address(), 100_000_000).await?; - info!("{:?}", account.address()); Ok(account) } diff --git a/crates/aptos-rest-client/src/faucet.rs b/crates/aptos-rest-client/src/faucet.rs index fba4b04ebc599..d3920627bb570 100644 --- a/crates/aptos-rest-client/src/faucet.rs +++ b/crates/aptos-rest-client/src/faucet.rs @@ -5,13 +5,14 @@ use crate::{error::FaucetClientError, Client, Result}; use aptos_types::transaction::SignedTransaction; use move_core_types::account_address::AccountAddress; -use reqwest::{Client as ReqwestClient, Url}; +use reqwest::{Client as ReqwestClient, Response, Url}; use std::time::Duration; pub struct FaucetClient { faucet_url: Url, inner: ReqwestClient, rest_client: Client, + token: Option, } impl FaucetClient { @@ -23,6 +24,7 @@ impl FaucetClient { .build() .unwrap(), rest_client: Client::new(rest_url), + token: None, } } @@ -39,9 +41,16 @@ impl FaucetClient { // versioned API however, so we just set it to `/`. .version_path_base("/".to_string()) .unwrap(), + token: None, } } + // Set auth token. + pub fn with_auth_token(mut self, token: String) -> Self { + self.token = Some(token); + self + } + /// Create an account with zero balance. pub async fn create_account(&self, address: AccountAddress) -> Result<()> { let mut url = self.faucet_url.clone(); @@ -49,13 +58,7 @@ impl FaucetClient { let query = format!("auth_key={}&amount=0&return_txns=true", address); url.set_query(Some(&query)); - let response = self - .inner - .post(url) - .header("content-length", 0) - .send() - .await - .map_err(FaucetClientError::request)?; + let response = self.build_and_submit_request(url).await?; let status_code = response.status(); let body = response.text().await.map_err(FaucetClientError::decode)?; if !status_code.is_success() { @@ -83,13 +86,7 @@ impl FaucetClient { // Faucet returns the transaction that creates the account and needs to be waited on before // returning. - let response = self - .inner - .post(url) - .header("content-length", 0) - .send() - .await - .map_err(FaucetClientError::request)?; + let response = self.build_and_submit_request(url).await?; let status_code = response.status(); let body = response.text().await.map_err(FaucetClientError::decode)?; if !status_code.is_success() { @@ -115,4 +112,17 @@ impl FaucetClient { Ok(()) } + + // Helper to carry out requests. + async fn build_and_submit_request(&self, url: Url) -> Result { + // build request + let mut request = self.inner.post(url).header("content-length", 0); + if let Some(token) = &self.token { + request = request.header("Authorization", format!("Bearer {}", token)); + } + + // carry out and return response + let response = request.send().await.map_err(FaucetClientError::request)?; + Ok(response) + } } diff --git a/sdk/src/token_client.rs b/sdk/src/token_client.rs index a6ab58811364a..0d4f05d8470d9 100644 --- a/sdk/src/token_client.rs +++ b/sdk/src/token_client.rs @@ -391,7 +391,7 @@ impl Default for TransactionOptions { } } -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub struct CollectionData { pub name: String, pub description: String, @@ -400,7 +400,7 @@ pub struct CollectionData { pub mutability_config: CollectionMutabilityConfig, } -#[derive(Deserialize, Debug, PartialEq)] +#[derive(Clone, Deserialize, Debug, PartialEq)] pub struct CollectionMutabilityConfig { pub description: bool, pub maximum: bool,