Skip to content

Commit 64f1632

Browse files
committed
Implement Celo transfer precompile
1 parent 460a958 commit 64f1632

File tree

3 files changed

+99
-1
lines changed

3 files changed

+99
-1
lines changed

crates/anvil/src/eth/backend/executor.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::{
88
error::InvalidTransactionError,
99
pool::transactions::PoolTransaction,
1010
},
11+
evm::celo_precompile::celo_precompile_lookup,
1112
inject_precompiles,
1213
mem::inspector::AnvilInspector,
1314
};
@@ -462,7 +463,12 @@ where
462463
op_context,
463464
inspector,
464465
EthInstructions::default(),
465-
PrecompilesMap::from_static(op_precompiles),
466+
if env.is_celo {
467+
PrecompilesMap::from_static(op_precompiles)
468+
.with_precompile_lookup(celo_precompile_lookup)
469+
} else {
470+
PrecompilesMap::from_static(op_precompiles)
471+
},
466472
));
467473

468474
let op = OpEvm::new(op_evm, true);

crates/anvil/src/evm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use foundry_evm_core::either_evm::EitherEvm;
99
use op_revm::OpContext;
1010
use revm::{Inspector, precompile::PrecompileWithAddress};
1111

12+
pub mod celo_precompile;
13+
1214
/// Object-safe trait that enables injecting extra precompiles when using
1315
/// `anvil` as a library.
1416
pub trait PrecompileFactory: Send + Sync + Unpin + Debug {
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2+
use alloy_primitives::{Address, U256, address};
3+
use revm::precompile::{PrecompileError, PrecompileOutput, PrecompileResult};
4+
5+
pub const CELO_TRANSFER_ADDRESS: Address = address!("0x00000000000000000000000000000000000000fd");
6+
7+
/// Gas cost for Celo transfer precompile
8+
const CELO_TRANSFER_GAS_COST: u64 = 9000;
9+
10+
/// Celo transfer precompile implementation using EvmInternals
11+
/// Address: 0xfd (253)
12+
/// Input: from (32 bytes) || to (32 bytes) || value (32 bytes) = 96 bytes total
13+
/// Gas cost: 9000
14+
///
15+
/// This implementation uses load_account to modify balances directly instead of
16+
/// requiring journal access, making it compatible with PrecompilesMap lookup function.
17+
pub fn celo_transfer_precompile(input: PrecompileInput<'_>) -> PrecompileResult {
18+
// Check minimum gas requirement
19+
if input.gas < CELO_TRANSFER_GAS_COST {
20+
return Err(PrecompileError::OutOfGas);
21+
}
22+
23+
// Validate input length (must be exactly 96 bytes: 32 + 32 + 32)
24+
if input.data.len() != 96 {
25+
return Err(PrecompileError::Other(format!(
26+
"Invalid input length for Celo transfer precompile: expected 96 bytes, got {}",
27+
input.data.len()
28+
)));
29+
}
30+
31+
// Parse input: from (bytes 12-32), to (bytes 44-64), value (bytes 64-96)
32+
let from_bytes = &input.data[12..32];
33+
let to_bytes = &input.data[44..64];
34+
let value_bytes = &input.data[64..96];
35+
36+
let from_address = Address::from_slice(from_bytes);
37+
let to_address = Address::from_slice(to_bytes);
38+
let value = U256::from_be_slice(value_bytes);
39+
40+
// Perform the transfer using load_account to modify balances directly
41+
let mut internals = input.internals;
42+
43+
// Load and check the from account balance first
44+
{
45+
let from_account = match internals.load_account(from_address) {
46+
Ok(account) => account,
47+
Err(e) => {
48+
return Err(PrecompileError::Other(format!("Failed to load from account: {e:?}")));
49+
}
50+
};
51+
52+
// Check if from account has sufficient balance
53+
if from_account.data.info.balance < value {
54+
return Err(PrecompileError::Other("Insufficient balance".into()));
55+
}
56+
57+
// Deduct balance from the from account
58+
from_account.data.info.balance -= value;
59+
}
60+
61+
// Load and update the to account
62+
{
63+
let to_account = match internals.load_account(to_address) {
64+
Ok(account) => account,
65+
Err(e) => {
66+
return Err(PrecompileError::Other(format!("Failed to load to account: {e:?}")));
67+
}
68+
};
69+
70+
// Check for overflow in to account
71+
if to_account.data.info.balance.checked_add(value).is_none() {
72+
return Err(PrecompileError::Other("Balance overflow in to account".into()));
73+
}
74+
75+
// Add balance to the to account
76+
to_account.data.info.balance += value;
77+
}
78+
79+
// No output data for successful transfer
80+
Ok(PrecompileOutput::new(CELO_TRANSFER_GAS_COST, alloy_primitives::Bytes::new()))
81+
}
82+
83+
/// Can be used as PrecompilesMap lookup function
84+
pub fn celo_precompile_lookup(address: &Address) -> Option<DynPrecompile> {
85+
if *address == CELO_TRANSFER_ADDRESS {
86+
Some(DynPrecompile::new_stateful(celo_transfer_precompile))
87+
} else {
88+
None
89+
}
90+
}

0 commit comments

Comments
 (0)