Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 0 additions & 6 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/evm/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ mod tests {
let slot = U256::from(7);
let expected = U256::from(99);
let l1 = TestL1::default();
l1.insert(TIP403_REGISTRY_ADDRESS, slot, anchor - 1, U256::from(98));
l1.insert(TIP403_REGISTRY_ADDRESS, slot, anchor, expected);
let mut db = AnchoredZoneDb::new(test_db(anchor), l1);

Expand Down
6 changes: 0 additions & 6 deletions crates/l1/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,17 @@ workspace = true
# zones
tempo-zone-contracts = { workspace = true, features = ["std", "serde", "rpc"] }
zone-precompiles = { workspace = true, features = ["std"] }
zone-primitives = { workspace = true, features = ["std", "serde"] }

# tempo
tempo-alloy.workspace = true
tempo-contracts.workspace = true
tempo-precompiles.workspace = true
tempo-primitives.workspace = true
tempo-revm.workspace = true
tempo-transaction-pool.workspace = true

# reth
reth-metrics.workspace = true
reth-primitives-traits.workspace = true
reth-provider.workspace = true
reth-storage-api.workspace = true
reth-tasks.workspace = true
reth-transaction-pool.workspace = true

# alloy
alloy-consensus.workspace = true
Expand Down
54 changes: 8 additions & 46 deletions crates/l1/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ pub struct L1BlockDeposits {
pub header: SealedHeader<TempoHeader>,
/// Portal events extracted from this block.
pub events: L1PortalEvents,
/// TIP-403 policy events extracted from this block's receipts.
pub policy_events: Vec<PolicyEvent>,
/// Deposit queue hash chain value before this block's deposits.
pub queue_hash_before: B256,
/// Deposit queue hash chain value after this block's deposits.
Expand All @@ -18,15 +16,13 @@ pub struct L1BlockDeposits {
impl L1BlockDeposits {
/// Prepare all deposits for the payload builder.
///
/// Decrypts encrypted deposits, checks TIP-403 policy authorization,
/// and ABI-encodes everything into the types the `advanceTempo` call expects.
/// The resulting [`PreparedL1Block`] is ready to be passed through payload
/// attributes to the builder.
/// Decrypts encrypted deposits and ABI-encodes into the types the `advanceTempo` call expects.
/// Mint-recipient policy is enforced by upstream TIP-20 after the L1 state is anchored.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flagging that this now depends on the raw-cache invalidation issue from #698. Once this precheck is removed, encrypted deposits rely on that cache for authorization, so a stale hit can mint where a cold node would bounce.

/// The resulting [`PreparedL1Block`] is ready to be passed via payload attributes to the builder.
pub async fn prepare(
self,
sequencer_key: &k256::SecretKey,
portal_address: Address,
policy_provider: &crate::state::PolicyProvider,
) -> eyre::Result<PreparedL1Block> {
use crate::precompiles::ecies;

Expand Down Expand Up @@ -54,7 +50,7 @@ impl L1BlockDeposits {
});
}
L1Deposit::Encrypted(d) => {
let mut queued = abi::QueuedDeposit {
let queued = abi::QueuedDeposit {
depositType: abi::DepositType::Encrypted,
depositData: Bytes::from(
abi::EncryptedDeposit {
Expand Down Expand Up @@ -96,42 +92,9 @@ impl L1BlockDeposits {
recipient = %dec.to,
token = %d.token,
amount = %d.amount,
"Decrypted encrypted deposit, checking policy"
"Decrypted encrypted deposit"
);

// Check TIP-403 policy via the provider (cache-first, RPC fallback).
// Errors are propagated so the engine retries rather than allowing
// unauthorized deposits through.
let authorized = policy_provider
.is_authorized_async(
d.token,
dec.to,
l1_block_number,
crate::state::AuthRole::MintRecipient,
)
.await?;

if authorized {
debug!(
target: "zone::engine",
recipient = %dec.to,
token = %d.token,
"Policy authorized encrypted deposit recipient"
);
} else {
warn!(
target: "zone::engine",
sender = %d.sender,
recipient = %dec.to,
token = %d.token,
amount = %d.amount,
"Encrypted deposit recipient unauthorized; queuing deposit bounce-back"
);
queued.rejected = true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we now never reject deposits? is it safe to just remove this logic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they are rejected at execution when the 403 logic runs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basically it would be this code which calls mint() on the zone's TIP20:

  • regular deposit:
    try IZoneToken(d.token).mint(d.to, d.amount) {
    emit DepositProcessed(
    currentHash, d.sender, d.to, d.token, d.amount, d.memo
    );
    } catch {
    _enqueueDepositBounceBack(d.token, d.amount, d.bouncebackRecipient);
    emit DepositFailed(
    currentHash, d.sender, d.to, d.token, d.amount, d.bouncebackRecipient
    );
    }
  • encrypted deposit:
    try IZoneToken(ed.token).mint(decryptedTo, ed.amount) {
    emit EncryptedDepositProcessed(
    currentHash, ed.sender, decryptedTo, ed.token, ed.amount, decryptedMemo
    );
    } catch {
    _failEncryptedDeposit(currentHash, ed);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes since this is rejected at execution time, should be safe to remove here.

queued_deposits.push(queued);
continue;
}

let decryption = abi::DecryptionData {
sharedSecret: dec.proof.shared_secret,
sharedSecretYParity: dec.proof.shared_secret_y_parity,
Expand Down Expand Up @@ -221,17 +184,16 @@ impl L1BlockDeposits {

/// An L1 block with deposits fully prepared for the payload builder.
///
/// All ECIES decryption, TIP-403 policy checks, and ABI encoding have been
/// performed. The builder only needs to RLP-encode the header and assemble
/// the `advanceTempo` calldata.
/// All ECIES decryption and ABI encoding have been performed.
/// The builder only needs to RLP-encode the header and assemble the `advanceTempo` calldata.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PreparedL1Block {
/// The sealed L1 block header.
pub header: SealedHeader<TempoHeader>,
/// ABI-encoded queued deposits (regular + encrypted).
#[serde(skip)]
pub queued_deposits: Vec<abi::QueuedDeposit>,
/// Decryption data for non-rejected encrypted deposits, in order.
/// Decryption data for encrypted deposits accepted for on-chain verification, in order.
#[serde(skip)]
pub decryptions: Vec<abi::DecryptionData>,
/// Tokens newly enabled for bridging in this block.
Expand Down
4 changes: 2 additions & 2 deletions crates/l1/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ use crate::{
SequencerTransferred, TokenEnabled, WithdrawalBounceBack, ZonePortalEvents,
},
},
state::{cache::L1StateCacheInner, tip403::PolicyEvent},
state::cache::L1StateCacheInner,
};

mod block;
Expand All @@ -93,7 +93,7 @@ pub use deposit::{Deposit, EncryptedDeposit, L1Deposit};
pub use event::{EnabledToken, L1PortalEvents, L1SequencerEvent};
pub use ext::{ChainTempoStateExt, TempoStateExt};
pub use queue::DepositQueue;
pub use state::{L1StateCache, PolicyCache, PolicyProvider};
pub use state::L1StateCache;
pub use subscriber::{L1Subscriber, L1SubscriberConfig};

pub(crate) use event::EnqueueOutcome;
Expand Down
43 changes: 10 additions & 33 deletions crates/l1/src/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ impl PendingDeposits {
&mut self,
header: SealedHeader<TempoHeader>,
events: L1PortalEvents,
policy_events: Vec<PolicyEvent>,
) -> EnqueueOutcome {
let block_number = header.number();
let block_hash = header.hash();
Expand Down Expand Up @@ -117,7 +116,7 @@ impl PendingDeposits {
// of parent hash — the parent was reorged but the zone already
// committed to it. The builder will detect the hash mismatch.
}
self.append(header, events, policy_events);
self.append(header, events);
return EnqueueOutcome::Accepted;
}

Expand Down Expand Up @@ -164,31 +163,21 @@ impl PendingDeposits {
// If new_expected == block_number, fall through to accept (it'll be the anchor)
}

self.append(header, events, policy_events);
self.append(header, events);
EnqueueOutcome::Accepted
}

/// Enqueue a block during backfill. Accepts or skips duplicates.
///
/// Panics on `NeedBackfill` — backfill blocks must be fetched sequentially.
pub(crate) fn enqueue(
&mut self,
header: TempoHeader,
events: L1PortalEvents,
policy_events: Vec<PolicyEvent>,
) {
match self.try_enqueue(SealedHeader::seal_slow(header), events, policy_events) {
pub(crate) fn enqueue(&mut self, header: TempoHeader, events: L1PortalEvents) {
match self.try_enqueue(SealedHeader::seal_slow(header), events) {
EnqueueOutcome::Accepted | EnqueueOutcome::Duplicate => {}
other => panic!("enqueue expected Accepted or Duplicate, got {other:?}"),
}
}

fn append(
&mut self,
header: SealedHeader<TempoHeader>,
events: L1PortalEvents,
policy_events: Vec<PolicyEvent>,
) {
fn append(&mut self, header: SealedHeader<TempoHeader>, events: L1PortalEvents) {
let queue_hash_before = self.enqueued_head_hash;
for deposit in &events.deposits {
self.enqueued_head_hash = deposit.hash_chain(self.enqueued_head_hash);
Expand All @@ -198,7 +187,6 @@ impl PendingDeposits {
self.pending.push(L1BlockDeposits {
header,
events,
policy_events,
queue_hash_before,
queue_hash_after,
});
Expand Down Expand Up @@ -362,10 +350,9 @@ impl DepositQueue {
&self,
header: SealedHeader<TempoHeader>,
events: L1PortalEvents,
policy_events: Vec<PolicyEvent>,
) -> EnqueueOutcome {
let mut queue = self.inner.lock();
let outcome = queue.try_enqueue(header, events, policy_events);
let outcome = queue.try_enqueue(header, events);
if matches!(outcome, EnqueueOutcome::Accepted) {
drop(queue);
self.notify.notify_one();
Expand All @@ -374,26 +361,16 @@ impl DepositQueue {
}

/// Enqueue an L1 block with its deposits and notify waiters.
pub fn enqueue(
&self,
header: TempoHeader,
events: L1PortalEvents,
policy_events: Vec<PolicyEvent>,
) {
self.inner.lock().enqueue(header, events, policy_events);
pub fn enqueue(&self, header: TempoHeader, events: L1PortalEvents) {
self.inner.lock().enqueue(header, events);
self.notify.notify_one();
}

/// Like [`enqueue`](Self::enqueue) but accepts an already-sealed header,
/// avoiding a redundant hash computation.
pub fn enqueue_sealed(
&self,
header: SealedHeader<TempoHeader>,
events: L1PortalEvents,
policy_events: Vec<PolicyEvent>,
) {
pub fn enqueue_sealed(&self, header: SealedHeader<TempoHeader>, events: L1PortalEvents) {
let mut queue = self.inner.lock();
match queue.try_enqueue(header, events, policy_events) {
match queue.try_enqueue(header, events) {
EnqueueOutcome::Accepted | EnqueueOutcome::Duplicate => {}
other => panic!("enqueue_sealed expected Accepted or Duplicate, got {other:?}"),
}
Expand Down
10 changes: 3 additions & 7 deletions crates/l1/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@
//! - [`L1StateCache`] — a shared in-memory cache of L1 contract storage slots.
//! - [`L1StateCacheInner`] — the block-versioned cache storage guarded by [`L1StateCache`].
//! - [`L1StateProvider`] — a cache-first, RPC-fallback reader for `eth_getStorageAt`.
//! - [`tip403`] — TIP-403 policy cache and provider.
//!
//! TIP-20 and TIP-403 policy semantics are evaluated by Tempo's upstream precompiles. This
//! module only supplies their exact-block raw L1 storage view.

pub mod cache;
pub mod provider;
pub mod tip403;
pub mod versioned;

pub use cache::{L1StateCache, L1StateCacheInner};
pub use provider::{L1StateProvider, L1StateProviderConfig};
pub use tip403::{
AuthRole, PolicyCache, PolicyCacheInner, PolicyEvent, PolicyProvider, PolicyTaskHandle,
PolicyTaskMessage, Tip403Metrics, spawn_policy_resolution_task, spawn_pool_prefetch_task,
};
Loading
Loading