Skip to content
Closed
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
feat(examples): custom precompile
chore(docs): Update top-level examples readme with custom precompile command
  • Loading branch information
refcell committed Dec 4, 2023
commit b610de11575f1e3f0093e0e016f2b351e1985ff6
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ Flamegraph also requires sudo mode to run (hence the `--root` cli arg) and will
cargo run -p revm --features ethersdb --example fork_ref_transact
```

```shell
cargo run -p revm --features std,serde,optimism --example custom_precompile
```

# Used by:

* [Foundry](https://github.com/foundry-rs/foundry) is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
Expand Down
5 changes: 5 additions & 0 deletions crates/revm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ name = "fork_ref_transact"
path = "../../examples/fork_ref_transact.rs"
required-features = ["ethersdb"]

[[example]]
name = "custom_precompile"
path = "../../examples/custom_precompile.rs"
required-features = ["optimism", "std", "serde"]

[[bench]]
name = "bench"
path = "benches/bench.rs"
Expand Down
132 changes: 132 additions & 0 deletions examples/custom_precompile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//! Custom Precompile Example
//!
//! This example walks through the process of creating a custom precompile and
//! executing it with an optimism deposit transaction.

use revm::inspectors::TracerEip3155;
use revm::primitives::{
address, b256, AccountInfo, Bytes, CanyonSpec, Env, OptimismFields, SpecId, TransactTo, U256,
};
use revm::{EVMImpl, InMemoryDB, JournaledState, Transact};
use revm_precompile::{Precompile, PrecompileResult, Precompiles};
use std::io::stdout;

const MINIMUM_GAS_LIMIT: u8 = 0xFF;

// A standard precompile function that returns opcodes to send 100 wei to a
// specific address.
fn precompile_func(_input: &[u8], _gas_limit: u64) -> PrecompileResult {
// CALL Stack Input
// [gas, to, value, arg_offset, arg_size, ret_offset, ret_size]
// [0xFF, caller, 100, 0, 0, 0, 0 ]
println!("---> Inside the precompile function!");

let zero = 0x00 as u8;
let push1 = 0x60 as u8;
let hundred = 0x64 as u8;
let push20 = 0x73 as u8;
let to = address!("deadca11deadca11deadca11deadca11deadca11");
let call = 0xF1 as u8;

let gas_used = 0;
let ret_bytes = [
&[
push1, zero, push1, zero, push1, zero, push1, zero, push1, hundred, push20,
],
&to.as_slice()[..],
&[push1, MINIMUM_GAS_LIMIT, call],
]
.concat();

println!("---> Precompile function returning: {:x?}", ret_bytes);
Ok((gas_used, ret_bytes))
}

fn main() -> anyhow::Result<()> {
// Build the custom precompile
let precompile_addr = address!("0000000000000000000000000000000000000420");
let precompile = revm_precompile::PrecompileWithAddress(
precompile_addr,
Precompile::Standard(precompile_func),
);
let precompiles = Precompiles {
inner: vec![precompile],
};

// Build the evm environment {block, cfg, tx}
let mut env = Env::default();
let caller = address!("deadca11deadca11deadca11deadca11deadca11");

env.block.number = U256::from(113055114);
env.block.coinbase = address!("deaddeaddeaddeaddeaddeaddeaddeaddead0001");
env.block.timestamp = U256::from(1_629_814_800);
env.block.gas_limit = U256::from(30_000_000);
env.block.basefee = U256::from(1);
env.block.difficulty = U256::from(1);

env.cfg.chain_id = 10;
env.cfg.spec_id = SpecId::CANYON;
env.cfg.optimism = true;

env.tx.caller = caller;
env.tx.gas_limit = 30000000;
env.tx.gas_price = U256::from(50);

env.tx.transact_to = TransactTo::call(precompile_addr);
env.tx.value = U256::from(100);
env.tx.data = Bytes::from_static(&[]);
env.tx.nonce = Some(0);
env.tx.chain_id = Some(10);
env.tx.gas_priority_fee = Some(U256::from(1));

env.tx.optimism = OptimismFields {
source_hash: None,
mint: Some(100_u128),
is_system_transaction: Some(false),
enveloped_tx: Some(Bytes::from_static(&[])),
};

// Insert the calling account into the database
let mut db = InMemoryDB::default();
db.insert_account_info(
caller,
AccountInfo {
nonce: 0,
balance: U256::from_str_radix("FFFFFFFFFFFF", 16).unwrap(),
code_hash: b256!("0000000000000000000000000000000000000000000000000000000000000000"),
code: None,
},
);
let mut journal = JournaledState::new(SpecId::CANYON, vec![]);
journal
.initial_account_load(caller, &[U256::from(100)], &mut db)
.unwrap();

let mut inspector = TracerEip3155::new(Box::new(stdout()), true, true);

let mut evm = Box::new(EVMImpl::<CanyonSpec, InMemoryDB>::new_with_spec(
&mut db,
&mut env,
Some(&mut inspector),
precompiles,
));

// Preverify for sanity
if let Err(e) = evm.preverify_transaction_inner() {
println!("Preverification error: {:?}", e);
return Err(anyhow::anyhow!("Preverification failed"));
}
println!("Preverification succeeded. Transacting...\n");

// Transact
let result = evm.transact();
match result {
Ok(r) => println!("\nTransact result\n{:?}", r),
Err(e) => {
println!("Transact error: {:?}", e);
return Err(anyhow::anyhow!("Transact failed"));
}
}

Ok(())
}