From ca51313350d0ea743dc61159346e97ca6dd96d5b Mon Sep 17 00:00:00 2001 From: apoorvsadana <95699312+apoorvsadana@users.noreply.github.com> Date: Mon, 19 Aug 2024 00:39:32 +0530 Subject: [PATCH] small changes --- .env.test | 2 +- crates/settlement-clients/ethereum/src/lib.rs | 106 ++++++------ .../ethereum/src/tests/mod.rs | 156 +++++++++++------- .../settlement-client-interface/src/lib.rs | 3 - 4 files changed, 157 insertions(+), 110 deletions(-) diff --git a/.env.test b/.env.test index d3bd9c0c..1dcb7b09 100644 --- a/.env.test +++ b/.env.test @@ -28,7 +28,7 @@ MONGODB_CONNECTION_STRING="mongodb://localhost:27017" # Ethereum Settlement -DEFAULT_SETTLEMENT_CLIENT_RPC="http://localhost:3000" +DEFAULT_SETTLEMENT_CLIENT_RPC="http://localhost:8545" DEFAULT_L1_CORE_CONTRACT_ADDRESS="0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4" SHOULD_IMPERSONATE_ACCOUNT="true" TEST_DUMMY_CONTRACT_ADDRESS="0xE5b6F5e695BA6E4aeD92B68c4CC8Df1160D69A81" \ No newline at end of file diff --git a/crates/settlement-clients/ethereum/src/lib.rs b/crates/settlement-clients/ethereum/src/lib.rs index 431fbfea..598e9902 100644 --- a/crates/settlement-clients/ethereum/src/lib.rs +++ b/crates/settlement-clients/ethereum/src/lib.rs @@ -17,6 +17,7 @@ use alloy::{ use alloy::eips::eip2930::AccessList; use alloy::eips::eip4844::BYTES_PER_BLOB; use alloy::hex; +use alloy::providers::ext::AnvilApi; // use alloy::node_bindings::Anvil; use alloy::rpc::types::TransactionRequest; use alloy_primitives::Bytes; @@ -29,6 +30,7 @@ use mockall::{automock, lazy_static, predicate::*}; use alloy::providers::ProviderBuilder; use conversion::{get_input_data_for_eip_4844, prepare_sidecar}; use settlement_client_interface::{SettlementClient, SettlementVerificationStatus, SETTLEMENT_SETTINGS_NAME}; +use url::Url; use utils::{env_utils::get_env_var_or_panic, settings::SettingsProvider}; use crate::clients::interfaces::validity_interface::StarknetValidityContractTrait; @@ -39,23 +41,12 @@ pub mod clients; pub mod config; pub mod conversion; -// IMPORTANT to understand #[cfg(test)], #[cfg(not(test))] and SHOULD_IMPERSONATE_ACCOUNT -// Two tests : `update_state_blob_with_dummy_contract_works` & `update_state_blob_with_impersonation_works` use a env var `SHOULD_IMPERSONATE_ACCOUNT` to inform the function `update_state_with_blobs` about the kind of testing, -// `SHOULD_IMPERSONATE_ACCOUNT` can have any of "0" or "1" value : -// - if "0" then : Testing via default Anvil address. -// - if "1" then : Testing via impersonating `Starknet Operator Address`. -// Note : changing between "0" and "1" is handled automatically by each test function, `no` manual change in `env.test` is needed. - #[cfg(test)] mod tests; pub mod types; -#[cfg(test)] use {alloy::providers::RootProvider, alloy::transports::http::Http, reqwest::Client}; -#[cfg(not(test))] -use types::EthHttpProvider; - pub const ENV_PRIVATE_KEY: &str = "ETHEREUM_PRIVATE_KEY"; lazy_static! { @@ -66,19 +57,16 @@ lazy_static! { .expect("Error loading trusted setup file"); } -#[allow(dead_code)] pub struct EthereumSettlementClient { core_contract_client: StarknetValidityContractClient, wallet: EthereumWallet, wallet_address: Address, - #[cfg(not(test))] - provider: Arc, + provider: Arc>>, #[cfg(test)] - provider: RootProvider>, + impersonate_account: Option
, } impl EthereumSettlementClient { - #[cfg(not(test))] pub fn with_settings(settings: &impl SettingsProvider) -> Self { let settlement_cfg: EthereumSettlementConfig = settings.get_settings(SETTLEMENT_SETTINGS_NAME).unwrap(); @@ -87,46 +75,63 @@ impl EthereumSettlementClient { let wallet_address = signer.address(); let wallet = EthereumWallet::from(signer); - let provider = Arc::new( + // provider without wallet + let provider = Arc::new(ProviderBuilder::new().on_http(settlement_cfg.rpc_url.clone())); + + // provider with wallet + let filler_provider = Arc::new( ProviderBuilder::new().with_recommended_fillers().wallet(wallet.clone()).on_http(settlement_cfg.rpc_url), ); + let core_contract_client = StarknetValidityContractClient::new( Address::from_str(&settlement_cfg.core_contract_address) .expect("Failed to convert the validity contract address.") .0 .into(), - provider.clone(), + filler_provider, ); - EthereumSettlementClient { provider, core_contract_client, wallet, wallet_address } + EthereumSettlementClient { + provider, + core_contract_client, + wallet, + wallet_address, + #[cfg(test)] + impersonate_account: None, + } } #[cfg(test)] - pub fn with_test_settings(settings: &impl SettingsProvider, provider: RootProvider>) -> Self { - use tests::{SHOULD_IMPERSONATE_ACCOUNT, TEST_DUMMY_CONTRACT_ADDRESS}; - let settlement_cfg: EthereumSettlementConfig = settings.get_settings(SETTLEMENT_SETTINGS_NAME).unwrap(); - + pub fn with_test_settings( + provider: RootProvider>, + core_contract_address: Option
, + rpc_url: Url, + impersonate_account: Option
, + ) -> Self { let private_key = get_env_var_or_panic(ENV_PRIVATE_KEY); let signer: PrivateKeySigner = private_key.parse().expect("Failed to parse private key"); let wallet_address = signer.address(); let wallet = EthereumWallet::from(signer); - let fill_provider = Arc::new( - ProviderBuilder::new().with_recommended_fillers().wallet(wallet.clone()).on_http(settlement_cfg.rpc_url), - ); + let fill_provider = + Arc::new(ProviderBuilder::new().with_recommended_fillers().wallet(wallet.clone()).on_http(rpc_url)); - let core_contract_address = if *SHOULD_IMPERSONATE_ACCOUNT { - &settlement_cfg.core_contract_address + let core_contract_address = if let Some(core_contract_address) = core_contract_address { + core_contract_address.clone() } else { - &*TEST_DUMMY_CONTRACT_ADDRESS + // dummy address + Address::from_str("0x0").unwrap() }; - let core_contract_client = StarknetValidityContractClient::new( - Address::from_str(core_contract_address).unwrap().0.into(), - fill_provider, - ); + let core_contract_client = StarknetValidityContractClient::new(core_contract_address, fill_provider); - EthereumSettlementClient { provider, core_contract_client, wallet, wallet_address } + EthereumSettlementClient { + provider: Arc::new(provider), + core_contract_client, + wallet, + wallet_address, + impersonate_account, + } } /// Build kzg proof for the x_0 point evaluation @@ -184,12 +189,6 @@ impl SettlementClient for EthereumSettlementClient { } /// Should be used to update state on core contract when DA is in blobs/alt DA - async fn update_state_blobs(&self, program_output: Vec<[u8; 32]>, kzg_proof: [u8; 48]) -> Result { - let program_output: Vec = vec_u8_32_to_vec_u256(&program_output)?; - let tx_receipt = self.core_contract_client.update_state_kzg(program_output, kzg_proof).await?; - Ok(format!("0x{:x}", tx_receipt.transaction_hash)) - } - async fn update_state_with_blobs( &self, program_output: Vec<[u8; 32]>, @@ -246,13 +245,14 @@ impl SettlementClient for EthereumSettlementClient { let signature = self.wallet.default_signer().sign_transaction(&mut variant).await?; let tx_signed = variant.into_signed(signature); let tx_envelope: TxEnvelope = tx_signed.into(); - // IMP: this conversion strips signature from the transaction + // IMP: this conversion strips signature from the transaction #[cfg(not(test))] let txn_request: TransactionRequest = tx_envelope.into(); #[cfg(test)] - let txn_request = test_config::configure_transaction(tx_envelope); + let txn_request = + test_config::configure_transaction(self.provider.clone(), tx_envelope, self.impersonate_account).await; let pending_transaction = self.provider.send_transaction(txn_request).await?; return Ok(pending_transaction.tx_hash().to_string()); @@ -297,14 +297,26 @@ impl SettlementClient for EthereumSettlementClient { mod test_config { use super::*; use alloy::network::TransactionBuilder; - use tests::{ADDRESS_TO_IMPERSONATE, SHOULD_IMPERSONATE_ACCOUNT, TEST_NONCE}; + use tests::STARKNET_OPERATOR_ADDRESS; - pub fn configure_transaction(tx_envelope: TxEnvelope) -> TransactionRequest { + pub async fn configure_transaction( + provider: Arc>>, + tx_envelope: TxEnvelope, + impersonate_account: Option
, + ) -> TransactionRequest { let mut txn_request: TransactionRequest = tx_envelope.into(); - if *SHOULD_IMPERSONATE_ACCOUNT { - txn_request.set_nonce(*TEST_NONCE); - txn_request = txn_request.with_from(*ADDRESS_TO_IMPERSONATE); + // IMPORTANT to understand #[cfg(test)], #[cfg(not(test))] and SHOULD_IMPERSONATE_ACCOUNT + // Two tests : `update_state_blob_with_dummy_contract_works` & `update_state_blob_with_impersonation_works` use a env var `SHOULD_IMPERSONATE_ACCOUNT` to inform the function `update_state_with_blobs` about the kind of testing, + // `SHOULD_IMPERSONATE_ACCOUNT` can have any of "0" or "1" value : + // - if "0" then : Testing via default Anvil address. + // - if "1" then : Testing via impersonating `Starknet Operator Address`. + // Note : changing between "0" and "1" is handled automatically by each test function, `no` manual change in `env.test` is needed. + if let Some(impersonate_account) = impersonate_account { + let nonce = + provider.get_transaction_count(impersonate_account).await.unwrap().to_string().parse::().unwrap(); + txn_request.set_nonce(nonce); + txn_request = txn_request.with_from(impersonate_account); } txn_request diff --git a/crates/settlement-clients/ethereum/src/tests/mod.rs b/crates/settlement-clients/ethereum/src/tests/mod.rs index d323a30f..a99e7115 100644 --- a/crates/settlement-clients/ethereum/src/tests/mod.rs +++ b/crates/settlement-clients/ethereum/src/tests/mod.rs @@ -1,3 +1,4 @@ +use alloy::eips::eip4844::BYTES_PER_BLOB; use alloy::node_bindings::AnvilInstance; use alloy::primitives::U256; use alloy::providers::{ext::AnvilApi, ProviderBuilder}; @@ -39,6 +40,7 @@ impl Pipe for S {} // TODO: betterment of file routes use lazy_static::lazy_static; +use url::Url; lazy_static! { static ref ENV_FILE_PATH: PathBuf = PathBuf::from(".env.test"); @@ -47,16 +49,11 @@ lazy_static! { .to_str() .expect("Path contains invalid Unicode") .to_string(); - static ref PORT: u16 = 3000_u16; static ref ETH_RPC: String = get_env_var_or_panic("ETHEREUM_BLAST_RPC_URL"); - static ref STARKNET_OPERATOR_ADDRESS: Address = - Address::from_str("0x2C169DFe5fBbA12957Bdd0Ba47d9CEDbFE260CA7").expect("Could not impersonate account."); + pub static ref STARKNET_OPERATOR_ADDRESS: Address = + Address::from_str("0x2C169DFe5fBbA12957Bdd0Ba47d9CEDbFE260CA7").expect("Unable to parse address"); static ref STARKNET_CORE_CONTRACT_ADDRESS: Address = Address::from_str("0xc662c410c0ecf747543f5ba90660f6abebd9c8c4").expect("Could not impersonate account."); - pub static ref ADDRESS_TO_IMPERSONATE: Address = - Address::from_str("0x2C169DFe5fBbA12957Bdd0Ba47d9CEDbFE260CA7").expect("Unable to parse address"); - pub static ref TEST_DUMMY_CONTRACT_ADDRESS: String = get_env_var_or_panic("TEST_DUMMY_CONTRACT_ADDRESS"); - pub static ref SHOULD_IMPERSONATE_ACCOUNT: bool = get_env_var_or_panic("SHOULD_IMPERSONATE_ACCOUNT") == *"true"; pub static ref TEST_NONCE: u64 = 666068; } @@ -83,55 +80,83 @@ pub struct TestSetup { pub provider: alloy::providers::RootProvider>, } -fn setup_ethereum_test(block_no: u64) -> TestSetup { - // Load ENV vars - dotenvy::from_filename(&*ENV_FILE_PATH).expect("Could not load .env.test file."); +struct EthereumTestBuilder { + fork_block: Option, + impersonator: Option
, +} + +struct EthereumTest { + anvil: AnvilInstance, + provider: alloy::providers::RootProvider>, + rpc_url: Url, +} + +impl EthereumTestBuilder { + fn new() -> Self { + EthereumTestBuilder { fork_block: None, impersonator: None } + } + + fn with_fork_block(mut self, block_no: u64) -> Self { + self.fork_block = Some(block_no); + self + } + + fn with_impersonator(mut self, impersonator: Address) -> Self { + self.impersonator = Some(impersonator); + self + } - // Setup Anvil - let anvil = Anvil::new() - .port(*PORT) - .fork(&*ETH_RPC) - .fork_block_number(block_no - 1) - .try_spawn() - .expect("Could not spawn Anvil."); + async fn build(&self) -> EthereumTest { + // Load ENV vars + dotenvy::from_filename(&*ENV_FILE_PATH).expect("Could not load .env.test file."); - // Setup Provider - let provider = ProviderBuilder::new().on_http(anvil.endpoint_url()); + // Setup Anvil + let anvil = match self.fork_block { + Some(fork_block) => { + Anvil::new().fork(&*ETH_RPC).fork_block_number(fork_block).try_spawn().expect("Could not spawn Anvil.") + } + None => Anvil::new().try_spawn().expect("Could not spawn Anvil."), + }; - // Setup EthereumSettlementClient - let settings_provider: DefaultSettingsProvider = DefaultSettingsProvider {}; - let ethereum_settlement_client = EthereumSettlementClient::with_test_settings(&settings_provider, provider.clone()); + // Setup Provider + let provider = ProviderBuilder::new().on_http(anvil.endpoint_url()); - TestSetup { anvil, ethereum_settlement_client, provider } + if let Some(impersonator) = self.impersonator.clone() { + provider.anvil_impersonate_account(impersonator).await.expect("Unable to impersonate account."); + } + + let rpc_url = anvil.endpoint_url(); + + EthereumTest { anvil, provider, rpc_url } + } } #[rstest] #[tokio::test] -#[case::basic(20468828)] /// Tests if the method is able to do a transaction with same function selector on a dummy contract. /// If we impersonate starknet operator then we loose out on testing for validity of signature in the transaction. /// Starknet core contract has a modifier `onlyOperator` that restricts anyone but the operator to send transaction to `updateStateKzgDa` method /// And hence to test the signature and transaction via a dummy contract that has same function selector as `updateStateKzgDa`. /// and anvil is for testing on fork Eth. -async fn update_state_blob_with_dummy_contract_works(#[case] fork_block_no: u64) { - env::set_var("SHOULD_IMPERSONATE_ACCOUNT", "false"); - let TestSetup { anvil, ethereum_settlement_client, provider } = setup_ethereum_test(fork_block_no); +async fn update_state_blob_with_dummy_contract_works() { + let setup = EthereumTestBuilder::new().build().await; - println!("{:?}", anvil); // Deploying a dummy contract - let contract = DummyCoreContract::deploy(&provider).await.expect("Unable to deploy address"); - assert_eq!( - contract.address().to_string(), - *TEST_DUMMY_CONTRACT_ADDRESS, - "Dummy Contract got deployed on unexpected address" + let contract = DummyCoreContract::deploy(&setup.provider).await.expect("Unable to deploy address"); + let ethereum_settlement_client = EthereumSettlementClient::with_test_settings( + setup.provider.clone(), + Some(*contract.address()), + setup.rpc_url, + None, ); // Getting latest nonce after deployment let nonce = ethereum_settlement_client.get_nonce().await.expect("Unable to fetch nonce"); - // generating program output and blob vector - let program_output = get_program_output(fork_block_no); - let blob_data_vec = get_blob_data(fork_block_no); + // keeping 9 elements because the code accesses 8th index as program output + let program_output = vec![[0; 32]; 9]; + // keeping one element as we've a check in build_proof + let blob_data_vec = vec![vec![0; BYTES_PER_BLOB]]; // Calling update_state_with_blobs let update_state_result = ethereum_settlement_client @@ -142,7 +167,8 @@ async fn update_state_blob_with_dummy_contract_works(#[case] fork_block_no: u64) // Asserting, Expected to receive transaction hash. assert!(!update_state_result.is_empty(), "No transaction Hash received."); - let txn = provider + let txn = setup + .provider .get_transaction_by_hash(FixedBytes::from_str(update_state_result.as_str()).expect("Unable to convert txn")) .await .expect("did not get txn from hash") @@ -150,7 +176,7 @@ async fn update_state_blob_with_dummy_contract_works(#[case] fork_block_no: u64) assert_eq!(txn.hash.to_string(), update_state_result.to_string()); assert!(txn.signature.is_some()); - assert_eq!(txn.to.unwrap().to_string(), *TEST_DUMMY_CONTRACT_ADDRESS); + assert_eq!(txn.to.unwrap(), *contract.address()); // Testing verify_tx_inclusion sleep(Duration::from_secs(2)).await; @@ -167,28 +193,34 @@ async fn update_state_blob_with_dummy_contract_works(#[case] fork_block_no: u64) #[rstest] #[tokio::test] -#[case::basic(20468828)] +#[case::basic(20468827)] /// tests if the method is able to impersonate the`Starknet Operator` and do an `update_state` transaction. /// We impersonate the Starknet Operator to send a transaction to the Core contract /// Here signature checks are bypassed and anvil is for testing on fork Eth. async fn update_state_blob_with_impersonation_works(#[case] fork_block_no: u64) { - let TestSetup { anvil, ethereum_settlement_client, provider } = setup_ethereum_test(fork_block_no); - - println!("{:?}", anvil); - - provider.anvil_impersonate_account(*STARKNET_OPERATOR_ADDRESS).await.expect("Unable to impersonate account."); + let setup = EthereumTestBuilder::new() + .with_fork_block(fork_block_no) + .with_impersonator(*STARKNET_OPERATOR_ADDRESS) + .build() + .await; + let ethereum_settlement_client = EthereumSettlementClient::with_test_settings( + setup.provider.clone(), + Some(*STARKNET_CORE_CONTRACT_ADDRESS), + setup.rpc_url, + Some(*STARKNET_OPERATOR_ADDRESS), + ); let nonce = ethereum_settlement_client.get_nonce().await.expect("Unable to fetch nonce"); // Create a contract instance. - let contract = STARKNET_CORE_CONTRACT::new(*STARKNET_CORE_CONTRACT_ADDRESS, provider.clone()); + let contract = STARKNET_CORE_CONTRACT::new(*STARKNET_CORE_CONTRACT_ADDRESS, setup.provider.clone()); // Call the contract, retrieve the current stateBlockNumber. let prev_block_number = contract.stateBlockNumber().call().await.unwrap(); // generating program output and blob vector - let program_output = get_program_output(fork_block_no); - let blob_data_vec = get_blob_data(fork_block_no); + let program_output = get_program_output(fork_block_no + 1); + let blob_data_vec = get_blob_data(fork_block_no + 1); // Calling update_state_with_blobs let update_state_result = ethereum_settlement_client @@ -199,34 +231,40 @@ async fn update_state_blob_with_impersonation_works(#[case] fork_block_no: u64) // Asserting, Expected to receive transaction hash. assert!(!update_state_result.is_empty(), "No transaction Hash received."); - // Call the contract, retrieve the latest stateBlockNumber. - let latest_block_number = contract.stateBlockNumber().call().await.unwrap(); - - assert_eq!(prev_block_number._0.as_u32() + 1, latest_block_number._0.as_u32()); - - // Testing verify_tx_inclusion sleep(Duration::from_secs(2)).await; ethereum_settlement_client .wait_for_tx_finality(update_state_result.as_str()) .await .expect("Could not wait for txn finality."); + let verified_inclusion = ethereum_settlement_client .verify_tx_inclusion(update_state_result.as_str()) .await .expect("Could not verify inclusion."); assert_eq!(verified_inclusion, SettlementVerificationStatus::Verified); + + // Call the contract, retrieve the latest stateBlockNumber. + let latest_block_number = contract.stateBlockNumber().call().await.unwrap(); + + assert_eq!(prev_block_number._0.as_u32() + 1, latest_block_number._0.as_u32()); } #[rstest] #[tokio::test] -#[case::typical(20468828)] +#[case::typical(20468827)] async fn get_last_settled_block_typical_works(#[case] fork_block_no: u64) { - dotenvy::from_filename(&*ENV_FILE_PATH).expect("Could not load .env.test file."); - env::set_var("DEFAULT_SETTLEMENT_CLIENT_RPC", &*ETH_RPC); - - let TestSetup { anvil: _, ethereum_settlement_client, provider: _ } = setup_ethereum_test(fork_block_no); + let setup = EthereumTestBuilder::new().with_fork_block(fork_block_no).build().await; + let ethereum_settlement_client = EthereumSettlementClient::with_test_settings( + setup.provider.clone(), + Some(*STARKNET_CORE_CONTRACT_ADDRESS), + setup.rpc_url, + None, + ); - let _ = ethereum_settlement_client.get_last_settled_block().await.expect("Could not get last settled block."); + assert_eq!( + ethereum_settlement_client.get_last_settled_block().await.expect("Could not get last settled block."), + 666039 + ); } #[rstest] diff --git a/crates/settlement-clients/settlement-client-interface/src/lib.rs b/crates/settlement-clients/settlement-client-interface/src/lib.rs index 655aa089..a827ad47 100644 --- a/crates/settlement-clients/settlement-client-interface/src/lib.rs +++ b/crates/settlement-clients/settlement-client-interface/src/lib.rs @@ -36,9 +36,6 @@ pub trait SettlementClient: Send + Sync { nonce: u64, ) -> Result; - /// Should be used to update state on core contract when DA is in blobs/alt DA - async fn update_state_blobs(&self, program_output: Vec<[u8; 32]>, kzg_proof: [u8; 48]) -> Result; - /// Should verify the inclusion of a tx in the settlement layer async fn verify_tx_inclusion(&self, tx_hash: &str) -> Result;