diff --git a/Cargo.toml b/Cargo.toml index 3c72bd310..2fc00fa3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "examples/demo-nft-module", "full-node/db/sov-db", "full-node/sov-sequencer", + "full-node/sov-ethereum", "module-system/sov-modules-stf-template", "module-system/sov-modules-macros", diff --git a/adapters/celestia/Cargo.toml b/adapters/celestia/Cargo.toml index 4d737976a..cc2116451 100644 --- a/adapters/celestia/Cargo.toml +++ b/adapters/celestia/Cargo.toml @@ -26,7 +26,7 @@ serde_cbor = "0.11.2" serde_json = { workspace = true } tokio = { workspace = true, optional = true } thiserror = { workspace = true } -tracing = "0.1.37" +tracing = { workspace = true } sov-rollup-interface = { path = "../../rollup-interface" } nmt-rs = { git = "https://github.com/Sovereign-Labs/nmt-rs.git", rev = "dd37588444fca72825d11fe4a46838f66525c49f", features = ["serde", "borsh"] } diff --git a/examples/demo-rollup/Cargo.toml b/examples/demo-rollup/Cargo.toml index 7c3a9458f..547776152 100644 --- a/examples/demo-rollup/Cargo.toml +++ b/examples/demo-rollup/Cargo.toml @@ -28,6 +28,7 @@ jupiter = { path = "../../adapters/celestia" } demo-stf = { path = "../demo-stf", features = ["native"] } sov-rollup-interface = { path = "../../rollup-interface" } sov-db = { path = "../../full-node/db/sov-db" } +sov-ethereum = { path = "../../full-node/sov-ethereum", optional = true } sov-sequencer = { path = "../../full-node/sov-sequencer" } risc0-adapter = { path = "../../adapters/risc0" } sov-modules-stf-template = { path = "../../module-system/sov-modules-stf-template" } diff --git a/examples/demo-rollup/rollup_config.toml b/examples/demo-rollup/rollup_config.toml index e957405c1..fa2f13b35 100644 --- a/examples/demo-rollup/rollup_config.toml +++ b/examples/demo-rollup/rollup_config.toml @@ -4,7 +4,7 @@ start_height = 1 [da] # The JWT used to authenticate with the celestia light client. Instructions for generating this token can be found in the README -celestia_rpc_auth_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJwdWJsaWMiLCJyZWFkIiwid3JpdGUiLCJhZG1pbiJdfQ.YNNrJz6mCD6ypuH9Ay6NuCy1B6oQVTa2wi5-etSeY-8" +celestia_rpc_auth_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJwdWJsaWMiLCJyZWFkIiwid3JpdGUiLCJhZG1pbiJdfQ.yEqKNCDpXwddRTQYQeMNkQ59O22LI95CS-HeZMcoN-k" # The address of the *trusted* Celestia light client to interact with celestia_rpc_address = "http://127.0.0.1:26658" # The largest response the rollup will accept from the Celestia node. Defaults to 100 MB diff --git a/examples/demo-rollup/src/main.rs b/examples/demo-rollup/src/main.rs index ff7d61954..8802a971d 100644 --- a/examples/demo-rollup/src/main.rs +++ b/examples/demo-rollup/src/main.rs @@ -16,6 +16,8 @@ use jupiter::types::NamespaceId; use jupiter::verifier::{CelestiaVerifier, ChainValidityCondition, RollupParams}; use risc0_adapter::host::Risc0Verifier; use sov_db::ledger_db::{LedgerDB, SlotCommit}; +#[cfg(feature = "experimental")] +use sov_ethereum::get_ethereum_rpc; use sov_modules_api::RpcRunner; use sov_rollup_interface::crypto::NoOpHasher; use sov_rollup_interface::da::DaVerifier; @@ -141,9 +143,13 @@ async fn main() -> Result<(), anyhow::Error> { let batch_builder = demo_runner.take_batch_builder().unwrap(); let r = get_sequencer_rpc(batch_builder, da_service.clone()); - methods.merge(r).expect("Failed to merge Txs RPC modules"); + #[cfg(feature = "experimental")] + let ethereum_rpc = get_ethereum_rpc(rollup_config.da.clone()); + #[cfg(feature = "experimental")] + methods.merge(ethereum_rpc).unwrap(); + let _handle = tokio::spawn(async move { start_rpc_server(methods, address).await; }); diff --git a/examples/demo-stf/Cargo.toml b/examples/demo-stf/Cargo.toml index 16dfd499b..f0f6cc4f4 100644 --- a/examples/demo-stf/Cargo.toml +++ b/examples/demo-stf/Cargo.toml @@ -33,7 +33,7 @@ sov-sequencer-registry = { path = "../../module-system/module-implementations/so sov-bank = { path = "../../module-system/module-implementations/sov-bank", default-features = false } sov-modules-stf-template = { path = "../../module-system/sov-modules-stf-template" } # no features available sov-value-setter = { path = "../../module-system/module-implementations/examples/sov-value-setter", default-features = false } -sov-accounts = { path = "../../module-system/module-implementations/sov-accounts", default-features = false } +sov-accounts = { path = "../../module-system/module-implementations/sov-accounts", default-features = false} sov-state = { path = "../../module-system/sov-state", default-features = false } sov-modules-api = { path = "../../module-system/sov-modules-api", default-features = false } sov-modules-macros = { path = "../../module-system/sov-modules-macros" } @@ -43,6 +43,9 @@ sov-db = { path = "../../full-node/db/sov-db", optional = true } const-rollup-config = { path = "../const-rollup-config" } sov-sequencer = { path = "../../full-node/sov-sequencer", optional = true } +# Only enable the evm on "experimental" feature +sov-evm = { path = "../../module-system/module-implementations/sov-evm", default-features = false, optional = true} + [dev-dependencies] sov-rollup-interface = { path = "../../rollup-interface", features = ["mocks"] } tempfile = { workspace = true } @@ -50,6 +53,9 @@ rand = "0.8" [features] default = ["native"] +experimental =["sov-evm/experimental"] + + native = [ "dep:sov-db", "dep:sov-schema-db", diff --git a/examples/demo-stf/src/genesis_config.rs b/examples/demo-stf/src/genesis_config.rs index 764a8c77b..23bdecff1 100644 --- a/examples/demo-stf/src/genesis_config.rs +++ b/examples/demo-stf/src/genesis_config.rs @@ -1,4 +1,6 @@ use sov_election::ElectionConfig; +#[cfg(feature = "experimental")] +use sov_evm::{AccountData, EvmConfig}; pub use sov_modules_api::default_context::DefaultContext; use sov_modules_api::default_signature::private_key::DefaultPrivateKey; use sov_modules_api::{Context, Hasher, PublicKey, Spec}; @@ -52,12 +54,28 @@ pub fn create_demo_genesis_config( admin: election_admin_private_key.pub_key().to_address(), }; + #[cfg(feature = "experimental")] + let genesis_evm_address = hex::decode("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266") + .unwrap() + .try_into() + .expect("EVM module initialized with invalid address"); + GenesisConfig::new( bank_config, sequencer_registry_config, election_config, value_setter_config, sov_accounts::AccountConfig { pub_keys: vec![] }, + #[cfg(feature = "experimental")] + EvmConfig { + data: vec![AccountData { + address: genesis_evm_address, + balance: AccountData::balance(1000000000), + code_hash: AccountData::empty_code(), + code: vec![], + nonce: 0, + }], + }, ) } diff --git a/examples/demo-stf/src/runtime.rs b/examples/demo-stf/src/runtime.rs index f5f52a13a..7e59b4c7e 100644 --- a/examples/demo-stf/src/runtime.rs +++ b/examples/demo-stf/src/runtime.rs @@ -5,6 +5,9 @@ use sov_bank::query::{BankRpcImpl, BankRpcServer}; #[cfg(feature = "native")] use sov_election::query::{ElectionRpcImpl, ElectionRpcServer}; #[cfg(feature = "native")] +#[cfg(feature = "experimental")] +use sov_evm::query::{EvmRpcImpl, EvmRpcServer}; +#[cfg(feature = "native")] pub use sov_modules_api::default_context::DefaultContext; use sov_modules_api::Context; #[cfg(feature = "native")] @@ -50,6 +53,23 @@ use sov_value_setter::query::{ValueSetterRpcImpl, ValueSetterRpcServer}; /// Similar mechanism works for queries with the difference that queries are submitted by users directly to the rollup node /// instead of going through the DA layer. +#[cfg(not(feature = "experimental"))] +#[cfg_attr( + feature = "native", + cli_parser(DefaultContext), + expose_rpc(DefaultContext) +)] +#[derive(Genesis, DispatchCall, MessageCodec, DefaultRuntime)] +#[serialization(borsh::BorshDeserialize, borsh::BorshSerialize)] +pub struct Runtime { + pub bank: sov_bank::Bank, + pub sequencer_registry: sov_sequencer_registry::SequencerRegistry, + pub election: sov_election::Election, + pub value_setter: sov_value_setter::ValueSetter, + pub accounts: sov_accounts::Accounts, +} + +#[cfg(feature = "experimental")] #[cfg_attr( feature = "native", cli_parser(DefaultContext), @@ -63,4 +83,5 @@ pub struct Runtime { pub election: sov_election::Election, pub value_setter: sov_value_setter::ValueSetter, pub accounts: sov_accounts::Accounts, + pub evm: sov_evm::Evm, } diff --git a/full-node/sov-ethereum/Cargo.toml b/full-node/sov-ethereum/Cargo.toml new file mode 100644 index 000000000..aacc91448 --- /dev/null +++ b/full-node/sov-ethereum/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "sov-ethereum" +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +rust-version = { workspace = true } +version = { workspace = true } +readme = "README.md" +resolver = "2" + + +[dependencies] +anyhow = { workspace = true } +jsonrpsee = { workspace = true, features = ["http-client", "server"] } +serde = { workspace = true, features = ["derive"] } +sov-rollup-interface = { path = "../../rollup-interface" } +ethers = {version = "2.0.7"} + +sov-evm = { path = "../../module-system/module-implementations/sov-evm", default-features = false } +demo-stf = { path = "../../examples/demo-stf", features = ["native", "experimental"] } +sov-modules-api = { path = "../../module-system/sov-modules-api", default-features = false } +const-rollup-config = { path = "../../examples/const-rollup-config" } +jupiter = { path = "../../adapters/celestia", features = ["native"] } + +borsh = { workspace = true } +serde_json = { workspace = true } + + +[dev-dependencies] +sov-rollup-interface = { path = "../../rollup-interface", features = ["mocks"] } +tokio = { workspace = true } + + +[features] +default = ["native", "experimental"] +experimental =["demo-stf/experimental", "sov-evm/experimental"] + +native = ["demo-stf/native", "sov-evm/native"] \ No newline at end of file diff --git a/full-node/sov-ethereum/src/lib.rs b/full-node/sov-ethereum/src/lib.rs new file mode 100644 index 000000000..3ea1b9161 --- /dev/null +++ b/full-node/sov-ethereum/src/lib.rs @@ -0,0 +1,127 @@ +use borsh::ser::BorshSerialize; +use const_rollup_config::ROLLUP_NAMESPACE_RAW; +use demo_stf::app::DefaultPrivateKey; +use demo_stf::runtime::{DefaultContext, Runtime}; +use ethers::types::transaction::eip2718::TypedTransaction; +use ethers::types::Bytes; +use ethers::utils::rlp::Rlp; +use jsonrpsee::core::client::ClientT; +use jsonrpsee::core::params::ArrayParams; +use jsonrpsee::http_client::{HeaderMap, HttpClient}; +use jsonrpsee::RpcModule; +use jupiter::da_service::DaServiceConfig; +use sov_evm::call::CallMessage; +use sov_evm::evm::EvmTransaction; +use sov_modules_api::transaction::Transaction; + +const GAS_PER_BYTE: usize = 120; + +pub fn get_ethereum_rpc(config: DaServiceConfig) -> RpcModule { + let mut rpc = RpcModule::new(Ethereum { config }); + register_rpc_methods(&mut rpc).expect("Failed to register sequencer RPC methods"); + rpc +} + +pub struct Ethereum { + config: DaServiceConfig, +} + +impl Ethereum { + fn make_client(&self) -> HttpClient { + let mut headers = HeaderMap::new(); + headers.insert( + "Authorization", + format!("Bearer {}", self.config.celestia_rpc_auth_token.clone()) + .parse() + .unwrap(), + ); + + jsonrpsee::http_client::HttpClientBuilder::default() + .set_headers(headers) + .max_request_body_size(default_max_response_size()) // 100 MB + .build(self.config.celestia_rpc_address.clone()) + .expect("Client initialization is valid") + } + + async fn send_tx_to_da( + &self, + raw: Vec, + ) -> Result { + let blob = vec![raw].try_to_vec().unwrap(); + let client = self.make_client(); + let fee: u64 = 2000; + let namespace = ROLLUP_NAMESPACE_RAW.to_vec(); + let gas_limit = (blob.len() + 512) * GAS_PER_BYTE + 1060; + + let mut params = ArrayParams::new(); + params.insert(namespace)?; + params.insert(blob)?; + params.insert(fee.to_string())?; + params.insert(gas_limit)?; + client + .request::("state.SubmitPayForBlob", params) + .await + } +} + +fn register_rpc_methods(rpc: &mut RpcModule) -> Result<(), jsonrpsee::core::Error> { + rpc.register_async_method( + "eth_sendRawTransaction", + |parameters, ethereum| async move { + let data: Bytes = parameters.one().unwrap(); + let data = data.as_ref(); + + // todo handle panics and unwraps. + if data[0] > 0x7f { + panic!("Invalid transaction type") + } + + let rlp = Rlp::new(data); + let (decoded_tx, _decoded_sig) = TypedTransaction::decode_signed(&rlp).unwrap(); + let tx_hash = decoded_tx.sighash(); + + let tx_request = match decoded_tx { + TypedTransaction::Legacy(_) => panic!("Legacy transaction type not supported"), + TypedTransaction::Eip2930(_) => panic!("Eip2930 not supported"), + TypedTransaction::Eip1559(request) => request, + }; + + let evm_tx = EvmTransaction { + caller: tx_request.from.unwrap().into(), + data: tx_request.data.unwrap().to_vec(), + // todo set `gas limit` + gas_limit: u64::MAX, + // todo set `gas price` + gas_price: Default::default(), + // todo set `max_priority_fee_per_gas` + max_priority_fee_per_gas: Default::default(), + // todo `set to` + to: None, + value: tx_request.value.unwrap().into(), + nonce: tx_request.nonce.unwrap().as_u64(), + access_lists: vec![], + }; + + // todo set nonce + let raw = make_raw_tx(evm_tx, 0).unwrap(); + ethereum.send_tx_to_da(raw).await?; + + Ok(tx_hash) + }, + )?; + + Ok(()) +} + +fn make_raw_tx(evm_tx: EvmTransaction, nonce: u64) -> Result, std::io::Error> { + let tx = CallMessage { tx: evm_tx }; + let message = Runtime::::encode_evm_call(tx); + // todo don't generate sender here. + let sender = DefaultPrivateKey::generate(); + let tx = Transaction::::new_signed_tx(&sender, message, nonce); + tx.try_to_vec() +} + +fn default_max_response_size() -> u32 { + 1024 * 1024 * 100 // 100 MB +} diff --git a/module-system/module-implementations/sov-evm/Cargo.toml b/module-system/module-implementations/sov-evm/Cargo.toml index f897a5399..8baa89a6c 100644 --- a/module-system/module-implementations/sov-evm/Cargo.toml +++ b/module-system/module-implementations/sov-evm/Cargo.toml @@ -27,10 +27,8 @@ thiserror = { workspace = true } borsh = { workspace = true, features = ["rc"] } hex = { workspace = true } jsonrpsee = { workspace = true, features = ["macros", "client-core", "server"], optional = true } +tracing = { workspace = true } - - -[dev-dependencies] # TODO: move these dependencies to the workspace when the EVM module is no longer in the experimental stage ethereum-types = "0.14.1" ethers-core = "2.0.7" @@ -38,8 +36,10 @@ ethers-contract = "2.0.7" ethers-middleware = { version = "2.0.7"} ethers-providers = { version = "2.0.7"} ethers-signers = { version = "2.0.7", default-features = false } -primitive-types = "0.12.1" + +[dev-dependencies] +primitive-types = "0.12.1" sov-modules-api = { path = "../../sov-modules-api", version = "0.1" } tokio = { workspace = true } @@ -50,4 +50,4 @@ bytes = { workspace = true } default = ["native"] serde = ["dep:serde", "dep:serde_json"] native = ["serde", "sov-state/native", "dep:jsonrpsee", "sov-modules-api/native"] -experimental = [] +experimental = ["native"] diff --git a/module-system/module-implementations/sov-evm/src/evm/mod.rs b/module-system/module-implementations/sov-evm/src/evm/mod.rs index 4936f9212..2ac7656c4 100644 --- a/module-system/module-implementations/sov-evm/src/evm/mod.rs +++ b/module-system/module-implementations/sov-evm/src/evm/mod.rs @@ -14,6 +14,7 @@ pub(crate) mod transaction; pub(crate) type EthAddress = [u8; 20]; pub(crate) type Bytes32 = [u8; 32]; +pub use transaction::EvmTransaction; // Stores information about an EVM account #[derive(borsh::BorshDeserialize, borsh::BorshSerialize, Debug, PartialEq, Clone, Default)] pub(crate) struct AccountInfo { diff --git a/module-system/module-implementations/sov-evm/src/lib.rs b/module-system/module-implementations/sov-evm/src/lib.rs index 45e453ed3..5dfcc997a 100644 --- a/module-system/module-implementations/sov-evm/src/lib.rs +++ b/module-system/module-implementations/sov-evm/src/lib.rs @@ -4,8 +4,8 @@ pub mod call; pub mod evm; #[cfg(feature = "experimental")] pub mod genesis; -#[cfg(feature = "experimental")] #[cfg(feature = "native")] +#[cfg(feature = "experimental")] pub mod query; #[cfg(feature = "experimental")] #[cfg(test)] @@ -15,6 +15,7 @@ pub use experimental::{AccountData, Evm, EvmConfig}; #[cfg(feature = "experimental")] mod experimental { + use revm::primitives::{KECCAK_EMPTY, U256}; use sov_modules_api::Error; use sov_modules_macros::ModuleInfo; use sov_state::WorkingSet; @@ -33,6 +34,16 @@ mod experimental { pub nonce: u64, } + impl AccountData { + pub fn empty_code() -> [u8; 32] { + KECCAK_EMPTY.to_fixed_bytes() + } + + pub fn balance(balance: u64) -> Bytes32 { + U256::from(balance).to_le_bytes() + } + } + #[derive(Clone)] pub struct EvmConfig { pub data: Vec, diff --git a/module-system/module-implementations/sov-evm/src/query.rs b/module-system/module-implementations/sov-evm/src/query.rs index 8b1378917..c18cbca2c 100644 --- a/module-system/module-implementations/sov-evm/src/query.rs +++ b/module-system/module-implementations/sov-evm/src/query.rs @@ -1 +1,126 @@ +use ethereum_types::{Address, H256, U256, U64}; +use ethers_core::types::transaction::eip2930::AccessListItem; +use ethers_core::types::{ + Block, BlockId, Bytes, FeeHistory, Transaction, TransactionReceipt, TxHash, +}; +use sov_modules_macros::rpc_gen; +use sov_state::WorkingSet; +use tracing::info; +use crate::Evm; + +#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct EthTransactionRequest { + /// from address + pub from: Option
, + /// to address + pub to: Option
, + /// legacy, gas Price + #[cfg_attr(feature = "serde", serde(default))] + pub gas_price: Option, + /// max base fee per gas sender is willing to pay + #[cfg_attr(feature = "serde", serde(default))] + pub max_fee_per_gas: Option, + /// miner tip + #[cfg_attr(feature = "serde", serde(default))] + pub max_priority_fee_per_gas: Option, + /// gas + pub gas: Option, + /// value of th tx in wei + pub value: Option, + /// Any additional data sent + pub data: Option, + /// Transaction nonce + pub nonce: Option, + /// chain id + #[cfg_attr(feature = "serde", serde(default))] + pub chain_id: Option, + /// warm storage access pre-payment + #[cfg_attr(feature = "serde", serde(default))] + pub access_list: Option>, + /// EIP-2718 type + #[cfg_attr(feature = "serde", serde(rename = "type"))] + pub transaction_type: Option, +} + +#[rpc_gen(client, server, namespace = "eth")] +impl Evm { + #[rpc_method(name = "chainId")] + pub fn chain_id(&self, _working_set: &mut WorkingSet) -> Option { + info!("evm module: eth_chainId"); + Some(U64::from(1u64)) + } + + #[rpc_method(name = "getBlockByNumber")] + pub fn get_block_by_number( + &self, + _block_number: Option, + _details: Option, + _working_set: &mut WorkingSet, + ) -> Option> { + info!("evm module: eth_getBlockByNumber"); + + let block = Block:: { + base_fee_per_gas: Some(100.into()), + ..Default::default() + }; + + Some(block) + } + + #[rpc_method(name = "feeHistory")] + pub fn fee_history(&self, _working_set: &mut WorkingSet) -> FeeHistory { + info!("evm module: eth_feeHistory"); + FeeHistory { + base_fee_per_gas: Default::default(), + gas_used_ratio: Default::default(), + oldest_block: Default::default(), + reward: Default::default(), + } + } + + #[rpc_method(name = "sendTransaction")] + pub fn send_transaction( + &self, + _request: EthTransactionRequest, + _working_set: &mut WorkingSet, + ) -> U256 { + unimplemented!("eth_sendTransaction not implemented") + } + + #[rpc_method(name = "blockNumber")] + pub fn block_number(&self, _working_set: &mut WorkingSet) -> U256 { + unimplemented!("eth_blockNumber not implemented") + } + + #[rpc_method(name = "getTransactionByHash")] + pub fn get_transaction_by_hash( + &self, + _hash: H256, + _working_set: &mut WorkingSet, + ) -> Option { + unimplemented!("eth_blockNumber not implemented") + } + + #[rpc_method(name = "getTransactionReceipt")] + pub fn get_transaction_receipt( + &self, + _hash: H256, + _working_set: &mut WorkingSet, + ) -> Option { + unimplemented!("eth_getTransactionReceipt not implemented") + } + + #[rpc_method(name = "getTransactionCount")] + pub fn get_transaction_count( + &self, + _address: Address, + _block_number: Option, + _working_set: &mut WorkingSet, + ) -> Option { + unimplemented!("eth_getTransactionCount not implemented") + } +} diff --git a/module-system/module-implementations/sov-evm/src/tests/tx_tests.rs b/module-system/module-implementations/sov-evm/src/tests/tx_tests.rs index 4774773f0..2aad09541 100644 --- a/module-system/module-implementations/sov-evm/src/tests/tx_tests.rs +++ b/module-system/module-implementations/sov-evm/src/tests/tx_tests.rs @@ -1,13 +1,14 @@ use std::str::FromStr; use ethers_core::abi::Address; +use ethers_core::k256::ecdsa::SigningKey; use ethers_core::types::transaction::eip2718::TypedTransaction; use ethers_core::types::{Bytes, Eip1559TransactionRequest}; use ethers_core::utils::rlp::Rlp; -use ethers_core::utils::Anvil; +use ethers_core::utils::{Anvil, AnvilInstance}; use ethers_middleware::SignerMiddleware; -use ethers_providers::{Middleware, Provider}; -use ethers_signers::{LocalWallet, Signer}; +use ethers_providers::{Http, Middleware, Provider}; +use ethers_signers::{LocalWallet, Signer, Wallet}; use crate::evm::test_helpers::SimpleStorageContract; @@ -47,83 +48,138 @@ async fn tx_rlp_encoding_test() -> Result<(), Box> { Ok(()) } +struct TestClient { + chain_id: u64, + from_addr: Address, + contract: SimpleStorageContract, + client: SignerMiddleware, Wallet>, + _anvil: Option, +} + +impl TestClient { + async fn new_anvil_client( + chain_id: u64, + key: Wallet, + from_addr: Address, + contract: SimpleStorageContract, + ) -> Self { + let anvil = Anvil::new().chain_id(chain_id).spawn(); + let provider = Provider::try_from(anvil.endpoint()).unwrap(); + + let client = SignerMiddleware::new_with_provider_chain(provider, key) + .await + .unwrap(); + + Self { + chain_id, + from_addr, + contract, + client, + _anvil: Some(anvil), + } + } + + #[allow(dead_code)] + async fn new_demo_rollup_client( + chain_id: u64, + key: Wallet, + from_addr: Address, + contract: SimpleStorageContract, + ) -> Self { + let endpoint = format!("http://localhost:{}", 12345); + let provider = Provider::try_from(endpoint).unwrap(); + + let client = SignerMiddleware::new_with_provider_chain(provider, key) + .await + .unwrap(); + + Self { + chain_id, + from_addr, + contract, + client, + _anvil: None, + } + } + + async fn execute(self) -> Result<(), Box> { + // Deploy contract + let contract_address = { + let request = Eip1559TransactionRequest::new() + .from(self.from_addr) + .chain_id(self.chain_id) + .nonce(0u64) + .max_priority_fee_per_gas(100u64) + .gas(9000000u64) + .data(self.contract.byte_code()); + + let typed_transaction = TypedTransaction::Eip1559(request); + + let receipt = self + .client + .send_transaction(typed_transaction, None) + .await? + .await?; + + receipt.unwrap().contract_address.unwrap() + }; + + // Call contract + let set_arg = 923; + { + let request = Eip1559TransactionRequest::new() + .from(self.from_addr) + .to(contract_address) + .chain_id(self.chain_id) + .nonce(1u64) + .max_priority_fee_per_gas(100u64) + .gas(9000000u64) + .data(self.contract.set_call_data(set_arg)); + + let typed_transaction = TypedTransaction::Eip1559(request); + + let _ = self + .client + .send_transaction(typed_transaction, None) + .await + .unwrap() + .await; + } + + // Query contract + { + let request = Eip1559TransactionRequest::new() + .from(self.from_addr) + .to(contract_address) + .chain_id(self.chain_id) + .data(self.contract.get_call_data()); + + let tx = TypedTransaction::Eip1559(request); + + let response = self.client.call(&tx, None).await?; + + let resp_array: [u8; 32] = response.to_vec().try_into().unwrap(); + let get_arg = ethereum_types::U256::from(resp_array); + + assert_eq!(set_arg, get_arg.as_u32()) + } + Ok(()) + } +} + #[tokio::test] -async fn send_tx_test_to_eth_node() -> Result<(), Box> { +async fn send_tx_test_to_eth() -> Result<(), Box> { let chain_id: u64 = 1; - let anvil = Anvil::new().chain_id(chain_id).spawn(); - - let provider = Provider::try_from(anvil.endpoint())?; let key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" .parse::() .unwrap() .with_chain_id(chain_id); - let client = SignerMiddleware::new_with_provider_chain(provider, key).await?; - let contract = SimpleStorageContract::new(); - // Create contract - let contract_address = { - let from_addr = anvil.addresses()[0]; - - let request = Eip1559TransactionRequest::new() - .from(from_addr) - .chain_id(chain_id) - .nonce(0u64) - .max_priority_fee_per_gas(100u64) - .gas(9000000u64) - .data(contract.byte_code()); - - let typed_transaction = TypedTransaction::Eip1559(request); - - let receipt = client - .send_transaction(typed_transaction, None) - .await? - .await?; - - receipt.unwrap().contract_address.unwrap() - }; - - // Call contract - let set_arg = 923; - { - let from = anvil.addresses()[0]; - let request = Eip1559TransactionRequest::new() - .from(from) - .to(contract_address) - .chain_id(chain_id) - .nonce(1u64) - .max_priority_fee_per_gas(100u64) - .gas(9000000u64) - .data(contract.set_call_data(set_arg)); - - let typed_transaction = TypedTransaction::Eip1559(request); - - let _ = client - .send_transaction(typed_transaction, None) - .await - .unwrap() - .await; - } - - // Query contract - { - let from = anvil.addresses()[0]; - - let request = Eip1559TransactionRequest::new() - .from(from) - .to(contract_address) - .chain_id(chain_id) - .data(contract.get_call_data()); - let tx = TypedTransaction::Eip1559(request); + let from_addr = Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap(); - let response = client.call(&tx, None).await?; - - let resp_array: [u8; 32] = response.to_vec().try_into().unwrap(); - let get_arg = ethereum_types::U256::from(resp_array); - - assert_eq!(set_arg, get_arg.as_u32()) - } - - Ok(()) + let test_client = TestClient::new_anvil_client(chain_id, key, from_addr, contract).await; + //let test_client = TestClient::new_demo_rollup_client(chain_id, key, from_addr, contract).await; + test_client.execute().await }