diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 21f480f5..a06251c2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -59,7 +59,7 @@ jobs: needs: [rustfmt] strategy: matrix: - crate: [rosetta-server-astar, rosetta-server-ethereum, rosetta-server-polkadot, rosetta-client, rosetta-testing-arbitrum, rosetta-testing-binance] + crate: [rosetta-server-astar, rosetta-server-ethereum, rosetta-server-polkadot, rosetta-client, rosetta-testing-arbitrum, rosetta-testing-binance, rosetta-testing-avalanche,] name: ${{ matrix.crate }} runs-on: self-hosted steps: @@ -104,7 +104,14 @@ jobs: run: | cd nitro-testnode ./test-node.bash --detach - + + - name: Setup avalanche-cli node + if: ${{matrix.crate == 'rosetta-testing-avalanche'}} + run: | + docker pull analoglabs/avalanche-cli + docker run -v MY_LOCAL_CLI_DIR:/root/.avalanche-cli/ analoglabs/avalanche-cli blockchain create localnew --evm --evm-token SUB --genesis ./genesis.json --teleporter=false --vm-version v0.6.9 + docker run -d -v MY_LOCAL_CLI_DIR:/root/.avalanche-cli/ -p 9650:9650 --entrypoint bash analoglabs/avalanche-cli -c "/avalanche blockchain deploy localnew --local;tail -f /dev/null" + - name: Setup BSC node if: ${{ matrix.crate == 'rosetta-testing-binance' }} run: | @@ -143,6 +150,7 @@ jobs: cargo clippy --locked --workspace --examples --tests --all-features \ --exclude rosetta-testing-arbitrum \ --exclude rosetta-server-astar \ + --exclude rosetta-testing-avalanche \ --exclude rosetta-testing-binance \ --exclude rosetta-server-ethereum \ --exclude rosetta-server-polkadot \ @@ -169,6 +177,7 @@ jobs: cargo test --locked --workspace --all-features \ --exclude rosetta-testing-arbitrum \ --exclude rosetta-server-astar \ + --exclude rosetta-testing-avalanche \ --exclude rosetta-testing-binance \ --exclude rosetta-server-ethereum \ --exclude rosetta-server-polkadot \ diff --git a/Cargo.lock b/Cargo.lock index d94661e3..87868a51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,12 +156,34 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9996daf962fd0a90d3c93b388033228865953b92de7bb1959b891d78750a4091" dependencies = [ - "alloy-primitives", + "alloy-primitives 0.8.3", "alloy-sol-type-parser", "serde", "serde_json", ] +[[package]] +name = "alloy-primitives" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb3ead547f4532bc8af961649942f0b9c16ee9226e26caa3f38420651cc0bf4" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more 0.99.18", + "hex-literal", + "itoa", + "k256", + "keccak-asm", + "proptest", + "rand 0.8.5", + "ruint", + "serde", + "tiny-keccak", +] + [[package]] name = "alloy-primitives" version = "0.8.3" @@ -194,27 +216,59 @@ dependencies = [ "bytes", ] +[[package]] +name = "alloy-sol-macro" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" +dependencies = [ + "alloy-sol-macro-expander 0.7.7", + "alloy-sol-macro-input 0.7.7", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "alloy-sol-macro" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0458ccb02a564228fcd76efb8eb5a520521a8347becde37b402afec9a1b83859" dependencies = [ - "alloy-sol-macro-expander", - "alloy-sol-macro-input", + "alloy-sol-macro-expander 0.8.3", + "alloy-sol-macro-input 0.8.3", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.77", ] +[[package]] +name = "alloy-sol-macro-expander" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" +dependencies = [ + "alloy-sol-macro-input 0.7.7", + "const-hex", + "heck 0.5.0", + "indexmap 2.5.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", + "syn-solidity 0.7.7", + "tiny-keccak", +] + [[package]] name = "alloy-sol-macro-expander" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bc65475025fc1e84bf86fc840f04f63fcccdcf3cf12053c99918e4054dfbc69" dependencies = [ - "alloy-sol-macro-input", + "alloy-sol-macro-input 0.8.3", "const-hex", "heck 0.5.0", "indexmap 2.5.0", @@ -222,10 +276,25 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.77", - "syn-solidity", + "syn-solidity 0.8.3", "tiny-keccak", ] +[[package]] +name = "alloy-sol-macro-input" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" +dependencies = [ + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.77", + "syn-solidity 0.7.7", +] + [[package]] name = "alloy-sol-macro-input" version = "0.8.3" @@ -238,7 +307,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.77", - "syn-solidity", + "syn-solidity 0.8.3", ] [[package]] @@ -251,6 +320,18 @@ dependencies = [ "winnow 0.6.18", ] +[[package]] +name = "alloy-sol-types" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91ca40fa20793ae9c3841b83e74569d1cc9af29a2f5237314fd3452d51e38c7" +dependencies = [ + "alloy-primitives 0.7.7", + "alloy-sol-macro 0.7.7", + "const-hex", + "serde", +] + [[package]] name = "alloy-sol-types" version = "0.8.3" @@ -258,8 +339,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1eb88e4da0a1b697ed6a9f811fdba223cf4d5c21410804fd1707836af73a462b" dependencies = [ "alloy-json-abi", - "alloy-primitives", - "alloy-sol-macro", + "alloy-primitives 0.8.3", + "alloy-sol-macro 0.8.3", "const-hex", "serde", ] @@ -5704,8 +5785,8 @@ dependencies = [ name = "rosetta-server-astar" version = "0.6.0" dependencies = [ - "alloy-primitives", - "alloy-sol-types", + "alloy-primitives 0.8.3", + "alloy-sol-types 0.8.3", "anyhow", "async-trait", "ethers-solc", @@ -5733,8 +5814,8 @@ dependencies = [ name = "rosetta-server-ethereum" version = "0.6.0" dependencies = [ - "alloy-primitives", - "alloy-sol-types", + "alloy-primitives 0.8.3", + "alloy-sol-types 0.8.3", "anyhow", "async-trait", "auto_impl", @@ -5789,7 +5870,28 @@ dependencies = [ name = "rosetta-testing-arbitrum" version = "0.1.0" dependencies = [ - "alloy-sol-types", + "alloy-sol-types 0.8.3", + "anyhow", + "ethers", + "ethers-solc", + "hex-literal", + "rand_core 0.6.4", + "rosetta-client", + "rosetta-config-ethereum", + "rosetta-core", + "rosetta-docker", + "rosetta-server-ethereum", + "sha3", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "rosetta-testing-avalanche" +version = "0.1.0" +dependencies = [ + "alloy-sol-types 0.7.7", "anyhow", "ethers", "ethers-solc", @@ -5800,6 +5902,7 @@ dependencies = [ "rosetta-core", "rosetta-docker", "rosetta-server-ethereum", + "serial_test", "sha3", "tokio", "tracing", @@ -5810,7 +5913,7 @@ dependencies = [ name = "rosetta-testing-binance" version = "0.1.0" dependencies = [ - "alloy-sol-types", + "alloy-sol-types 0.8.3", "anyhow", "ethers", "ethers-solc", @@ -5827,7 +5930,7 @@ dependencies = [ name = "rosetta-testing-polygon" version = "0.1.0" dependencies = [ - "alloy-sol-types", + "alloy-sol-types 0.8.3", "anyhow", "ethers", "ethers-solc", @@ -6352,6 +6455,15 @@ dependencies = [ "yap", ] +[[package]] +name = "scc" +version = "2.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ff467073ddaff34c3a39e5b454f25dd982484a26fff50254ca793c56a1b714" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.24" @@ -6419,6 +6531,12 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "sdd" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177258b64c0faaa9ffd3c65cd3262c2bc7e2588dbbd9c1641d0346145c1bbda8" + [[package]] name = "sec1" version = "0.7.3" @@ -6668,6 +6786,31 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -7747,6 +7890,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "syn-solidity" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index bb605535..df91943a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "rosetta-utils", "chains/polygon/rosetta-testing-polygon", "chains/binance", + "chains/avalanche", ] resolver = "2" diff --git a/chains/arbitrum/testing/rosetta-testing-arbitrum/src/lib.rs b/chains/arbitrum/testing/rosetta-testing-arbitrum/src/lib.rs index ccadfae9..d83123c9 100644 --- a/chains/arbitrum/testing/rosetta-testing-arbitrum/src/lib.rs +++ b/chains/arbitrum/testing/rosetta-testing-arbitrum/src/lib.rs @@ -296,7 +296,7 @@ mod tests { .await .unwrap(); let value = 10 * u128::pow(10, client.config().currency_decimals); - let _ = wallet.faucet(value).await; + let _ = wallet.faucet(value, None).await; let amount = wallet.balance().await.unwrap(); assert_eq!(amount, value); }) @@ -349,7 +349,7 @@ mod tests { ) .await .unwrap(); - wallet.faucet(faucet).await.unwrap(); + wallet.faucet(faucet, None).await.unwrap(); let bytes = compile_snippet( r" @@ -402,7 +402,7 @@ mod tests { ) .await .unwrap(); - wallet.faucet(faucet).await.unwrap(); + wallet.faucet(faucet, None).await.unwrap(); let bytes = compile_snippet( r" function identity(bool a) public view returns (bool) { diff --git a/chains/astar/server/src/lib.rs b/chains/astar/server/src/lib.rs index abb0db72..f76e331f 100644 --- a/chains/astar/server/src/lib.rs +++ b/chains/astar/server/src/lib.rs @@ -267,7 +267,12 @@ impl BlockchainClient for AstarClient { Ok(balance) } - async fn faucet(&self, address: &Address, value: u128) -> Result> { + async fn faucet( + &self, + address: &Address, + value: u128, + _high_gas_price: Option, + ) -> Result> { // convert address let dest = { let address: H160 = address.address().parse()?; @@ -396,7 +401,7 @@ mod tests { 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(); + wallet.faucet(faucet, None).await.unwrap(); let bytes = compile_snippet( r" @@ -465,7 +470,7 @@ mod tests { 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(); + wallet.faucet(faucet, None).await.unwrap(); let bytes = compile_snippet( r" diff --git a/chains/avalanche/Cargo.toml b/chains/avalanche/Cargo.toml new file mode 100644 index 00000000..74a282a7 --- /dev/null +++ b/chains/avalanche/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "rosetta-testing-avalanche" +version = "0.1.0" +edition = "2021" +license = "MIT" +repository = "https://github.com/analog-labs/chain-connectors" +description = "avalanche rosetta test." + +[dependencies] +alloy-sol-types = { version = "0.7" } +anyhow = "1.0" +ethers = { version = "2.0", default-features = true, features = ["abigen", "rustls", "ws"] } +ethers-solc = "2.0" +hex-literal = "0.4" +rand_core = { version = "0.6", features = ["getrandom"] } +rosetta-client.workspace = true +rosetta-config-ethereum.workspace = true +rosetta-core.workspace = true +rosetta-docker = { workspace = true, features = ["tests"] } +rosetta-server-ethereum.workspace = true +sha3 = "0.10" +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +tracing = "0.1.40" +url = "2.4" + +[dev-dependencies] +serial_test = "3.1.1" diff --git a/chains/avalanche/src/lib.rs b/chains/avalanche/src/lib.rs new file mode 100644 index 00000000..e50c9790 --- /dev/null +++ b/chains/avalanche/src/lib.rs @@ -0,0 +1,286 @@ +#![allow(clippy::large_futures)] + +//! # Avalanche Testnet Rosetta Server +//! +//! This module contains the production test for an Avalanche Rosetta server implementation +//! specifically designed for interacting with the Avalanche Nitro Testnet. 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 Avalanche Nitro +//! Testnet. +//! - 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. +//! - `rosetta_client`: Client library for Rosetta API interactions. +//! - `rosetta_config_ethereum`: Configuration for Ethereum Rosetta server. +//! - `rosetta_server_avalanche`: Custom client implementation for interacting with Avalanche. +//! - `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-avalanche --lib -- tests --nocapture +//! ``` +//! +//! Note: The code assumes a local Avalanche Nitro Testnet node running on `ws://127.0.0.1:8548` and +//! a local Ethereum node on `http://localhost:8545`. Ensure that these endpoints are 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_client::Wallet; + use rosetta_config_ethereum::{AtBlock, CallResult}; + use rosetta_core::BlockchainClient; + use rosetta_server_ethereum::MaybeWsEthereumClient; + use serial_test::serial; + use sha3::Digest; + use std::{collections::BTreeMap, future::Future, path::Path}; + + /// Account used to fund other testing accounts. + const FUNDING_ACCOUNT_PRIVATE_KEY: [u8; 32] = + hex!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"); + + /// Avalanche rpc url + const AVALANCHE_RPC_WS_URL: &str = "ws://127.0.0.1:9650/ext/bc/localnew/ws"; + + 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 binance to mine new blocks + /// # Panic + /// Panics if the future panics + async fn run_test + 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::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()); + } + } + + #[tokio::test] + #[serial] + async fn network_status() { + run_test(async move { + let client = MaybeWsEthereumClient::new("avalanche", "dev", AVALANCHE_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; + } + + #[tokio::test] + #[serial] + async fn test_account() { + run_test(async move { + let client = MaybeWsEthereumClient::new( + "avalanche", + "dev", + AVALANCHE_RPC_WS_URL, + Some(FUNDING_ACCOUNT_PRIVATE_KEY), + ) + .await + .expect("Error creating AvalancheClient"); + let wallet = Wallet::from_config( + client.config().clone(), + AVALANCHE_RPC_WS_URL, + None, + Some(FUNDING_ACCOUNT_PRIVATE_KEY), + ) + .await + .unwrap(); + let value = 10 * u128::pow(10, client.config().currency_decimals); + let _ = wallet.faucet(value, Some(25_000_000_000)).await; + let amount = wallet.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] + #[serial] + async fn test_smart_contract() { + run_test(async move { + let client = MaybeWsEthereumClient::new( + "avalanche", + "dev", + AVALANCHE_RPC_WS_URL, + Some(FUNDING_ACCOUNT_PRIVATE_KEY), + ) + .await + .expect("Error creating AvalancheClient"); + let faucet = 10 * u128::pow(10, client.config().currency_decimals); + let wallet = Wallet::from_config( + client.config().clone(), + AVALANCHE_RPC_WS_URL, + None, + Some(FUNDING_ACCOUNT_PRIVATE_KEY), + ) + .await + .unwrap(); + wallet.faucet(faucet, Some(50_000_000_000)).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(), 1); + let topic = receipt.logs[0].topics[0]; + let expected = H256(sha3::Keccak256::digest("AnEvent()").into()); + assert_eq!(topic, expected); + }) + .await; + } + + #[tokio::test] + #[serial] + async fn test_smart_contract_view() { + run_test(async move { + let client = MaybeWsEthereumClient::new( + "avalanche", + "dev", + AVALANCHE_RPC_WS_URL, + Some(FUNDING_ACCOUNT_PRIVATE_KEY), + ) + .await + .expect("Error creating AvalancheClient"); + let faucet = 10 * u128::pow(10, client.config().currency_decimals); + let wallet = Wallet::from_config( + client.config().clone(), + AVALANCHE_RPC_WS_URL, + None, + Some(FUNDING_ACCOUNT_PRIVATE_KEY), + ) + .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) { + 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/chains/binance/src/lib.rs b/chains/binance/src/lib.rs index 6ba60578..19a630a7 100644 --- a/chains/binance/src/lib.rs +++ b/chains/binance/src/lib.rs @@ -130,7 +130,7 @@ mod tests { .await .unwrap(); let value = 10 * u128::pow(10, client.config().currency_decimals); - let _ = wallet.faucet(value).await; + let _ = wallet.faucet(value, None).await; let amount = wallet.balance().await.unwrap(); assert_eq!(amount, value); }) @@ -161,7 +161,7 @@ mod tests { assert_eq!(balance, 0); // Transfer faucets to alice - alice.faucet(faucet).await.unwrap(); + alice.faucet(faucet, None).await.unwrap(); let balance = alice.balance().await.unwrap(); assert_eq!(balance, faucet); @@ -209,7 +209,7 @@ mod tests { Wallet::from_config(client.config().clone(), BINANCE_RPC_WS_URL, None, None) .await .unwrap(); - wallet.faucet(faucet).await.unwrap(); + wallet.faucet(faucet, None).await.unwrap(); let bytes = compile_snippet( r" @@ -252,7 +252,7 @@ mod tests { Wallet::from_config(client.config().clone(), BINANCE_RPC_WS_URL, None, None) .await .unwrap(); - wallet.faucet(faucet).await.unwrap(); + wallet.faucet(faucet, None).await.unwrap(); let bytes = compile_snippet( r" function identity(bool a) public view returns (bool) { diff --git a/chains/ethereum/config/src/lib.rs b/chains/ethereum/config/src/lib.rs index 44dc19d1..1d91db33 100644 --- a/chains/ethereum/config/src/lib.rs +++ b/chains/ethereum/config/src/lib.rs @@ -248,6 +248,21 @@ pub fn binance_config(network: &str) -> anyhow::Result { Ok(evm_config("binance", network, "bnb", bip44_id, is_dev)) } +/// Retrieve the [`BlockchainConfig`] from the provided avalanche `network` +/// +/// # Errors +/// Returns `Err` if the network is not supported +pub fn avalanche_config(network: &str) -> anyhow::Result { + // All available networks are listed here: + let (network, bip44_id, is_dev) = match network { + "dev" => ("dev", 1, true), + "fuji" => ("goerli", 1, true), + "mainnet" => ("mainnet", 42161, false), + _ => anyhow::bail!("unsupported network: {}", network), + }; + Ok(evm_config("avalanche", network, "AVAX", bip44_id, is_dev)) +} + /// Retrieve the [`BlockchainConfig`] from the provided ethereum `network` /// /// # Errors @@ -278,6 +293,11 @@ pub fn config(network: &str) -> anyhow::Result { "binance" => return binance_config("mainnet"), "testnet" => return binance_config("testnet"), + // Avalanche + "avalanche-local" => return avalanche_config("dev"), + "avalanche" => return avalanche_config("mainnet"), + "avalanche-fuji" => return avalanche_config("fuji"), + network => return astar_config(network), }; diff --git a/chains/ethereum/server/src/client.rs b/chains/ethereum/server/src/client.rs index 3637112a..e02bded7 100644 --- a/chains/ethereum/server/src/client.rs +++ b/chains/ethereum/server/src/client.rs @@ -236,19 +236,29 @@ where } #[allow(clippy::single_match_else, clippy::missing_errors_doc)] - pub async fn faucet(&self, address: &Address, param: u128) -> Result> { + pub async fn faucet( + &self, + address: &Address, + param: u128, + high_gas_price: Option, + ) -> Result> { match self.private_key { Some(private_key) => { let chain_id = self.chain_id; let wallet = Keypair::from_bytes(private_key)?; let address: H160 = address.address().parse()?; let nonce = self.nonce.load(Ordering::Relaxed); + let gas_price = if let Some(high_gas_price) = high_gas_price { + U256::from(high_gas_price) + } else { + U256::from(500_000_000) // Default gas price + }; // Create a transaction request let transaction_request = LegacyTransaction { to: Some(address), value: U256::from(param), gas_limit: 210_000, - gas_price: U256::from(500_000_000), + gas_price, nonce, data: Bytes::default(), chain_id: Some(chain_id), diff --git a/chains/ethereum/server/src/lib.rs b/chains/ethereum/server/src/lib.rs index e0c37593..13691714 100644 --- a/chains/ethereum/server/src/lib.rs +++ b/chains/ethereum/server/src/lib.rs @@ -50,7 +50,7 @@ pub enum MaybeWsEthereumClient { impl MaybeWsEthereumClient { /// Creates a new ethereum client from `network` and `addr`. - /// Supported blockchains are `ethereum`, `polygon`, `arbitrum` and binance. + /// Supported blockchains are `ethereum`, `polygon`, `arbitrum`, binance and avalanche. /// /// # Errors /// Will return `Err` when the network is invalid, or when the provided `addr` is unreacheable. @@ -65,6 +65,7 @@ impl MaybeWsEthereumClient { "polygon" => rosetta_config_ethereum::polygon_config(network)?, "arbitrum" => rosetta_config_ethereum::arbitrum_config(network)?, "binance" => rosetta_config_ethereum::binance_config(network)?, + "avalanche" => rosetta_config_ethereum::avalanche_config(network)?, blockchain => anyhow::bail!("unsupported blockchain: {blockchain}"), }; Self::from_config(config, addr, private_key).await @@ -171,10 +172,15 @@ impl BlockchainClient for MaybeWsEthereumClient { } } - async fn faucet(&self, address: &Address, param: u128) -> Result> { + async fn faucet( + &self, + address: &Address, + param: u128, + high_gas_price: Option, + ) -> Result> { match self { - Self::Http(http_client) => http_client.faucet(address, param).await, - Self::Ws(ws_client) => ws_client.faucet(address, param).await, + Self::Http(http_client) => http_client.faucet(address, param, high_gas_price).await, + Self::Ws(ws_client) => ws_client.faucet(address, param, high_gas_price).await, } } @@ -309,7 +315,7 @@ mod tests { let wallet = env.ephemeral_wallet().await.unwrap(); let faucet = 100 * u128::pow(10, config.currency_decimals); - wallet.faucet(faucet).await.unwrap(); + wallet.faucet(faucet, None).await.unwrap(); let bytes = compile_snippet( r" @@ -379,7 +385,7 @@ mod tests { 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(); + wallet.faucet(faucet, None).await.unwrap(); let bytes = compile_snippet( r" diff --git a/chains/polkadot/server/src/lib.rs b/chains/polkadot/server/src/lib.rs index cc605abc..045ab840 100644 --- a/chains/polkadot/server/src/lib.rs +++ b/chains/polkadot/server/src/lib.rs @@ -118,7 +118,12 @@ impl BlockchainClient for PolkadotClient { Ok(account_info.data.free) } - async fn faucet(&self, address: &Address, value: u128) -> Result> { + async fn faucet( + &self, + address: &Address, + value: u128, + _high_gas_price: Option, + ) -> Result> { let address: AccountId32 = address .address() .parse() diff --git a/chains/polygon/rosetta-testing-polygon/src/lib.rs b/chains/polygon/rosetta-testing-polygon/src/lib.rs index 64bf1ad9..513692c1 100644 --- a/chains/polygon/rosetta-testing-polygon/src/lib.rs +++ b/chains/polygon/rosetta-testing-polygon/src/lib.rs @@ -132,7 +132,7 @@ mod tests { .await .unwrap(); let value = 10 * u128::pow(10, client.config().currency_decimals); - let _ = wallet.faucet(value).await; + let _ = wallet.faucet(value, None).await; let amount = wallet.balance().await.unwrap(); assert_eq!(amount, value); }) @@ -164,7 +164,7 @@ mod tests { assert_eq!(balance, 0); // Transfer faucets to alice - alice.faucet(faucet).await.unwrap(); + alice.faucet(faucet, None).await.unwrap(); let balance = alice.balance().await.unwrap(); assert_eq!(balance, faucet); @@ -213,7 +213,7 @@ mod tests { Wallet::from_config(client.config().clone(), POLYGON_RPC_WS_URL, None, None) .await .unwrap(); - wallet.faucet(faucet).await.unwrap(); + wallet.faucet(faucet, None).await.unwrap(); let bytes = compile_snippet( r" @@ -257,7 +257,7 @@ mod tests { Wallet::from_config(client.config().clone(), POLYGON_RPC_WS_URL, None, None) .await .unwrap(); - wallet.faucet(faucet).await.unwrap(); + wallet.faucet(faucet, None).await.unwrap(); let bytes = compile_snippet( r" function identity(bool a) public view returns (bool) { diff --git a/rosetta-client/src/client.rs b/rosetta-client/src/client.rs index 4405bb7b..94adb73c 100644 --- a/rosetta-client/src/client.rs +++ b/rosetta-client/src/client.rs @@ -58,6 +58,10 @@ impl GenericClient { let client = EthereumClient::new("binance", network, url, private_key).await?; Self::Ethereum(client) }, + Blockchain::Avalanche => { + let client = EthereumClient::new("avalanche", network, url, private_key).await?; + Self::Ethereum(client) + }, Blockchain::Astar => { let client = AstarClient::new(network, url).await?; Self::Astar(client) @@ -82,7 +86,8 @@ impl GenericClient { Blockchain::Ethereum | Blockchain::Polygon | Blockchain::Arbitrum | - Blockchain::Binance => { + Blockchain::Binance | + Blockchain::Avalanche => { let client = EthereumClient::from_config(config, url, private_key).await?; Self::Ethereum(client) }, @@ -207,8 +212,13 @@ impl BlockchainClient for GenericClient { } } - async fn faucet(&self, address: &Address, param: u128) -> Result> { - dispatch!(self.faucet(address, param).await) + async fn faucet( + &self, + address: &Address, + param: u128, + high_gas_price: Option, + ) -> Result> { + dispatch!(self.faucet(address, param, high_gas_price).await) } async fn metadata( diff --git a/rosetta-client/src/lib.rs b/rosetta-client/src/lib.rs index 8990e9ca..9a44d678 100644 --- a/rosetta-client/src/lib.rs +++ b/rosetta-client/src/lib.rs @@ -49,6 +49,8 @@ pub enum Blockchain { Arbitrum, /// Binance Binance, + /// Avalanche + Avalanche, } impl std::str::FromStr for Blockchain { @@ -66,6 +68,7 @@ impl std::str::FromStr for Blockchain { "polygon" => Self::Polygon, "arbitrum" => Self::Arbitrum, "binance" => Self::Binance, + "avalanche" => Self::Avalanche, _ => anyhow::bail!("unsupported blockchain {}", blockchain), }) } diff --git a/rosetta-client/src/tx_builder.rs b/rosetta-client/src/tx_builder.rs index d036b668..bc9733ad 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" => { + "ethereum" | "polygon" | "arbitrum" | "binance" | "avalanche" => { Self::Ethereum(rosetta_tx_ethereum::EthereumTransactionBuilder) }, "polkadot" | "westend" | "rococo" => { diff --git a/rosetta-client/src/wallet.rs b/rosetta-client/src/wallet.rs index bf476550..94f0604f 100644 --- a/rosetta-client/src/wallet.rs +++ b/rosetta-client/src/wallet.rs @@ -197,10 +197,14 @@ impl Wallet { /// Parameters: /// - `faucet_parameter`: the amount to seed the account with #[allow(clippy::missing_errors_doc)] - pub async fn faucet(&self, faucet_parameter: u128) -> Result> { + pub async fn faucet( + &self, + faucet_parameter: u128, + high_gas_price: Option, + ) -> Result> { let address = Address::new(self.client.config().address_format, self.account.address.clone()); - self.client.faucet(&address, faucet_parameter).await + self.client.faucet(&address, faucet_parameter, high_gas_price).await } /// deploys contract to chain diff --git a/rosetta-core/src/lib.rs b/rosetta-core/src/lib.rs index 55528713..a2c7eba6 100644 --- a/rosetta-core/src/lib.rs +++ b/rosetta-core/src/lib.rs @@ -168,7 +168,12 @@ pub trait BlockchainClient: Sized + Send + Sync + 'static { async fn current_block(&self) -> Result; async fn finalized_block(&self) -> Result; async fn balance(&self, address: &Address, block: &Self::AtBlock) -> Result; - async fn faucet(&self, address: &Address, param: u128) -> Result>; + async fn faucet( + &self, + address: &Address, + param: u128, + high_gas_price: Option, + ) -> Result>; async fn metadata( &self, public_key: &PublicKey, @@ -230,8 +235,13 @@ where BlockchainClient::balance(Self::as_ref(self), address, block).await } - async fn faucet(&self, address: &Address, param: u128) -> Result> { - BlockchainClient::faucet(Self::as_ref(self), address, param).await + async fn faucet( + &self, + address: &Address, + param: u128, + high_gas_price: Option, + ) -> Result> { + BlockchainClient::faucet(Self::as_ref(self), address, param, high_gas_price).await } async fn metadata( diff --git a/rosetta-docker/src/lib.rs b/rosetta-docker/src/lib.rs index 81eac368..a2fd58a5 100644 --- a/rosetta-docker/src/lib.rs +++ b/rosetta-docker/src/lib.rs @@ -423,7 +423,7 @@ pub mod tests { crate::run_test(env, |env| async move { let value = 100 * u128::pow(10, config.currency_decimals); let wallet = env.ephemeral_wallet().await.unwrap(); - wallet.faucet(value).await.unwrap(); + wallet.faucet(value, None).await.unwrap(); let balance = wallet.balance().await.unwrap(); assert_eq!(balance, value); }) @@ -461,7 +461,7 @@ pub mod tests { assert_eq!(balance, 0); // Transfer faucets to alice - alice.faucet(faucet).await.unwrap(); + alice.faucet(faucet, None).await.unwrap(); let balance = alice.balance().await.unwrap(); assert_eq!(balance, faucet);