Skip to content

Commit

Permalink
Base Chain connector (#263)
Browse files Browse the repository at this point in the history
  • Loading branch information
ManojJiSharma authored Nov 18, 2024
1 parent c607965 commit 6d1c052
Show file tree
Hide file tree
Showing 12 changed files with 356 additions and 6 deletions.
15 changes: 13 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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, rosetta-testing-avalanche,]
crate: [rosetta-server-astar, rosetta-server-ethereum, rosetta-server-polkadot, rosetta-client, rosetta-testing-arbitrum, rosetta-testing-binance, rosetta-testing-avalanche, rosetta-testing-base,]
name: ${{ matrix.crate }}
runs-on: self-hosted
steps:
Expand Down Expand Up @@ -98,13 +98,22 @@ jobs:
- name: Checkout nitro-testnode
if: ${{ matrix.crate == 'rosetta-testing-arbitrum' }}
run: git clone -b release --depth=1 --no-tags --recurse-submodules https://github.com/ManojJiSharma/nitro-testnode.git


- name: Start arbitrum nitro-testnode
if: ${{ matrix.crate == 'rosetta-testing-arbitrum' }}
run: |
cd nitro-testnode
./test-node.bash --detach
- name: Checkout Optimism
if: ${{ matrix.crate == 'rosetta-testing-base' }}
run: |
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
make devnet-up

- name: Setup avalanche-cli node
if: ${{matrix.crate == 'rosetta-testing-avalanche'}}
run: |
Expand Down Expand Up @@ -153,6 +162,7 @@ jobs:
--exclude rosetta-testing-avalanche \
--exclude rosetta-testing-binance \
--exclude rosetta-server-ethereum \
--exclude rosetta-testing-base \
--exclude rosetta-server-polkadot \
--exclude rosetta-client \
-- \
Expand Down Expand Up @@ -180,6 +190,7 @@ jobs:
--exclude rosetta-testing-avalanche \
--exclude rosetta-testing-binance \
--exclude rosetta-server-ethereum \
--exclude rosetta-testing-base \
--exclude rosetta-server-polkadot \
--exclude rosetta-client
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# will have compiled files and executables
**/target/
/target
/bin

# These are backup files generated by rustfmt
**/*.rs.bk
Expand Down
23 changes: 23 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 @@ -22,6 +22,7 @@ members = [
"chains/binance",
"chains/avalanche",
"chains/rosetta-chain-testing",
"chains/base",
]
resolver = "2"

Expand Down
28 changes: 28 additions & 0 deletions chains/base/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "rosetta-testing-base"
version = "0.1.0"
edition = "2021"
license = "MIT"
repository = "https://github.com/analog-labs/chain-connectors"
description = "base 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-chain-testing = { path = "../rosetta-chain-testing" }
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"
254 changes: 254 additions & 0 deletions chains/base/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#![allow(clippy::large_futures)]

//! # Base Testnet Rosetta Server
//!
//! This module contains the production test for an Base Rosetta server implementation
//! specifically designed for interacting with the Base 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 Base 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_base`: Custom client implementation for interacting with Base.
//! - `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-base --lib -- tests --nocapture
//! ```
//!
//! Note: The code assumes a local Base 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_chain_testing::run_test;
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, path::Path};

/// Account used to fund other testing accounts.
const FUNDING_ACCOUNT_PRIVATE_KEY: [u8; 32] =
hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");

/// Base rpc url
const BASE_RPC_WS_URL: &str = "http://127.0.0.1:9545";

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

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

#[tokio::test]
#[serial]
async fn network_status() {
run_test(async move {
let client = MaybeWsEthereumClient::new("base", "dev", BASE_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(
"base",
"dev",
BASE_RPC_WS_URL,
Some(FUNDING_ACCOUNT_PRIVATE_KEY),
)
.await
.expect("Error creating BaseClient");
let wallet = Wallet::from_config(
client.config().clone(),
BASE_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<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)
}

#[tokio::test]
#[serial]
async fn test_smart_contract() {
run_test(async move {
let client = MaybeWsEthereumClient::new(
"base",
"dev",
BASE_RPC_WS_URL,
Some(FUNDING_ACCOUNT_PRIVATE_KEY),
)
.await
.expect("Error creating BaseClient");
let faucet = 10 * u128::pow(10, client.config().currency_decimals);
let wallet = Wallet::from_config(
client.config().clone(),
BASE_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(
"base",
"dev",
BASE_RPC_WS_URL,
Some(FUNDING_ACCOUNT_PRIVATE_KEY),
)
.await
.expect("Error creating BaseClient");
let faucet = 10 * u128::pow(10, client.config().currency_decimals);
let wallet = Wallet::from_config(
client.config().clone(),
BASE_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;
}
}
Loading

0 comments on commit 6d1c052

Please sign in to comment.