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
52 changes: 46 additions & 6 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ zone-info identifier:
cargo run -p tempo-xtask -- zone-info {{identifier}}

[group('zone')]
[doc('Creates a new zone on L1 via ZoneFactory and generates genesis + zone.json in generated/<name>/. Optional second positional argument selects the initial TIP-20 enabled on the portal; defaults to pathUSD. Requires L1_RPC_URL, PRIVATE_KEY, SEQUENCER_KEY, and ADMIN_KEY or ADMIN_ADDR env vars. Set ZONE_FACTORY to override the Moderato default.')]
create-zone name token="":
[doc('Creates a new zone on L1 via ZoneFactory and generates genesis + zone.json in generated/<name>/. Defaults to open account access and open callback targets. Initial membership and gateways are optional via ZONE_ALLOWED_ACCOUNTS and ZONE_GATEWAYS.')]
create-zone name token="" mode="open" gateway_mode="open":
#!/bin/bash
set -euo pipefail
PK="${PRIVATE_KEY:?Set PRIVATE_KEY env var}"
Expand Down Expand Up @@ -172,13 +172,33 @@ create-zone name token="":
echo "Initial portal token: $ZONE_TOKEN_L1"
echo "Admin: $ADMIN_ADDR"
echo "Sequencer: $SEQUENCER_ADDR"
ACCESS_MODE="{{mode}}"
GATEWAY_MODE="{{gateway_mode}}"
CREATE_ARGS=(--access-mode "$ACCESS_MODE" --gateway-mode "$GATEWAY_MODE")
if [[ "$ACCESS_MODE" != "closed" && "$ACCESS_MODE" != "open" ]]; then
echo "Error: mode must be 'open' or 'closed'" >&2
exit 1
fi
IFS=',' read -ra ALLOWED <<< "${ZONE_ALLOWED_ACCOUNTS:-}"
for account in "${ALLOWED[@]}"; do
[[ -n "$account" ]] && CREATE_ARGS+=(--allowed-account "$account")
done
if [[ "$GATEWAY_MODE" != "enforced" && "$GATEWAY_MODE" != "open" ]]; then
echo "Error: gateway_mode must be 'enforced' or 'open'" >&2
exit 1
fi
IFS=',' read -ra GATEWAYS <<< "${ZONE_GATEWAYS:-}"
for gateway in "${GATEWAYS[@]}"; do
[[ -n "$gateway" ]] && CREATE_ARGS+=(--zone-gateway "$gateway")
done
cargo run -p tempo-xtask -- create-zone \
--output "$OUTPUT" \
--l1-rpc-url "$HTTP_RPC" \
--initial-token "$ZONE_TOKEN_L1" \
--admin "$ADMIN_ADDR" \
--sequencer "$SEQUENCER_ADDR" \
--private-key "$PK"
--private-key "$PK" \
"${CREATE_ARGS[@]}"
echo "Zone '{{name}}' created. Artifacts in $OUTPUT/"

[group('zone')]
Expand Down Expand Up @@ -732,8 +752,8 @@ check-balance-private name token="0x20C0000000000000000000000000000000000000" rp
echo "Balance of $ACCOUNT: $BALANCE"

[group('zone')]
[doc('End-to-end: generates admin and sequencer keys, funds them on L1, creates a zone on-chain, generates genesis, and starts the zone node. Optional second positional argument selects the initial TIP-20 enabled on the portal; defaults to pathUSD. Requires L1_RPC_URL. Set ADMIN_KEY or ADMIN_ADDR to choose the portal admin. Set ZONE_FACTORY to override the Moderato default.')]
deploy-zone name token="":
[doc('End-to-end zone deployment. Defaults to open account access and open callback targets. Initial membership and gateways are optional via ZONE_ALLOWED_ACCOUNTS and ZONE_GATEWAYS.')]
deploy-zone name token="" mode="open" gateway_mode="open":
#!/bin/bash
set -euo pipefail
L1_RPC="${L1_RPC_URL:?Set L1_RPC_URL env var (wss://...)}"
Expand Down Expand Up @@ -797,13 +817,33 @@ deploy-zone name token="":
# Step 4: Create zone on L1 and generate genesis
echo "Step 4: Creating zone on L1 via ZoneFactory..."
mkdir -p "$OUTPUT"
ACCESS_MODE="{{mode}}"
GATEWAY_MODE="{{gateway_mode}}"
CREATE_ARGS=(--access-mode "$ACCESS_MODE" --gateway-mode "$GATEWAY_MODE")
if [[ "$ACCESS_MODE" != "closed" && "$ACCESS_MODE" != "open" ]]; then
echo "Error: mode must be 'open' or 'closed'" >&2
exit 1
fi
IFS=',' read -ra ALLOWED <<< "${ZONE_ALLOWED_ACCOUNTS:-}"
for account in "${ALLOWED[@]}"; do
[[ -n "$account" ]] && CREATE_ARGS+=(--allowed-account "$account")
done
if [[ "$GATEWAY_MODE" != "enforced" && "$GATEWAY_MODE" != "open" ]]; then
echo "Error: gateway_mode must be 'enforced' or 'open'" >&2
exit 1
fi
IFS=',' read -ra GATEWAYS <<< "${ZONE_GATEWAYS:-}"
for gateway in "${GATEWAYS[@]}"; do
[[ -n "$gateway" ]] && CREATE_ARGS+=(--zone-gateway "$gateway")
done
cargo run -p tempo-xtask -- create-zone \
--output "$OUTPUT" \
--l1-rpc-url "$HTTP_RPC" \
--initial-token "$ZONE_TOKEN_L1" \
--admin "$ADMIN_ADDR" \
--sequencer "$SEQUENCER_ADDR" \
--private-key "$SEQUENCER_KEY"
--private-key "$SEQUENCER_KEY" \
"${CREATE_ARGS[@]}"
echo ""

