diff --git a/Cargo.lock b/Cargo.lock index ee66f736..3764d7ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6421,9 +6421,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" +checksum = "3b187f0231d56fe41bfb12034819dd2bf336422a5866de41bc3fec4b2e3883e8" [[package]] name = "smol" diff --git a/chains/astar/server/src/lib.rs b/chains/astar/server/src/lib.rs index b59fbef7..11004dc7 100644 --- a/chains/astar/server/src/lib.rs +++ b/chains/astar/server/src/lib.rs @@ -240,7 +240,7 @@ mod tests { use alloy_sol_types::{sol, SolCall}; use ethers_solc::{artifacts::Source, CompilerInput, EvmVersion, Solc}; use rosetta_config_ethereum::{AtBlock, CallResult}; - use rosetta_docker::Env; + use rosetta_docker::{run_test, Env}; use sha3::Digest; use std::{collections::BTreeMap, path::Path}; @@ -307,33 +307,36 @@ mod tests { let config = rosetta_config_astar::config("dev")?; let env = Env::new("astar-smart-contract", config.clone(), client_from_config).await?; - - let faucet = 100 * u128::pow(10, config.currency_decimals); - let wallet = env.ephemeral_wallet().await?; - wallet.faucet(faucet).await?; - - let bytes = compile_snippet( - r" - event AnEvent(); - function emitEvent() public { - emit AnEvent(); - } - ", - )?; - let tx_hash = wallet.eth_deploy_contract(bytes).await?; - let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); - let contract_address = receipt.contract_address.unwrap(); - let tx_hash = { - let data = TestContract::emitEventCall::SELECTOR.to_vec(); - wallet.eth_send_call(contract_address.0, data, 0).await? - }; - let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); - let logs = receipt.logs; - assert_eq!(logs.len(), 1); - let topic = logs[0].topics[0]; - let expected = H256::from_slice(sha3::Keccak256::digest("AnEvent()").as_ref()); - assert_eq!(topic, expected); - env.shutdown().await?; + run_test(env, |env| async move { + let faucet = 100 * u128::pow(10, config.currency_decimals); + let wallet = env.ephemeral_wallet().await.unwrap(); + wallet.faucet(faucet).await.unwrap(); + + let bytes = compile_snippet( + r" + event AnEvent(); + function emitEvent() public { + emit AnEvent(); + } + ", + ) + .unwrap(); + let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap(); + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let contract_address = receipt.contract_address.unwrap(); + let tx_hash = { + let data = TestContract::emitEventCall::SELECTOR.to_vec(); + wallet.eth_send_call(contract_address.0, data, 0).await.unwrap() + }; + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let logs = receipt.logs; + assert_eq!(logs.len(), 1); + let topic = logs[0].topics[0]; + let expected = H256::from_slice(sha3::Keccak256::digest("AnEvent()").as_ref()); + assert_eq!(topic, expected); + Ok(()) + }) + .await; Ok(()) } @@ -341,41 +344,46 @@ mod tests { #[allow(clippy::needless_raw_string_hashes)] async fn test_smart_contract_view() -> Result<()> { let config = rosetta_config_astar::config("dev")?; - let faucet = 100 * u128::pow(10, config.currency_decimals); - - let env = Env::new("astar-smart-contract-view", config, client_from_config).await?; - - let wallet = env.ephemeral_wallet().await?; - wallet.faucet(faucet).await?; - - let bytes = compile_snippet( - r" - function identity(bool a) public view returns (bool) { - return a; - } - ", - )?; - let tx_hash = wallet.eth_deploy_contract(bytes).await?; - let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); - let contract_address = receipt.contract_address.unwrap(); - - let response = { - let call = TestContract::identityCall { a: true }; - wallet - .eth_view_call(contract_address.0, call.abi_encode(), AtBlock::Latest) - .await? - }; - assert_eq!( - response, - CallResult::Success( - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1 - ] - .to_vec() + + let env = Env::new("astar-smart-contract-view", config.clone(), client_from_config).await?; + + run_test(env, |env| async move { + let faucet = 100 * u128::pow(10, config.currency_decimals); + let wallet = env.ephemeral_wallet().await.unwrap(); + wallet.faucet(faucet).await.unwrap(); + + let bytes = compile_snippet( + r" + function identity(bool a) public view returns (bool) { + return a; + } + ", ) - ); - env.shutdown().await?; + .unwrap(); + let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap(); + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let contract_address = receipt.contract_address.unwrap(); + + let response = { + let call = TestContract::identityCall { a: true }; + wallet + .eth_view_call(contract_address.0, call.abi_encode(), AtBlock::Latest) + .await + .unwrap() + }; + assert_eq!( + response, + CallResult::Success( + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1 + ] + .to_vec() + ) + ); + Ok(()) + }) + .await; Ok(()) } } diff --git a/chains/ethereum/server/src/lib.rs b/chains/ethereum/server/src/lib.rs index cc134ad0..aa9f364d 100644 --- a/chains/ethereum/server/src/lib.rs +++ b/chains/ethereum/server/src/lib.rs @@ -218,7 +218,7 @@ mod tests { use ethabi::ethereum_types::H256; use ethers_solc::{artifacts::Source, CompilerInput, EvmVersion, Solc}; use rosetta_config_ethereum::{AtBlock, CallResult}; - use rosetta_docker::Env; + use rosetta_docker::{run_test, Env}; use sha3::Digest; use std::{collections::BTreeMap, path::Path}; @@ -291,78 +291,89 @@ mod tests { #[tokio::test] #[allow(clippy::needless_raw_string_hashes)] async fn test_smart_contract() -> Result<()> { - let config = rosetta_config_ethereum::config("dev")?; + let config = rosetta_config_ethereum::config("dev").unwrap(); let env = Env::new("ethereum-smart-contract", config.clone(), client_from_config).await?; - let faucet = 100 * u128::pow(10, config.currency_decimals); - let wallet = env.ephemeral_wallet().await?; - wallet.faucet(faucet).await?; + run_test(env, |env| async move { + let wallet = env.ephemeral_wallet().await.unwrap(); + + let faucet = 100 * u128::pow(10, config.currency_decimals); + wallet.faucet(faucet).await.unwrap(); - let bytes = compile_snippet( - r" + let bytes = compile_snippet( + r" event AnEvent(); function emitEvent() public { emit AnEvent(); } ", - )?; - let tx_hash = wallet.eth_deploy_contract(bytes).await?; - let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); - let contract_address = receipt.contract_address.unwrap(); - let tx_hash = { - let call = TestContract::emitEventCall {}; - wallet.eth_send_call(contract_address.0, call.abi_encode(), 0).await? - }; - let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); - assert_eq!(receipt.logs.len(), 1); - let topic = receipt.logs[0].topics[0]; - let expected = H256(sha3::Keccak256::digest("AnEvent()").into()); - assert_eq!(topic, expected); - env.shutdown().await?; + ) + .unwrap(); + let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap(); + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let contract_address = receipt.contract_address.unwrap(); + let tx_hash = { + let call = TestContract::emitEventCall {}; + wallet.eth_send_call(contract_address.0, call.abi_encode(), 0).await.unwrap() + }; + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + assert_eq!(receipt.logs.len(), 1); + let topic = receipt.logs[0].topics[0]; + let expected = H256(sha3::Keccak256::digest("AnEvent()").into()); + assert_eq!(topic, expected); + Ok(()) + }) + .await; Ok(()) } #[tokio::test] #[allow(clippy::needless_raw_string_hashes)] async fn test_smart_contract_view() -> Result<()> { - let config = rosetta_config_ethereum::config("dev")?; - - let env = - Env::new("ethereum-smart-contract-view", config.clone(), client_from_config).await?; - - let faucet = 100 * u128::pow(10, config.currency_decimals); - let wallet = env.ephemeral_wallet().await?; - wallet.faucet(faucet).await?; - - let bytes = compile_snippet( - r" - function identity(bool a) public view returns (bool) { - return a; - } - ", - )?; - let tx_hash = wallet.eth_deploy_contract(bytes).await?; - let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); - let contract_address = receipt.contract_address.unwrap(); - - let response = { - let call = TestContract::identityCall { a: true }; - wallet - .eth_view_call(contract_address.0, call.abi_encode(), AtBlock::Latest) - .await? - }; - assert_eq!( - response, - CallResult::Success( - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1 - ] - .to_vec() + let config = rosetta_config_ethereum::config("dev").unwrap(); + let env = Env::new("ethereum-smart-contract-view", config.clone(), client_from_config) + .await + .unwrap(); + + //here is run test function + run_test(env, |env| async move { + let wallet = env.ephemeral_wallet().await.unwrap(); + let faucet = 100 * u128::pow(10, config.currency_decimals); + wallet.faucet(faucet).await.unwrap(); + + let bytes = compile_snippet( + r" + function identity(bool a) public view returns (bool) { + return a; + } + ", ) - ); - env.shutdown().await?; + .unwrap(); + let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap(); + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let contract_address = receipt.contract_address.unwrap(); + + let response = { + let call = TestContract::identityCall { a: true }; + wallet + .eth_view_call(contract_address.0, call.abi_encode(), AtBlock::Latest) + .await + .unwrap() + }; + assert_eq!( + response, + CallResult::Success( + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1 + ] + .to_vec() + ) + ); + Ok(()) + }) + .await; Ok(()) } } diff --git a/rosetta-docker/src/lib.rs b/rosetta-docker/src/lib.rs index 25c8c9cc..a6473f15 100644 --- a/rosetta-docker/src/lib.rs +++ b/rosetta-docker/src/lib.rs @@ -310,10 +310,38 @@ async fn wait_for_http + Send>(url: S, container: &Container) -> R }) } +#[allow(clippy::future_not_send)] +pub async fn run_test(env: Env, cb: F) +where + T: Sync + Send + 'static + rosetta_core::BlockchainClient, + Fut: Future> + Send + 'static, + F: FnOnce(&'static mut Env) -> Fut + Sync + Send, +{ + // Convert the context into a raw pointer + let ptr = Box::into_raw(Box::new(env)); + + // Execute the test and catch any panics + let result = unsafe { + let handler = tokio::spawn(cb(&mut *ptr)); + handler.await + }; + + // Convert the raw pointer back into a context + let env = unsafe { Box::from_raw(ptr) }; + + let _ = Env::shutdown(*env).await; + + // Now is safe to panic + if let Err(err) = result { + // Resume the panic on the main task + std::panic::resume_unwind(err.into_panic()); + } +} + #[cfg(feature = "tests")] pub mod tests { use super::Env; - use anyhow::Result; + use anyhow::{Ok, Result}; use nanoid::nanoid; use rosetta_core::{types::PartialBlockIdentifier, BlockchainClient, BlockchainConfig}; use std::future::Future; @@ -325,7 +353,7 @@ pub mod tests { ) } - #[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)] + #[allow(clippy::missing_panics_doc, clippy::missing_errors_doc, clippy::future_not_send)] pub async fn network_status( start_connector: F, config: BlockchainConfig, @@ -339,43 +367,44 @@ pub mod tests { let env = Env::new(&format!("{env_id}-network-status"), config.clone(), start_connector).await?; - let client = env.node(); - - // Check if the genesis is consistent - let expected_genesis = client.genesis_block().clone(); - let actual_genesis = client - .block(&PartialBlockIdentifier { index: Some(0), hash: None }) - .await? - .block_identifier; - assert_eq!(expected_genesis, actual_genesis); - - // Check if the current block is consistent - let expected_current = client.current_block().await?; - let actual_current = client - .block(&PartialBlockIdentifier { - index: None, - hash: Some(expected_current.hash.clone()), - }) - .await? - .block_identifier; - assert_eq!(expected_current, actual_current); - - // Check if the finalized block is consistent - let expected_finalized = client.finalized_block().await?; - let actual_finalized = client - .block(&PartialBlockIdentifier { - index: None, - hash: Some(expected_finalized.hash.clone()), - }) - .await? - .block_identifier; - assert_eq!(expected_finalized, actual_finalized); - - env.shutdown().await?; + crate::run_test(env, |env| async move { + let client = env.node(); + + // Check if the genesis is consistent + let expected_genesis = client.genesis_block().clone(); + let actual_genesis = client + .block(&PartialBlockIdentifier { index: Some(0), hash: None }) + .await? + .block_identifier; + assert_eq!(expected_genesis, actual_genesis); + // Check if the current block is consistent + let expected_current = client.current_block().await?; + let actual_current = client + .block(&PartialBlockIdentifier { + index: None, + hash: Some(expected_current.hash.clone()), + }) + .await? + .block_identifier; + assert_eq!(expected_current, actual_current); + + // Check if the finalized block is consistent + let expected_finalized = client.finalized_block().await?; + let actual_finalized = client + .block(&PartialBlockIdentifier { + index: None, + hash: Some(expected_finalized.hash.clone()), + }) + .await? + .block_identifier; + assert_eq!(expected_finalized, actual_finalized); + Ok(()) + }) + .await; Ok(()) } - #[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)] + #[allow(clippy::missing_panics_doc, clippy::missing_errors_doc, clippy::future_not_send)] pub async fn account(start_connector: F, config: BlockchainConfig) -> Result<()> where T: BlockchainClient, @@ -384,20 +413,21 @@ pub mod tests { { let env_id = env_id(); let env = Env::new(&format!("{env_id}-account"), config.clone(), start_connector).await?; - - let value = 100 * u128::pow(10, config.currency_decimals); - let wallet = env.ephemeral_wallet().await?; - wallet.faucet(value).await?; - let amount = wallet.balance().await?; - assert_eq!(amount.value, value.to_string()); - assert_eq!(amount.currency, config.currency()); - assert!(amount.metadata.is_none()); - - env.shutdown().await?; + crate::run_test(env, |env| async move { + let value = 100 * u128::pow(10, config.currency_decimals); + let wallet = env.ephemeral_wallet().await?; + wallet.faucet(value).await?; + let amount = wallet.balance().await?; + assert_eq!(amount.value, value.to_string()); + assert_eq!(amount.currency, config.currency()); + assert!(amount.metadata.is_none()); + Ok(()) + }) + .await; Ok(()) } - #[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)] + #[allow(clippy::missing_panics_doc, clippy::missing_errors_doc, clippy::future_not_send)] pub async fn construction(start_connector: F, config: BlockchainConfig) -> Result<()> where T: BlockchainClient, @@ -408,29 +438,31 @@ pub mod tests { let env = Env::new(&format!("{env_id}-construction"), config.clone(), start_connector).await?; - let faucet = 100 * u128::pow(10, config.currency_decimals); - let value = u128::pow(10, config.currency_decimals); - let alice = env.ephemeral_wallet().await?; - let bob = env.ephemeral_wallet().await?; - assert_ne!(alice.public_key(), bob.public_key()); - - // Alice and bob have no balance - let balance = alice.balance().await?; - assert_eq!(balance.value, "0"); - let balance = bob.balance().await?; - assert_eq!(balance.value, "0"); - - // Transfer faucets to alice - alice.faucet(faucet).await?; - let balance = alice.balance().await?; - assert_eq!(balance.value, faucet.to_string()); - - // Alice transfers to bob - alice.transfer(bob.account(), value).await?; - let amount = bob.balance().await?; - assert_eq!(amount.value, value.to_string()); - - env.shutdown().await?; + crate::run_test(env, |env| async move { + let faucet = 100 * u128::pow(10, config.currency_decimals); + let value = u128::pow(10, config.currency_decimals); + let alice = env.ephemeral_wallet().await?; + let bob = env.ephemeral_wallet().await?; + assert_ne!(alice.public_key(), bob.public_key()); + + // Alice and bob have no balance + let balance = alice.balance().await?; + assert_eq!(balance.value, "0"); + let balance = bob.balance().await?; + assert_eq!(balance.value, "0"); + + // Transfer faucets to alice + alice.faucet(faucet).await?; + let balance = alice.balance().await?; + assert_eq!(balance.value, faucet.to_string()); + + // Alice transfers to bob + alice.transfer(bob.account(), value).await?; + let amount = bob.balance().await?; + assert_eq!(amount.value, value.to_string()); + Ok(()) + }) + .await; Ok(()) } }