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

feat(anvil): add support for injecting precompiles #7589

Merged
merged 17 commits into from
Apr 9, 2024

Conversation

alexfertel
Copy link
Contributor

@alexfertel alexfertel commented Apr 7, 2024

Motivation

Resolves #7498

Solution

We add a new object-safe trait to anvil's public API called: PrecompileFactory.

pub trait PrecompileFactory {
    fn precompiles(&self) -> Vec<(Address, Precompile)>;
}

Alongside this trait we add a new precompile_factory field to NodeConfig which we populate in the with_precompile_factory builder function:

struct NodeConfig {
    pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
}

impl NodeConfig {
    #[must_use]
    pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self {
        self.precompile_factory = Some(Arc::new(factory));
        self
    }
}

This is used in the newly added inject_precompiles():

pub fn inject_precompiles<DB, I>(
    evm: &mut revm::Evm<'_, I, DB>,
    precompiles: Vec<(Address, Precompile)>,
) where
    DB: revm::Database,
{
  ...
}

Usage

From a consumer's standpoint, this looks like this (simplified for showcasing):

use alloy_network::TransactionBuilder;
use alloy_primitives::{address, Address};
use alloy_rpc_types::{TransactionRequest, WithOtherFields};
use anvil::{NodeConfig, PrecompileFactory};
use revm_primitives::{Bytes, Precompile, PrecompileResult};

const STYLUS_RPC_URL: &str = "https://stylus-testnet.arbitrum.io/rpc";
const ARB_WASM_ADDRESS: Address = address!("0000000000000000000000000000000000000071");
const ALICE: Address = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d");

fn my_precompile(bytes: &Bytes, _gas_limit: u64) -> PrecompileResult {
    if *bytes == Bytes::from_static(b"hello") {
        Ok((10, Bytes::from_static(b"world")))
    } else {
        Ok((10, Bytes::from_static(b"hello")))
    }
}

struct CustomPrecompileFactory;

impl PrecompileFactory for CustomPrecompileFactory {
    fn precompiles(&self) -> Vec<(Address, Precompile)> {
        vec![(ARB_WASM_ADDRESS, Precompile::Standard(my_precompile))]
    }
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn spawns_anvil() {
    let config = NodeConfig::default()
        .silent()
        .with_eth_rpc_url(Some(STYLUS_RPC_URL))
        .with_extra_precompiles(CustomPrecompileFactory);
    let (api, _) = anvil::spawn(config).await;
    assert_eq!(api.chain_id(), 23011913);

    let tx = TransactionRequest::default()
        .with_from(ALICE)
        .with_to(ARB_WASM_ADDRESS.into())
        .with_input(Bytes::from_static(b"hello"));
    let result = api
        .call(WithOtherFields::new(tx), None, None)
        .await
        .unwrap();
    assert_eq!(result, Bytes::from_static(b"world"));
}

This looks quite clean IMO. This should mark the first step in adding support for Arbitrum precompiles and being able to decouple Optimism from the codebase.


Notes

  • Did not use Evm::builder because of this comment.

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

this is great!

Before making any changes to the evm crate, I think we can make this work for anvil but injecting precompiles to the evm directly which does not require changes in the utils crate

crates/anvil/src/config.rs Outdated Show resolved Hide resolved
crates/evm/core/src/utils.rs Outdated Show resolved Hide resolved
Seshomaruuu

This comment was marked as spam.

@alexfertel alexfertel requested a review from mattsse April 8, 2024 07:58
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

this is great, this should work.

for some reason fmt keeps appending ; on some machines...
I'd appreciate if you could remove those from unchanged files so this doesn't cause conflicts with other prs that perhaps touch that code.

lgtm otherwise

crates/anvil/src/config.rs Outdated Show resolved Hide resolved
crates/anvil/src/evm.rs Outdated Show resolved Hide resolved
@alexfertel alexfertel requested a review from mattsse April 8, 2024 13:54
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

okay, I realized this needs a few more changes,

when we create the backend, we need to clone the precompilesfactory and keep this separately, so it's no longer behind the lock.

we also need to pass this to the executor, because currently the executor doesn't inject the precompiles, so the executor needs a Arc<dyn> clone as well

Comment on lines 818 to 823
let mut evm = new_evm_with_inspector_ref(&*db, env, &mut inspector);

let ResultAndState { result, state } =
new_evm_with_inspector_ref(&*db, env, &mut inspector).transact()?;
let cfg = self.node_config.read().await;
if let Some(ref factory) = cfg.precompile_factory {
inject_precompiles(&mut evm, factory.precompiles());
}
Copy link
Member

Choose a reason for hiding this comment

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

we can put this in a function of Backend::new_evm_with_inspector_ref

@alexfertel
Copy link
Contributor Author

when we create the backend, we need to clone the precompilesfactory and keep this separately, so it's no longer behind the lock.

we also need to pass this to the executor, because currently the executor doesn't inject the precompiles, so the executor needs a Arc clone as well

I see, that makes sense 👍

@alexfertel alexfertel requested a review from mattsse April 9, 2024 14:57
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

lgtm,
we have some open prs that likely introduce conflicts, will resolve them and get this merged asap

@alexfertel
Copy link
Contributor Author

Lovely! Thank you for the patience and the pointers.

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

this is great,

once you have arb precompiles lmk, we'd love to inject them by default if the forked chain is arb for example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support injecting custom precompiles to anvil
3 participants