# Save generated keys into zone.json for later use.
Expand Down
12 changes: 8 additions & 4 deletions crates/contracts/src/precompiles/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod common;
pub mod swap_and_deposit_router;
pub mod tempo_state;
pub mod zone_config;
pub mod zone_factory;
pub mod zone_inbox;
pub mod zone_outbox;
Expand All @@ -10,6 +11,7 @@ pub mod zone_tx_context;
pub use common::*;
pub use swap_and_deposit_router::*;
pub use tempo_state::*;
pub use zone_config::*;
pub use zone_factory::*;
pub use zone_inbox::*;
pub use zone_outbox::*;
Expand All @@ -20,8 +22,10 @@ pub use zone_tx_context::*;
// (shared with the proof system) and are re-exported here so callers can reach them through the
// 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_ALLOWED_ACCOUNT_SLOT, PORTAL_PENDING_SEQUENCER_SLOT, PORTAL_SEQUENCER_SLOT,
PORTAL_ZONE_GATEWAY_SLOT, TEMPO_STATE_ADDRESS, ZONE_CONFIG_ADDRESS, ZONE_INBOX_ADDRESS,
ZONE_OUTBOX_ADDRESS, ZONE_TOKEN_ADDRESS, ZONE_TX_CONTEXT_ADDRESS,
ACCOUNT_ALLOWLIST_ENFORCED_FLAG, EMPTY_SENTINEL, GATEWAY_ALLOWLIST_ENFORCED_FLAG,
MAX_WITHDRAWAL_GAS_LIMIT, NO_QUEUE_INDEX, PORTAL_ACCESS_MODE_SLOT, PORTAL_ADMIN_SLOT,
PORTAL_ALLOWED_ACCOUNT_SLOT, PORTAL_ENFORCEMENT_FLAGS_SLOT, PORTAL_GATEWAY_MODE_SLOT,
PORTAL_PENDING_SEQUENCER_SLOT, PORTAL_SEQUENCER_SLOT, PORTAL_ZONE_GATEWAY_SLOT,
TEMPO_STATE_ADDRESS, ZONE_CONFIG_ADDRESS, ZONE_INBOX_ADDRESS, ZONE_OUTBOX_ADDRESS,
ZONE_TOKEN_ADDRESS, ZONE_TX_CONTEXT_ADDRESS,
};
12 changes: 12 additions & 0 deletions crates/contracts/src/precompiles/zone_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//! `ZoneConfig` — Zone L2 system contract (0x1c00...0003).
crate::sol! {
#[derive(Debug)]
contract ZoneConfig {
function accessMode() external view returns (uint8);
function gatewayMode() external view returns (uint8);
function isAllowedAccount(address account) external view returns (bool);
function isZoneGateway(address gateway) external view returns (bool);
function isEnabledToken(address token) external view returns (bool);
}
}
16 changes: 15 additions & 1 deletion crates/contracts/src/precompiles/zone_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,28 @@

use alloy_primitives::{Address, address};

pub use ZoneFactory::ZoneInfo;
pub use ZoneFactory::{ZoneAccessMode, ZoneGatewayMode, ZoneInfo};

/// Protocol-managed ZoneFactory address defined by TIP-1091.
pub const ZONE_FACTORY_ADDRESS: Address = address!("0x5aF2000000000000000000000000000000000000");

