Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polygon test and config #234

Merged
merged 11 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ members = [
"rosetta-types",
"chains/arbitrum/testing/rosetta-testing-arbitrum",
"rosetta-utils",
"chains/polygon/rosetta-testing-polygon",
]
resolver = "2"

Expand Down
22 changes: 22 additions & 0 deletions chains/polygon/rosetta-testing-polygon/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "rosetta-testing-polygon"
version = "0.1.0"
edition = "2021"
license = "MIT"
repository = "https://github.com/analog-labs/chain-connectors"
description = "Polygon rosetta test."

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
alloy-sol-types = { version = "0.6" }
anyhow = "1.0"
ethers = { version = "2.0", default-features = true, features = ["abigen", "rustls", "ws"] }
ethers-solc = "2.0"
hex-literal = "0.4"
rosetta-client.workspace = true
rosetta-config-ethereum.workspace = true
rosetta-core.workspace = true
rosetta-server-ethereum.workspace = true
sha3 = "0.10"
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
290 changes: 290 additions & 0 deletions chains/polygon/rosetta-testing-polygon/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
//! # Polygon Rosetta Server Test Suite
//!
//! This module contains a test suite for an Ethereum Rosetta server implementation
//! specifically designed for interacting with the Polygon network. The code includes
//! tests for network status, account management, and smart contract interaction.
//!
//! ## Features
//!
//! - Network status tests to ensure proper connection and consistency with the Polygon network.
//! - Account tests, including faucet funding, balance retrieval, and error handling.
//! - Smart contract tests covering deployment, event emission, and view function calls.
//!
//! ## Dependencies
//!
//! - `anyhow`: For flexible error handling.
//! - `alloy_sol_types`: Custom types and macros for interacting with Solidity contracts.
//! - `ethers`: Ethereum library for interaction with Ethereum clients.
//! - `ethers_solc`: Integration for compiling Solidity code using the Solc compiler.
//! - `hex_literal`: Macro for creating byte array literals from hexadecimal strings.
//! - `rosetta_client`: Client library for Rosetta API interactions.
//! - `rosetta_config_ethereum`: Configuration for Ethereum Rosetta server.
//! - `rosetta_server_ethereum`: Custom client implementation for interacting with Ethereum.
//! - `sha3`: SHA-3 (Keccak) implementation for hashing.
//! - `tokio`: Asynchronous runtime for running async functions.
//!
//! ## Usage
//!
//! To run the tests, execute the following command:
//!
//! ```sh
//! cargo test --package rosetta-testing-polygon --lib -- tests --nocapture
//! ```
//!
//! Note: The code assumes a local Polygon RPC node running on `ws://127.0.0.1:8546`. Ensure
//! that this endpoint is configured correctly.

#[allow(clippy::ignored_unit_patterns)]
#[cfg(test)]
mod tests {
use alloy_sol_types::{sol, SolCall};
use anyhow::Result;
use ethers::types::H256;

use ethers_solc::{artifacts::Source, CompilerInput, EvmVersion, Solc};
use hex_literal::hex;
use rosetta_client::Wallet;
use rosetta_config_ethereum::{AtBlock, CallResult};
use rosetta_core::BlockchainClient;
use rosetta_server_ethereum::MaybeWsEthereumClient;
use sha3::Digest;
use std::{collections::BTreeMap, future::Future, path::Path};

/// Polygon rpc url
const POLYGON_RPC_WS_URL: &str = "ws://127.0.0.1:8546";

sol! {
interface TestContract {
event AnEvent();
function emitEvent() external;

function identity(bool a) external view returns (bool);
}
}

/// Run the test in another thread while sending txs to force polygon to mine new blocks
/// # Panic
/// Panics if the future panics
async fn run_test<Fut: Future<Output = ()> + Send + 'static>(future: Fut) {
// Guarantee that only one test is incrementing blocks at a time
static LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());

// Run the test in another thread
let test_handler: tokio::task::JoinHandle<()> = tokio::spawn(future);

// Acquire Lock
let guard = LOCK.lock().await;

// Check if the test is finished after acquiring the lock
if test_handler.is_finished() {
// Release lock
drop(guard);

// Now is safe to panic
if let Err(err) = test_handler.await {
std::panic::resume_unwind(err.into_panic());
}
return;
}

// Now is safe to panic
if let Err(err) = test_handler.await {
// Resume the panic on the main task
std::panic::resume_unwind(err.into_panic());
}
}

#[ignore = "No Polygon CI"]
#[tokio::test]
async fn network_status() {
run_test(async move {
let client = MaybeWsEthereumClient::new("polygon", "dev", POLYGON_RPC_WS_URL, None)
.await
.expect("Error creating client");
// Check if the genesis is consistent
let genesis_block = client.genesis_block();
assert_eq!(genesis_block.index, 0);

// Check if the current block is consistent
let current_block = client.current_block().await.unwrap();
if current_block.index > 0 {
assert_ne!(current_block.hash, genesis_block.hash);
} else {
assert_eq!(current_block.hash, genesis_block.hash);
}

// Check if the finalized block is consistent
let finalized_block = client.finalized_block().await.unwrap();
assert!(finalized_block.index >= genesis_block.index);
})
.await;
}

