From 77277836e2bf52fab7cbc2199676ed3fe2b95ead Mon Sep 17 00:00:00 2001 From: ManojJiSharma Date: Thu, 21 Nov 2024 12:05:55 +0530 Subject: [PATCH 1/2] config --- Cargo.lock | 18 +++ Cargo.toml | 1 + chains/ethereum/config/src/lib.rs | 24 ++- chains/ethereum/server/src/lib.rs | 1 + chains/zkevm/Cargo.toml | 21 +++ chains/zkevm/src/lib.rs | 258 ++++++++++++++++++++++++++++++ rosetta-client/src/client.rs | 5 + rosetta-client/src/lib.rs | 3 + rosetta-client/src/tx_builder.rs | 2 +- 9 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 chains/zkevm/Cargo.toml create mode 100644 chains/zkevm/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a8edf127..aea08da8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5976,6 +5976,24 @@ dependencies = [ "tokio", ] +[[package]] +name = "rosetta-testing-zkevm" +version = "0.1.0" +dependencies = [ + "alloy-sol-types 0.8.3", + "anyhow", + "ethers", + "ethers-solc", + "hex-literal", + "rosetta-chain-testing", + "rosetta-client", + "rosetta-config-ethereum", + "rosetta-core", + "rosetta-server-ethereum", + "sha3", + "tokio", +] + [[package]] name = "rosetta-tx-ethereum" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index f00e54fb..c17551c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "chains/avalanche", "chains/rosetta-chain-testing", "chains/base", + "chains/zkevm", ] resolver = "2" diff --git a/chains/ethereum/config/src/lib.rs b/chains/ethereum/config/src/lib.rs index 54bdc8bf..a07d77ad 100644 --- a/chains/ethereum/config/src/lib.rs +++ b/chains/ethereum/config/src/lib.rs @@ -276,7 +276,23 @@ pub fn base_config(network: &str) -> anyhow::Result { "mainnet" => ("mainnet", 8453, false), _ => anyhow::bail!("unsupported network: {}", network), }; - Ok(evm_config("avalanche", network, "AVAX", bip44_id, is_dev)) + Ok(evm_config("base", network, "ETH", bip44_id, is_dev)) +} + +/// Retrieve the [`BlockchainConfig`] from the provided polygon-zkevm `network` +/// +/// # Errors +/// Returns `Err` if the network is not supported +pub fn zkevm_config(network: &str) -> anyhow::Result { + // All available networks are listed here: + let (network, bip44_id, is_dev) = match network { + "dev" => ("dev", 1, true), + "testnet" => ("sepolia", 1442, true), + "cardona" => ("sepolia", 2442, true), + "mainnet" => ("mainnet", 1101, false), + _ => anyhow::bail!("unsupported network: {}", network), + }; + Ok(evm_config("zkevm", network, "ETH", bip44_id, is_dev)) } /// Retrieve the [`BlockchainConfig`] from the provided ethereum `network` @@ -320,6 +336,12 @@ pub fn config(network: &str) -> anyhow::Result { "base" => return base_config("mainnet"), "base-sepolia" => return base_config("fuji"), + // zkevm + "zkevm-local" => return zkevm_config("dev"), + "zkevm" => return zkevm_config("mainnet"), + "zkevm-testnet" => return zkevm_config("testnet"), + "zkevm-cardona" => return zkevm_config("cardona"), + network => return astar_config(network), }; diff --git a/chains/ethereum/server/src/lib.rs b/chains/ethereum/server/src/lib.rs index a1961b49..7a19ad1a 100644 --- a/chains/ethereum/server/src/lib.rs +++ b/chains/ethereum/server/src/lib.rs @@ -67,6 +67,7 @@ impl MaybeWsEthereumClient { "binance" => rosetta_config_ethereum::binance_config(network)?, "avalanche" => rosetta_config_ethereum::avalanche_config(network)?, "base" => rosetta_config_ethereum::base_config(network)?, + "zkevm" => rosetta_config_ethereum::zkevm_config(network)?, blockchain => anyhow::bail!("unsupported blockchain: {blockchain}"), }; Self::from_config(config, addr, private_key).await diff --git a/chains/zkevm/Cargo.toml b/chains/zkevm/Cargo.toml new file mode 100644 index 00000000..b42c93e5 --- /dev/null +++ b/chains/zkevm/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rosetta-testing-zkevm" +version = "0.1.0" +edition = "2021" +license = "MIT" +repository = "https://github.com/analog-labs/chain-connectors" +description = "zkevm rosetta test." + +[dependencies] +alloy-sol-types = { version = "0.8" } +anyhow = "1.0" +ethers = { version = "2.0", default-features = true, features = ["abigen", "rustls", "ws"] } +ethers-solc = "2.0" +hex-literal = "0.4" +rosetta-chain-testing = { path = "../rosetta-chain-testing" } +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"] } diff --git a/chains/zkevm/src/lib.rs b/chains/zkevm/src/lib.rs new file mode 100644 index 00000000..9f74c449 --- /dev/null +++ b/chains/zkevm/src/lib.rs @@ -0,0 +1,258 @@ +//! # Polygonzkevm Rosetta Server Test Suite +//! +//! This module contains a test suite for an Ethereum Rosetta server implementation +//! specifically designed for interacting with the Polygonzkevm 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 Polygonzkevm 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-zkevm --lib -- tests --nocapture +//! ``` +//! +//! Note: The code assumes a local Polygonzkevm RPC node running on `ws://127.0.0.1:8546`. Ensure +//! that this endpoint is configured correctly. + +#[allow(clippy::ignored_unit_patterns, clippy::pub_underscore_fields)] +#[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_chain_testing::run_test; + 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, path::Path}; + + /// Account used to fund other testing accounts. + const FUNDING_ACCOUNT_PRIVATE_KEY: [u8; 32] = + hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); + + /// Polygonzkevm rpc url + const POLYGON_RPC_HTTP_URL: &str = "http://localhost:8123"; + + sol! { + interface TestContract { + event AnEvent(); + function emitEvent() external; + + function identity(bool a) external view returns (bool); + } + } + + #[tokio::test] + async fn network_status() { + run_test(async move { + let client = MaybeWsEthereumClient::new("zkevm", "dev", POLYGON_RPC_HTTP_URL, Some(FUNDING_ACCOUNT_PRIVATE_KEY)) + .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; + } + + #[tokio::test] + async fn test_account() { + run_test(async move { + let client = MaybeWsEthereumClient::new("zkevm", "dev", POLYGON_RPC_HTTP_URL, Some(FUNDING_ACCOUNT_PRIVATE_KEY)) + .await + .expect("Error creating PolygonzkevmClient"); + let wallet = + Wallet::from_config(client.config().clone(), POLYGON_RPC_HTTP_URL, None, Some(FUNDING_ACCOUNT_PRIVATE_KEY)) + .await + .unwrap(); + let value = 10 * u128::pow(10, client.config().currency_decimals); + let _ = wallet.faucet(value, None).await; + let amount = wallet.balance().await.unwrap(); + assert_eq!(amount, value); + }) + .await; + } + + #[tokio::test] + async fn test_construction() { + run_test(async move { + let client = MaybeWsEthereumClient::new("zkevm", "dev", POLYGON_RPC_HTTP_URL, Some(FUNDING_ACCOUNT_PRIVATE_KEY)) + .await + .expect("Error creating PolygonzkevmClient"); + 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_HTTP_URL, None, None) + .await + .unwrap(); + let bob = Wallet::from_config(client.config().clone(), POLYGON_RPC_HTTP_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, None).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> { + 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) + } + + #[tokio::test] + async fn test_smart_contract() { + run_test(async move { + let client = MaybeWsEthereumClient::new("zkevm", "dev", POLYGON_RPC_HTTP_URL, None) + .await + .expect("Error creating PolygonzkevmClient"); + let faucet = 10 * u128::pow(10, client.config().currency_decimals); + let wallet = + Wallet::from_config(client.config().clone(), POLYGON_RPC_HTTP_URL, None, None) + .await + .unwrap(); + wallet.faucet(faucet, None).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; + } + + #[tokio::test] + async fn test_smart_contract_view() { + run_test(async move { + let client = MaybeWsEthereumClient::new("zkevm", "dev", POLYGON_RPC_HTTP_URL, None) + .await + .expect("Error creating PolygonzkevmClient"); + let faucet = 10 * u128::pow(10, client.config().currency_decimals); + let wallet = + Wallet::from_config(client.config().clone(), POLYGON_RPC_HTTP_URL, None, None) + .await + .unwrap(); + wallet.faucet(faucet, None).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; + } +} diff --git a/rosetta-client/src/client.rs b/rosetta-client/src/client.rs index f12b584c..2862d5ad 100644 --- a/rosetta-client/src/client.rs +++ b/rosetta-client/src/client.rs @@ -50,6 +50,10 @@ impl GenericClient { let client = EthereumClient::new("polygon", network, url, private_key).await?; Self::Ethereum(client) }, + Blockchain::Polygonzkevm => { + let client = EthereumClient::new("zkevm", network, url, private_key).await?; + Self::Ethereum(client) + }, Blockchain::Arbitrum => { let client = EthereumClient::new("arbitrum", network, url, private_key).await?; Self::Ethereum(client) @@ -89,6 +93,7 @@ impl GenericClient { Ok(match blockchain { Blockchain::Ethereum | Blockchain::Polygon | + Blockchain::Polygonzkevm | Blockchain::Arbitrum | Blockchain::Binance | Blockchain::Base | diff --git a/rosetta-client/src/lib.rs b/rosetta-client/src/lib.rs index 794f7e3a..c2cd2d9c 100644 --- a/rosetta-client/src/lib.rs +++ b/rosetta-client/src/lib.rs @@ -45,6 +45,8 @@ pub enum Blockchain { Wococo, /// Polygon Polygon, + /// Polygon-zkevm + Polygonzkevm, /// Arbitrum Arbitrum, /// Binance @@ -68,6 +70,7 @@ impl std::str::FromStr for Blockchain { "westend" => Self::Westend, "wococo" => Self::Wococo, "polygon" => Self::Polygon, + "zkevm" => Self::Polygonzkevm, "arbitrum" => Self::Arbitrum, "binance" => Self::Binance, "avalanche" => Self::Avalanche, diff --git a/rosetta-client/src/tx_builder.rs b/rosetta-client/src/tx_builder.rs index 4c894e7a..4527dfd5 100644 --- a/rosetta-client/src/tx_builder.rs +++ b/rosetta-client/src/tx_builder.rs @@ -17,7 +17,7 @@ impl GenericTransactionBuilder { pub fn new(config: &BlockchainConfig) -> Result { Ok(match config.blockchain { "astar" => Self::Astar(rosetta_tx_ethereum::EthereumTransactionBuilder), - "ethereum" | "polygon" | "arbitrum" | "binance" | "base" | "avalanche" => { + "ethereum" | "polygon" | "zkevm" | "arbitrum" | "binance" | "base" | "avalanche" => { Self::Ethereum(rosetta_tx_ethereum::EthereumTransactionBuilder) }, "polkadot" | "westend" | "rococo" => { From 0147720406ac8bd904edd861875b71c2e92b43ad Mon Sep 17 00:00:00 2001 From: ManojJiSharma Date: Mon, 9 Dec 2024 14:11:25 +0530 Subject: [PATCH 2/2] update gas --- chains/zkevm/src/lib.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/chains/zkevm/src/lib.rs b/chains/zkevm/src/lib.rs index 9f74c449..6f5acc1a 100644 --- a/chains/zkevm/src/lib.rs +++ b/chains/zkevm/src/lib.rs @@ -103,7 +103,7 @@ mod tests { .await .unwrap(); let value = 10 * u128::pow(10, client.config().currency_decimals); - let _ = wallet.faucet(value, None).await; + let _ = wallet.faucet(value, Some(25_000_000_000)).await; let amount = wallet.balance().await.unwrap(); assert_eq!(amount, value); }) @@ -119,10 +119,10 @@ mod tests { 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_HTTP_URL, None, None) + Wallet::from_config(client.config().clone(), POLYGON_RPC_HTTP_URL, None, Some(FUNDING_ACCOUNT_PRIVATE_KEY)) .await .unwrap(); - let bob = Wallet::from_config(client.config().clone(), POLYGON_RPC_HTTP_URL, None, None) + let bob = Wallet::from_config(client.config().clone(), POLYGON_RPC_HTTP_URL, None, Some(FUNDING_ACCOUNT_PRIVATE_KEY)) .await .unwrap(); assert_ne!(alice.public_key(), bob.public_key()); @@ -134,12 +134,12 @@ mod tests { assert_eq!(balance, 0); // Transfer faucets to alice - alice.faucet(faucet, None).await.unwrap(); + alice.faucet(faucet, Some(25_000_000_000)).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(); + alice.transfer(bob.account(), value, None, Some(25_000_000_000)).await.unwrap(); let amount = bob.balance().await.unwrap(); assert_eq!(amount, value); }) @@ -179,10 +179,10 @@ mod tests { .expect("Error creating PolygonzkevmClient"); let faucet = 10 * u128::pow(10, client.config().currency_decimals); let wallet = - Wallet::from_config(client.config().clone(), POLYGON_RPC_HTTP_URL, None, None) + Wallet::from_config(client.config().clone(), POLYGON_RPC_HTTP_URL, None, Some(FUNDING_ACCOUNT_PRIVATE_KEY)) .await .unwrap(); - wallet.faucet(faucet, None).await.unwrap(); + wallet.faucet(faucet, Some(25_000_000_000)).await.unwrap(); let bytes = compile_snippet( r" @@ -222,10 +222,10 @@ mod tests { .expect("Error creating PolygonzkevmClient"); let faucet = 10 * u128::pow(10, client.config().currency_decimals); let wallet = - Wallet::from_config(client.config().clone(), POLYGON_RPC_HTTP_URL, None, None) + Wallet::from_config(client.config().clone(), POLYGON_RPC_HTTP_URL, None, Some(FUNDING_ACCOUNT_PRIVATE_KEY)) .await .unwrap(); - wallet.faucet(faucet, None).await.unwrap(); + wallet.faucet(faucet, Some(25_000_000_000)).await.unwrap(); let bytes = compile_snippet( r" function identity(bool a) public view returns (bool) {