Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7c77ad3
feat(precompiles): l1-backed storage provider
0xrusowsky Jul 13, 2026
329b185
style: minimize diff
0xrusowsky Jul 13, 2026
a786877
fix: clippy
0xrusowsky Jul 13, 2026
f2478df
fix: preserve revert and RPC error semantics
0xrusowsky Jul 14, 2026
4937b11
fix: make TIP-20 packed slot writes field-aware
0xrusowsky Jul 14, 2026
2b233bc
refactor(precompiles): use composed EVM hardfork
0xrusowsky Jul 15, 2026
1370279
refactor(precompiles): encapsulate L1 anchor loading
0xrusowsky Jul 15, 2026
4c59b6c
feat(precompiles): centralize zone precompile execution
0xrusowsky Jul 13, 2026
bc219b0
refactor(precompiles): model zone call rules explicitly
0xrusowsky Jul 14, 2026
b8022cd
style: cleanup
0xrusowsky Jul 14, 2026
8ab6ae3
fix: run CallRule checks before execution
0xrusowsky Jul 14, 2026
5e6bb71
feat: make `CallRules` future-proof
0xrusowsky Jul 14, 2026
a6e898d
refactor(precompiles): initialize storage from L1 anchor
0xrusowsky Jul 15, 2026
a545039
docs(precompiles): clarify CallRules execution phases
0xrusowsky Jul 15, 2026
fa262c4
feat(precompiles): drop 403 and ZTIP20 proxy impl
0xrusowsky Jul 14, 2026
0c58d54
fix: seed all slots so test fixtures without rpc don't hang
0xrusowsky Jul 14, 2026
474772f
fix: cache enabled tokens transfer policy id
0xrusowsky Jul 14, 2026
2dfc2fb
feat: make `CallRules` future-proof
0xrusowsky Jul 14, 2026
0d55857
test(precompiles): use composed hardfork context
0xrusowsky Jul 15, 2026
7e01f9e
wip: zone outbox precompile
0xrusowsky Jul 14, 2026
e23ddc7
wip: leverage precompile execution
0xrusowsky Jul 16, 2026
9a4d9fb
feat: improve error and `CallRules` devex
0xrusowsky Jul 16, 2026
088ce1a
wip: cleanup impl
0xrusowsky Jul 15, 2026
e7c4694
chore: simplify
0xrusowsky Jul 15, 2026
4db5323
wip: more cleanup
0xrusowsky Jul 15, 2026
996616a
test: migrate test suite
0xrusowsky Jul 15, 2026
0f6322f
fix: align outbox with fallback nonce stack
0xrusowsky Jul 15, 2026
68f6d2a
test: use `TIP20Setup`
0xrusowsky Jul 16, 2026
c296e4a
fix: merge conflicts
0xrusowsky Jul 16, 2026
12244dd
chore: use ZonePortalError
0xrusowsky Jul 16, 2026
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
4 changes: 1 addition & 3 deletions Cargo.lock

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

9 changes: 5 additions & 4 deletions crates/contracts/src/precompiles/mod.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
pub mod common;
pub mod outbox;
pub mod swap_and_deposit_router;
pub mod tempo_state;
pub mod zone_factory;
pub mod zone_inbox;
pub mod zone_outbox;
pub mod zone_portal;
pub mod zone_tx_context;

pub use common::*;
pub use outbox::*;
pub use swap_and_deposit_router::*;
pub use tempo_state::*;
pub use zone_factory::*;
pub use zone_inbox::*;
pub use zone_outbox::*;
pub use zone_portal::*;
pub use zone_tx_context::*;