#[ignore = "No Polygon CI"]
#[tokio::test]
async fn test_account() {
run_test(async move {
let client = MaybeWsEthereumClient::new("polygon", "dev", POLYGON_RPC_WS_URL, None)
.await
.expect("Error creating ArbitrumClient");
let wallet =
Wallet::from_config(client.config().clone(), POLYGON_RPC_WS_URL, None, None)
.await
.unwrap();
let value = 10 * u128::pow(10, client.config().currency_decimals);
let _ = wallet.faucet(value).await;
let amount = wallet.balance().await.unwrap();
assert_eq!(amount, value);
})
.await;
}

#[ignore = "No Polygon CI"]
#[tokio::test]
async fn test_construction() {
run_test(async move {
let client = MaybeWsEthereumClient::new("polygon", "dev", POLYGON_RPC_WS_URL, None)
.await
.expect("Error creating ArbitrumClient");
let faucet = 100 * u128::pow(10, client.config().currency_decimals);
let value = u128::pow(10, client.config().currency_decimals);
let alice =
Wallet::from_config(client.config().clone(), POLYGON_RPC_WS_URL, None, None)
.await
.unwrap();
let bob = Wallet::from_config(client.config().clone(), POLYGON_RPC_WS_URL, None, None)
.await
.unwrap();
assert_ne!(alice.public_key(), bob.public_key());

// Alice and bob have no balance
let balance = alice.balance().await.unwrap();
assert_eq!(balance, 0);
let balance = bob.balance().await.unwrap();
assert_eq!(balance, 0);

// Transfer faucets to alice
alice.faucet(faucet).await.unwrap();
let balance = alice.balance().await.unwrap();
assert_eq!(balance, faucet);

// Alice transfers to bob
alice.transfer(bob.account(), value, None, None).await.unwrap();
let amount = bob.balance().await.unwrap();
assert_eq!(amount, value);
})
.await;
}

fn compile_snippet(source: &str) -> Result<Vec<u8>> {
let solc = Solc::default();
let source = format!("contract Contract {{ {source} }}");
let mut sources = BTreeMap::new();
sources.insert(Path::new("contract.sol").into(), Source::new(source));
let input = CompilerInput::with_sources(sources)[0]
.clone()
.evm_version(EvmVersion::Homestead);
let output = solc.compile_exact(&input)?;
let file = output.contracts.get("contract.sol").unwrap();
let contract = file.get("Contract").unwrap();
let bytecode = contract
.evm
.as_ref()
.unwrap()
.bytecode
.as_ref()
.unwrap()
.object
.as_bytes()
.unwrap()
.to_vec();
Ok(bytecode)
}

#[ignore = "No Polygon CI"]
#[tokio::test]
async fn test_smart_contract() {
run_test(async move {
let client = MaybeWsEthereumClient::new("polygon", "dev", POLYGON_RPC_WS_URL, None)
.await
.expect("Error creating ArbitrumClient");
let faucet = 10 * u128::pow(10, client.config().currency_decimals);
let wallet =
Wallet::from_config(client.config().clone(), POLYGON_RPC_WS_URL, None, None)
.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().tx_hash().0;
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, None, None)
.await
.unwrap()
.tx_hash()
.0
};
let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap();
assert_eq!(receipt.logs.len(), 2);
let topic = receipt.logs[0].topics[0];
let expected = H256(sha3::Keccak256::digest("AnEvent()").into());
assert_eq!(topic, expected);
})
.await;
}

#[ignore = "No Polygon CI"]
#[tokio::test]
async fn test_smart_contract_view() {
run_test(async move {
let client = MaybeWsEthereumClient::new("polygon", "dev", POLYGON_RPC_WS_URL, None)
.await
.expect("Error creating ArbitrumClient");
let faucet = 10 * u128::pow(10, client.config().currency_decimals);
let wallet =
Wallet::from_config(client.config().clone(), POLYGON_RPC_WS_URL, None, None)
.await
.unwrap();
wallet.faucet(faucet).await.unwrap();
let bytes = compile_snippet(
r"
function identity(bool a) public view returns (bool) {
return a;
}
",
)
.unwrap();
let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap().tx_hash().0;
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(
hex!("0000000000000000000000000000000000000000000000000000000000000001")
.to_vec()
)
);
})
.await;
}
}
2 changes: 2 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ ignore = [
'RUSTSEC-2021-0064', # Crate `cpuid-bool` has been renamed to `cpufeatures`
'RUSTSEC-2021-0139', # ansi_term is Unmaintained
'RUSTSEC-2022-0093', # related issue: https://github.com/Analog-Labs/chain-connectors/issues/162
'RUSTSEC-2024-0336', # a `close_notify` alert is received during a handshake, `complete_io`does not terminate. https://rustsec.org/advisories/RUSTSEC-2024-0336.html
'RUSTSEC-2024-0332', # https://rustsec.org/advisories/RUSTSEC-2024-0332.html
]

# This section is considered when running `cargo deny check sources`.
Expand Down
Loading