From 998f5e03753dddd29d7d235ee0d0c76bd04bdd95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Delabrouille?= <34384633+tdelabro@users.noreply.github.com> Date: Tue, 2 Jan 2024 17:27:01 +0100 Subject: [PATCH] add crate eth sandbox (#7) --- Cargo.lock | 45 +++++----- Cargo.toml | 6 +- crates/sandbox/Cargo.toml | 13 +++ crates/sandbox/src/lib.rs | 83 +++++++++++++++++++ .../starknet-core-contract-client/Cargo.toml | 5 -- .../src/clients/sovereign.rs | 59 ++++++++----- .../src/clients/validity.rs | 30 +++---- .../starknet-core-contract-client/src/lib.rs | 10 ++- rust-toolchain.toml | 2 + 9 files changed, 188 insertions(+), 65 deletions(-) create mode 100644 crates/sandbox/Cargo.toml create mode 100644 crates/sandbox/src/lib.rs create mode 100644 rust-toolchain.toml diff --git a/Cargo.lock b/Cargo.lock index 6cb5a1e..2187387 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -766,8 +766,7 @@ dependencies = [ [[package]] name = "ethers" version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5344eea9b20effb5efeaad29418215c4d27017639fd1f908260f59cbbd226e" +source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -782,8 +781,7 @@ dependencies = [ [[package]] name = "ethers-addressbook" version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c405f24ea3a517899ba7985385c43dc4a7eb1209af3b1e0a1a32d7dcc7f8d09" +source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" dependencies = [ "ethers-core", "once_cell", @@ -794,8 +792,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0111ead599d17a7bff6985fd5756f39ca7033edc79a31b23026a8d5d64fa95cd" +source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -813,8 +810,7 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51258120c6b47ea9d9bec0d90f9e8af71c977fbefbef8213c91bfed385fe45eb" +source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" dependencies = [ "Inflector", "const-hex", @@ -837,8 +833,7 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936e7a0f1197cee2b62dc89f63eff3201dbf87c283ff7e18d86d38f83b845483" +source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" dependencies = [ "Inflector", "const-hex", @@ -853,8 +848,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f03e0bdc216eeb9e355b90cf610ef6c5bb8aca631f97b5ae9980ce34ea7878d" +source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" dependencies = [ "arrayvec", "bytes", @@ -883,8 +877,7 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abbac2c890bdbe0f1b8e549a53b00e2c4c1de86bb077c1094d1f38cdf9381a56" +source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" dependencies = [ "chrono", "ethers-core", @@ -899,8 +892,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681ece6eb1d10f7cf4f873059a77c04ff1de4f35c63dd7bccde8f438374fcb93" +source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" dependencies = [ "async-trait", "auto_impl", @@ -926,8 +918,7 @@ dependencies = [ [[package]] name = "ethers-providers" version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25d6c0c9455d93d4990c06e049abf9b30daf148cf461ee939c11d88907c60816" +source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" dependencies = [ "async-trait", "auto_impl", @@ -963,8 +954,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb1b714e227bbd2d8c53528adb580b203009728b17d0d0e4119353aa9bc5532" +source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" dependencies = [ "async-trait", "coins-bip32", @@ -982,8 +972,7 @@ dependencies = [ [[package]] name = "ethers-solc" version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64f710586d147864cff66540a6d64518b9ff37d73ef827fee430538265b595f" +source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" dependencies = [ "cfg-if", "const-hex", @@ -2502,6 +2491,18 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sandbox" +version = "0.1.0" +dependencies = [ + "dirs", + "ethers", + "hex", + "serde_json", + "starknet-core-contract-client", + "thiserror", +] + [[package]] name = "scale-info" version = "2.10.0" diff --git a/Cargo.toml b/Cargo.toml index e3ef01d..3c31b8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ "crates/starknet-core-contract-client", + "crates/sandbox", ] [workspace.package] @@ -11,11 +12,14 @@ repository = "https://github.com/keep-starknet-strange/zaun/" version = "0.1.0" [workspace.dependencies] -ethers = "2.0.7" +ethers = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } log = "0.4.20" thiserror = "1.0.51" num-traits = "0.2.17" async-trait = "0.1.74" +dirs = "5.0.1" +serde_json = "1.0.108" +hex = "0.4.3" diff --git a/crates/sandbox/Cargo.toml b/crates/sandbox/Cargo.toml new file mode 100644 index 0000000..9aa734a --- /dev/null +++ b/crates/sandbox/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "sandbox" +edition.workspace = true +version.workspace = true +authors.workspace = true + +[dependencies] +starknet-core-contract-client = { path = "../starknet-core-contract-client" } +ethers = { workspace = true } +dirs = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +hex = { workspace = true } diff --git a/crates/sandbox/src/lib.rs b/crates/sandbox/src/lib.rs new file mode 100644 index 0000000..4c042b4 --- /dev/null +++ b/crates/sandbox/src/lib.rs @@ -0,0 +1,83 @@ +use ethers::abi::Tokenize; +use ethers::prelude::{ContractFactory, ContractInstance}; +use ethers::types::{Address, Bytes}; +use ethers::utils::hex::FromHex; +use ethers::utils::{Anvil, AnvilInstance}; +use starknet_core_contract_client::clients::StarknetSovereignContractClient; +use starknet_core_contract_client::{LocalWalletSignerMiddleware, StarknetCoreContractClient}; +use std::path::PathBuf; +use std::sync::Arc; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), + #[error("['bytecode']['object'] is not a string")] + BytecodeObject, + #[error(transparent)] + Hex(#[from] hex::FromHexError), +} + +pub struct EthereumSandbox { + _anvil: AnvilInstance, + client: Arc, +} + +impl EthereumSandbox { + pub fn new( + core_contract_address: Address, + ether_client: Arc, + anvil_path: Option, + ) -> Self { + let anvil_path: PathBuf = anvil_path + .or_else(|| std::env::var("ANVIL_PATH").map(Into::into).ok()) + .unwrap_or_else(|| dirs::home_dir().unwrap().join(".foundry/bin/anvil")); + + // Will panic if invalid path + let anvil = Anvil::at(anvil_path).spawn(); + + let client = StarknetSovereignContractClient::new(core_contract_address, ether_client); + Self { + _anvil: anvil, + client: Arc::new(client), + } + } + + pub fn client(&self) -> Arc { + self.client.clone() + } + + pub fn address(&self) -> Address { + self.client.address() + } + + pub async fn deploy( + &self, + contract_build_artifacts: &str, + contructor_args: T, + ) -> Result< + ContractInstance, LocalWalletSignerMiddleware>, + Error, + > { + let (abi, bytecode) = { + let mut artifacts: serde_json::Value = serde_json::from_str(contract_build_artifacts)?; + let abi = serde_json::from_value(artifacts["abi"].take())?; + let bytecode = Bytes::from_hex( + artifacts["bytecode"]["object"] + .as_str() + .ok_or(Error::BytecodeObject)?, + )?; + (abi, bytecode) + }; + + let factory = ContractFactory::new(abi, bytecode, self.client.client().clone()); + + Ok(factory + .deploy(contructor_args) + .expect("Failed to deploy contract") + .send() + .await + .expect("Ethereum polling error")) + } +} diff --git a/crates/starknet-core-contract-client/Cargo.toml b/crates/starknet-core-contract-client/Cargo.toml index 890e398..1065dc9 100644 --- a/crates/starknet-core-contract-client/Cargo.toml +++ b/crates/starknet-core-contract-client/Cargo.toml @@ -10,8 +10,3 @@ log = { workspace = true } thiserror = { workspace = true } num-traits = { workspace = true } async-trait = { workspace = true } - - - - - diff --git a/crates/starknet-core-contract-client/src/clients/sovereign.rs b/crates/starknet-core-contract-client/src/clients/sovereign.rs index 9287420..8da2a6f 100644 --- a/crates/starknet-core-contract-client/src/clients/sovereign.rs +++ b/crates/starknet-core-contract-client/src/clients/sovereign.rs @@ -1,61 +1,76 @@ use std::sync::Arc; use crate::{ - interfaces::{Operator, ProxySupport, StarknetMessaging, StarknetSovereignContract, StarknetGovernance, GovernedFinalizable}, - LocalMiddleware, + interfaces::{ + GovernedFinalizable, Operator, ProxySupport, StarknetGovernance, StarknetMessaging, + StarknetSovereignContract, + }, + LocalWalletSignerMiddleware, StarknetCoreContractClient, }; use ethers::types::Address; /// Client to interact with a Starknet core contract running in `Sovereign` mode pub struct StarknetSovereignContractClient { - core_contract: StarknetSovereignContract, - messaging: StarknetMessaging, - operator: Operator, - proxy_support: ProxySupport, - governance: StarknetGovernance, - governed_finalizable: GovernedFinalizable + core_contract: StarknetSovereignContract, + messaging: StarknetMessaging, + operator: Operator, + proxy_support: ProxySupport, + governance: StarknetGovernance, + governed_finalizable: GovernedFinalizable, } impl StarknetSovereignContractClient { - pub fn new(address: Address, client: Arc) -> Self { + pub fn new(address: Address, client: Arc) -> Self { Self { core_contract: StarknetSovereignContract::new(address, client.clone()), messaging: StarknetMessaging::new(address, client.clone()), operator: Operator::new(address, client.clone()), proxy_support: ProxySupport::new(address, client.clone()), governance: StarknetGovernance::new(address, client.clone()), - governed_finalizable: GovernedFinalizable::new(address, client.clone()) + governed_finalizable: GovernedFinalizable::new(address, client.clone()), } } } -impl AsRef> for StarknetSovereignContractClient { - fn as_ref(&self) -> &StarknetSovereignContract { +impl AsRef> + for StarknetSovereignContractClient +{ + fn as_ref(&self) -> &StarknetSovereignContract { &self.core_contract } } -impl AsRef> for StarknetSovereignContractClient { - fn as_ref(&self) -> &StarknetMessaging { +impl AsRef> for StarknetSovereignContractClient { + fn as_ref(&self) -> &StarknetMessaging { &self.messaging } } -impl AsRef> for StarknetSovereignContractClient { - fn as_ref(&self) -> &ProxySupport { +impl AsRef> for StarknetSovereignContractClient { + fn as_ref(&self) -> &ProxySupport { &self.proxy_support } } -impl AsRef> for StarknetSovereignContractClient { - fn as_ref(&self) -> &Operator { +impl AsRef> for StarknetSovereignContractClient { + fn as_ref(&self) -> &Operator { &self.operator } } -impl AsRef> for StarknetSovereignContractClient { - fn as_ref(&self) -> &StarknetGovernance { +impl AsRef> for StarknetSovereignContractClient { + fn as_ref(&self) -> &StarknetGovernance { &self.governance } } -impl AsRef> for StarknetSovereignContractClient { - fn as_ref(&self) -> &GovernedFinalizable { +impl AsRef> for StarknetSovereignContractClient { + fn as_ref(&self) -> &GovernedFinalizable { &self.governed_finalizable } } + +impl StarknetCoreContractClient for StarknetSovereignContractClient { + fn address(&self) -> Address { + self.core_contract.address() + } + + fn client(&self) -> Arc { + self.core_contract.client() + } +} diff --git a/crates/starknet-core-contract-client/src/clients/validity.rs b/crates/starknet-core-contract-client/src/clients/validity.rs index 0bea0a1..56a5ba0 100644 --- a/crates/starknet-core-contract-client/src/clients/validity.rs +++ b/crates/starknet-core-contract-client/src/clients/validity.rs @@ -4,19 +4,19 @@ use ethers::abi::Address; use crate::{ interfaces::{Operator, ProxySupport, StarknetMessaging, StarknetValidityContract}, - LocalMiddleware, + LocalWalletSignerMiddleware, }; /// Client to interact with a Starknet core contract running in `Validity` mode pub struct StarknetValidityContractClient { - core_contract: StarknetValidityContract, - messaging: StarknetMessaging, - operator: Operator, - proxy_support: ProxySupport, + core_contract: StarknetValidityContract, + messaging: StarknetMessaging, + operator: Operator, + proxy_support: ProxySupport, } impl StarknetValidityContractClient { - pub fn new(address: Address, client: Arc) -> Self { + pub fn new(address: Address, client: Arc) -> Self { Self { core_contract: StarknetValidityContract::new(address, client.clone()), messaging: StarknetMessaging::new(address, client.clone()), @@ -26,23 +26,25 @@ impl StarknetValidityContractClient { } } -impl AsRef> for StarknetValidityContractClient { - fn as_ref(&self) -> &StarknetValidityContract { +impl AsRef> + for StarknetValidityContractClient +{ + fn as_ref(&self) -> &StarknetValidityContract { &self.core_contract } } -impl AsRef> for StarknetValidityContractClient { - fn as_ref(&self) -> &StarknetMessaging { +impl AsRef> for StarknetValidityContractClient { + fn as_ref(&self) -> &StarknetMessaging { &self.messaging } } -impl AsRef> for StarknetValidityContractClient { - fn as_ref(&self) -> &ProxySupport { +impl AsRef> for StarknetValidityContractClient { + fn as_ref(&self) -> &ProxySupport { &self.proxy_support } } -impl AsRef> for StarknetValidityContractClient { - fn as_ref(&self) -> &Operator { +impl AsRef> for StarknetValidityContractClient { + fn as_ref(&self) -> &Operator { &self.operator } } diff --git a/crates/starknet-core-contract-client/src/lib.rs b/crates/starknet-core-contract-client/src/lib.rs index c6ab650..5f00a15 100644 --- a/crates/starknet-core-contract-client/src/lib.rs +++ b/crates/starknet-core-contract-client/src/lib.rs @@ -2,10 +2,18 @@ pub mod clients; mod error; pub mod interfaces; +use std::sync::Arc; + pub use error::Error; use ethers::prelude::SignerMiddleware; use ethers::providers::{Http, Provider}; use ethers::signers::LocalWallet; +use ethers::types::Address; + +pub type LocalWalletSignerMiddleware = SignerMiddleware, LocalWallet>; -pub type LocalMiddleware = SignerMiddleware, LocalWallet>; +pub trait StarknetCoreContractClient { + fn address(&self) -> Address; + fn client(&self) -> Arc; +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..eec4540 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +components = ["rustfmt", "clippy", "rust-analyzer"]