Skip to content

Commit 28f9480

Browse files
committed
feat(evm): align with Tempo T11 precompiles
1 parent d717cd9 commit 28f9480

9 files changed

Lines changed: 1332 additions & 1036 deletions

File tree

Cargo.lock

Lines changed: 1007 additions & 894 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 73 additions & 91 deletions
Large diffs are not rendered by default.

crates/precompiles/src/account_keychain.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
33
use alloy_primitives::Address;
44
use alloy_sol_types::SolInterface;
5+
use tempo_chainspec::hardfork::TempoHardfork;
56
use tempo_contracts::precompiles::IAccountKeychain;
7+
use tempo_precompiles::dispatch::abi_decoder_config_for_spec;
68

79
use crate::{
810
execution::{CallCheck, CallRules},
@@ -14,8 +16,11 @@ use crate::{
1416
pub(crate) struct AccountKeychainRules;
1517

1618
impl CallRules for AccountKeychainRules {
17-
fn admit(&self, data: &[u8], caller: Address) -> CallCheck {
18-
let Ok(call) = IAccountKeychain::IAccountKeychainCalls::abi_decode(data) else {
19+
fn admit(&self, data: &[u8], caller: Address, spec: TempoHardfork) -> CallCheck {
20+
let Ok(call) = IAccountKeychain::IAccountKeychainCalls::abi_decode_with_config(
21+
data,
22+
abi_decoder_config_for_spec(spec),
23+
) else {
1924
// Preserve the upstream error and gas behavior for malformed or unknown calldata.
2025
return CallCheck::Continue;
2126
};
@@ -74,12 +79,12 @@ mod tests {
7479
outsider: Address,
7580
) {
7681
assert!(matches!(
77-
rules.admit(&call.abi_encode(), owner),
82+
rules.admit(&call.abi_encode(), owner, TempoHardfork::T8),
7883
CallCheck::Continue
7984
));
8085
for caller in [sequencer, outsider] {
8186
assert!(matches!(
82-
rules.admit(&call.clone().abi_encode(), caller),
87+
rules.admit(&call.clone().abi_encode(), caller, TempoHardfork::T8),
8388
CallCheck::Revert(data) if data == Unauthorized {}.abi_encode()
8489
));
8590
}
@@ -170,7 +175,8 @@ mod tests {
170175
assert!(matches!(
171176
rules.admit(
172177
&IAccountKeychain::getTransactionKeyCall {}.abi_encode(),
173-
caller
178+
caller,
179+
TempoHardfork::T8,
174180
),
175181
CallCheck::Continue
176182
));
@@ -180,9 +186,32 @@ mod tests {
180186
keyId: Address::repeat_byte(0x22),
181187
}
182188
.abi_encode(),
183-
caller
189+
caller,
190+
TempoHardfork::T8,
184191
),
185192
CallCheck::Continue
186193
));
187194
}
195+
196+
#[test]
197+
fn t11_defers_noncanonical_address_calldata_to_upstream() {
198+
let owner = Address::repeat_byte(0x11);
199+
let outsider = Address::repeat_byte(0x22);
200+
let rules = AccountKeychainRules;
201+
let mut data = IAccountKeychain::getKeyCall {
202+
account: owner,
203+
keyId: Address::repeat_byte(0x33),
204+
}
205+
.abi_encode();
206+
data[4] = 1;
207+
208+
assert!(matches!(
209+
rules.admit(&data, outsider, TempoHardfork::T8),
210+
CallCheck::Revert(data) if data == Unauthorized {}.abi_encode()
211+
));
212+
assert!(matches!(
213+
rules.admit(&data, outsider, TempoHardfork::T11),
214+
CallCheck::Continue
215+
));
216+
}
188217
}

crates/precompiles/src/execution.rs

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ pub(crate) trait CallRules: 'static {
8888
None
8989
}
9090

91-
/// Applies pure Zone-specific admission rules before storage setup.
92-
fn admit(&self, _data: &[u8], _caller: Address) -> CallCheck {
91+
/// Applies Zone-specific admission rules using the active Tempo hardfork.
92+
fn admit(&self, _data: &[u8], _caller: Address, _spec: TempoHardfork) -> CallCheck {
9393
CallCheck::Continue
9494
}
9595
}
@@ -115,7 +115,13 @@ pub(crate) fn create_precompile(
115115
}
116116

117117
let (data, caller) = (input.data, input.caller);
118-
if input.gas < input_cost(data.len()) {
118+
let Ok(input_gas) = input_cost(env.cfg.spec, data.len()) else {
119+
return Ok(PrecompileOutput::halt(
120+
PrecompileHalt::OutOfGas,
121+
input.reservoir,
122+
));
123+
};
124+
if input.gas < input_gas {
119125
return Ok(PrecompileOutput::halt(
120126
PrecompileHalt::OutOfGas,
121127
input.reservoir,
@@ -147,17 +153,19 @@ pub(crate) fn create_precompile(
147153
storage.set_tip1060_storage_credits(false);
148154
}
149155

150-
let mut result = StorageCtx::enter(&mut storage, || match rules.admit(data, caller) {
151-
CallCheck::Continue => execute(data, caller),
152-
CallCheck::Revert(output) => {
153-
let s = StorageCtx::default();
154-
let output = s.revert_output(output);
155-
add_input_cost(s, data, Ok(output))
156-
}
157-
CallCheck::Error(error) => {
158-
let s = StorageCtx::default();
159-
let result = s.error_result(error);
160-
add_input_cost(s, data, result)
156+
let mut result = StorageCtx::enter(&mut storage, || {
157+
match rules.admit(data, caller, env.cfg.spec) {
158+
CallCheck::Continue => execute(data, caller),
159+
CallCheck::Revert(output) => {
160+
let s = StorageCtx::default();
161+
let output = s.revert_output(output);
162+
add_input_cost(s, data, Ok(output))
163+
}
164+
CallCheck::Error(error) => {
165+
let s = StorageCtx::default();
166+
let result = s.error_result(error);
167+
add_input_cost(s, data, result)
168+
}
161169
}
162170
});
163171
if let (Ok(output), Some(gas)) = (&mut result, fixed_gas) {
@@ -207,7 +215,7 @@ mod tests {
207215
Some(FIXED_GAS)
208216
}
209217

210-
fn admit(&self, data: &[u8], caller: Address) -> CallCheck {
218+
fn admit(&self, data: &[u8], caller: Address, _spec: TempoHardfork) -> CallCheck {
211219
*self.0.borrow_mut() = Some((
212220
Bytes::copy_from_slice(data),
213221
selector_from_calldata(data),
@@ -371,7 +379,7 @@ mod tests {
371379
Some(FIXED_GAS)
372380
}
373381

374-
fn admit(&self, _data: &[u8], _caller: Address) -> CallCheck {
382+
fn admit(&self, _data: &[u8], _caller: Address, _spec: TempoHardfork) -> CallCheck {
375383
self.0.set(true);
376384
CallCheck::Revert(Bytes::from_static(b"denied"))
377385
}
@@ -418,10 +426,44 @@ mod tests {
418426
assert_eq!(rejected.bytes, Bytes::from_static(b"denied"));
419427
}
420428

429+
#[test]
430+
fn input_gas_threshold_tracks_t11() {
431+
let calldata = [0u8; 32];
432+
433+
for (spec, required_gas) in [(TempoHardfork::T10, 6), (TempoHardfork::T11, 30)] {
434+
let mut cfg = revm::context::CfgEnv::<TempoHardfork>::default();
435+
cfg.spec = spec;
436+
let env = ZonePrecompileEnv::new(
437+
&cfg,
438+
zone_hardfork::ZoneHardfork::Z0,
439+
StorageActions::disabled(),
440+
Rc::new(RefCell::new(NonCreditableSlots::empty())),
441+
);
442+
let precompile = create_precompile("InputGasTest", &env, NoCallRules, |_, _| {
443+
Ok(StorageCtx::default().success_output(Bytes::new()))
444+
});
445+
let mut ctx = test_context();
446+
447+
let insufficient = precompile
448+
.call(input(&mut ctx, &calldata, Address::ZERO, required_gas - 1))
449+
.unwrap();
450+
assert_eq!(
451+
insufficient.halt_reason(),
452+
Some(&PrecompileHalt::OutOfGas),
453+
"{spec:?} must require {required_gas} input gas"
454+
);
455+
456+
let sufficient = precompile
457+
.call(input(&mut ctx, &calldata, Address::ZERO, required_gas))
458+
.unwrap();
459+
assert!(!sufficient.is_halt(), "{spec:?} must accept its exact cost");
460+
}
461+
}
462+
421463
struct FatalRules;
422464

423465
impl CallRules for FatalRules {
424-
fn admit(&self, _data: &[u8], _caller: Address) -> CallCheck {
466+
fn admit(&self, _data: &[u8], _caller: Address, _spec: TempoHardfork) -> CallCheck {
425467
StorageCtx::default().deduct_gas(10).unwrap();
426468
CallCheck::Error(TempoPrecompileError::Fatal("boom".into()))
427469
}

crates/precompiles/src/nonce.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
33
use alloy_primitives::Address;
44
use alloy_sol_types::SolInterface;
5+
use tempo_chainspec::hardfork::TempoHardfork;
56
use tempo_contracts::precompiles::INonce;
7+
use tempo_precompiles::dispatch::abi_decoder_config_for_spec;
68

79
use crate::{
810
execution::{CallCheck, CallRules},
@@ -14,8 +16,10 @@ use crate::{
1416
pub(crate) struct NonceRules;
1517

1618
impl CallRules for NonceRules {
17-
fn admit(&self, data: &[u8], caller: Address) -> CallCheck {
18-
let Ok(call) = INonce::INonceCalls::abi_decode(data) else {
19+
fn admit(&self, data: &[u8], caller: Address, spec: TempoHardfork) -> CallCheck {
20+
let Ok(call) =
21+
INonce::INonceCalls::abi_decode_with_config(data, abi_decoder_config_for_spec(spec))
22+
else {
1923
// Preserve the upstream error and gas behavior for malformed or unknown calldata.
2024
return CallCheck::Continue;
2125
};
@@ -55,15 +59,37 @@ mod tests {
5559

5660
StorageCtx::enter(&mut storage, || {
5761
assert!(matches!(
58-
rules.admit(&call.abi_encode(), owner),
62+
rules.admit(&call.abi_encode(), owner, TempoHardfork::T8),
5963
CallCheck::Continue
6064
));
6165
for caller in [sequencer, outsider, intermediary] {
6266
assert!(matches!(
63-
rules.admit(&call.abi_encode(), caller),
67+
rules.admit(&call.abi_encode(), caller, TempoHardfork::T8),
6468
CallCheck::Revert(data) if data == Unauthorized {}.abi_encode()
6569
));
6670
}
6771
});
6872
}
73+
74+
#[test]
75+
fn t11_defers_noncanonical_address_calldata_to_upstream() {
76+
let owner = Address::repeat_byte(0x11);
77+
let outsider = Address::repeat_byte(0x22);
78+
let rules = NonceRules;
79+
let mut data = INonce::getNonceCall {
80+
account: owner,
81+
nonceKey: U256::from(1),
82+
}
83+
.abi_encode();
84+
data[4] = 1;
85+
86+
assert!(matches!(
87+
rules.admit(&data, outsider, TempoHardfork::T8),
88+
CallCheck::Revert(data) if data == Unauthorized {}.abi_encode()
89+
));
90+
assert!(matches!(
91+
rules.admit(&data, outsider, TempoHardfork::T11),
92+
CallCheck::Continue
93+
));
94+
}
6995
}

crates/precompiles/src/receive_policy_guard.rs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ use crate::{
1010
};
1111
use alloy_primitives::Address;
1212
use alloy_sol_types::{SolCall, SolError};
13+
use tempo_chainspec::hardfork::TempoHardfork;
1314
use tempo_contracts::precompiles::IReceivePolicyGuard;
14-
use tempo_precompiles::{address_registry::AddressRegistry, dispatch::selector_from_calldata};
15+
use tempo_precompiles::{
16+
address_registry::AddressRegistry,
17+
dispatch::{abi_decoder_config_for_spec, selector_from_calldata},
18+
};
1519
use tempo_zone_contracts::Unauthorized;
1620

1721
/// Stakeholder-only admission for receipt balance lookups.
@@ -26,12 +30,15 @@ impl CallRules for ReceivePolicyGuardRules {
2630
.then_some(TIP20_FIXED_TRANSFER_GAS)
2731
}
2832

29-
fn admit(&self, data: &[u8], caller: Address) -> CallCheck {
33+
fn admit(&self, data: &[u8], caller: Address, spec: TempoHardfork) -> CallCheck {
3034
if selector_from_calldata(data) != Some(IReceivePolicyGuard::balanceOfCall::SELECTOR) {
3135
return CallCheck::Continue;
3236
}
3337

34-
let Ok(call) = IReceivePolicyGuard::balanceOfCall::abi_decode_raw(&data[4..]) else {
38+
let Ok(call) = IReceivePolicyGuard::balanceOfCall::abi_decode_raw_with_config(
39+
&data[4..],
40+
abi_decoder_config_for_spec(spec),
41+
) else {
3542
// Preserve the upstream ABI error for malformed calldata.
3643
return CallCheck::Continue;
3744
};
@@ -115,7 +122,7 @@ mod tests {
115122
caller: Address,
116123
) {
117124
assert!(matches!(
118-
rules.admit(&balance_call(receipt), caller),
125+
rules.admit(&balance_call(receipt), caller, TempoHardfork::T8),
119126
CallCheck::Continue
120127
));
121128
}
@@ -126,7 +133,7 @@ mod tests {
126133
caller: Address,
127134
) {
128135
assert!(matches!(
129-
rules.admit(&balance_call(receipt), caller),
136+
rules.admit(&balance_call(receipt), caller, TempoHardfork::T8),
130137
CallCheck::Revert(data) if data == Unauthorized {}.abi_encode()
131138
));
132139
}
@@ -181,7 +188,7 @@ mod tests {
181188

182189
StorageCtx::enter(&mut storage, || {
183190
assert!(matches!(
184-
rules.admit(&balance_call(&receipt), OUTSIDER),
191+
rules.admit(&balance_call(&receipt), OUTSIDER, TempoHardfork::T8),
185192
CallCheck::Error(tempo_precompiles::error::TempoPrecompileError::OutOfGas)
186193
));
187194
});
@@ -201,11 +208,34 @@ mod tests {
201208
}
202209
.abi_encode();
203210

204-
assert!(matches!(rules.admit(&claim, OUTSIDER), CallCheck::Continue));
205211
assert!(matches!(
206-
rules.admit(&malformed, OUTSIDER),
212+
rules.admit(&claim, OUTSIDER, TempoHardfork::T8),
207213
CallCheck::Continue
208214
));
215+
assert!(matches!(
216+
rules.admit(&malformed, OUTSIDER, TempoHardfork::T8),
217+
CallCheck::Continue
218+
));
219+
}
220+
221+
#[test]
222+
fn t11_defers_noncanonical_balance_calldata_to_upstream() {
223+
let rules = ReceivePolicyGuardRules;
224+
let mut data = balance_call(&receipt(RECEIVER, RECOVERY)).to_vec();
225+
data.extend([0; 32]);
226+
let mut ctx = test_context();
227+
let mut storage = test_storage_provider(&mut ctx, u64::MAX, true);
228+
229+
StorageCtx::enter(&mut storage, || {
230+
assert!(matches!(
231+
rules.admit(&data, OUTSIDER, TempoHardfork::T8),
232+
CallCheck::Revert(data) if data == Unauthorized {}.abi_encode()
233+
));
234+
assert!(matches!(
235+
rules.admit(&data, OUTSIDER, TempoHardfork::T11),
236+
CallCheck::Continue
237+
));
238+
});
209239
}
210240

211241
#[test]

0 commit comments

Comments
 (0)