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,