Expand All @@ -21,6 +21,7 @@ pub use zone_tx_context::*;
// contracts crate, e.g. `tempo_zone_contracts::TEMPO_STATE_ADDRESS`.
pub use zone_primitives::constants::{
EMPTY_SENTINEL, MAX_WITHDRAWAL_GAS_LIMIT, NO_QUEUE_INDEX, PORTAL_ADMIN_SLOT,
PORTAL_PENDING_SEQUENCER_SLOT, PORTAL_SEQUENCER_SLOT, TEMPO_STATE_ADDRESS, ZONE_CONFIG_ADDRESS,
ZONE_INBOX_ADDRESS, ZONE_OUTBOX_ADDRESS, ZONE_TOKEN_ADDRESS, ZONE_TX_CONTEXT_ADDRESS,
PORTAL_PENDING_SEQUENCER_SLOT, PORTAL_SEQUENCER_SLOT, PORTAL_TOKEN_CONFIGS_SLOT,
TEMPO_STATE_ADDRESS, ZONE_CONFIG_ADDRESS, ZONE_INBOX_ADDRESS, ZONE_OUTBOX_ADDRESS,
ZONE_TOKEN_ADDRESS, ZONE_TX_CONTEXT_ADDRESS,
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
//! `ZoneOutbox` — deployed on the Zone L2.

pub use ZoneOutbox::LastBatch;
pub use IZoneOutbox::{
IZoneOutboxErrors as ZoneOutboxError, IZoneOutboxEvents as ZoneOutboxEvent, LastBatch,
PendingWithdrawal, StaticCallNotAllowed,
};

crate::sol! {
#[derive(Debug)]
contract ZoneOutbox {
// -- Shared types --

#[derive(Debug, PartialEq, Eq)]
contract IZoneOutbox {
struct LastBatch {
bytes32 withdrawalQueueHash;
uint64 withdrawalBatchIndex;
Expand Down Expand Up @@ -43,6 +44,8 @@ crate::sol! {
);

event BatchFinalized(bytes32 indexed withdrawalQueueHash, uint64 withdrawalBatchIndex);
event TempoGasRateUpdated(uint128 tempoGasRate);
event MaxWithdrawalsPerBlockUpdated(uint32 maxWithdrawalsPerBlock);

// -- Errors --

Expand All @@ -52,9 +55,21 @@ crate::sol! {
error InvalidWithdrawalCount(uint256 actual, uint256 expected);
error InvalidEncryptedSenderCount(uint256 actual, uint256 expected);
error InvalidEncryptedSenderLength(uint256 actual, uint256 expected);
error InvalidFallbackRecipient();
error CallbackDataTooLarge();
error GasFeeRateTooHigh();
error TransferFailed();
error InvalidBlockNumber();
error TooManyWithdrawalsThisBlock();
error InvalidRevealTo();
error InvalidCurrentTxHash();
error StaticCallNotAllowed();

// -- View functions --

function config() external view returns (address);
function tempoGasRate() external view returns (uint128);
function maxWithdrawalsPerBlock() external view returns (uint32);
function lastBatch() external view returns (LastBatch memory);
function withdrawalBatchIndex() external view returns (uint64);
function lastFinalizedTimestamp() external view returns (uint64);
Expand All @@ -64,10 +79,17 @@ crate::sol! {
function getPendingWithdrawals() external view returns (PendingWithdrawal[] memory);
function consumeFallbackRecipient(uint64 fallbackNonce) external returns (address recipient);
function calculateWithdrawalFee(uint64 gasLimit) external view returns (uint128 fee);
function MAX_CALLBACK_DATA_SIZE() external view returns (uint256);
function MAX_WITHDRAWAL_GAS_LIMIT() external view returns (uint64);
function MAX_GAS_FEE_RATE() external view returns (uint128);
function WITHDRAWAL_BASE_GAS() external view returns (uint64);
function REVEAL_TO_KEY_LENGTH() external view returns (uint256);
function AUTHENTICATED_WITHDRAWAL_CIPHERTEXT_LENGTH() external view returns (uint256);

// -- State-changing functions --

function setTempoGasRate(uint128 _tempoGasRate) external;
function setMaxWithdrawalsPerBlock(uint32 _maxWithdrawalsPerBlock) external;
function requestWithdrawal(
address token,
address to,
Expand All @@ -86,3 +108,34 @@ crate::sol! {
function finalizeWithdrawalBatch(uint256 count, uint64 blockNumber, bytes[] calldata encryptedSenders) external returns (bytes32 withdrawalQueueHash);
}
}

crate::sol! {
/// Legacy seven-argument withdrawal interface. The current overload adds `revealTo`.
#[derive(Debug)]
interface ILegacyZoneOutbox {
function requestWithdrawal(
address token,
address to,
uint128 amount,
bytes32 memo,
uint64 gasLimit,
address fallbackRecipient,
bytes calldata data
) external;
}
}

impl From<ILegacyZoneOutbox::requestWithdrawalCall> for IZoneOutbox::requestWithdrawalCall {
fn from(call: ILegacyZoneOutbox::requestWithdrawalCall) -> Self {
Self {
token: call.token,
to: call.to,
amount: call.amount,
memo: call.memo,
gasLimit: call.gasLimit,
fallbackRecipient: call.fallbackRecipient,
data: call.data,
revealTo: Default::default(),
}
}
}
25 changes: 19 additions & 6 deletions crates/contracts/src/precompiles/zone_portal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

pub use ZonePortal::{
BlockTransition, DepositQueueTransition, EncryptedDeposit, EncryptedDepositPayload, Withdrawal,
ZonePortalErrors as ZonePortalError,
};

use crate::ZoneOutbox;
use crate::IZoneOutbox;
use alloy_primitives::{Address, B256, Bytes, keccak256};
use alloy_sol_types::SolValue;
use zone_primitives::constants::EMPTY_SENTINEL;
use zone_primitives::constants::{EMPTY_SENTINEL, PORTAL_TOKEN_CONFIGS_SLOT};

crate::sol! {
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
contract ZonePortal {
// -- Shared types --

Expand Down Expand Up @@ -168,6 +169,7 @@ crate::sol! {
error InvalidTempoBlockNumber();
error PolicyForbids();
error InvalidBouncebackRecipient();
error TokenNotEnabled();

// -- View functions --

Expand Down Expand Up @@ -344,6 +346,7 @@ impl core::fmt::Display for ZonePortal::ZonePortalErrors {
Self::InvalidTempoBlockNumber(_) => f.write_str("InvalidTempoBlockNumber"),
Self::PolicyForbids(_) => f.write_str("PolicyForbids"),
Self::InvalidBouncebackRecipient(_) => f.write_str("InvalidBouncebackRecipient"),
Self::TokenNotEnabled(_) => f.write_str("TokenNotEnabled"),
}
}
}
Expand All @@ -364,7 +367,7 @@ impl Withdrawal {

/// Reconstruct the public L1-facing withdrawal from a zone-side withdrawal request event.
pub fn from_requested_event(
event: &ZoneOutbox::WithdrawalRequested,
event: &IZoneOutbox::WithdrawalRequested,
tx_hash: B256,
encrypted_sender: Bytes,
) -> Self {
Expand All @@ -388,6 +391,11 @@ impl Withdrawal {
}
}

/// Hash this withdrawal as one link in a withdrawal queue.
pub fn hash_with_tail(&self, tail: B256) -> B256 {
keccak256((self.clone(), tail).abi_encode_params())
}

/// Compute the withdrawal queue hash for a slice of withdrawals.
///
/// The hash chain has the oldest withdrawal at the outermost layer for efficient FIFO removal:
Expand All @@ -404,9 +412,14 @@ impl Withdrawal {
}

let mut hash = EMPTY_SENTINEL;
for w in withdrawals.iter().rev() {
hash = keccak256((w.clone(), hash).abi_encode_params());
for withdrawal in withdrawals.iter().rev() {
hash = withdrawal.hash_with_tail(hash);
}
hash
}
}

/// Return the storage slot for `token` in the portal token-config mapping.
pub fn portal_token_config_slot(token: Address) -> B256 {
keccak256((token, PORTAL_TOKEN_CONFIGS_SLOT).abi_encode())
}
3 changes: 0 additions & 3 deletions crates/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ workspace = true

[dependencies]
# zones
tempo-zone-contracts = { workspace = true, features = ["std", "serde", "rpc"] }
zone-chainspec.workspace = true
zone-l1.workspace = true
zone-primitives.workspace = true
Expand All @@ -38,14 +37,12 @@ alloy-consensus.workspace = true
alloy-evm.workspace = true
alloy-primitives.workspace = true
alloy-provider = { workspace = true, features = ["reqwest"] }
alloy-sol-types.workspace = true

# revm
revm.workspace = true

# misc
tokio.workspace = true
tracing.workspace = true

[dev-dependencies]
eyre.workspace = true
Expand Down
5 changes: 3 additions & 2 deletions crates/evm/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use tempo_primitives::{TempoReceipt, TempoTxEnvelope, TempoTxType};
use tempo_revm::{TempoStateAccess, evm::TempoContext};
use zone_chainspec::ZoneChainSpec;

use crate::{ZoneEvm, tx_context};
use crate::ZoneEvm;

/// Simplified block executor for zone nodes.
///
Expand Down Expand Up @@ -102,7 +102,8 @@ where
// transaction's resolved fee token, so the handler skips FeeAMM.
self.override_validator_token();

let _tx_hash_guard = tx_context::set_current_tx_hash(*recovered.tx().tx_hash());
let _tx_hash_guard =
zone_precompiles::tx_context::set_current_tx_hash(*recovered.tx().tx_hash());
self.inner
.execute_transaction_without_commit((tx_env, recovered))
}
Expand Down
Loading
Loading