Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WEB3-99: feat: add EvmEnvBuilder #215

Merged
merged 6 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
add EvmEnvBuilder
  • Loading branch information
Wollac committed Sep 2, 2024
commit e4f8403d8f70fe81a2eaa31ace68f4741aa1c6e0
5 changes: 2 additions & 3 deletions examples/erc20-counter/apps/src/bin/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ use erc20_counter_methods::{BALANCE_OF_ELF, BALANCE_OF_ID};
use risc0_ethereum_contracts::encode_seal;
use risc0_steel::{
ethereum::{EthEvmEnv, ETH_SEPOLIA_CHAIN_SPEC},
host::BlockNumberOrTag,
Commitment, Contract,
};
use risc0_zkvm::{default_prover, sha::Digest, ExecutorEnv, ProverOpts, VerifierContext};
Expand Down Expand Up @@ -102,8 +101,8 @@ async fn main() -> Result<()> {
.wallet(wallet)
.on_http(args.eth_rpc_url);

// Create an EVM environment from that provider and a block number.
let mut env = EthEvmEnv::from_provider(provider.clone(), BlockNumberOrTag::Latest).await?;
// Create an EVM environment from that provider defaulting to the latest block.
let mut env = EthEvmEnv::builder().provider(provider.clone()).build().await?;
// The `with_chain_spec` method is used to specify the chain configuration.
env = env.with_chain_spec(&ETH_SEPOLIA_CHAIN_SPEC);

Expand Down
4 changes: 2 additions & 2 deletions examples/erc20/host/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ async fn main() -> Result<()> {
// Parse the command line arguments.
let args = Args::parse();

// Create an EVM environment from an RPC endpoint and a block number or tag.
let mut env = EthEvmEnv::from_rpc(args.rpc_url, BlockNumberOrTag::Latest).await?;
// Create an EVM environment from an RPC endpoint defaulting to the latest block.
let mut env = EthEvmEnv::builder().rpc(args.rpc_url).build().await?;
// The `with_chain_spec` method is used to specify the chain configuration.
env = env.with_chain_spec(&ETH_SEPOLIA_CHAIN_SPEC);

Expand Down
5 changes: 2 additions & 3 deletions examples/token-stats/host/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use core::{APRCommitment, CometMainInterface, CONTRACT};
use methods::TOKEN_STATS_ELF;
use risc0_steel::{
ethereum::{EthEvmEnv, ETH_MAINNET_CHAIN_SPEC},
host::BlockNumberOrTag,
Contract,
};
use risc0_zkvm::{default_executor, ExecutorEnv};
Expand All @@ -44,8 +43,8 @@ async fn main() -> Result<()> {
// Parse the command line arguments.
let args = Args::parse();

// Create an EVM environment from an RPC endpoint and a block number.
let mut env = EthEvmEnv::from_rpc(args.rpc_url, BlockNumberOrTag::Latest).await?;
// Create an EVM environment from an RPC endpoint defaulting to the latest block.
let mut env = EthEvmEnv::builder().rpc(args.rpc_url).build().await?;
// The `with_chain_spec` method is used to specify the chain configuration.
env = env.with_chain_spec(&ETH_MAINNET_CHAIN_SPEC);

Expand Down
1 change: 1 addition & 0 deletions steel/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
### ⚡️ Features

- Add support for creating a commitment to a beacon block root using `EvmEnv::into_beacon_input`, which can be verified using the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) beacon roots contract.
- Add the `EvmEnvBuilder` to simplify the creation of an `EvmEnv` on the host.

### 🚨 Breaking Changes

Expand Down
22 changes: 11 additions & 11 deletions steel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ Here is a snippet of the [relevant code](../examples/erc20/methods/guest/src/mai

```rust
/// Specify the function to call using the [`sol!`] macro.
/// This parses the Solidity syntax to generate a struct that implements the [SolCall] trait.
/// This parses the Solidity syntax to generate a struct that implements the `SolCall` trait.
sol! {
/// ERC-20 balance function signature.
interface IERC20 {
function balanceOf(address account) external view returns (uint);
}
}

/// Function to call, implements the [SolCall] trait.
/// Function to call, implements the `SolCall` trait.
const CALL: IERC20::balanceOfCall = IERC20::balanceOfCall {
account: address!("9737100D2F42a196DE56ED0d1f6fF598a250E7E4"),
};
Expand All @@ -41,27 +41,27 @@ fn main() {
// Read the input from the guest environment.
let input: EthEvmInput = env::read();

// Converts the input into a `ViewCallEnv` for execution. The `with_chain_spec` method is used
// to specify the chain configuration.
let view_call_env = input.into_env().with_chain_spec(&ETH_SEPOLIA_CHAIN_SPEC);
// Commit the block hash and number used when deriving `view_call_env` to the journal.
env::commit_slice(&view_call_env.block_commitment().abi_encode());
// Converts the input into a `EvmEnv` for execution. The `with_chain_spec` method is used
// to specify the chain configuration. It checks that the state matches the state root in the
// header provided in the input.
let env = input.into_env().with_chain_spec(&ETH_SEPOLIA_CHAIN_SPEC);
// Commit the block hash and number used when deriving `EvmEnv` to the journal.
env::commit_slice(&env.commitment().abi_encode());

// Execute the view call; it returns the result in the type generated by the `sol!` macro.
let contract = Contract::new(CONTRACT, &view_call_env);
let contract = Contract::new(CONTRACT, &env);
let returns = contract.call_builder(&CALL).from(CALLER).call();
println!("View call result: {}", returns._0);
}

```

## Host code

Here is a snippet to the [relevant code](../examples/erc20/host/src/main.rs) on the host, it requires the same arguments as the guest:

```rust
// Create an EVM environment from an RPC endpoint and a block number or tag.
let mut env = EthEvmEnv::from_rpc(args.rpc_url, BlockNumberOrTag::Latest).await?;
// Create an EVM environment from an RPC endpoint defaulting to the latest block.
let mut env = EthEvmEnv::builder().rpc(args.rpc_url).build().await?;
// The `with_chain_spec` method is used to specify the chain configuration.
env = env.with_chain_spec(&ETH_SEPOLIA_CHAIN_SPEC);

Expand Down
121 changes: 104 additions & 17 deletions steel/src/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

//! Functionality that is only needed for the host and not the guest.
use std::fmt::Display;
use std::{fmt::Display, marker::PhantomData};

use crate::{
beacon::BeaconInput,
Expand All @@ -23,7 +23,7 @@ use crate::{
};
use alloy::{
network::{Ethereum, Network},
providers::{Provider, ProviderBuilder, RootProvider},
providers::{Provider, ProviderBuilder, ReqwestProvider, RootProvider},
rpc::types::Header as RpcHeader,
transports::{
http::{Client, Http},
Expand All @@ -45,8 +45,11 @@ pub(crate) type HostEvmEnv<D, H> = EvmEnv<TraceDb<D>, H>;
impl EthEvmEnv<TraceDb<AlloyDb<Http<Client>, Ethereum, RootProvider<Http<Client>>>>> {
/// Creates a new provable [EvmEnv] for Ethereum from an HTTP RPC endpoint.
pub async fn from_rpc(url: Url, number: BlockNumberOrTag) -> Result<Self> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this now redundant function be removed, or at least marked as #[deprecated]?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we mark it as deprecated for one release to avoid needless pain to our devs.

Copy link
Contributor Author

@Wollac Wollac Sep 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 4188638

let provider = ProviderBuilder::new().on_http(url);
EvmEnv::from_provider(provider, number).await
EthEvmEnv::builder()
.rpc(url)
.block_number_or_tag(number)
.build()
.await
}
}

Expand All @@ -60,20 +63,11 @@ where
{
/// Creates a new provable [EvmEnv] from an alloy [Provider].
pub async fn from_provider(provider: P, number: BlockNumberOrTag) -> Result<Self> {
let rpc_block = provider
.get_block_by_number(number, false)
EvmEnv::builder()
.provider(provider)
.block_number_or_tag(number)
.build()
.await
.context("eth_getBlockByNumber failed")?
.with_context(|| format!("block {} not found", number))?;
let header: H = rpc_block
.header
.try_into()
.map_err(|err| anyhow!("header invalid: {}", err))?;
log::info!("Environment initialized for block {}", header.number());

let db = TraceDb::new(AlloyDb::new(provider, header.number()));

Ok(EvmEnv::new(db, header.seal_slow()))
}
}

Expand Down Expand Up @@ -103,3 +97,96 @@ where
))
}
}

impl<H> EvmEnv<(), H> {
/// Returns a builder for this environment.
pub fn builder() -> EvmEnvBuilder<NoProvider, H> {
EvmEnvBuilder::default()
}
}

/// Builder to construct an [EvmEnv] on the host.
#[derive(Clone, Debug)]
pub struct EvmEnvBuilder<P, H> {
provider: P,
block: BlockNumberOrTag,
phantom: PhantomData<H>,
}

/// First stage of the [EvmEnvBuilder] without a specified [Provider].
pub struct NoProvider {}

impl<H> Default for EvmEnvBuilder<NoProvider, H> {
fn default() -> Self {
Self {
provider: NoProvider {},
block: BlockNumberOrTag::Latest,
phantom: PhantomData,
}
}
}

impl EvmEnvBuilder<NoProvider, EthBlockHeader> {
/// Sets the Ethereum HTTP RPC endpoint that will be used by the [EvmEnv].
pub fn rpc(self, url: Url) -> EvmEnvBuilder<ReqwestProvider<Ethereum>, EthBlockHeader> {
self.provider(ProviderBuilder::new().on_http(url))
}
}

impl<H: EvmBlockHeader> EvmEnvBuilder<NoProvider, H> {
/// Sets the [Provider] that will be used by the [EvmEnv].
pub fn provider<T, N, P>(self, provider: P) -> EvmEnvBuilder<P, H>
where
T: Transport + Clone,
N: Network,
P: Provider<T, N>,
H: EvmBlockHeader + TryFrom<RpcHeader>,
<H as TryFrom<RpcHeader>>::Error: Display,
{
let Self { block, phantom, .. } = self;
EvmEnvBuilder {
provider,
block,
phantom,
}
}
}

impl<P, H> EvmEnvBuilder<P, H> {
/// Sets the block number.
pub fn block_number(self, number: u64) -> Self {
self.block_number_or_tag(BlockNumberOrTag::Number(number))
}

/// Sets the block number (or tag - "latest", "earliest", "pending").
pub fn block_number_or_tag(mut self, block: BlockNumberOrTag) -> Self {
self.block = block;
self
}

/// Builds the new provable [EvmEnv].
pub async fn build<T, N>(self) -> Result<EvmEnv<TraceDb<AlloyDb<T, N, P>>, H>>
where
T: Transport + Clone,
N: Network,
P: Provider<T, N>,
H: EvmBlockHeader + TryFrom<RpcHeader>,
<H as TryFrom<RpcHeader>>::Error: Display,
{
let rpc_block = self
.provider
.get_block_by_number(self.block, false)
.await
.context("eth_getBlockByNumber failed")?
.with_context(|| format!("block {} not found", self.block))?;
let header: H = rpc_block
.header
.try_into()
.map_err(|err| anyhow!("header invalid: {}", err))?;
log::info!("Environment initialized for block {}", header.number());

let db = TraceDb::new(AlloyDb::new(self.provider, header.number()));

Ok(EvmEnv::new(db, header.seal_slow()))
}
}
12 changes: 6 additions & 6 deletions steel/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ use alloy_primitives::{Address, Sealable, U256};
use alloy_sol_types::SolCall;
use once_cell::sync::Lazy;
use revm::primitives::SpecId;
use risc0_steel::{
config::ChainSpec, ethereum::EthEvmEnv, host::BlockNumberOrTag, CallBuilder, Contract,
};
use risc0_steel::{config::ChainSpec, ethereum::EthEvmEnv, CallBuilder, Contract};

pub static ANVIL_CHAIN_SPEC: Lazy<ChainSpec> =
Lazy::new(|| ChainSpec::new_single(31337, SpecId::CANCUN));
Expand All @@ -39,10 +37,12 @@ where
C: SolCall + Send + 'static,
C::Return: PartialEq + Debug + Send,
{
let mut env = EthEvmEnv::from_provider(provider, BlockNumberOrTag::Latest)
let env = EthEvmEnv::builder()
.provider(provider)
.build()
.await
.unwrap()
.with_chain_spec(&ANVIL_CHAIN_SPEC);
.unwrap();
let mut env = env.with_chain_spec(&ANVIL_CHAIN_SPEC);
let block_hash = env.header().hash_slow();
let block_number = env.header().inner().number;

Expand Down
Loading