crate::sol! {
#[derive(Debug)]
contract ZoneFactory {
enum ZoneAccessMode {
Closed,
Open,
}
enum ZoneGatewayMode {
Enforced,
Open,
}
struct ZoneInfo {
uint32 zoneId;
address portal;
address initialToken;
ZoneAccessMode accessMode;
ZoneGatewayMode gatewayMode;
address admin;
address sequencer;
address verifier;
Expand All @@ -29,6 +39,8 @@ crate::sol! {
}
struct CreateZoneParams {
address initialToken;
ZoneAccessMode accessMode;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ [ISSUE] Protocol/native ZoneFactory is incompatible with the new creation ABI and portal storage

The local ABI now sends expanded CreateZoneParams to the fixed TIP-1091 factory address, but the protocol factory is a native Tempo precompile. The pinned/available native implementation still exposes the old selector/metadata and initializes a portal storage mirror that stops before the new mode and membership slots. Calls against the old native ABI fail, and a partial runtime-only rollout can leave requested Closed/Enforced zones initialized as zero-slot Open/Open.

Recommended Fix: Coordinate an atomic Tempo protocol update for native factory ABI/dispatch, ZoneCreated/zones() encoding, verifier()/messenger(), overlap validation, and slots 19-21 initialization; gate old Moderato defaults until that target is live.

ZoneGatewayMode gatewayMode;
address[] allowedAccounts;
address[] zoneGateways;
address admin;
Expand All @@ -41,6 +53,8 @@ crate::sol! {
uint32 indexed zoneId,
address indexed portal,
address initialToken,
ZoneAccessMode accessMode,
ZoneGatewayMode gatewayMode,
address admin,
address sequencer,
address verifier,
Expand Down
5 changes: 5 additions & 0 deletions crates/contracts/src/precompiles/zone_portal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ crate::sol! {

event ZoneGatewayUpdated(address indexed gateway, bool enabled);
event AllowedAccountUpdated(address indexed account, bool enabled);
event EnforcementModesUpdated(uint8 accessMode, uint8 gatewayMode);

// -- Errors --

Expand All @@ -181,6 +182,10 @@ crate::sol! {
function zoneId() external view returns (uint32);
function admin() external view returns (address);
function messenger() external view returns (address);
function accessMode() external view returns (uint8);
function setAccessMode(uint8 newMode) external;
function gatewayMode() external view returns (uint8);
function setGatewayMode(uint8 newMode) external;
function zoneGateway(address gateway) external view returns (bool);
function allowedAccount(address account) external view returns (bool);
function setZoneGateway(address gateway, bool enabled) external;
Expand Down
23 changes: 22 additions & 1 deletion crates/l1/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ pub enum L1MembershipEvent {
AllowedAccountUpdated { account: Address, enabled: bool },
}

/// An enforcement-mode update emitted by the L1 portal.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum L1ModeEvent {
Updated { access_mode: u8, gateway_mode: u8 },
}

/// Result of attempting to enqueue an L1 block into the deposit queue.
#[derive(Debug)]
pub(crate) enum EnqueueOutcome {
Expand All @@ -45,6 +51,8 @@ pub struct L1PortalEvents {
pub sequencer_events: Vec<L1SequencerEvent>,
/// Closed-loop membership updates in the order they appeared in the block.
pub membership_events: Vec<L1MembershipEvent>,
/// Enforcement-mode updates in the order they appeared in the block.
pub mode_events: Vec<L1ModeEvent>,
}

/// A token newly enabled for bridging, with metadata for L2 creation.
Expand Down Expand Up @@ -74,7 +82,7 @@ impl EnabledToken {

impl L1PortalEvents {
/// Event signature hashes that this container knows how to decode.
const SIGNATURE_HASHES: [B256; 8] = [
const SIGNATURE_HASHES: [B256; 9] = [
DepositMade::SIGNATURE_HASH,
EncryptedDepositMade::SIGNATURE_HASH,
WithdrawalBounceBack::SIGNATURE_HASH,
Expand All @@ -83,6 +91,7 @@ impl L1PortalEvents {
SequencerTransferred::SIGNATURE_HASH,
ZoneGatewayUpdated::SIGNATURE_HASH,
AllowedAccountUpdated::SIGNATURE_HASH,
EnforcementModesUpdated::SIGNATURE_HASH,
];

/// Create portal events from deposits only.
Expand Down Expand Up @@ -211,6 +220,18 @@ impl L1PortalEvents {
enabled: event.enabled,
});
}
ZonePortalEvents::EnforcementModesUpdated(event) => {
info!(
l1_block = block_number,
access_mode = event.accessMode,
gateway_mode = event.gatewayMode,
"Enforcement modes updated on L1"
);
self.mode_events.push(L1ModeEvent::Updated {
access_mode: event.accessMode,
gateway_mode: event.gatewayMode,
});
}
_ => {}
}
Ok(())
Expand Down
13 changes: 7 additions & 6 deletions crates/l1/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,14 @@ pub(crate) mod rpc {

use crate::{
abi::{
EncryptedDeposit as AbiEncryptedDeposit,
EncryptedDepositPayload as AbiEncryptedDepositPayload, PORTAL_ALLOWED_ACCOUNT_SLOT,
PORTAL_PENDING_SEQUENCER_SLOT, PORTAL_SEQUENCER_SLOT, PORTAL_ZONE_GATEWAY_SLOT,
ACCOUNT_ALLOWLIST_ENFORCED_FLAG, EncryptedDeposit as AbiEncryptedDeposit,
EncryptedDepositPayload as AbiEncryptedDepositPayload, GATEWAY_ALLOWLIST_ENFORCED_FLAG,
PORTAL_ALLOWED_ACCOUNT_SLOT, PORTAL_ENFORCEMENT_FLAGS_SLOT, PORTAL_PENDING_SEQUENCER_SLOT,
PORTAL_SEQUENCER_SLOT, PORTAL_ZONE_GATEWAY_SLOT, ZoneAccessMode, ZoneGatewayMode,
ZonePortal::{
self, AllowedAccountUpdated, DepositMade, EncryptedDepositMade,
SequencerTransferStarted, SequencerTransferred, TokenEnabled, WithdrawalBounceBack,
ZoneGatewayUpdated, ZonePortalEvents,
EnforcementModesUpdated, SequencerTransferStarted, SequencerTransferred, TokenEnabled,
WithdrawalBounceBack, ZoneGatewayUpdated, ZonePortalEvents,
},
},
state::{cache::L1StateCacheInner, tip403::PolicyEvent},
Expand All @@ -91,7 +92,7 @@ mod tests;

pub use block::{L1BlockDeposits, PreparedL1Block};
pub use deposit::{Deposit, EncryptedDeposit, L1Deposit};
pub use event::{EnabledToken, L1MembershipEvent, L1PortalEvents, L1SequencerEvent};
pub use event::{EnabledToken, L1MembershipEvent, L1ModeEvent, L1PortalEvents, L1SequencerEvent};
pub use ext::{ChainTempoStateExt, TempoStateExt};
pub use queue::DepositQueue;
pub use state::{L1StateCache, PolicyCache, PolicyProvider};
Expand Down
38 changes: 37 additions & 1 deletion crates/l1/src/subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,10 @@ impl L1Subscriber {
block_number: u64,
portal_events: &L1PortalEvents,
) {
if portal_events.sequencer_events.is_empty() && portal_events.membership_events.is_empty() {
if portal_events.sequencer_events.is_empty()
&& portal_events.membership_events.is_empty()
&& portal_events.mode_events.is_empty()
{
return;
}

Expand All @@ -685,6 +688,12 @@ impl L1Subscriber {
block_number,
&portal_events.membership_events,
);
apply_mode_events_to_cache(
&mut cache,
self.config.portal_address,
block_number,
&portal_events.mode_events,
);
}

/// Update the L1 state cache anchor. Detects reorgs by comparing
Expand Down Expand Up @@ -836,6 +845,33 @@ pub(crate) fn apply_membership_events_to_cache(
}
}

pub(crate) fn apply_mode_events_to_cache(
cache: &mut L1StateCacheInner,
portal_address: Address,
block_number: u64,
mode_events: &[L1ModeEvent],
) {
for event in mode_events {
let L1ModeEvent::Updated {
access_mode,
gateway_mode,
} = *event;
let mut flags = 0;
if access_mode == ZoneAccessMode::Closed as u8 {
flags |= ACCOUNT_ALLOWLIST_ENFORCED_FLAG;
}
if gateway_mode == ZoneGatewayMode::Enforced as u8 {
flags |= GATEWAY_ALLOWLIST_ENFORCED_FLAG;
}
cache.set(
portal_address,
PORTAL_ENFORCEMENT_FLAGS_SLOT,
block_number,
B256::with_last_byte(flags),
);
}
}

pub(crate) fn mapping_storage_slot(key: Address, base_slot: B256) -> B256 {
keccak256((key, U256::from_be_bytes(base_slot.0)).abi_encode())
}
Expand Down
Loading
Loading