Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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
5 changes: 2 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 8 additions & 5 deletions crates/interpreter/src/gas/calc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,11 @@ pub fn call_cost<SPEC: Spec>(
is_cold: bool,
is_call_or_callcode: bool,
is_call_or_staticcall: bool,
is_authcall: bool,
) -> u64 {
call_gas::<SPEC>(is_cold)
+ xfer_cost(is_call_or_callcode, transfers_value)
+ new_cost::<SPEC>(is_call_or_staticcall, is_new, transfers_value)
+ xfer_cost(is_call_or_callcode, transfers_value, is_authcall)
+ new_cost::<SPEC>(is_call_or_staticcall, is_new, transfers_value, is_authcall)
}

#[inline]
Expand All @@ -312,17 +313,19 @@ pub fn warm_cold_cost<SPEC: Spec>(is_cold: bool, regular_value: u64) -> u64 {
}

#[inline]
fn xfer_cost(is_call_or_callcode: bool, transfers_value: bool) -> u64 {
fn xfer_cost(is_call_or_callcode: bool, transfers_value: bool, is_authcall: bool) -> u64 {
if is_call_or_callcode && transfers_value {
CALLVALUE
} else if is_authcall {
AUTHCALLVALUE
} else {
0
}
}

#[inline]
fn new_cost<SPEC: Spec>(is_call_or_staticcall: bool, is_new: bool, transfers_value: bool) -> u64 {
if !is_call_or_staticcall || !is_new {
fn new_cost<SPEC: Spec>(is_call_or_staticcall: bool, is_new: bool, transfers_value: bool, is_authcall: bool) -> u64 {
if !is_call_or_staticcall || !is_authcall || !is_new {
return 0;
}

Expand Down
4 changes: 4 additions & 0 deletions crates/interpreter/src/gas/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub const JUMPDEST: u64 = 1;
pub const SELFDESTRUCT: i64 = 24000;
pub const CREATE: u64 = 32000;
pub const CALLVALUE: u64 = 9000;
pub const AUTHCALLVALUE: u64 = 6700;
pub const NEWACCOUNT: u64 = 25000;
pub const EXP: u64 = 10;
pub const MEMORY: u64 = 3;
Expand Down Expand Up @@ -42,3 +43,6 @@ pub const WARM_SSTORE_RESET: u64 = SSTORE_RESET - COLD_SLOAD_COST;
pub const INITCODE_WORD_COST: u64 = 2;

pub const CALL_STIPEND: u64 = 2300;

// EIP-3074: AUTH and AUTHCALL
pub const AUTH: u64 = 3100;
3 changes: 3 additions & 0 deletions crates/interpreter/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ pub trait Host {
/// Get code hash of `address` and if the account is cold.
fn code_hash(&mut self, address: Address) -> Option<(B256, bool)>;

/// Get nonce of `address`.
fn nonce(&mut self, address: Address) -> Option<u64>;

/// Get storage value of `address` at `index` and if the account is cold.
fn sload(&mut self, address: Address, index: U256) -> Option<(U256, bool)>;

Expand Down
5 changes: 5 additions & 0 deletions crates/interpreter/src/host/dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ impl Host for DummyHost {
Some((KECCAK_EMPTY, false))
}

#[inline]
fn nonce(&mut self, _address: Address) -> Option<u64> {
Some(0)
}

#[inline]
fn sload(&mut self, __address: Address, index: U256) -> Option<(U256, bool)> {
match self.storage.entry(index) {
Expand Down
2 changes: 2 additions & 0 deletions crates/interpreter/src/inner_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ pub enum CallScheme {
DelegateCall,
/// `STATICCALL`
StaticCall,
/// `AUTHCALL`
AuthCall,
}

/// Context of a runtime call.
Expand Down
6 changes: 6 additions & 0 deletions crates/interpreter/src/instruction_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub enum InstructionResult {
OpcodeNotFound,
CallNotAllowedInsideStatic,
StateChangeDuringStaticCall,
ActiveAccountUnsetAuthCall,
InvalidFEOpcode,
InvalidJump,
NotActivated,
Expand Down Expand Up @@ -84,6 +85,7 @@ impl From<HaltReason> for InstructionResult {
HaltReason::CreateInitCodeSizeLimit => Self::CreateInitCodeSizeLimit,
HaltReason::OverflowPayment => Self::OverflowPayment,
HaltReason::StateChangeDuringStaticCall => Self::StateChangeDuringStaticCall,
HaltReason::ActiveAccountUnsetAuthCall => Self::ActiveAccountUnsetAuthCall,
HaltReason::CallNotAllowedInsideStatic => Self::CallNotAllowedInsideStatic,
HaltReason::OutOfFunds => Self::OutOfFunds,
HaltReason::CallTooDeep => Self::CallTooDeep,
Expand Down Expand Up @@ -121,6 +123,7 @@ macro_rules! return_error {
| InstructionResult::OpcodeNotFound
| InstructionResult::CallNotAllowedInsideStatic
| InstructionResult::StateChangeDuringStaticCall
| InstructionResult::ActiveAccountUnsetAuthCall
| InstructionResult::InvalidFEOpcode
| InstructionResult::InvalidJump
| InstructionResult::NotActivated
Expand Down Expand Up @@ -237,6 +240,9 @@ impl From<InstructionResult> for SuccessOrHalt {
InstructionResult::StateChangeDuringStaticCall => {
Self::Halt(HaltReason::StateChangeDuringStaticCall)
}
InstructionResult::ActiveAccountUnsetAuthCall => {
Self::Halt(HaltReason::ActiveAccountUnsetAuthCall)
}
InstructionResult::InvalidFEOpcode => Self::Halt(HaltReason::InvalidFEOpcode),
InstructionResult::InvalidJump => Self::Halt(HaltReason::InvalidJump),
InstructionResult::NotActivated => Self::Halt(HaltReason::NotActivated),
Expand Down
135 changes: 132 additions & 3 deletions crates/interpreter/src/instructions/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
SStoreResult, Transfer, MAX_INITCODE_SIZE,
};
use core::cmp::min;
use revm_primitives::BLOCK_HASH_HISTORY;
use revm_primitives::{alloy_primitives::B512, Address, BLOCK_HASH_HISTORY};
use std::{boxed::Box, vec::Vec};

pub fn balance<H: Host, SPEC: Spec>(interpreter: &mut Interpreter, host: &mut H) {
Expand Down Expand Up @@ -338,6 +338,7 @@ pub fn call<H: Host, SPEC: Spec>(interpreter: &mut Interpreter, host: &mut H) {
local_gas_limit,
true,
true,
false,
) else {
return;
};
Expand Down Expand Up @@ -393,6 +394,7 @@ pub fn call_code<H: Host, SPEC: Spec>(interpreter: &mut Interpreter, host: &mut
local_gas_limit,
true,
false,
false,
) else {
return;
};
Expand Down Expand Up @@ -441,7 +443,7 @@ pub fn delegate_call<H: Host, SPEC: Spec>(interpreter: &mut Interpreter, host: &
};

let Some(gas_limit) =
calc_call_gas::<H, SPEC>(interpreter, host, to, false, local_gas_limit, false, false)
calc_call_gas::<H, SPEC>(interpreter, host, to, false, local_gas_limit, false, false, false)
else {
return;
};
Expand Down Expand Up @@ -488,7 +490,7 @@ pub fn static_call<H: Host, SPEC: Spec>(interpreter: &mut Interpreter, host: &mu
};

let Some(gas_limit) =
calc_call_gas::<H, SPEC>(interpreter, host, to, false, local_gas_limit, false, true)
calc_call_gas::<H, SPEC>(interpreter, host, to, false, local_gas_limit, false, true, false)
else {
return;
};
Expand Down Expand Up @@ -520,3 +522,130 @@ pub fn static_call<H: Host, SPEC: Spec>(interpreter: &mut Interpreter, host: &mu
};
interpreter.instruction_result = InstructionResult::CallOrCreate;
}

pub fn auth_call<H: Host, SPEC: Spec>(interpreter: &mut Interpreter, host: &mut H) {
pop!(interpreter, local_gas_limit);
pop_address!(interpreter, to);
// max gas limit is not possible in real ethereum situation.
let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);

pop!(interpreter, value);

let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(interpreter) else {
return;
};

let Some(mut gas_limit) = calc_call_gas::<H, SPEC>(
interpreter,
host,
to,
value != U256::ZERO,
local_gas_limit,
false,
false,
true,
) else {
return;
};

gas!(interpreter, gas_limit);

// add call stipend if there is value to be transferred.
if value != U256::ZERO {
gas_limit = gas_limit.saturating_add(gas::CALL_STIPEND);
}

let authority = {
let authority = interpreter.authorized;
if interpreter.authorized.is_none() {
interpreter.instruction_result = InstructionResult::ActiveAccountUnsetAuthCall;
return;
}
authority.unwrap()
};

// Call host to interact with target contract
interpreter.next_action = InterpreterAction::Call {
inputs: Box::new(CallInputs {
contract: to,
transfer: Transfer {
source: authority,
target: to,
value,
},
input,
gas_limit,
context: CallContext {
address: to,
caller: authority,
code_address: to,
apparent_value: value,
scheme: CallScheme::Call,
},
is_static: interpreter.is_static,
return_memory_offset,
}),
};
interpreter.instruction_result = InstructionResult::CallOrCreate;
}

pub fn auth<H: Host, SPEC: Spec>(interpreter: &mut Interpreter, host: &mut H) {
// Pop the `authority` off of the stack
pop_address!(interpreter, authority);
// Pop the signature offset and length off of the stack
pop!(interpreter, signature_offset, signature_len);

let signature_offset = as_usize_or_fail!(interpreter, signature_offset);
let signature_len = as_usize_or_fail!(interpreter, signature_len);

// Charge the fixed cost for the `AUTH` opcode
gas!(interpreter, gas::AUTH);

// Grab the memory region for the input data and charge for any memory expansion
resize_memory!(interpreter, signature_offset, signature_len);
let mem_slice = interpreter
.shared_memory
.slice(signature_offset, signature_len);

// recId (yParity) is the first byte.
let recid = mem_slice[0];
// signature (r & s values) are the next 64 bytes.
let signature = <&B512>::try_from(&mem_slice[1..65]).unwrap();
let sig_len = signature.len() + 1;

// If the memory region is longer than 65 bytes, pull the commit from the next [1, 32] bytes.
let commit = (mem_slice.len() > sig_len).then(|| {
let mut buf = B256::ZERO;
let remaining = mem_slice.len() - sig_len;
let cpy_size = (remaining > 32).then_some(32).unwrap_or(remaining);
buf[0..remaining].copy_from_slice(&mem_slice[sig_len..sig_len + cpy_size]);
buf
});

// Build the original auth message and compute the hash.
let mut message = [0u8; 129];
message[0] = 0x04; // AUTH_MAGIC - add constant?
message[1..33].copy_from_slice(
U256::from(host.env().cfg.chain_id)
.to_be_bytes::<32>()
.as_ref(),
);
message[33..65].copy_from_slice(
U256::from(host.nonce(authority).unwrap())
.to_be_bytes::<32>()
.as_ref(),
);
message[65..97].copy_from_slice(interpreter.contract().address.into_word().as_ref());
message[97..129].copy_from_slice(commit.unwrap_or_default().as_ref());
let message_hash = revm_primitives::keccak256(&message);

// Verify the signature
let recovered = revm_primitives::secp256k1::ecrecover(&signature, recid, &message_hash);

if recovered.is_err() || Address::from_word(recovered.unwrap_or_default()) != authority {
push!(interpreter, U256::ZERO);
} else {
push!(interpreter, U256::from(1));
interpreter.authorized = Some(authority);
}
}
2 changes: 2 additions & 0 deletions crates/interpreter/src/instructions/host/call_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub fn calc_call_gas<H: Host, SPEC: Spec>(
local_gas_limit: u64,
is_call_or_callcode: bool,
is_call_or_staticcall: bool,
is_authcall: bool,
) -> Option<u64> {
let Some((is_cold, exist)) = host.load_account(to) else {
interpreter.instruction_result = InstructionResult::FatalExternalError;
Expand All @@ -55,6 +56,7 @@ pub fn calc_call_gas<H: Host, SPEC: Spec>(
is_cold,
is_call_or_callcode,
is_call_or_staticcall,
is_authcall,
);

gas!(interpreter, call_cost, None);
Expand Down
Loading