diff --git a/Justfile b/Justfile index 0ee9785d1..309d52753 100644 --- a/Justfile +++ b/Justfile @@ -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//. 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//. 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}" @@ -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')] @@ -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://...)}" @@ -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. diff --git a/crates/contracts/src/precompiles/mod.rs b/crates/contracts/src/precompiles/mod.rs index c218d9ee2..1435879e5 100644 --- a/crates/contracts/src/precompiles/mod.rs +++ b/crates/contracts/src/precompiles/mod.rs @@ -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; @@ -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::*; @@ -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, }; diff --git a/crates/contracts/src/precompiles/zone_config.rs b/crates/contracts/src/precompiles/zone_config.rs new file mode 100644 index 000000000..db4d25f35 --- /dev/null +++ b/crates/contracts/src/precompiles/zone_config.rs @@ -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); + } +} diff --git a/crates/contracts/src/precompiles/zone_factory.rs b/crates/contracts/src/precompiles/zone_factory.rs index 049097354..e34d57775 100644 --- a/crates/contracts/src/precompiles/zone_factory.rs +++ b/crates/contracts/src/precompiles/zone_factory.rs @@ -2,7 +2,7 @@ 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"); @@ -10,10 +10,20 @@ pub const ZONE_FACTORY_ADDRESS: Address = address!("0x5aF20000000000000000000000 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; @@ -29,6 +39,8 @@ crate::sol! { } struct CreateZoneParams { address initialToken; + ZoneAccessMode accessMode; + ZoneGatewayMode gatewayMode; address[] allowedAccounts; address[] zoneGateways; address admin; @@ -41,6 +53,8 @@ crate::sol! { uint32 indexed zoneId, address indexed portal, address initialToken, + ZoneAccessMode accessMode, + ZoneGatewayMode gatewayMode, address admin, address sequencer, address verifier, diff --git a/crates/contracts/src/precompiles/zone_portal.rs b/crates/contracts/src/precompiles/zone_portal.rs index dcc962558..7b81243a4 100644 --- a/crates/contracts/src/precompiles/zone_portal.rs +++ b/crates/contracts/src/precompiles/zone_portal.rs @@ -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 -- @@ -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; diff --git a/crates/l1/src/event.rs b/crates/l1/src/event.rs index 6e01de5fa..1e4727f2c 100644 --- a/crates/l1/src/event.rs +++ b/crates/l1/src/event.rs @@ -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 { @@ -45,6 +51,8 @@ pub struct L1PortalEvents { pub sequencer_events: Vec, /// Closed-loop membership updates in the order they appeared in the block. pub membership_events: Vec, + /// Enforcement-mode updates in the order they appeared in the block. + pub mode_events: Vec, } /// A token newly enabled for bridging, with metadata for L2 creation. @@ -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, @@ -83,6 +91,7 @@ impl L1PortalEvents { SequencerTransferred::SIGNATURE_HASH, ZoneGatewayUpdated::SIGNATURE_HASH, AllowedAccountUpdated::SIGNATURE_HASH, + EnforcementModesUpdated::SIGNATURE_HASH, ]; /// Create portal events from deposits only. @@ -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(()) diff --git a/crates/l1/src/lib.rs b/crates/l1/src/lib.rs index d44df335d..8460d4f69 100644 --- a/crates/l1/src/lib.rs +++ b/crates/l1/src/lib.rs @@ -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}, @@ -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}; diff --git a/crates/l1/src/subscriber.rs b/crates/l1/src/subscriber.rs index 85f8a5b00..d52240f06 100644 --- a/crates/l1/src/subscriber.rs +++ b/crates/l1/src/subscriber.rs @@ -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; } @@ -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 @@ -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()) } diff --git a/crates/l1/src/tests.rs b/crates/l1/src/tests.rs index 6a2ef2e73..dfe7f0ac8 100644 --- a/crates/l1/src/tests.rs +++ b/crates/l1/src/tests.rs @@ -667,6 +667,51 @@ fn test_push_log_decodes_membership_updates() { ); } +#[test] +fn test_push_log_decodes_mode_updates() { + let portal_address = address!("0x0000000000000000000000000000000000000ABC"); + let mut events = L1PortalEvents::default(); + + events + .push_log( + &make_portal_log( + portal_address, + EnforcementModesUpdated { + accessMode: 1, + gatewayMode: 1, + }, + ), + 123, + ) + .expect("mode update should decode"); + events + .push_log( + &make_portal_log( + portal_address, + EnforcementModesUpdated { + accessMode: 1, + gatewayMode: 0, + }, + ), + 123, + ) + .expect("second mode update should decode"); + + assert_eq!( + events.mode_events, + vec![ + L1ModeEvent::Updated { + access_mode: 1, + gateway_mode: 1, + }, + L1ModeEvent::Updated { + access_mode: 1, + gateway_mode: 0, + }, + ] + ); +} + #[test] fn test_apply_sequencer_events_to_cache_sets_pending_sequencer() { let portal_address = address!("0x0000000000000000000000000000000000000ABC"); @@ -815,6 +860,36 @@ fn test_apply_portal_state_events_overrides_cached_membership() { ); } +#[test] +fn test_apply_portal_state_events_updates_dedicated_mode_slot() { + let portal_address = address!("0x0000000000000000000000000000000000000ABC"); + let subscriber = test_subscriber( + Arc::new(SequenceLocalTempoCheckpointReader::new(VecDeque::new())), + None, + ); + subscriber.apply_portal_state_events( + 42, + &L1PortalEvents { + mode_events: vec![L1ModeEvent::Updated { + access_mode: ZoneAccessMode::Closed as u8, + gateway_mode: ZoneGatewayMode::Enforced as u8, + }], + ..Default::default() + }, + ); + + let updated = subscriber + .config + .l1_state_cache + .read() + .get(portal_address, PORTAL_ENFORCEMENT_FLAGS_SLOT, 42) + .expect("packed mode slot should be cached"); + assert_eq!( + updated, + B256::with_last_byte(ACCOUNT_ALLOWLIST_ENFORCED_FLAG | GATEWAY_ALLOWLIST_ENFORCED_FLAG) + ); +} + #[test] fn test_deposit_queue_hash_chain() { let mut queue = PendingDeposits::default(); diff --git a/crates/node/assets/zone-dev-genesis.json b/crates/node/assets/zone-dev-genesis.json index 9dccce0d9..108acccb6 100644 --- a/crates/node/assets/zone-dev-genesis.json +++ b/crates/node/assets/zone-dev-genesis.json @@ -20,17 +20,17 @@ }, "0x1c00000000000000000000000000000000000001": { "balance": "0x0", - "code": "0x60806040526004361015610011575f80fd5b5f3560e01c80631fbb25ad146100945780632d4884821461008f57806379502c551461008a57806382648c3b14610085578063857e85f81461008057806386516ec01461007b578063a21de6d9146100765763bffa55d514610071575f80fd5b610356565b610312565b610252565b61018a565b610150565b61010c565b6100e6565b346100d8575f3660031901126100d8577f0000000000000000000000001c000000000000000000000000000000000000006001600160a01b03166080908152602090f35b5f80fd5b5f9103126100d857565b346100d8575f3660031901126100d85760206001600160401b0360015416604051908152f35b346100d8575f3660031901126100d8576040517f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03168152602090f35b346100d8575f3660031901126100d85760205f54604051908152f35b6001600160a01b038116036100d857565b35906101888261016c565b565b346100d85760403660031901126100d85760206001600160801b036101e86004356101b48161016c565b602435906101c18261016c565b60018060a01b03165f526002845260405f209060018060a01b03165f5260205260405f2090565b5416604051908152f35b9181601f840112156100d8578235916001600160401b0383116100d8576020808501948460051b0101116100d857565b9181601f840112156100d8578235916001600160401b0383116100d8576020808501948460071b0101116100d857565b346100d85760803660031901126100d8576004356001600160401b0381116100d857366023820112156100d8578060040135906001600160401b0382116100d85736602483830101116100d8576024356001600160401b0381116100d8576102be9036906004016101f2565b6044929192356001600160401b0381116100d8576102e0903690600401610222565b91606435946001600160401b0386116100d8576103109661030760249736906004016101f2565b97909601610c10565b005b346100d8575f3660031901126100d8576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346100d85760203660031901126100d8576004356103738161016c565b6001600160a01b0381165f8181526002602081815260408084203380865281845291852054959094529190526001600160801b0390921692916103db916103cb91905b9060018060a01b03165f5260205260405f2090565b80546001600160801b0319169055565b6001600160a01b031690813b156100d8576040516340c10f1960e01b8152915f838061040b853360048401610aad565b038183855af19283156104915761047393610477575b506040516001600160801b038316815233907fffd3bbab073ab4b2d0792c270104924c14c285a153b9acddabae166395d2eb5c90602090a36040516001600160801b0390911681529081906020820190565b0390f35b806104855f61048b936104e5565b806100dc565b5f610421565b61051e565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b038211176104c557604052565b610496565b60c081019081106001600160401b038211176104c557604052565b90601f801991011681019081106001600160401b038211176104c557604052565b908160209103126100d8575161051b8161016c565b90565b6040513d5f823e3d90fd5b908060209392818452848401375f828201840152601f01601f1916010190565b91602061051b938181520191610529565b634e487b7160e01b5f52603260045260245ffd5b91908110156105905760051b81013590607e19813603018212156100d8570190565b61055a565b3561051b8161016c565b903590601e19813603018212156100d857018035906001600160401b0382116100d8576020019181360383136100d857565b9593916105fd9061060b9461051b99979360018060a01b03168952608060208a01526080890191610529565b918683036040880152610529565b926060818503910152610529565b9492909361063761051b979561064594606089526060890191610529565b918683036020880152610529565b926040818503910152610529565b91908110156105905760051b81013590605e19813603018212156100d8570190565b6002111561067f57565b634e487b7160e01b5f52602160045260245ffd5b3560028110156100d85790565b35906001600160801b03821682036100d857565b60ff8116036100d857565b6001600160401b0381116104c557601f01601f191660200190565b35906001600160a01b0319821682036100d857565b35906001600160801b0319821682036100d857565b919060a0838203126100d8576040519061071d826104aa565b8193803583526020810135610731816106b4565b602084015260408101356001600160401b0381116100d857810182601f820112156100d857803591610762836106bf565b9361077060405195866104e5565b838552602084840101116100d8576080935f6020856107b19682899701838601378301015260408601526107a6606082016106da565b6060860152016106ef565b910152565b6020818303126100d8578035906001600160401b0382116100d8570160c0818303126100d857604051916107e9836104ca565b81356107f48161016c565b83526108026020830161017d565b6020840152610813604083016106a0565b60408401526108246060830161017d565b60608401526080820135608084015260a08201356001600160401b0381116100d8576108509201610704565b60a082015290565b90600282101561067f5752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b92919060a060409161089c866001610858565b60606020870152600180831b038151166060870152600180831b0360208201511660808701526001600160801b03838201511682870152600180831b0360608201511660c0870152608081015160e0870152015160c0610100860152805161012086015260ff6020820151166101408601526109288282015160a06101608801526101c0870190610865565b60608201516001600160a01b0319166101808701526080909101516001600160801b0319166101a0860152930152565b801515036100d857565b3561051b81610958565b634e487b7160e01b5f52601160045260245ffd5b5f19811461098e5760010190565b61096c565b91908110156105905760071b0190565b3561051b816106b4565b908160209103126100d8575161051b81610958565b604051906109d16040836104e5565b600d82526c65636965732d6165732d6b657960981b6020830152565b91906040838203126100d85782516001600160401b0381116100d85783019080601f830112156100d8578151610a22816106bf565b91610a3060405193846104e5565b818352602082850101116100d8576020815f92828096018386015e8301015292015161051b81610958565b92610a919060809360209397969786526bffffffffffffffffffffffff60a01b168386015260a0604086015260a0850190610865565b83810360608501525f815201936001600160801b031916910152565b6001600160a01b0390911681526001600160801b03909116602082015260400190565b908160c09103126100d85760a060405191610aea836104ca565b8035610af58161016c565b83526020810135610b058161016c565b60208401526040810135610b188161016c565b6040840152610b29606082016106a0565b60608401526080810135610b3c8161016c565b6080840152013560a082015290565b60e0909392919360a0610100820195610b64835f610858565b600180831b038151166020840152600180831b036020820151166040840152600180831b0360408201511660608401526001600160801b036060820151166080840152600180831b0360808201511682840152015160c08201520152565b908160209103126100d8575190565b906001600160401b03809116911601906001600160401b03821161098e57565b908160209103126100d857516001600160401b03811681036100d85790565b959194939296903315158061172b575b61171c577f0000000000000000000000001c000000000000000000000000000000000000006001600160a01b031696873b156100d85760405163fe77009960e01b8152915f9183918291610c78919060048401610549565b0381838b5af1801561049157611708575b505f9492945b8181106115ed575050505f54925f915f915b878310610e9f5750505003610e90576040516381e3da6b60e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316600482015260056024820152602081604481865afa801561049157610e73575b50805f55610d4e610d326001600160401b038516610d2d6001546001600160401b031690565b610bd1565b6001600160401b03166001600160401b03196001541617600155565b604051631014997960e31b815291602083600481845afa928315610491575f93610e3e575b50602060049160405192838092631d04645f60e01b82525afa8015610491577fd2d2bf1e295f62cd08f0f0ab45818efeaba78b58310526f7b7e9686b8aeded1a926001600160401b03925f92610e09575b50610e0490610ddb6001546001600160401b031690565b6040805198895260208901929092526001600160401b0316908701529116939081906060820190565b0390a3565b610e04919250610e309060203d602011610e37575b610e2881836104e5565b810190610bf1565b9190610dc4565b503d610e1e565b6004919350610e64602091823d8411610e6c575b610e5c81836104e5565b810190610bc2565b939150610d73565b503d610e52565b610e8b9060203d602011610e6c57610e5c81836104e5565b610d07565b6361aba18160e11b5f5260045ffd5b909194610ead868985610653565b95610eb787610693565b610ec081610675565b6111c957610edc610ed4602089018961059f565b810190610ad0565b91604051610f0181610ef360208201948786610b4b565b03601f1981018352826104e5565b51902060808301805191989091610f28906001600160a01b03165b6001600160a01b031690565b610f41575050610f39600192611bd0565b019190610ca1565b6040610f4d9101610962565b15610fa35760208301518351600194610f9e936001600160a01b03938416939092610f9791610f8991606091169301516001600160801b031690565b92516001600160a01b031690565b928b611828565b610f39565b8251610fb990610f1c906001600160a01b031681565b604084018051909291906001600160a01b0316946060810192610fe384516001600160801b031690565b92803b156100d8575f8d946110129260019a836040518096819582946340c10f1960e01b845260048401610aad565b03925af190816111b5575b506111135781517f3c1af70310b8c9d43b0a7207217f398e4a3114f1728987c40addb451399ac87c9290611070906001600160a01b031686516001600160801b031684516001600160a01b031691611b45565b61110b6110ca6110bc6110ae6110a0611092602087015160018060a01b031690565b9a516001600160a01b031690565b94516001600160a01b031690565b97516001600160801b031690565b93516001600160a01b031690565b604080516001600160a01b0398891681526001600160801b03909516602086015296169583019590955260a088901b88900390811695169381906060820190565b0390a4610f39565b5060208101517fd5277bc9597c7da3fab9cdbba4de6005f48b9eb7389cf2389c4ea9eea3172c219190611157906001600160a01b031695516001600160a01b031690565b815161110b9060a090611172906001600160a01b03166110ae565b930151604080516001600160a01b0390981688526001600160801b0390941660208801529286019290925260a088901b8890039081169516939081906060820190565b806104855f6111c3936104e5565b5f61101d565b61120660406111e66111de60208b018b61059f565b8101906107b6565b9381516111fc81610ef360208201948986610889565b5190209801610962565b61159b578585101561158c5761122661121e86610980565b958785610993565b6080830161123481516118a2565b92906020604060a08801936112b9855161125385825192015160ff1690565b988335966112628786016109a3565b8651635f8a996960e01b8152600481019490945260ff9b8c166024850152604484018990528b166064840152608483015290981660a48901529101803560c48801526020013560e487015285908190610104820190565b0381731c000000000000000000000000000000000001005afa938415610491575f9461155c575b508392606094611444575b505050158015611438575b61142b5761130390611b12565b835191939161131c90610f1c906001600160a01b031681565b604083019161133283516001600160801b031690565b95823b156100d8575f806113618e9560019a6040519485809481936340c10f1960e01b83528960048401610aad565b03925af19081611417575b5061137e575050610f9e929150611a8e565b60208501517ffc236d1b4402e76c2b0a882db36ad3a6fb0f5b6b7edc8ae317d993f10434c43692919061110b906113d8906113ca906001600160a01b031698516001600160a01b031690565b96516001600160801b031690565b604080516001600160a01b0390981688526001600160801b03909116602088015286019290925260a088901b8890039081169516939081906060820190565b806104855f611425936104e5565b5f61136c565b50610f9e60019288611a8e565b506040815114156112f6565b518251516040516bffffffffffffffffffffffff197f000000000000000000000000000000000000000000000000000000000000000060601b166020820152603481019290925260548083019190915281525f93506114ce916114bb91906114ad6074836104e5565b6114b56109c2565b90611a22565b9151938401516001600160a01b03191690565b906115076114ec608060408701519601516001600160801b03191690565b60405163f4a7eb1360e01b8152958694859460048601610a5b565b0381731c000000000000000000000000000000000001015afa8015610491575f915f91611538575b505f80806112eb565b905061155691503d805f833e61154e81836104e5565b8101906109ed565b5f61152f565b61157e91945060203d8111611585575b61157681836104e5565b8101906109ad565b925f6112e0565b503d61156c565b6351de8c1f60e01b5f5260045ffd5b6020820151600192610f9e916001600160a01b031681519091906001600160a01b03166115e660606115d760408501516001600160801b031690565b9301516001600160a01b031690565b928b6117ba565b6115fb81838598969861056e565b9061160582610595565b916020810190611615828261059f565b90926040830193611626858561059f565b91909760608601986116388a8861059f565b9061083f60921b3b156100d8575f9561166693604051998a9788976374ae5b3760e11b8952600489016105d1565b03818361083f60921b5af18015610491576001967f4ac4dcc08b0c26c3fb6b58c64c1392b7934b1ce6b0382a5986ea5c3de795e053946116c9946116e8936116f4575b506116d16116c06116b983610595565b958361059f565b9690988361059f565b93909261059f565b9290916040519687968c8060a01b03169987610619565b0390a201949294610c8f565b806104855f611702936104e5565b5f6116a9565b806104855f611716936104e5565b5f610c89565b63bb62587160e01b5f5260045ffd5b50604051630b83774760e31b81526020816004817f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03165afa908115610491575f9161178b575b506001600160a01b0316331415610c20565b6117ad915060203d6020116117b3575b6117a581836104e5565b810190610506565b5f611779565b503d61179b565b92907f4620415fad9c416306a56ca0ee640b3418628a5f2e45ddde3ddf7452a7a654fb92946001600160801b036080936117f583828a611b45565b60405197611804896001610858565b6001600160a01b0390811660208a01529116604088015290811660608701521693a3565b92907f4620415fad9c416306a56ca0ee640b3418628a5f2e45ddde3ddf7452a7a654fb92946001600160801b0360809361186383828a611b45565b60405197611804895f610858565b908160011b918083046002149015171561098e57565b906001820180921161098e57565b9190820180921161098e57565b6118d3906118cd60405160208101906118c481610ef384906007602083019252565b51902091611871565b90611895565b9061195c6118e083611887565b6040516381e3da6b60e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000818116600484015260248301969096527f0000000000000000000000001c0000000000000000000000000000000000000016949092909190602090849081906044820190565b0381885afa928315610491575f936119ef575b5082156119e0576040516381e3da6b60e01b81526001600160a01b03929092166004830152602482015292602090849060449082905afa928315610491575f936119bc575b509160ff1690565b60ff9193506119d99060203d602011610e6c57610e5c81836104e5565b92906119b4565b63fb1f4a4960e01b5f5260045ffd5b611a0991935060203d602011610e6c57610e5c81836104e5565b915f61196f565b805191908290602001825e015f815290565b91611a766001611a5061051b95611a619560405191602083015260208252611a4b6040836104e5565b611e00565b926040519485916020830190611a10565b8260f81b815203601e198101855201836104e5565b60405190602082015260208152611a4b6040826104e5565b815160408301805160608501517f95705d99ac13cf82894fd274cd871942e6f301c98c186271337e8fedbbb9d7ea93611adf926001600160a01b03928316926001600160801b039091169116611b45565b6020840151935190516040516001600160a01b039586169590928392610e04926001600160801b03909116911683610aad565b90815160408103611b2e57506034602083015160601c92015190565b633fbbeba160e21b5f52600452604060245260445ffd5b9060026007609a1b013b156100d8576040516338b8fb9760e21b81526001600160a01b0392831660048201526001600160801b03919091166024820152911660448201525f816064818360026007609a1b015af1801561049157611ba65750565b5f610188916104e5565b906001600160801b03809116911601906001600160801b03821161098e57565b6040810151611c289190602090611bff90611bf3906001600160a01b0316610f1c565b6001600160401b031690565b60405163a3a124c360e01b81526001600160401b03909116600482015292839081906024820190565b03815f60026007609a1b015af1918215610491575f92611dc9575b508051611c5a90610f1c906001600160a01b031681565b916060820192611c7184516001600160801b031690565b90803b156100d8576040516340c10f1960e01b8152915f918391829084908290611c9f908960048401610aad565b03925af19081611db5575b50611d7e57611d5e611d507f92d91a50ae3561d4edd8eadad5f27f09116faaa4f733bd37682c49b4cbc20e3993611d43611ceb87516001600160801b031690565b82516001600160a01b03165f908152600260205260409020611d2990611d129088906103b6565b91611d2483546001600160801b031690565b611bb0565b6001600160801b03166001600160801b0319825416179055565b516001600160a01b031690565b93516001600160801b031690565b6040516001600160a01b03909216938291611d799183610aad565b0390a2565b90517fbc1cf3ff6e619587619408b396a13775509216474757afb9d29bda1a96f590f09190611d5e906001600160a01b0316611d50565b806104855f611dc3936104e5565b5f611caa565b611de391925060203d6020116117b3576117a581836104e5565b905f611c43565b61051b9392604092825260208201520190611a10565b5f908051604081115f14611f0c57505f611e2260209260405191828092611a10565b039060025afa156104915760205f611e9d610ef3611e918351965b6040519283917f363636363636363636363636363636363636363636363636363636363636363689187f36363636363636363636363636363636363636363636363636363636363636368b18898501611dea565b60405191828092611a10565b039060025afa1561049157611efc7f5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c611e915f93610ef36020968651908560405196879518911889850191606093918352602083015260408201520190565b039060025afa15610491575f5190565b6020808301519490821193509160018414611f7b57505f925b60208210611f65575b6040821016611f4b575b505f611e9d610ef3611e91602094611e3d565b600160409190910360031b1b5f190119909116905f611f38565b935f1960018360200360031b1b01191693611f2e565b6040015192611f2556fea26469706673582212201c423c8a4c3f3b20d121031ea3e984509904ffedb9f8c68b793e847fa12bef8364736f6c63430008230033", + "code": "0x60806040526004361015610011575f80fd5b5f3560e01c80631fbb25ad146100945780632d4884821461008f57806379502c551461008a57806382648c3b14610085578063857e85f81461008057806386516ec01461007b578063a21de6d9146100765763bffa55d514610071575f80fd5b610356565b610312565b610252565b61018a565b610150565b61010c565b6100e6565b346100d8575f3660031901126100d8577f0000000000000000000000001c000000000000000000000000000000000000006001600160a01b03166080908152602090f35b5f80fd5b5f9103126100d857565b346100d8575f3660031901126100d85760206001600160401b0360015416604051908152f35b346100d8575f3660031901126100d8576040517f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03168152602090f35b346100d8575f3660031901126100d85760205f54604051908152f35b6001600160a01b038116036100d857565b35906101888261016c565b565b346100d85760403660031901126100d85760206001600160801b036101e86004356101b48161016c565b602435906101c18261016c565b60018060a01b03165f526002845260405f209060018060a01b03165f5260205260405f2090565b5416604051908152f35b9181601f840112156100d8578235916001600160401b0383116100d8576020808501948460051b0101116100d857565b9181601f840112156100d8578235916001600160401b0383116100d8576020808501948460071b0101116100d857565b346100d85760803660031901126100d8576004356001600160401b0381116100d857366023820112156100d8578060040135906001600160401b0382116100d85736602483830101116100d8576024356001600160401b0381116100d8576102be9036906004016101f2565b6044929192356001600160401b0381116100d8576102e0903690600401610222565b91606435946001600160401b0386116100d8576103109661030760249736906004016101f2565b97909601610c10565b005b346100d8575f3660031901126100d8576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346100d85760203660031901126100d8576004356103738161016c565b6001600160a01b0381165f8181526002602081815260408084203380865281845291852054959094529190526001600160801b0390921692916103db916103cb91905b9060018060a01b03165f5260205260405f2090565b80546001600160801b0319169055565b6001600160a01b031690813b156100d8576040516340c10f1960e01b8152915f838061040b853360048401610aad565b038183855af19283156104915761047393610477575b506040516001600160801b038316815233907fffd3bbab073ab4b2d0792c270104924c14c285a153b9acddabae166395d2eb5c90602090a36040516001600160801b0390911681529081906020820190565b0390f35b806104855f61048b936104e5565b806100dc565b5f610421565b61051e565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b038211176104c557604052565b610496565b60c081019081106001600160401b038211176104c557604052565b90601f801991011681019081106001600160401b038211176104c557604052565b908160209103126100d8575161051b8161016c565b90565b6040513d5f823e3d90fd5b908060209392818452848401375f828201840152601f01601f1916010190565b91602061051b938181520191610529565b634e487b7160e01b5f52603260045260245ffd5b91908110156105905760051b81013590607e19813603018212156100d8570190565b61055a565b3561051b8161016c565b903590601e19813603018212156100d857018035906001600160401b0382116100d8576020019181360383136100d857565b9593916105fd9061060b9461051b99979360018060a01b03168952608060208a01526080890191610529565b918683036040880152610529565b926060818503910152610529565b9492909361063761051b979561064594606089526060890191610529565b918683036020880152610529565b926040818503910152610529565b91908110156105905760051b81013590605e19813603018212156100d8570190565b6002111561067f57565b634e487b7160e01b5f52602160045260245ffd5b3560028110156100d85790565b35906001600160801b03821682036100d857565b60ff8116036100d857565b6001600160401b0381116104c557601f01601f191660200190565b35906001600160a01b0319821682036100d857565b35906001600160801b0319821682036100d857565b919060a0838203126100d8576040519061071d826104aa565b8193803583526020810135610731816106b4565b602084015260408101356001600160401b0381116100d857810182601f820112156100d857803591610762836106bf565b9361077060405195866104e5565b838552602084840101116100d8576080935f6020856107b19682899701838601378301015260408601526107a6606082016106da565b6060860152016106ef565b910152565b6020818303126100d8578035906001600160401b0382116100d8570160c0818303126100d857604051916107e9836104ca565b81356107f48161016c565b83526108026020830161017d565b6020840152610813604083016106a0565b60408401526108246060830161017d565b60608401526080820135608084015260a08201356001600160401b0381116100d8576108509201610704565b60a082015290565b90600282101561067f5752565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b92919060a060409161089c866001610858565b60606020870152600180831b038151166060870152600180831b0360208201511660808701526001600160801b03838201511682870152600180831b0360608201511660c0870152608081015160e0870152015160c0610100860152805161012086015260ff6020820151166101408601526109288282015160a06101608801526101c0870190610865565b60608201516001600160a01b0319166101808701526080909101516001600160801b0319166101a0860152930152565b801515036100d857565b3561051b81610958565b634e487b7160e01b5f52601160045260245ffd5b5f19811461098e5760010190565b61096c565b91908110156105905760071b0190565b3561051b816106b4565b908160209103126100d8575161051b81610958565b604051906109d16040836104e5565b600d82526c65636965732d6165732d6b657960981b6020830152565b91906040838203126100d85782516001600160401b0381116100d85783019080601f830112156100d8578151610a22816106bf565b91610a3060405193846104e5565b818352602082850101116100d8576020815f92828096018386015e8301015292015161051b81610958565b92610a919060809360209397969786526bffffffffffffffffffffffff60a01b168386015260a0604086015260a0850190610865565b83810360608501525f815201936001600160801b031916910152565b6001600160a01b0390911681526001600160801b03909116602082015260400190565b908160c09103126100d85760a060405191610aea836104ca565b8035610af58161016c565b83526020810135610b058161016c565b60208401526040810135610b188161016c565b6040840152610b29606082016106a0565b60608401526080810135610b3c8161016c565b6080840152013560a082015290565b60e0909392919360a0610100820195610b64835f610858565b600180831b038151166020840152600180831b036020820151166040840152600180831b0360408201511660608401526001600160801b036060820151166080840152600180831b0360808201511682840152015160c08201520152565b908160209103126100d8575190565b906001600160401b03809116911601906001600160401b03821161098e57565b908160209103126100d857516001600160401b03811681036100d85790565b959194939296903315158061172b575b61171c577f0000000000000000000000001c000000000000000000000000000000000000006001600160a01b031696873b156100d85760405163fe77009960e01b8152915f9183918291610c78919060048401610549565b0381838b5af1801561049157611708575b505f9492945b8181106115ed575050505f54925f915f915b878310610e9f5750505003610e90576040516381e3da6b60e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316600482015260056024820152602081604481865afa801561049157610e73575b50805f55610d4e610d326001600160401b038516610d2d6001546001600160401b031690565b610bd1565b6001600160401b03166001600160401b03196001541617600155565b604051631014997960e31b815291602083600481845afa928315610491575f93610e3e575b50602060049160405192838092631d04645f60e01b82525afa8015610491577fd2d2bf1e295f62cd08f0f0ab45818efeaba78b58310526f7b7e9686b8aeded1a926001600160401b03925f92610e09575b50610e0490610ddb6001546001600160401b031690565b6040805198895260208901929092526001600160401b0316908701529116939081906060820190565b0390a3565b610e04919250610e309060203d602011610e37575b610e2881836104e5565b810190610bf1565b9190610dc4565b503d610e1e565b6004919350610e64602091823d8411610e6c575b610e5c81836104e5565b810190610bc2565b939150610d73565b503d610e52565b610e8b9060203d602011610e6c57610e5c81836104e5565b610d07565b6361aba18160e11b5f5260045ffd5b909194610ead868985610653565b95610eb787610693565b610ec081610675565b6111c957610edc610ed4602089018961059f565b810190610ad0565b91604051610f0181610ef360208201948786610b4b565b03601f1981018352826104e5565b51902060808301805191989091610f28906001600160a01b03165b6001600160a01b031690565b610f41575050610f39600192611bd0565b019190610ca1565b6040610f4d9101610962565b15610fa35760208301518351600194610f9e936001600160a01b03938416939092610f9791610f8991606091169301516001600160801b031690565b92516001600160a01b031690565b928b611828565b610f39565b8251610fb990610f1c906001600160a01b031681565b604084018051909291906001600160a01b0316946060810192610fe384516001600160801b031690565b92803b156100d8575f8d946110129260019a836040518096819582946340c10f1960e01b845260048401610aad565b03925af190816111b5575b506111135781517f3c1af70310b8c9d43b0a7207217f398e4a3114f1728987c40addb451399ac87c9290611070906001600160a01b031686516001600160801b031684516001600160a01b031691611b45565b61110b6110ca6110bc6110ae6110a0611092602087015160018060a01b031690565b9a516001600160a01b031690565b94516001600160a01b031690565b97516001600160801b031690565b93516001600160a01b031690565b604080516001600160a01b0398891681526001600160801b03909516602086015296169583019590955260a088901b88900390811695169381906060820190565b0390a4610f39565b5060208101517fd5277bc9597c7da3fab9cdbba4de6005f48b9eb7389cf2389c4ea9eea3172c219190611157906001600160a01b031695516001600160a01b031690565b815161110b9060a090611172906001600160a01b03166110ae565b930151604080516001600160a01b0390981688526001600160801b0390941660208801529286019290925260a088901b8890039081169516939081906060820190565b806104855f6111c3936104e5565b5f61101d565b61120660406111e66111de60208b018b61059f565b8101906107b6565b9381516111fc81610ef360208201948986610889565b5190209801610962565b61159b578585101561158c5761122661121e86610980565b958785610993565b6080830161123481516118a2565b92906020604060a08801936112b9855161125385825192015160ff1690565b988335966112628786016109a3565b8651635f8a996960e01b8152600481019490945260ff9b8c166024850152604484018990528b166064840152608483015290981660a48901529101803560c48801526020013560e487015285908190610104820190565b0381731c000000000000000000000000000000000001005afa938415610491575f9461155c575b508392606094611444575b505050158015611438575b61142b5761130390611b12565b835191939161131c90610f1c906001600160a01b031681565b604083019161133283516001600160801b031690565b95823b156100d8575f806113618e9560019a6040519485809481936340c10f1960e01b83528960048401610aad565b03925af19081611417575b5061137e575050610f9e929150611a8e565b60208501517ffc236d1b4402e76c2b0a882db36ad3a6fb0f5b6b7edc8ae317d993f10434c43692919061110b906113d8906113ca906001600160a01b031698516001600160a01b031690565b96516001600160801b031690565b604080516001600160a01b0390981688526001600160801b03909116602088015286019290925260a088901b8890039081169516939081906060820190565b806104855f611425936104e5565b5f61136c565b50610f9e60019288611a8e565b506040815114156112f6565b518251516040516bffffffffffffffffffffffff197f000000000000000000000000000000000000000000000000000000000000000060601b166020820152603481019290925260548083019190915281525f93506114ce916114bb91906114ad6074836104e5565b6114b56109c2565b90611a22565b9151938401516001600160a01b03191690565b906115076114ec608060408701519601516001600160801b03191690565b60405163f4a7eb1360e01b8152958694859460048601610a5b565b0381731c000000000000000000000000000000000001015afa8015610491575f915f91611538575b505f80806112eb565b905061155691503d805f833e61154e81836104e5565b8101906109ed565b5f61152f565b61157e91945060203d8111611585575b61157681836104e5565b8101906109ad565b925f6112e0565b503d61156c565b6351de8c1f60e01b5f5260045ffd5b6020820151600192610f9e916001600160a01b031681519091906001600160a01b03166115e660606115d760408501516001600160801b031690565b9301516001600160a01b031690565b928b6117ba565b6115fb81838598969861056e565b9061160582610595565b916020810190611615828261059f565b90926040830193611626858561059f565b91909760608601986116388a8861059f565b9061083f60921b3b156100d8575f9561166693604051998a9788976374ae5b3760e11b8952600489016105d1565b03818361083f60921b5af18015610491576001967f4ac4dcc08b0c26c3fb6b58c64c1392b7934b1ce6b0382a5986ea5c3de795e053946116c9946116e8936116f4575b506116d16116c06116b983610595565b958361059f565b9690988361059f565b93909261059f565b9290916040519687968c8060a01b03169987610619565b0390a201949294610c8f565b806104855f611702936104e5565b5f6116a9565b806104855f611716936104e5565b5f610c89565b63bb62587160e01b5f5260045ffd5b50604051630b83774760e31b81526020816004817f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03165afa908115610491575f9161178b575b506001600160a01b0316331415610c20565b6117ad915060203d6020116117b3575b6117a581836104e5565b810190610506565b5f611779565b503d61179b565b92907f4620415fad9c416306a56ca0ee640b3418628a5f2e45ddde3ddf7452a7a654fb92946001600160801b036080936117f583828a611b45565b60405197611804896001610858565b6001600160a01b0390811660208a01529116604088015290811660608701521693a3565b92907f4620415fad9c416306a56ca0ee640b3418628a5f2e45ddde3ddf7452a7a654fb92946001600160801b0360809361186383828a611b45565b60405197611804895f610858565b908160011b918083046002149015171561098e57565b906001820180921161098e57565b9190820180921161098e57565b6118d3906118cd60405160208101906118c481610ef384906007602083019252565b51902091611871565b90611895565b9061195c6118e083611887565b6040516381e3da6b60e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000818116600484015260248301969096527f0000000000000000000000001c0000000000000000000000000000000000000016949092909190602090849081906044820190565b0381885afa928315610491575f936119ef575b5082156119e0576040516381e3da6b60e01b81526001600160a01b03929092166004830152602482015292602090849060449082905afa928315610491575f936119bc575b509160ff1690565b60ff9193506119d99060203d602011610e6c57610e5c81836104e5565b92906119b4565b63fb1f4a4960e01b5f5260045ffd5b611a0991935060203d602011610e6c57610e5c81836104e5565b915f61196f565b805191908290602001825e015f815290565b91611a766001611a5061051b95611a619560405191602083015260208252611a4b6040836104e5565b611e00565b926040519485916020830190611a10565b8260f81b815203601e198101855201836104e5565b60405190602082015260208152611a4b6040826104e5565b815160408301805160608501517f95705d99ac13cf82894fd274cd871942e6f301c98c186271337e8fedbbb9d7ea93611adf926001600160a01b03928316926001600160801b039091169116611b45565b6020840151935190516040516001600160a01b039586169590928392610e04926001600160801b03909116911683610aad565b90815160408103611b2e57506034602083015160601c92015190565b633fbbeba160e21b5f52600452604060245260445ffd5b9060026007609a1b013b156100d8576040516338b8fb9760e21b81526001600160a01b0392831660048201526001600160801b03919091166024820152911660448201525f816064818360026007609a1b015af1801561049157611ba65750565b5f610188916104e5565b906001600160801b03809116911601906001600160801b03821161098e57565b6040810151611c289190602090611bff90611bf3906001600160a01b0316610f1c565b6001600160401b031690565b60405163a3a124c360e01b81526001600160401b03909116600482015292839081906024820190565b03815f60026007609a1b015af1918215610491575f92611dc9575b508051611c5a90610f1c906001600160a01b031681565b916060820192611c7184516001600160801b031690565b90803b156100d8576040516340c10f1960e01b8152915f918391829084908290611c9f908960048401610aad565b03925af19081611db5575b50611d7e57611d5e611d507f92d91a50ae3561d4edd8eadad5f27f09116faaa4f733bd37682c49b4cbc20e3993611d43611ceb87516001600160801b031690565b82516001600160a01b03165f908152600260205260409020611d2990611d129088906103b6565b91611d2483546001600160801b031690565b611bb0565b6001600160801b03166001600160801b0319825416179055565b516001600160a01b031690565b93516001600160801b031690565b6040516001600160a01b03909216938291611d799183610aad565b0390a2565b90517fbc1cf3ff6e619587619408b396a13775509216474757afb9d29bda1a96f590f09190611d5e906001600160a01b0316611d50565b806104855f611dc3936104e5565b5f611caa565b611de391925060203d6020116117b3576117a581836104e5565b905f611c43565b61051b9392604092825260208201520190611a10565b5f908051604081115f14611f0c57505f611e2260209260405191828092611a10565b039060025afa156104915760205f611e9d610ef3611e918351965b6040519283917f363636363636363636363636363636363636363636363636363636363636363689187f36363636363636363636363636363636363636363636363636363636363636368b18898501611dea565b60405191828092611a10565b039060025afa1561049157611efc7f5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c611e915f93610ef36020968651908560405196879518911889850191606093918352602083015260408201520190565b039060025afa15610491575f5190565b6020808301519490821193509160018414611f7b57505f925b60208210611f65575b6040821016611f4b575b505f611e9d610ef3611e91602094611e3d565b600160409190910360031b1b5f190119909116905f611f38565b935f1960018360200360031b1b01191693611f2e565b6040015192611f2556fea2646970667358221220414ee9ad328bf393b4ccbda5ae5d9b06dcc359bdb8a18e9c206617b01bb9a90564736f6c63430008230033", "nonce": "0x1" }, "0x1c00000000000000000000000000000000000002": { "balance": "0x0", - "code": "0x60806040526004361015610011575f80fd5b5f3560e01c80633406527214610184578063378fa8fa1461017f5780633d1c5a931461017a578063413e9cde1461017557806343c3cb831461017057806348aa41081461016b57806353a8d73914610166578063545525f11461016157806379502c551461015c57806379fa3289146101575780637b9c9aa41461015257806386f47e551461014d578063a3a124c314610148578063a94cd93114610143578063b3b200aa1461013e578063b9d1fd6814610139578063bba9282e14610134578063c37fc8a81461012f578063c9b6ca9b1461012a578063ce7025e914610125578063d93af1d214610120578063e2e3ee5c1461011b5763f490ca9614610116575f80fd5b610d33565b610b8c565b610b6f565b610aea565b610ace565b6109e3565b61086c565b61075e565b6106c6565b610634565b610546565b610529565b6104ec565b610358565b6102d2565b6102ac565b610290565b610238565b61021d565b6101fd565b6101d7565b6101bc565b610197565b5f91031261019357565b5f80fd5b34610193575f3660031901126101935760206001600160801b035f5416604051908152f35b34610193575f36600319011261019357602060405160218152f35b34610193575f3660031901126101935760206001600160401b0360045416604051908152f35b34610193575f36600319011261019357602060025460c01c604051908152f35b34610193575f36600319011261019357602060405160718152f35b34610193575f366003190112610193575f602060405161025781610d69565b828152015260406001546001600160401b0380600254166020845161027b81610d69565b84815201908152835192835251166020820152f35b34610193575f3660031901126101935760206040516104008152f35b34610193575f36600319011261019357602063ffffffff60025460401c16604051908152f35b34610193575f366003190112610193576040517f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03168152602090f35b600435906001600160801b038216820361019357565b604435906001600160801b038216820361019357565b602435906001600160801b038216820361019357565b3461019357602036600319011261019357610371610316565b33151580610416575b61040757670de0b6b3a76400006001600160801b038216116103f8576103f3816103d97f6f864cce5237e12ffc9a99fc6c59af17222c2bbb3457690cc8753ab16b5d715e936001600160801b03166001600160801b03195f5416175f55565b6040516001600160801b0390911681529081906020820190565b0390a1005b630d62f21160e11b5f5260045ffd5b63bb62587160e01b5f5260045ffd5b50604051630b83774760e31b81526020816004817f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03165afa9081156104a5575f91610476575b506001600160a01b031633141561037a565b610498915060203d60201161049e575b6104908183610da5565b810190610de8565b5f610464565b503d610486565b610e00565b600435906001600160401b038216820361019357565b608435906001600160401b038216820361019357565b602435906001600160401b038216820361019357565b3461019357602036600319011261019357602061051861050a6104aa565b61051381611490565b6114d2565b6001600160801b0360405191168152f35b34610193575f366003190112610193576020604051629896808152f35b346101935760203660031901126101935761055f6104aa565b6007609a1b1933016105e7576001600160401b03165f818152600560205260409020546001600160a01b031680156105d8576105d4915f5260056020526105ba60405f206bffffffffffffffffffffffff60a01b8154169055565b6040516001600160a01b0390911681529081906020820190565b0390f35b63c29f0c7160e01b5f5260045ffd5b6303300c7360e31b5f5260045ffd5b6001600160a01b0381160361019357565b9181601f84011215610193578235916001600160401b038311610193576020838186019501011161019357565b346101935760e036600319011261019357600435610651816105f6565b6024359061065e826105f6565b61066661032c565b916064356106726104c0565b60a4359161067f836105f6565b60c435956001600160401b038711610193576106ac6106a56106c4983690600401610607565b3691610e26565b94604051966106bc602089610da5565b5f88526115ea565b005b3461019357610100366003190112610193576004356106e4816105f6565b6024356106f0816105f6565b6106f861032c565b916064356107046104c0565b9060a435610711816105f6565b60c4356001600160401b03811161019357610730903690600401610607565b93909260e435976001600160401b038911610193576107566106c4993690600401610607565b989097610e70565b346101935760203660031901126101935760043563ffffffff8116810361019357331515806107ed575b610407576103f3816107d67f5340f6cf6e1274bffd0c7188c75f885ed6c90cd6b0879a646290a92f84a6dce39363ffffffff60401b6002549160401b169063ffffffff60401b191617600255565b60405163ffffffff90911681529081906020820190565b50604051630b83774760e31b81526020816004817f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03165afa9081156104a5575f9161084d575b506001600160a01b0316331415610788565b610866915060203d60201161049e576104908183610da5565b5f61083b565b34610193575f3660031901126101935760206001600160401b035f5460801c16604051908152f35b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b602081016020825282518091526040820191602060408360051b8301019401925f915b8383106108ea57505050505090565b90919293946020806109d4600193603f1986820301875289519061091681835160018060a01b03169052565b818401516001600160a01b031681850152604082810151908201526060808301516001600160a01b0316908201526080828101516001600160801b03169082015260a0828101516001600160801b03169082015260c082015160c082015261098e60e083015160e08301906001600160401b03169052565b610100828101516001600160401b0316908201526101406109c2610120840151610160610120850152610160840190610894565b92015190610140818403910152610894565b970193019301919392906108db565b34610193575f366003190112610193576003546109ff81610e97565b90610a0d6040519283610da5565b808252601f19610a1c82610e97565b015f5b818110610a705750505f5b818110610a3f57604051806105d485826108b8565b80610a54610a4e600193610ec2565b50610fe6565b610a5e8286610efc565b52610a698185610efc565b5001610a2a565b602090604051610a7f81610d89565b5f81525f838201525f60408201525f60608201525f60808201525f60a08201525f60c08201525f60e08201525f6101008201526060610120820152606061014082015282828701015201610a1f565b34610193575f36600319011261019357602060405161c3508152f35b3461019357606036600319011261019357610b036104d6565b604435906001600160401b0382116101935736602383011215610193578160040135906001600160401b038211610193573660248360051b8501011161019357610b56610b5f9260246105d495016110ee565b90600435611dd0565b6040519081529081906020820190565b34610193575f366003190112610193576020600354604051908152f35b3461019357606036600319011261019357600435610ba9816105f6565b610bb1610342565b90604435610bbe816105f6565b6007609a1b1933016105e7576001600160401b037f34ca953f3eed14157d2f660c7e92a5bd9c05be0d61a188830f3bf1cb7d094f9691610d2e5f95610c76610c04610dc6565b6001600160a01b038816815260208101899052604081018990526001600160a01b03851660608201526001600160801b03831660808201528860a08201528860c08201528860e082015288610100820152610c5d610e5c565b610120820152610c6b610e5c565b6101408201526112ae565b865460801c6001600160401b031692610cbd610c918561146e565b5f805467ffffffffffffffff60801b191660809290921b67ffffffffffffffff60801b16919091179055565b604080516001600160a01b0398891681529790911660208801526001600160801b03909116908601525f606086018190526080860181905260a0860181905260c0860181905261012060e0870181905286018190526101406101008701819052860152911692908190610160820190565b0390a3005b34610193575f366003190112610193576020604051670de0b6b3a76400008152f35b634e487b7160e01b5f52604160045260245ffd5b604081019081106001600160401b03821117610d8457604052565b610d55565b61016081019081106001600160401b03821117610d8457604052565b90601f801991011681019081106001600160401b03821117610d8457604052565b60405190610dd661016083610da5565b565b60405190610dd661014083610da5565b908160209103126101935751610dfd816105f6565b90565b6040513d5f823e3d90fd5b6001600160401b038111610d8457601f01601f191660200190565b929192610e3282610e0b565b91610e406040519384610da5565b829481845281830111610193578281602093845f960137010152565b60405190610e6b602083610da5565b5f8252565b97610e89610e91929394959697610dd69b993691610e26565b973691610e26565b966115ea565b6001600160401b038111610d845760051b60200190565b634e487b7160e01b5f52603260045260245ffd5b600354811015610ede5760035f52600960205f20910201905f90565b610eae565b8054821015610ede575f52600960205f20910201905f90565b8051821015610ede5760209160051b010190565b90600182811c92168015610f3e575b6020831014610f2a57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610f1f565b9060405191825f825492610f5b84610f10565b8084529360018116908115610fc45750600114610f80575b50610dd692500383610da5565b90505f9291925260205f20905f915b818310610fa8575050906020610dd6928201015f610f73565b6020919350806001915483858901015201910190918492610f8f565b905060209250610dd694915060ff191682840152151560051b8201015f610f73565b906110e66008610ff4610dc6565b84546001600160a01b031681529360018101546001600160a01b031660208601526002810154604086015260038101546001600160a01b031660608601526110766110666004830154611060611050826001600160801b031690565b6001600160801b031660808a0152565b60801c90565b6001600160801b031660a0870152565b600581015460c08601526110ce6110bd60068301546110ae61109e826001600160401b031690565b6001600160401b031660e08a0152565b60401c6001600160401b031690565b6001600160401b0316610100870152565b6110da60078201610f48565b61012086015201610f48565b610140830152565b906110f881610e97565b916111066040519384610da5565b818352602083019160051b8101903682116101935780925b82841061112c575050505090565b83356001600160401b03811161019357820136601f820112156101935760209161115d839236908481359101610e26565b81520193019261111e565b634e487b7160e01b5f525f60045260245ffd5b5f5b82811061118957505050565b5f8282015560010161117d565b91601f82116111a457505050565b8082116111b057505050565b610dd6925f5260205f20916020601f830160051c92106111db575b601f82910160051c03910161117b565b5f91506111cb565b91909182516001600160401b038111610d845761120a816112048454610f10565b84611196565b6020601f821160011461124957819061123a9394955f9261123e575b50508160011b915f199060031b1c19161790565b9055565b015190505f80611226565b601f1982169061125c845f5260205f2090565b915f5b8181106112965750958360019596971061127e575b505050811b019055565b01515f1960f88460031b161c191690555f8080611274565b9192602060018192868b01518155019401920161125f565b60035468010000000000000000811015610d84578060016112d492016003556003610ee3565b61145557815181546001600160a01b0319166001600160a01b03909116178155610dd6916008906101409060208101516001850180546001600160a01b0319166001600160a01b039092169190911790556040810151600285015560608101516003850180546001600160a01b0319166001600160a01b039092169190911790556113bd6004850161138f61137360808501516001600160801b031690565b82546001600160801b0319166001600160801b03909116178255565b60a08301516001600160801b031681546001600160801b031660809190911b6001600160801b031916179055565b60c0810151600585015561143a600685016114026113e560e08501516001600160401b031690565b825467ffffffffffffffff19166001600160401b03909116178255565b61010083015181546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff000000000000000016179055565b61144c610120820151600786016111e3565b015191016111e3565b611168565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b03166001600160401b03811461148b5760010190565b61145a565b6001600160401b03629896809116116114a557565b637d2e4eb560e01b5f5260045ffd5b6001600160401b036001911601906001600160401b03821161148b57565b6001600160401b031661c350016001600160401b03811161148b576001600160401b036001600160801b035f54169116026001600160801b03811690810361148b5790565b90816020910312610193575180151581036101935790565b63ffffffff1663ffffffff811461148b5760010190565b906001600160801b03809116911601906001600160801b03821161148b57565b90816020910312610193575190565b6001600160a01b039182168152911660208201526001600160801b0391821660408201529116606082015260808101919091526001600160401b0391821660a0820152911660c082015261012060e08201819052610dfd9391926115db91840190610894565b91610100818403910152610894565b95969092949693919360018060a01b038816156105d85760405163037d635760e31b81526001600160a01b0388811660048301527f0000000000000000000000001c000000000000000000000000000000000000031690602081602481855afa9081156104a5575f91611bbe575b5015611baf57610400825111611ba0576001600160401b038416611b2d5760405163a05e344560e01b81526001600160a01b0386166004820152602081602481855afa9081156104a5575f91611b0e575b50611aff5760405163b23bc7d760e01b81526001600160a01b038616600482015290602090829060249082905afa9081156104a5575f91611ae0575b5015611ac4575b6116f582612145565b600254604081901c63ffffffff166119db575b50611712836114d2565b9761171d8987611546565b60405163868a2e9d60e01b8152979094906020896004815f60056007609a1b015af19889156104a5575f996119aa575b50881561199b576040516323b872dd60e01b81523360048201523060248201526001600160801b03871660448201526001600160a01b038b16906020816064815f865af19081156104a5575f9161196c575b501561195d57803b1561019357604051630852cd8d60e31b81526001600160801b039790971660048801525f908790602490829084905af180156104a5577f34ca953f3eed14157d2f660c7e92a5bd9c05be0d61a188830f3bf1cb7d094f969961193e9761190b92611943575b5061188561182a6118256004546001600160401b031690565b61146e565b9561184b876001600160401b03166001600160401b03196004541617600455565b611866876001600160401b03165f52600560205260405f2090565b80546001600160a01b0319166001600160a01b03909216919091179055565b61188d610dc6565b6001600160a01b038d1681529033602083015260408201526001600160a01b03891660608201526001600160801b038a1660808201526001600160801b038d1660a082015260c081018390526001600160401b03841660e08201526001600160401b03851661010082015285610120820152866101408201526112ae565b5f5460801c6001600160401b03169a611926610c918d61146e565b6040519889986001600160401b03339e169c8a611575565b0390a3565b806119515f61195793610da5565b80610189565b5f61180c565b6312171d8360e31b5f5260045ffd5b61198e915060203d602011611994575b6119868183610da5565b810190611517565b5f61179f565b503d61197c565b632968ee7f60e21b5f5260045ffd5b6119cd91995060203d6020116119d4575b6119c58183610da5565b810190611566565b975f61174d565b503d6119bb565b611a086119fc6001600160401b034316926001600160401b039060801c1690565b6001600160401b031690565b8103611a8d575b50600254611a3a611a31606083901c63ffffffff169260401c63ffffffff1690565b63ffffffff1690565b63ffffffff82161015611a7e57611a53611a789161152f565b6002805463ffffffff60601b191660609290921b63ffffffff60601b16919091179055565b5f611708565b63124ab48560e11b5f5260045ffd5b6002805460809290921b67ffffffffffffffff60801b166bffffffffffffffffffffffff60601b199092169190911790555f611a0f565b6376ecd09f60e11b5f526001600160a01b03841660045260245ffd5b611af9915060203d602011611994576119868183610da5565b5f6116e5565b63793cd94b60e11b5f5260045ffd5b611b27915060203d602011611994576119868183610da5565b5f6116a9565b611b3684611490565b60405163a05e344560e01b81526001600160a01b038616600482015290602090829060249082905afa9081156104a5575f91611b81575b506116ec5763793cd94b60e11b5f5260045ffd5b611b9a915060203d602011611994576119868183610da5565b5f611b6d565b634b8a874d60e11b5f5260045ffd5b631fcf8c4760e11b5f5260045ffd5b611bd7915060203d602011611994576119868183610da5565b5f611658565b5f1981019190821161148b57565b604080825282516001600160a01b031690820152929190602090611cb09080830151606087015260408101516001600160a01b0316608087015260608101516001600160801b031660a087015260808101516001600160801b031660c087015260a081015160e087015260c08101516001600160401b031661010087015260e08101516001600160401b0316610120870152610120611c9b610100830151610140808a0152610180890190610894565b910151868203603f1901610160880152610894565b930152565b611cbf8154610f10565b9081611cc9575050565b81601f5f9311600114611cda575055565b81835260208320611cf791601f0160051c8419019060010161117b565b808252602082209081548360011b9084198560031b1c191617905555565b90611455576008815f610dd693555f60018201555f60028201555f60038201555f60048201555f60058201555f6006820155611d5360078201611cb5565b01611cb5565b6003545f60035580611d685750565b8060090290600982040361148b5760035f5260205f205f5b828110611d8c57505050565b80611dca600860099385015f81555f60018201555f60028201555f60038201555f60048201555f60058201555f6006820155611d5360078201611cb5565b01611d80565b9092915f93331515806120c6575b610407576001600160401b038043169116036120b7576003548083036120a057508051828103612089575081611ec7575b5050611e4a611e2e611e296002546001600160401b031690565b6114b4565b6001600160401b03166001600160401b03196002541617600255565b817fec4aff46c65f485f4b15e3c2edadda1d57d002995f5aa262a27c76b9a680ec16611ec2611e816002546001600160401b031690565b611e8a84600155565b600280546001600160c01b03164260c01b6001600160c01b0319161790556040516001600160401b0390911681529081906020820190565b0390a2565b5f19935090805b611ee3575050611edc611d59565b5f80611e0f565b611eec81611bdd565b93611ef685610ec2565b50611f0090610fe6565b611f0a8685610efc565b518061014083015190611f1c916121a1565b8151602080840151604080860151905160609290921b6bffffffffffffffffffffffff191692820192835260348083019190915281526001600160a01b039092169391611f6a605482610da5565b51902060608201519091906001600160a01b031660808201516001600160801b031660a08301516001600160801b031660c08401519160e0850151611fb5906001600160401b031690565b93610100860151611fcc906001600160401b031690565b95610120015196611fdb610dd8565b6001600160a01b03909a168a5260208a01526001600160a01b031660408901526001600160801b031660608801526001600160801b0316608087015260a08601526001600160401b031660c08501526001600160401b031660e0840152610100830152610120820152604051809160208201936120589185611beb565b03601f198101825261206a9082610da5565b5190209361207790610ec2565b61208091611d15565b5f190180611ece565b630db2128560e11b5f52600452602482905260445ffd5b6324a839db60e11b5f52600483905260245260445ffd5b631391e11b60e21b5f5260045ffd5b50604051630b83774760e31b81526020816004817f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03165afa9081156104a5575f91612126575b506001600160a01b0316331415611dde565b61213f915060203d60201161049e576104908183610da5565b5f612114565b8051801561219d5760210361218e57805115610ede576020810151612174906121709060f81c6121d2565b1590565b61218e57612170602161218892015161223d565b61218e57565b6361d0136b60e11b5f5260045ffd5b5050565b516121ca575f905b518181036121b5575050565b63521a2d4d60e11b5f5260045260245260445ffd5b6071906121a9565b60ff16600281149081156121e4575090565b600391501490565b3d15612216573d906121fd82610e0b565b9161220b6040519384610da5565b82523d5f602084013e565b606090565b60208151910151906020811061222f575090565b5f199060200360031b1b1690565b801580156122e9575b6122d8575f9081906401000003d0199060079082908181800909086040805160208082018181529282018190526060820152608081019290925263800001e9600160ff1b0360a08301526401000003d01960c0808401919091528252906122ae60e082610da5565b519060055afa6122bc6121ec565b901580156122dd575b6122d8576122d460019161221b565b1490565b505f90565b506020815114156122c5565b506401000003d01981101561224656fea26469706673582212201329d347aaacaaf45a2c63fce972751360fe6646c0ce6430df91702f980ee34664736f6c63430008230033", + "code": "0x60806040526004361015610011575f80fd5b5f3560e01c80633406527214610184578063378fa8fa1461017f5780633d1c5a931461017a578063413e9cde1461017557806343c3cb831461017057806348aa41081461016b57806353a8d73914610166578063545525f11461016157806379502c551461015c57806379fa3289146101575780637b9c9aa41461015257806386f47e551461014d578063a3a124c314610148578063a94cd93114610143578063b3b200aa1461013e578063b9d1fd6814610139578063bba9282e14610134578063c37fc8a81461012f578063c9b6ca9b1461012a578063ce7025e914610125578063d93af1d214610120578063e2e3ee5c1461011b5763f490ca9614610116575f80fd5b610d33565b610b8c565b610b6f565b610aea565b610ace565b6109e3565b61086c565b61075e565b6106c6565b610634565b610546565b610529565b6104ec565b610358565b6102d2565b6102ac565b610290565b610238565b61021d565b6101fd565b6101d7565b6101bc565b610197565b5f91031261019357565b5f80fd5b34610193575f3660031901126101935760206001600160801b035f5416604051908152f35b34610193575f36600319011261019357602060405160218152f35b34610193575f3660031901126101935760206001600160401b0360045416604051908152f35b34610193575f36600319011261019357602060025460c01c604051908152f35b34610193575f36600319011261019357602060405160718152f35b34610193575f366003190112610193575f602060405161025781610d69565b828152015260406001546001600160401b0380600254166020845161027b81610d69565b84815201908152835192835251166020820152f35b34610193575f3660031901126101935760206040516104008152f35b34610193575f36600319011261019357602063ffffffff60025460401c16604051908152f35b34610193575f366003190112610193576040517f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03168152602090f35b600435906001600160801b038216820361019357565b604435906001600160801b038216820361019357565b602435906001600160801b038216820361019357565b3461019357602036600319011261019357610371610316565b33151580610416575b61040757670de0b6b3a76400006001600160801b038216116103f8576103f3816103d97f6f864cce5237e12ffc9a99fc6c59af17222c2bbb3457690cc8753ab16b5d715e936001600160801b03166001600160801b03195f5416175f55565b6040516001600160801b0390911681529081906020820190565b0390a1005b630d62f21160e11b5f5260045ffd5b63bb62587160e01b5f5260045ffd5b50604051630b83774760e31b81526020816004817f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03165afa9081156104a5575f91610476575b506001600160a01b031633141561037a565b610498915060203d60201161049e575b6104908183610da5565b810190610de8565b5f610464565b503d610486565b610e00565b600435906001600160401b038216820361019357565b608435906001600160401b038216820361019357565b602435906001600160401b038216820361019357565b3461019357602036600319011261019357602061051861050a6104aa565b61051381611490565b6114d2565b6001600160801b0360405191168152f35b34610193575f366003190112610193576020604051629896808152f35b346101935760203660031901126101935761055f6104aa565b6007609a1b1933016105e7576001600160401b03165f818152600560205260409020546001600160a01b031680156105d8576105d4915f5260056020526105ba60405f206bffffffffffffffffffffffff60a01b8154169055565b6040516001600160a01b0390911681529081906020820190565b0390f35b63c29f0c7160e01b5f5260045ffd5b6303300c7360e31b5f5260045ffd5b6001600160a01b0381160361019357565b9181601f84011215610193578235916001600160401b038311610193576020838186019501011161019357565b346101935760e036600319011261019357600435610651816105f6565b6024359061065e826105f6565b61066661032c565b916064356106726104c0565b60a4359161067f836105f6565b60c435956001600160401b038711610193576106ac6106a56106c4983690600401610607565b3691610e26565b94604051966106bc602089610da5565b5f8852611620565b005b3461019357610100366003190112610193576004356106e4816105f6565b6024356106f0816105f6565b6106f861032c565b916064356107046104c0565b9060a435610711816105f6565b60c4356001600160401b03811161019357610730903690600401610607565b93909260e435976001600160401b038911610193576107566106c4993690600401610607565b989097610e70565b346101935760203660031901126101935760043563ffffffff8116810361019357331515806107ed575b610407576103f3816107d67f5340f6cf6e1274bffd0c7188c75f885ed6c90cd6b0879a646290a92f84a6dce39363ffffffff60401b6002549160401b169063ffffffff60401b191617600255565b60405163ffffffff90911681529081906020820190565b50604051630b83774760e31b81526020816004817f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03165afa9081156104a5575f9161084d575b506001600160a01b0316331415610788565b610866915060203d60201161049e576104908183610da5565b5f61083b565b34610193575f3660031901126101935760206001600160401b035f5460801c16604051908152f35b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b602081016020825282518091526040820191602060408360051b8301019401925f915b8383106108ea57505050505090565b90919293946020806109d4600193603f1986820301875289519061091681835160018060a01b03169052565b818401516001600160a01b031681850152604082810151908201526060808301516001600160a01b0316908201526080828101516001600160801b03169082015260a0828101516001600160801b03169082015260c082015160c082015261098e60e083015160e08301906001600160401b03169052565b610100828101516001600160401b0316908201526101406109c2610120840151610160610120850152610160840190610894565b92015190610140818403910152610894565b970193019301919392906108db565b34610193575f366003190112610193576003546109ff81610e97565b90610a0d6040519283610da5565b808252601f19610a1c82610e97565b015f5b818110610a705750505f5b818110610a3f57604051806105d485826108b8565b80610a54610a4e600193610ec2565b50610fe6565b610a5e8286610efc565b52610a698185610efc565b5001610a2a565b602090604051610a7f81610d89565b5f81525f838201525f60408201525f60608201525f60808201525f60a08201525f60c08201525f60e08201525f6101008201526060610120820152606061014082015282828701015201610a1f565b34610193575f36600319011261019357602060405161c3508152f35b3461019357606036600319011261019357610b036104d6565b604435906001600160401b0382116101935736602383011215610193578160040135906001600160401b038211610193573660248360051b8501011161019357610b56610b5f9260246105d495016110ee565b90600435611ecf565b6040519081529081906020820190565b34610193575f366003190112610193576020600354604051908152f35b3461019357606036600319011261019357600435610ba9816105f6565b610bb1610342565b90604435610bbe816105f6565b6007609a1b1933016105e7576001600160401b037f34ca953f3eed14157d2f660c7e92a5bd9c05be0d61a188830f3bf1cb7d094f9691610d2e5f95610c76610c04610dc6565b6001600160a01b038816815260208101899052604081018990526001600160a01b03851660608201526001600160801b03831660808201528860a08201528860c08201528860e082015288610100820152610c5d610e5c565b610120820152610c6b610e5c565b6101408201526112ae565b865460801c6001600160401b031692610cbd610c918561146e565b5f805467ffffffffffffffff60801b191660809290921b67ffffffffffffffff60801b16919091179055565b604080516001600160a01b0398891681529790911660208801526001600160801b03909116908601525f606086018190526080860181905260a0860181905260c0860181905261012060e0870181905286018190526101406101008701819052860152911692908190610160820190565b0390a3005b34610193575f366003190112610193576020604051670de0b6b3a76400008152f35b634e487b7160e01b5f52604160045260245ffd5b604081019081106001600160401b03821117610d8457604052565b610d55565b61016081019081106001600160401b03821117610d8457604052565b90601f801991011681019081106001600160401b03821117610d8457604052565b60405190610dd661016083610da5565b565b60405190610dd661014083610da5565b908160209103126101935751610dfd816105f6565b90565b6040513d5f823e3d90fd5b6001600160401b038111610d8457601f01601f191660200190565b929192610e3282610e0b565b91610e406040519384610da5565b829481845281830111610193578281602093845f960137010152565b60405190610e6b602083610da5565b5f8252565b97610e89610e91929394959697610dd69b993691610e26565b973691610e26565b96611620565b6001600160401b038111610d845760051b60200190565b634e487b7160e01b5f52603260045260245ffd5b600354811015610ede5760035f52600960205f20910201905f90565b610eae565b8054821015610ede575f52600960205f20910201905f90565b8051821015610ede5760209160051b010190565b90600182811c92168015610f3e575b6020831014610f2a57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610f1f565b9060405191825f825492610f5b84610f10565b8084529360018116908115610fc45750600114610f80575b50610dd692500383610da5565b90505f9291925260205f20905f915b818310610fa8575050906020610dd6928201015f610f73565b6020919350806001915483858901015201910190918492610f8f565b905060209250610dd694915060ff191682840152151560051b8201015f610f73565b906110e66008610ff4610dc6565b84546001600160a01b031681529360018101546001600160a01b031660208601526002810154604086015260038101546001600160a01b031660608601526110766110666004830154611060611050826001600160801b031690565b6001600160801b031660808a0152565b60801c90565b6001600160801b031660a0870152565b600581015460c08601526110ce6110bd60068301546110ae61109e826001600160401b031690565b6001600160401b031660e08a0152565b60401c6001600160401b031690565b6001600160401b0316610100870152565b6110da60078201610f48565b61012086015201610f48565b610140830152565b906110f881610e97565b916111066040519384610da5565b818352602083019160051b8101903682116101935780925b82841061112c575050505090565b83356001600160401b03811161019357820136601f820112156101935760209161115d839236908481359101610e26565b81520193019261111e565b634e487b7160e01b5f525f60045260245ffd5b5f5b82811061118957505050565b5f8282015560010161117d565b91601f82116111a457505050565b8082116111b057505050565b610dd6925f5260205f20916020601f830160051c92106111db575b601f82910160051c03910161117b565b5f91506111cb565b91909182516001600160401b038111610d845761120a816112048454610f10565b84611196565b6020601f821160011461124957819061123a9394955f9261123e575b50508160011b915f199060031b1c19161790565b9055565b015190505f80611226565b601f1982169061125c845f5260205f2090565b915f5b8181106112965750958360019596971061127e575b505050811b019055565b01515f1960f88460031b161c191690555f8080611274565b9192602060018192868b01518155019401920161125f565b60035468010000000000000000811015610d84578060016112d492016003556003610ee3565b61145557815181546001600160a01b0319166001600160a01b03909116178155610dd6916008906101409060208101516001850180546001600160a01b0319166001600160a01b039092169190911790556040810151600285015560608101516003850180546001600160a01b0319166001600160a01b039092169190911790556113bd6004850161138f61137360808501516001600160801b031690565b82546001600160801b0319166001600160801b03909116178255565b60a08301516001600160801b031681546001600160801b031660809190911b6001600160801b031916179055565b60c0810151600585015561143a600685016114026113e560e08501516001600160401b031690565b825467ffffffffffffffff19166001600160401b03909116178255565b61010083015181546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff000000000000000016179055565b61144c610120820151600786016111e3565b015191016111e3565b611168565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b03166001600160401b03811461148b5760010190565b61145a565b6001600160401b03629896809116116114a557565b637d2e4eb560e01b5f5260045ffd5b6001600160401b036001911601906001600160401b03821161148b57565b6001600160401b031661c350016001600160401b03811161148b576001600160401b036001600160801b035f54169116026001600160801b03811690810361148b5790565b90816020910312610193575180151581036101935790565b90816020910312610193575160028110156101935790565b6002111561155157565b634e487b7160e01b5f52602160045260245ffd5b63ffffffff1663ffffffff811461148b5760010190565b906001600160801b03809116911601906001600160801b03821161148b57565b90816020910312610193575190565b6001600160a01b039182168152911660208201526001600160801b0391821660408201529116606082015260808101919091526001600160401b0391821660a0820152911660c082015261012060e08201819052610dfd93919261161191840190610894565b91610100818403910152610894565b95969092949693919360018060a01b038816156105d85760405163037d635760e31b81526001600160a01b0388811660048301527f0000000000000000000000001c000000000000000000000000000000000000031690602081602481855afa9081156104a5575f91611cbd575b5015611cae57610400825111611c9f576001600160401b038416611bcd576040516311dd959760e01b8152602081600481855afa9081156104a5575f91611b9e575b506116da81611547565b1580611b45575b611b365760405163b23bc7d760e01b81526001600160a01b038616600482015290602090829060249082905afa9081156104a5575f91611b17575b5015611afb575b61172c82612244565b600254604081901c63ffffffff16611a12575b50611749836114d2565b97611754898761157c565b60405163868a2e9d60e01b8152979094906020896004815f60056007609a1b015af19889156104a5575f996119e1575b5088156119d2576040516323b872dd60e01b81523360048201523060248201526001600160801b03871660448201526001600160a01b038b16906020816064815f865af19081156104a5575f916119a3575b501561199457803b1561019357604051630852cd8d60e31b81526001600160801b039790971660048801525f908790602490829084905af180156104a5577f34ca953f3eed14157d2f660c7e92a5bd9c05be0d61a188830f3bf1cb7d094f9699611975976119429261197a575b506118bc61186161185c6004546001600160401b031690565b61146e565b95611882876001600160401b03166001600160401b03196004541617600455565b61189d876001600160401b03165f52600560205260405f2090565b80546001600160a01b0319166001600160a01b03909216919091179055565b6118c4610dc6565b6001600160a01b038d1681529033602083015260408201526001600160a01b03891660608201526001600160801b038a1660808201526001600160801b038d1660a082015260c081018390526001600160401b03841660e08201526001600160401b03851661010082015285610120820152866101408201526112ae565b5f5460801c6001600160401b03169a61195d610c918d61146e565b6040519889986001600160401b03339e169c8a6115ab565b0390a3565b806119885f61198e93610da5565b80610189565b5f611843565b6312171d8360e31b5f5260045ffd5b6119c5915060203d6020116119cb575b6119bd8183610da5565b810190611517565b5f6117d6565b503d6119b3565b632968ee7f60e21b5f5260045ffd5b611a0491995060203d602011611a0b575b6119fc8183610da5565b81019061159c565b975f611784565b503d6119f2565b611a3f611a336001600160401b034316926001600160401b039060801c1690565b6001600160401b031690565b8103611ac4575b50600254611a71611a68606083901c63ffffffff169260401c63ffffffff1690565b63ffffffff1690565b63ffffffff82161015611ab557611a8a611aaf91611565565b6002805463ffffffff60601b191660609290921b63ffffffff60601b16919091179055565b5f61173f565b63124ab48560e11b5f5260045ffd5b6002805460809290921b67ffffffffffffffff60801b166bffffffffffffffffffffffff60601b199092169190911790555f611a46565b6376ecd09f60e11b5f526001600160a01b03841660045260245ffd5b611b30915060203d6020116119cb576119bd8183610da5565b5f61171c565b63793cd94b60e11b5f5260045ffd5b5060405163a05e344560e01b81526001600160a01b0386166004820152602081602481855afa9081156104a5575f91611b7f575b506116e1565b611b98915060203d6020116119cb576119bd8183610da5565b5f611b79565b611bc0915060203d602011611bc6575b611bb88183610da5565b81019061152f565b5f6116d0565b503d611bae565b611bd684611490565b6040516311dd959760e01b8152602081600481855afa9081156104a5575f91611c80575b50611c0481611547565b159081611c21575b50156117235763793cd94b60e11b5f5260045ffd5b60405163a05e344560e01b81526001600160a01b03871660048201529150602090829060249082905afa9081156104a5575f91611c61575b50155f611c0c565b611c7a915060203d6020116119cb576119bd8183610da5565b5f611c59565b611c99915060203d602011611bc657611bb88183610da5565b5f611bfa565b634b8a874d60e11b5f5260045ffd5b631fcf8c4760e11b5f5260045ffd5b611cd6915060203d6020116119cb576119bd8183610da5565b5f61168e565b5f1981019190821161148b57565b604080825282516001600160a01b031690820152929190602090611daf9080830151606087015260408101516001600160a01b0316608087015260608101516001600160801b031660a087015260808101516001600160801b031660c087015260a081015160e087015260c08101516001600160401b031661010087015260e08101516001600160401b0316610120870152610120611d9a610100830151610140808a0152610180890190610894565b910151868203603f1901610160880152610894565b930152565b611dbe8154610f10565b9081611dc8575050565b81601f5f9311600114611dd9575055565b81835260208320611df691601f0160051c8419019060010161117b565b808252602082209081548360011b9084198560031b1c191617905555565b90611455576008815f610dd693555f60018201555f60028201555f60038201555f60048201555f60058201555f6006820155611e5260078201611db4565b01611db4565b6003545f60035580611e675750565b8060090290600982040361148b5760035f5260205f205f5b828110611e8b57505050565b80611ec9600860099385015f81555f60018201555f60028201555f60038201555f60048201555f60058201555f6006820155611e5260078201611db4565b01611e7f565b9092915f93331515806121c5575b610407576001600160401b038043169116036121b65760035480830361219f57508051828103612188575081611fc6575b5050611f49611f2d611f286002546001600160401b031690565b6114b4565b6001600160401b03166001600160401b03196002541617600255565b817fec4aff46c65f485f4b15e3c2edadda1d57d002995f5aa262a27c76b9a680ec16611fc1611f806002546001600160401b031690565b611f8984600155565b600280546001600160c01b03164260c01b6001600160c01b0319161790556040516001600160401b0390911681529081906020820190565b0390a2565b5f19935090805b611fe2575050611fdb611e58565b5f80611f0e565b611feb81611cdc565b93611ff585610ec2565b50611fff90610fe6565b6120098685610efc565b51806101408301519061201b916122a0565b8151602080840151604080860151905160609290921b6bffffffffffffffffffffffff191692820192835260348083019190915281526001600160a01b039092169391612069605482610da5565b51902060608201519091906001600160a01b031660808201516001600160801b031660a08301516001600160801b031660c08401519160e08501516120b4906001600160401b031690565b936101008601516120cb906001600160401b031690565b956101200151966120da610dd8565b6001600160a01b03909a168a5260208a01526001600160a01b031660408901526001600160801b031660608801526001600160801b0316608087015260a08601526001600160401b031660c08501526001600160401b031660e0840152610100830152610120820152604051809160208201936121579185611cea565b03601f19810182526121699082610da5565b5190209361217690610ec2565b61217f91611e14565b5f190180611fcd565b630db2128560e11b5f52600452602482905260445ffd5b6324a839db60e11b5f52600483905260245260445ffd5b631391e11b60e21b5f5260045ffd5b50604051630b83774760e31b81526020816004817f0000000000000000000000001c000000000000000000000000000000000000036001600160a01b03165afa9081156104a5575f91612225575b506001600160a01b0316331415611edd565b61223e915060203d60201161049e576104908183610da5565b5f612213565b8051801561229c5760210361228d57805115610ede5760208101516122739061226f9060f81c6122d1565b1590565b61228d5761226f602161228792015161233c565b61228d57565b6361d0136b60e11b5f5260045ffd5b5050565b516122c9575f905b518181036122b4575050565b63521a2d4d60e11b5f5260045260245260445ffd5b6071906122a8565b60ff16600281149081156122e3575090565b600391501490565b3d15612315573d906122fc82610e0b565b9161230a6040519384610da5565b82523d5f602084013e565b606090565b60208151910151906020811061232e575090565b5f199060200360031b1b1690565b801580156123e8575b6123d7575f9081906401000003d0199060079082908181800909086040805160208082018181529282018190526060820152608081019290925263800001e9600160ff1b0360a08301526401000003d01960c0808401919091528252906123ad60e082610da5565b519060055afa6123bb6122eb565b901580156123dc575b6123d7576123d360019161231a565b1490565b505f90565b506020815114156123c4565b506401000003d01981101561234556fea26469706673582212203609c71c14b5820b100aac97a919e1bbf1e30d0f131cfa9352aec8e7332520bf64736f6c63430008230033", "nonce": "0x1" }, "0x1c00000000000000000000000000000000000003": { "balance": "0x0", - "code": "0x6080806040526004361015610012575f80fd5b5f3560e01c9081631beb1ab8146106db575080631fbb25ad146106975780633488ce0d146104575780635c1bba38146103b55780636d46e9871461030d578063a05e3445146102c2578063a21de6d91461027e578063b23bc7d7146101655763e202d9951461007f575f80fd5b3461014e575f36600319011261014e576040516381e3da6b60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260026024830152602090829060449082907f0000000000000000000000001c00000000000000000000000000000000000000165afa801561015a575f90610123575b6040516001600160a01b039091168152602090f35b506020813d602011610152575b8161013d602093836107a3565b8101031261014e576020905161010e565b5f80fd5b3d9150610130565b6040513d5f823e3d90fd5b3461014e57602036600319011261014e576004356001600160a01b0381169081900361014e5760206102039160405182810191825260146040820152604081526101b06060826107a3565b5190206040516381e3da6b60e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000166004820152602481019190915291829081906044820190565b03817f0000000000000000000000001c000000000000000000000000000000000000006001600160a01b03165afa801561015a575f9061024b575b6020906040519015158152f35b506020813d602011610276575b81610265602093836107a3565b8101031261014e576020905161023e565b3d9150610258565b3461014e575f36600319011261014e576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b3461014e57602036600319011261014e576004356001600160a01b0381169081900361014e5760206102039160405182810191825260136040820152604081526101b06060826107a3565b3461014e57602036600319011261014e576004356001600160a01b0381169081900361014e57604051630b83774760e31b8152602081600481305afa90811561015a575f91610370575b506040516001600160a01b039091169091148152602090f35b90506020813d6020116103ad575b8161038b602093836107a3565b8101031261014e5751906001600160a01b038216820361014e57906020610357565b3d915061037e565b3461014e575f36600319011261014e576040516381e3da6b60e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301525f6024830152602090829060449082907f0000000000000000000000001c00000000000000000000000000000000000000165afa801561015a575f90610123576040516001600160a01b039091168152602090f35b3461014e575f36600319011261014e576040516381e3da6b60e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b038181166004840152600760248401527f0000000000000000000000001c00000000000000000000000000000000000000169190602082604481865afa91821561015a575f92610663575b50811561065457604051602081019060078252602081526105096040826107a3565b519020915f198101908111610640578060011b9080820460021490151715610640578201809211610640576001820190818311610640576040516381e3da6b60e01b81526001600160a01b03821660048201526024810193909352602083604481875afa92831561015a575f93610608575b506040516381e3da6b60e01b81526001600160a01b03909116600482015260248101919091529160209083908180604481015b03915afa91821561015a575f926105d3575b5060ff6040928351928352166020820152f35b91506020823d602011610600575b816105ee602093836107a3565b8101031261014e5790519060ff6105c0565b3d91506105e1565b919092506020823d602011610638575b81610625602093836107a3565b8101031261014e579051916105ae61057b565b3d9150610618565b634e487b7160e01b5f52601160045260245ffd5b630c322fb560e31b5f5260045ffd5b9091506020813d60201161068f575b8161067f602093836107a3565b8101031261014e575190836104e7565b3d9150610672565b3461014e575f36600319011261014e576040517f0000000000000000000000001c000000000000000000000000000000000000006001600160a01b03168152602090f35b3461014e57602036600319011261014e576004356001600160a01b038116919082900361014e576107259181602080930191825260086040820152604081526101b06060826107a3565b03817f0000000000000000000000001c000000000000000000000000000000000000006001600160a01b03165afa801561015a575f90610770575b60209060ff604051911615158152f35b506020813d60201161079b575b8161078a602093836107a3565b8101031261014e5760209051610760565b3d915061077d565b90601f8019910116810190811067ffffffffffffffff8211176107c557604052565b634e487b7160e01b5f52604160045260245ffdfea26469706673582212200521e5b92dfcc86f5d75bb5bc5f058cbac06744a18621d194f482cbc540b723864736f6c63430008230033", + "code": "0x6080806040526004361015610012575f80fd5b5f3560e01c90816311dd9597146107d3575080631beb1ab81461070a5780631fbb25ad146106c65780633488ce0d146104865780635c1bba38146103e45780636d46e9871461033c5780637b5c6e2814610311578063a05e3445146101f8578063a21de6d9146101b4578063b23bc7d71461017b5763e202d99514610095575f80fd5b34610164575f366003190112610164576040516381e3da6b60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260026024830152602090829060449082907f0000000000000000000000001c00000000000000000000000000000000000000165afa8015610170575f90610139575b6040516001600160a01b039091168152602090f35b506020813d602011610168575b81610153602093836108d3565b810103126101645760209051610124565b5f80fd5b3d9150610146565b6040513d5f823e3d90fd5b34610164576020366003190112610164576004356001600160a01b0381168103610164576101aa6020916109ce565b6040519015158152f35b34610164575f366003190112610164576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610164576020366003190112610164576004356001600160a01b038116908190036101645760206102969160405182810191825260136040820152604081526102436060826108d3565b5190206040516381e3da6b60e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000166004820152602481019190915291829081906044820190565b03817f0000000000000000000000001c000000000000000000000000000000000000006001600160a01b03165afa8015610170575f906102de575b6020906040519015158152f35b506020813d602011610309575b816102f8602093836108d3565b8101031261016457602090516102d1565b3d91506102eb565b34610164575f36600319011261016457602061032b610909565b60405190610338816108b5565b8152f35b34610164576020366003190112610164576004356001600160a01b0381169081900361016457604051630b83774760e31b8152602081600481305afa908115610170575f9161039f575b506040516001600160a01b039091169091148152602090f35b90506020813d6020116103dc575b816103ba602093836108d3565b810103126101645751906001600160a01b038216820361016457906020610386565b3d91506103ad565b34610164575f366003190112610164576040516381e3da6b60e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301525f6024830152602090829060449082907f0000000000000000000000001c00000000000000000000000000000000000000165afa8015610170575f90610139576040516001600160a01b039091168152602090f35b34610164575f366003190112610164576040516381e3da6b60e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b038181166004840152600760248401527f0000000000000000000000001c00000000000000000000000000000000000000169190602082604481865afa918215610170575f92610692575b50811561068357604051602081019060078252602081526105386040826108d3565b519020915f19810190811161066f578060011b908082046002149015171561066f57820180921161066f57600182019081831161066f576040516381e3da6b60e01b81526001600160a01b03821660048201526024810193909352602083604481875afa928315610170575f93610637575b506040516381e3da6b60e01b81526001600160a01b03909116600482015260248101919091529160209083908180604481015b03915afa918215610170575f92610602575b5060ff6040928351928352166020820152f35b91506020823d60201161062f575b8161061d602093836108d3565b810103126101645790519060ff6105ef565b3d9150610610565b919092506020823d602011610667575b81610654602093836108d3565b81010312610164579051916105dd6105aa565b3d9150610647565b634e487b7160e01b5f52601160045260245ffd5b630c322fb560e31b5f5260045ffd5b9091506020813d6020116106be575b816106ae602093836108d3565b8101031261016457519083610516565b3d91506106a1565b34610164575f366003190112610164576040517f0000000000000000000000001c000000000000000000000000000000000000006001600160a01b03168152602090f35b34610164576020366003190112610164576004356001600160a01b038116908190036101645760206107559160405182810191825260086040820152604081526102436060826108d3565b03817f0000000000000000000000001c000000000000000000000000000000000000006001600160a01b03165afa8015610170575f906107a0575b60209060ff604051911615158152f35b506020813d6020116107cb575b816107ba602093836108d3565b810103126101645760209051610790565b3d91506107ad565b34610164575f366003190112610164576381e3da6b60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260156024830152602090829060449082907f0000000000000000000000001c00000000000000000000000000000000000000165afa8015610170575f90610882575b6002161590506108795760205f60405190610338816108b5565b6020600161032b565b506020813d6020116108ad575b8161089c602093836108d3565b81010312610164576002905161085f565b3d915061088f565b600211156108bf57565b634e487b7160e01b5f52602160045260245ffd5b90601f8019910116810190811067ffffffffffffffff8211176108f557604052565b634e487b7160e01b5f52604160045260245ffd5b6040516381e3da6b60e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116600483015260156024830152602090829060449082907f0000000000000000000000001c00000000000000000000000000000000000000165afa8015610170575f9061099b575b600116159050610996575f90565b600190565b506020813d6020116109c6575b816109b5602093836108d3565b810103126101645760019051610988565b3d91506109a8565b60016109d8610909565b6109e1816108b5565b14610a8957604080516001600160a01b039092166020838101918252601484840152918352610a15926102436060826108d3565b03817f0000000000000000000000001c000000000000000000000000000000000000006001600160a01b03165afa908115610170575f91610a57575b50151590565b90506020813d602011610a81575b81610a72602093836108d3565b8101031261016457515f610a51565b3d9150610a65565b5060019056fea2646970667358221220b6778f385e1dae1295a20516125e04885b5c84652cd44b33400ccff653c6926264736f6c63430008230033", "nonce": "0x1" }, "0x20c0000000000000000000000000000000000000": { @@ -138,5 +138,5 @@ "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x42", "timestamp": "0x0", - "zoneFactoryBytecode": "0x60808060405234610138575f805463ffffffff191660011781556003600755600680546001600160a01b03191633908117909155907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a36101a28181016001600160401b0381118382101761012457829161535e833903905ff08015610119576001600160a01b03165f8181526003602052604090819020805460ff19166001179055600480546001600160a01b0319169092179091555161060d808201906001600160401b0382118383101761012457602091839161550083393081520301905ff0801561011957600580546001600160a01b0319166001600160a01b0392909216919091179055604051615221908161013d8239f35b6040513d5f823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b5f80fdfe6080806040526004361015610012575f80fd5b5f905f3560e01c90816301b290d31461107e575080632b7ac3f3146110565780633cb747bf1461102e57806364eebea41461045557806374134d371461043757806376628f83146103f85780638da5cb5b146103cf57806390b7f6fd14610170578063eee83499146101235763f2fde38b1461008c575f80fd5b34610120576020366003190112610120576100a56110b7565b600654906001600160a01b0382169033829003610111576001600160a01b0316918215610102576001600160a01b03191682176006557f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b6349e27cff60e01b8452600484fd5b6330cd747160e01b8452600484fd5b80fd5b50346101205780600319360112610120575f1963ffffffff825416019063ffffffff821161015c5760208263ffffffff60405191168152f35b634e487b7160e01b81526011600452602490fd5b50346101205760203660031901126101205760043563ffffffff81168091036103cb5760606101206040516101a4816110e1565b84815284602082015284604082015284838201528460808201528460a08201528460c08201528460e0820152846101008201520152815260016020526040812090604051906101f2826110e1565b825463ffffffff811683526001600160a01b03602091821c811691840191825260018501548116604080860191825260028701548316606087019081526003880154841660808801908152600489015490941660a08801908152600589015460c0890190815260068a015460e08a0190815260078b01546001600160401b03166101008b0190815294516008909b018054989b989597919692959394919391929189918b91906102a182611201565b80855291600181169081156103ad5750600114610373575b5050036102c690896110fd565b6101208a81019889526040805160208082529c5163ffffffff168d8201529c516001600160a01b03908116918e01919091529151821660608d01529151811660808c01529151821660a08b015291511660c0890152905160e0880152905161010087015290516001600160401b031690850152516101408085015280516101608501819052849390929183910161018085015e8183016101800152601f1990601f01168101036101800190f35b8c5260208c208c92505b81831061039257505081016020015f806102b9565b80602092948385600194549201015201910190918a9261037d565b9150506020925060ff191682840152151560051b8201015f806102b9565b5080fd5b50346101205780600319360112610120576006546040516001600160a01b039091168152602090f35b50346101205760203660031901126101205760209060ff906040906001600160a01b036104236110b7565b168152600384522054166040519015158152f35b5034610120578060031936011261012057602060405162e4e1c08152f35b5034610d99576020366003190112610d99576001600160401b0360043511610d995761014060043536036003190112610d99576006546001600160a01b0316330361101f576104a86004356004016110cd565b6040516335ec42c960e01b81526001600160a01b03909116600482015260208160248161083f60921b5afa908115610d8e575f91610fe4575b5015610fd557604460043501906104fd8260043560040161111e565b9050158015610fb8575b610572576005546001600160a01b031690600435602401905f5b6105308360043560040161111e565b905081101561058157838161056461055f6105508760043560040161111e565b6001600160a01b039491611153565b6110cd565b161461057257600101610521565b630a1eaf7360e41b5f5260045ffd5b505f9184915b6105968360043560040161111e565b905084101561061457916105bc61055f856105b68460043560040161111e565b90611153565b6001600160a01b0316925f5b6105d78460043560040161111e565b90508110156106055784816105f761055f6105508860043560040161111e565b1614610572576001016105c8565b50600190940193909250610587565b916001600160a01b0361062b6004356064016110cd565b1615610fa9576001600160a01b036106476004356084016110cd565b1615610f9a576001600160a01b0361066360043560a4016110cd565b165f52600360205260ff60405f20541615610f8b5762e4e1c05a10610f7c575f549163ffffffff80841614610f6d57600163ffffffff84160163ffffffff8111610f595763ffffffff1663ffffffff198416175f5560075460018101808211610f5957600755606081610db1575050604051606b60f91b6020820152602560fa1b60218201523060601b6022820152600160ff1b60368201526017815261070b6037826110fd565b60018060a01b0390602081519101201690604051613fb28082018281106001600160401b03821117610d9d57829161123a833903905ff0928315610d8e5761077761076961075d6004356004016110cd565b9360043560040161111e565b91909260043560040161111e565b6005546001600160a01b031692906107936004356064016110cd565b6107a16084600435016110cd565b6107af60a4600435016110cd565b906107bf61010460043501611177565b926107d56101246004350160043560040161118b565b9790956001600160a01b038e163b15610d99578e9a8998604051809e819e635ce41c8160e01b835263ffffffff166004830152600160a01b6001900316906024015260448d0161016090526101648d019061082f926111bd565b906003198c83030160648d0152610845926111bd565b60848a01989098526001600160a01b0390811660a48a015290811660c4808a0191909152911660e488015260043501356101048701526001600160401b03166101248601528484036003190161014486015281845260208401378082016020015f9052601f1990601f0116010360200181600160a01b60019003851691815a5f948591f18015610d8e57610d79575b506001600160a01b03821603610d1d576108f26004356004016110cd565b6109006064600435016110cd565b61090e6084600435016110cd565b906001600160401b0361092560a4600435016110cd565b61093461010460043501611177565b9061094a6101246004350160043560040161118b565b9590946040519761095a896110e1565b63ffffffff8b1689526001600160a01b038a811660208b015290811660408a0152908116606089015290811660808801521660a086015260043560c481013560c087015260e4013560e0860152166101008401526001600160401b038211610d05578590604051926109d66020601f19601f84011601856110fd565b8084523681830111610d1957806020928386013783010152610120820190815263ffffffff848116865260016020818152604080892086518154848901516001600160c01b031990911691909616179490921b640100000000600160c01b03169390931781559184015190820180546001600160a01b03199081166001600160a01b0393841617909155606085015160028401805483169184169190911790556080850151600384018054831691841691909117905560a0850151600484018054909216921691909117905560c0830151600582015560e083015160068201556101009092015160078301805467ffffffffffffffff19166001600160401b0392831617905590518051918211610d0557610af46008840154611201565b601f8111610cb0575b50602090601f8311600114610c3f57828793604098936008938a979592610c34575b50508160011b915f199060031b1c1916179101555b6001600160a01b0383168152600260205220805460ff19166001179055610b5e60048035016110cd565b6001600160401b03610b746064600435016110cd565b610b826084600435016110cd565b610b9060a4600435016110cd565b90610ba061010460043501611177565b88516001600160a01b039687168152938616602085015290851683890152908416606083015260043560c4810135608084015260e4013560a08301529190911660c08201529082169063ffffffff8416907fa0d8b561b80ba334b8b367f1fd53ab8d8719fa1c348a8de4bbfd8bbffa90b3379060e090a3825163ffffffff9290921682526001600160a01b03166020820152f35b015190508980610b1f565b9060088401875280872091875b601f1985168110610c9857508360409894936008936001938b9896601f19811610610c80575b505050811b01910155610b34565b01515f1960f88460031b161c19169055898080610c72565b91926020600181928685015181550194019201610c4c565b82811115610afd5760088401875260208720601f840160051c9060208510610cfd575b81601f9101920160051c0390875b828110610cef575050610afd565b808960019284015501610ce1565b889150610cd3565b634e487b7160e01b86526041600452602486fd5b8280fd5b60405162461bcd60e51b815260206004820152602e60248201527f506f7274616c2061646472657373206d69736d61746368202d206e6f6e63652060448201526d3a3930b1b5b4b7339032b93937b960911b6064820152608490fd5b610d869194505f906110fd565b5f92846108d4565b6040513d5f823e3d90fd5b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b607f8211610e035750604051606b60f91b6020820152602560fa1b60218201523060601b602282015260f89190911b6001600160f81b031916603682015260178152610dfe6037826110fd565b61070b565b60ff8211610e5a575060405160d760f81b6020820152602560fa1b60218201523060601b6022820152608160f81b603682015260f89190911b6001600160f81b031916603782015260188152610dfe6038826110fd565b61ffff8211610eb25750604051601b60fb1b6020820152602560fa1b60218201523060601b6022820152604160f91b603682015260f09190911b6001600160f01b031916603782015260198152610dfe6039826110fd565b62ffffff8211610f0b575060405160d960f81b6020820152602560fa1b60218201523060601b6022820152608360f81b603682015260e89190911b6001600160e81b0319166037820152601a8152610dfe603a826110fd565b604051606d60f91b6020820152602560fa1b60218201523090911b6022820152602160fa1b603682015260e09190911b6001600160e01b0319166037820152601b8152610dfe603b826110fd565b634e487b7160e01b5f52601160045260245ffd5b63e558bfa360e01b5f5260045ffd5b6307099c5360e21b5f5260045ffd5b63baa3de5f60e01b5f5260045ffd5b633c9f858360e21b5f5260045ffd5b630b5eba9f60e41b5f5260045ffd5b50610fcd60246004350160043560040161111e565b905015610507565b63c1ab6dc160e01b5f5260045ffd5b90506020813d602011611017575b81610fff602093836110fd565b81010312610d9957518015158103610d99575f6104e1565b3d9150610ff2565b6330cd747160e01b5f5260045ffd5b34610d99575f366003190112610d99576005546040516001600160a01b039091168152602090f35b34610d99575f366003190112610d99576004546040516001600160a01b039091168152602090f35b34610d99576020366003190112610d99576020906001600160a01b036110a26110b7565b165f526002825260ff60405f20541615158152f35b600435906001600160a01b0382168203610d9957565b356001600160a01b0381168103610d995790565b61014081019081106001600160401b03821117610d9d57604052565b90601f801991011681019081106001600160401b03821117610d9d57604052565b903590601e1981360301821215610d9957018035906001600160401b038211610d9957602001918160051b36038313610d9957565b91908110156111635760051b0190565b634e487b7160e01b5f52603260045260245ffd5b356001600160401b0381168103610d995790565b903590601e1981360301821215610d9957018035906001600160401b038211610d9957602001918136038313610d9957565b916020908281520191905f905b8082106111d75750505090565b90919283359060018060a01b038216809203610d99576020816001938293520194019201906111ca565b90600182811c9216801561122f575b602083101461121b57565b634e487b7160e01b5f52602260045260245ffd5b91607f169161121056fe60808060405234601557613f98908161001a8239f35b5f80fdfe6080806040526004361015610012575f80fd5b5f905f3560e01c9081625d97ef146129f357508062ebfdb414612969578063022318811461292a57806309a0a2341461264a5780630d0c743e146122135780630e18b681146121845780630e86bbdc146120ef57806326782247146120c657806327c71b50146120405780632b7ac3f3146120175780632c37826e14611fed5780632dfdf0b514611fc65780633488ce0d14611f5b57806337f981d814611f06578063385cadd014611e5957806339dc015d14611cf05780633cb747bf14611cc75780634256ce3814611ca957806345c5d2fb14611c8b578063580bbcfc14611c615780635c1bba3814611c3a5780635ce41c81146118bb578063652ef10b146116e0578063748538d9146116a157806375829def1461162b57806378097c50146115ec5780638081a8b2146115ce57806383a146b3146115b3578063857e85f81461155757806386f47e55146115395780638976248d1461150d57806390f595981461142757806394ce88b014611409578063959f47c3146113355780639a97784b146112925780639ecd4cc01461125a578063b01f22e414610c22578063b02690f214610bea578063be2a63ee14610bc0578063bf8e244014610b3c578063bffa55d514610a74578063c13a2d4b146109fb578063c690908a14610907578063cfaabb4d146108dd578063d179978a146108b9578063e09eae1014610827578063e202d995146107fe578063e4643e8514610771578063e471784914610753578063e84abe6914610729578063ecf79a4e14610702578063ef10b187146103ba578063f22a195e1461039c578063f490ca9614610379578063f706cfbf1461035b578063f851a44014610332578063fb2f1206146103115763fe136c4e1461029a575f80fd5b3461030e57602036600319011261030e57604080916102b7612be6565b81602084516102c581612d9e565b82815201526001600160a01b031681526008602052208151906102e782612d9e565b5460ff6020818316151593848152019160081c161515815282519182525115156020820152f35b80fd5b503461030e578060031936011261030e57602060065460c01c604051908152f35b503461030e578060031936011261030e576001546040516001600160a01b039091168152602090f35b503461030e578060031936011261030e576020604051620186a08152f35b503461030e578060031936011261030e576020604051670de0b6b3a76400008152f35b503461030e578060031936011261030e576020600454604051908152f35b503461030e5760a036600319011261030e5760043560243560ff8116908181036106fe576044359060ff82168092036106fa5784546001600160a01b031633036106eb5761040790613cce565b156106dc5761041583613d0a565b156106dc5783608060209260405184810190308252876040820152866060820152606081526104448482612db9565b51902090604051918252848201526064356040820152608435606082015282805260015afa156106d157825183806401000003d019600781878181800909086040516020810191602083526020604083015260206060830152608082015263400000f4600160fe1b0360a08201526401000003d01960c082015260c081526104cd60e082612db9565b519060055afa6104db6139bd565b90806106c6575b15610691576104f090613ce8565b60028314600182161503610667575b60405190602082019085825260408301526040825261051f606083612db9565b905190206001600160a01b0391821680159290911690821561065c575b505061064d576001600160401b0343169060405161055981612d83565b83815260208101908282526040810190848252600754600160401b8110156106395780600161058b9201600755612ea7565b9190916106255760ff916001915181550192511668ffffffffffffffff008354925160081b169168ffffffffffffffffff191617179055600754905f19820191821161061157917f82b5f4090f18a082bc8156b956154bfe0319307f5e5a7e903ef33f14ad2cb17e9391608093604051938452602084015260408301526060820152a180f35b634e487b7160e01b85526011600452602485fd5b634e487b7160e01b88526004889052602488fd5b634e487b7160e01b88526041600452602488fd5b6392536faf60e01b8352600483fd5b141590505f8061053c565b6401000003d019036401000003d0198111156104ff57634e487b7160e01b85526011600452602485fd5b60405162461bcd60e51b815260206004820152600d60248201526c1b5bd9195e1c0819985a5b1959609a1b6044820152606490fd5b5060208151146104e2565b6040513d84823e3d90fd5b632f0be49b60e11b8452600484fd5b6314f8a09f60e21b8552600485fd5b8480fd5b8380fd5b503461030e578060031936011261030e5760206001600160801b0360035416604051908152f35b503461030e578060031936011261030e5760206001600160401b0360065460401c16604051908152f35b503461030e578060031936011261030e576020600954604051908152f35b503461030e578060031936011261030e576002546001600160a01b03811690811580156107f4575b6107e55782546001600160a01b0319808216841785559091166002556001600160a01b03167f9945af900637995146537e5ea7a7be455dfd08bbb46fdd79e9c3a2bad0c4eef88380a380f35b63d71402d960e01b8352600483fd5b5081331415610799565b503461030e578060031936011261030e576002546040516001600160a01b039091168152602090f35b503461030e57602036600319011261030e57610841612c66565b81546001600160a01b031633036108aa57600680546001600160c01b031660c083901b6001600160c01b0319161790556040516001600160401b0390911681527f66bcd750662bb66118e25a8e421ae73974634d9af2d44fb9e600d250917fe69090602090a180f35b6314f8a09f60e21b8252600482fd5b503461030e578060031936011261030e57602063ffffffff60115416604051908152f35b503461030e57602036600319011261030e5760406020916004358152600d83522054604051908152f35b503461030e57602036600319011261030e57610921612be6565b6001546001600160a01b031633036109ec576001600160a01b0381168083526008602052604083205460ff166109dd576040516335ec42c960e01b8152600481019190915260208160248161083f60921b5afa9081156109d25783916109a3575b50156109945761099190613b10565b80f35b631fcf8c4760e11b8252600482fd5b6109c5915060203d6020116109cb575b6109bd8183612db9565b810190612dfe565b5f610982565b503d6109b3565b6040513d85823e3d90fd5b632909298f60e11b8352600483fd5b637bfa4b9f60e01b8252600482fd5b503461030e57604036600319011261030e576004356001600160401b038111610a70576101406003198236030112610a705781546001600160a01b031633036108aa57601054610a6157610a5a90600160105560243590600401613084565b8060105580f35b630b5c738160e01b8252600482fd5b5080fd5b503461030e57602036600319011261030e57610a8e612be6565b610a973361371a565b6001600160a01b038116808352600a60208181526040808620335f818152918452828220548689529484528288208183529093522080546001600160801b03191690556001600160801b0390911692610af1918491613dc3565b15610b2d57602092506040518281527fffd3bbab073ab4b2d0792c270104924c14c285a153b9acddabae166395d2eb5c843392a3604051908152f35b63be51ced360e01b8352600483fd5b503461030e57602036600319011261030e57610b56612be6565b81546001600160a01b03169033829003610bb157600280546001600160a01b0319166001600160a01b03929092169182179055907f12cf9e4d760566fb6a3e0193406f2295fc4b53f2b60817145a61f29e470cf7d68380a380f35b6314f8a09f60e21b8352600483fd5b503461030e578060031936011261030e5760206001600160401b0360065460801c16604051908152f35b503461030e57602036600319011261030e576020610c09600435613058565b905460405160039290921b1c6001600160a01b03168152f35b503461030e5760a036600319011261030e57610c3c612be6565b906024356001600160801b0381168103610a70576001600160401b0360643511610a705760a060643536036003190112610a7057610c78612c12565b906001600160a01b0382161561124b57338352601360205260ff6040842054161561123d575b610ca78261371a565b610cb08461374e565b604051639c4bad2960e01b81526020816004816001600160a01b0389165afa9081156112055783610d1f926020928791611210575b506040516337de09eb60e11b81526001600160401b0390911660048201526001600160a01b03909116602482015291829081906044820190565b038161100f60921b5afa9081156112055784916111e6575b50156111d757610d53610d4e602460643501612ffd565b613cce565b156111c857610d6760643560040135613d0a565b156111c8576040610d8260446064350160643560040161300b565b90500361119d57610d94604435612f69565b501561110b57610da490846137d9565b9060405192610db284612d54565b6001600160a01b0386811685523360208601526001600160801b03841660408087019190915290821660608601526044356080860152519360a085016001600160401b038111868210176110f7576040526064356004013585526024606435013560ff811681036110f35760208601526001600160401b0360446064350135116110ef573660643560448101350160230112156110ef57610e5e6064356044810135016004013561303d565b610e6b6040519182612db9565b60643560448101350160048101358083523660249190920101116110f35760643560448101350160048101359060240160208301376064803560448101358101600401358301602001899052604088019290925201356001600160a01b0319811690036110ef57606480350135606086015260846064350135946001600160801b03198616958681036110eb57926110a888608061101f6001600160401b03968660209e99847f4debdbd2891da07eb466d2025028a25aab636034cf3c941afd1025a0c590a07a9c99015260a082019081526005549060405194602093869485019750600188526060604086015260018060a01b038151168286015260018060a01b0360208201511660a08601526001600160801b0360408201511660c086015260018060a01b0360608201511660e08601520151610100840152519060c061012084015281516101408401528f60ff9083015116610160840152610fe1604083015160a06101808601526101e0850190612dda565b6060808401516001600160a01b0319166101a08601526080909301516001600160801b0319166101c08501529183015203601f198101835282612db9565b51902098899760ff6110308a613987565b956001600160801b03611047602460643501612ffd565b918161105d60446064350160643560040161300b565b969097506040519c60018060a01b03168d521660208c01521660408a015260443560608a01526064356004013560808a01521660a088015261016060c0880152610160870191612e16565b6064803501356001600160a01b03191660e08601526101008501979097526001600160a01b031661012084015216610140820152339381900390a3604051908152f35b8780fd5b8580fd5b8680fd5b634e487b7160e01b87526041600452602487fd5b82600754604435101561118857611123604435612ea7565b5090600160443501918260443511611174576001600160401b036001818161114c606497612ea7565b5094015460081c1692015460081c169063034dff0f60e01b8352604435600452602452604452fd5b634e487b7160e01b82526011600452602482fd5b637f455d3760e11b8152604435600452602490fd5b6044836111b3826064350160643560040161300b565b632331a96360e11b8352600452506040602452fd5b632f0be49b60e11b8352600483fd5b6354cfe65960e01b8352600483fd5b6111ff915060203d6020116109cb576109bd8183612db9565b5f610d37565b6040513d86823e3d90fd5b6112309150833d8511611236575b6112288183612db9565b810190612fde565b5f610ce5565b503d61121e565b6112463361371a565b610c9e565b638dc30b3960e01b8352600483fd5b503461030e57602036600319011261030e5760406001600160401b03611281600435612f69565b835191151582529091166020820152f35b503461030e57602036600319011261030e576004359080604080516112b681612d83565b82815282602082015201526007548210156113235760606112d683612ea7565b506001600160401b036040516112eb81612d83565b60ff600184549485845201549183604060208301928486168452019360081c1683526040519485525116602084015251166040820152f35b602491637f455d3760e11b8252600452fd5b503461030e578060031936011261030e576040519080600e549061135882612d1c565b80855291600181169081156113e25750600114611398575b6113948461138081860382612db9565b604051918291602083526020830190612dda565b0390f35b600e81525f516020613f235f395f51905f52939250905b8082106113c85750909150810160200161138082611370565b9192600181602092548385880101520191019092916113af565b60ff191660208087019190915292151560051b850190920192506113809150839050611370565b503461030e578060031936011261030e576020600b54604051908152f35b503461030e5761143636612cbd565b6001549091906001600160a01b031633036114fe5781806114bd575b6114ae576001600160a01b03168083526014602090815260408420805460ff191660ff8515151617905590917f85a68034076f99047d5db3dd6a0adaa05addad1c2d330a044b888fbf7aa47e9391905b6040519015158152a280f35b63719e350160e01b8352600483fd5b506001600160a01b0381168084526013602052604084205460ff169081156114e6575b50611452565b60115460201c6001600160a01b03161490505f6114e0565b637bfa4b9f60e01b8352600483fd5b503461030e578060031936011261030e576020611528612f44565b6001600160801b0360405191168152f35b503461030e578060031936011261030e576020604051629896808152f35b503461030e57604036600319011261030e576040611573612be6565b9161157c612bfc565b9260018060a01b03168152600a602052209060018060a01b03165f5260205260206001600160801b0360405f205416604051908152f35b503461030e578060031936011261030e576020611528612efb565b503461030e578060031936011261030e576020600c54604051908152f35b503461030e57602036600319011261030e5760209060ff906040906001600160a01b03611617612be6565b168152601384522054166040519015158152f35b503461030e57602036600319011261030e57611645612be6565b6001546001600160a01b031690338290036114fe57600f80546001600160a01b0319166001600160a01b03929092169182179055907fe5cd1c804f1c9cc6d7009e4c0fb532f0e2d8863524c3323a6b3790c3f80bf25c8380a380f35b503461030e57602036600319011261030e5760209060ff906040906001600160a01b036116cc612be6565b168152600884522054166040519015158152f35b503461030e57602036600319011261030e576004356001600160401b038111610a7057611711903690600401612c90565b8254909291906001600160a01b031633036108aa576001600160401b0383116118a75761173f600e54612d1c565b601f811161184b575b508192601f81116001146117bd578083947ff4e00967b25e707df96d88676243b33be84847ef27615af8ef91290b52294fc694916117b2575b508160011b905f198360031b1c191617600e555b6117ac604051928392602084526020840191612e16565b0390a180f35b90508201355f611781565b600e8352601f198116935f516020613f235f395f51905f5290845b8681106118335750827ff4e00967b25e707df96d88676243b33be84847ef27615af8ef91290b52294fc695961061181a575b5050600181811b01600e55611795565b8301355f19600384901b60f8161c191690555f8061180a565b909160206001819285880135815501930191016117d8565b8381111561174857600e8352601f840160051c906020851061189f575b601f82910160051c0390835b828110611882575050611748565b80855f516020613f235f395f51905f526001938501015501611874565b839150611868565b634e487b7160e01b82526041600452602482fd5b503461030e5761016036600319011261030e5760043563ffffffff8116809103610a70576118e7612bfc565b906044356001600160401b0381116106fe57611907903690600401612cec565b916064356001600160401b0381116110ef57611927903690600401612cec565b91611930612c12565b60a4356001600160a01b0381169290839003611c365760c4356001600160a01b03811690819003611c325760e4356001600160a01b0381169190829003611c2e5761012435906001600160401b0382168203611c2a57610144356001600160401b038111611c26576119a6903690600401612c90565b969095735af1ffffffffffffffffffffffffffffffffffff193301611c17576012549560ff8760e01c16611c0957601180546001600160c01b0319169190911760209290921b640100000000600160c01b0316919091179055600180546001600160a01b03199081169290921790558c5416178b55610104356004556001600160e81b03199092161760a09190911b67ffffffffffffffff60a01b1617600160e01b176012556001600160401b038211610639578190611a67600e54612d1c565b601f8111611ba4575b508890601f8311600114611b33578992611b28575b50508160011b915f199060031b1c191617600e555b855b828110611af357505050835b828110611ab9578461099185613b10565b6001906001600160a01b03611ad7611ad2838787612ed7565b612ee7565b168652601460205260408620805460ff19168317905501611aa8565b6001906001600160a01b03611b0c611ad2838787612ed7565b168852601360205260408820805460ff19168317905501611a9c565b013590505f80611a85565b600e8a525f516020613f235f395f51905f52925090601f1984168a5b818110611b8c5750908460019594939210611b73575b505050811b01600e55611a9a565b01355f19600384901b60f8161c191690555f8080611b65565b91936020600181928787013581550195019201611b4f565b82811115611a7057909150600e8952601f830160051c9060208410611c01575b9080601f8594930160051c03908a5b828110611be1575050611a70565b8082015f516020613f235f395f51905f52018c9055859450600101611bd3565b899150611bc4565b62dc149f60e41b8f5260048ffd5b631966391b60e11b8e5260048efd5b8c80fd5b8b80fd5b8a80fd5b8980fd5b8880fd5b503461030e578060031936011261030e57546040516001600160a01b039091168152602090f35b503461030e578060031936011261030e5760206001600160401b0360125460a01c16604051908152f35b503461030e578060031936011261030e576020600554604051908152f35b503461030e578060031936011261030e576020600754604051908152f35b503461030e578060031936011261030e57601154604051602091821c6001600160a01b03168152f35b503461030e57602036600319011261030e57611d0a612c66565b906007549081158015611dff575b611de35780915f19810190811161117457836001600160401b0384929116925b818310611d6957606083611d4b81612ea7565b509060ff600183549301541660405192835260208301526040820152f35b9091828101808211611dcf5760018101809111611dcf5760011c90846001600160401b036001611d9885612ea7565b50015460081c1611611dad5750915b90611d38565b92505f19810190811115611da757634e487b7160e01b82526011600452602482fd5b634e487b7160e01b83526011600452602483fd5b63302c66ab60e21b81526001600160401b038316600452602490fd5b5060075415611e4557600781527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c6895460081c6001600160401b0390811690841610611d18565b634e487b7160e01b81526032600452602490fd5b503461030e57611e6836612cbd565b6001549091906001600160a01b031633036114fe578180611ee7575b611ed8576001600160a01b03168083526013602090815260408420805460ff191660ff8515151617905590917fb359c7f3988b64787c42818b9235b2a6294c092435969764520af8ca7facae1491906114a2565b63793cd94b60e11b8352600483fd5b506001600160a01b03811683526014602052604083205460ff16611e84565b503461030e57602036600319011261030e576020906040906001600160a01b03611f2e612be6565b16815260088352205460ff81169081611f4d575b506040519015158152f35b60ff915060081c165f611f42565b503461030e578060031936011261030e57600754908115611fb7575f198201918211611fa3576040611f8c83612ea7565b5060ff600182549201541682519182526020820152f35b634e487b7160e01b81526011600452602490fd5b630c322fb560e31b8152600490fd5b503461030e578060031936011261030e5760206001600160401b0360065416604051908152f35b503461030e578060031936011261030e5760206001600160401b0360035460801c16604051908152f35b503461030e578060031936011261030e576012546040516001600160a01b039091168152602090f35b503461030e57602036600319011261030e5761205a612be6565b6001546001600160a01b031633036109ec576001600160a01b03168082526008602052604082205460ff161561099457808252600860205260408220805461ff00191690557feb225a736fbfee3f85ccb72bdf84ff0396ab358b7970e2cc351ab3e3fd92358d8280a280f35b503461030e578060031936011261030e57600f546040516001600160a01b039091168152602090f35b503461030e57602036600319011261030e576004356001600160801b038116809103610a705781546001600160a01b031633036108aa57670de0b6b3a76400008111612175576020817fc62141e607d6fcbf7d11fd2b6d8e18e5ebef6d3fff8136ca98822801abbaea38926001600160801b03196003541617600355604051908152a180f35b630d62f21160e11b8252600482fd5b503461030e578060031936011261030e57600f546001600160a01b0381169081158015612209575b6121fa57600180546001600160a01b031980821685179092559116600f556001600160a01b03167ff8ccb027dfcd135e000e9d45e6cc2d662578a8825d4c45b5e32e0adf67e79ec68380a380f35b63058d9a1b60e01b8352600483fd5b50813314156121ac565b503461030e5761016036600319011261030e5761222e612c66565b6024356001600160401b038116918282036106fe5760403660431901126106fe5760803660831901126106fe576101043591610124356001600160401b0381116110ef57612280903690600401612c90565b9094610144356001600160401b0381116110eb576122a2903690600401612c90565b88546001600160a01b031691903383900361263b5760443593600454850361262c57601254906001600160401b038916976001600160401b038360a01c1689106125ed578161260b57505087944388116125fc576122ff886139ec565b945b85156125ed5761230f612e36565b98600654996001600160401b03808c60401c169116148015906125ca575b80156125ae575b61259f57908d96959493929163ffffffff6011541696600354996001600160401b038b60801c16995060018a01926001600160401b038411612588576001600160401b0393929184916040519b632d8a30cd60e21b8d5260048d015260248c01521660448a0152606489015216608487015260a486015260c4850152606435968760e486015260843561010486015260a4359b8c61012487015260c4356001600160401b0381168091036125845761014487015260e435916001600160401b0383168093036125845760209587956124308f946124439489979688976101648901526101848801526101e06101a48801526101e4870191612e16565b848103600319016101c486015291612e16565b03916001600160a01b03165afa90811561257957899161255a575b501561254b5767ffffffffffffffff60801b9061247a90612e76565b60801b16906001600160401b0360801b1916176003556004556fffffffffffffffff00000000000000006124ac612e4c565b60401b16916001600160401b0360801b9060801b169077ffffffffffffffffffffffffffffffff0000000000000000191617176006557f5a66941dc92cb865480c966eff640c02b1d00d544b74332fd67c6f1cbfccdf39608061250e83613a5a565b936001600160401b03600354831c1693600454906001600160401b0360065460401c1691604051938452602084015260408301526060820152a380f35b6309bde33960e01b8852600488fd5b612573915060203d6020116109cb576109bd8183612db9565b5f61245e565b6040513d8b823e3d90fd5b8e80fd5b50505060248f634e487b7160e01b81526011600452fd5b633d9dffbd60e01b8e5260048efd5b506125b7612e4c565b6001600160401b03808c16911611612334565b506125d3612e4c565b6001600160401b03806125e4612e36565b1691161061232d565b632705871360e01b8d5260048dfd5b632705871360e01b8c5260048cfd5b8882979211156125ed574387116125ed5761262690966139ec565b94612301565b6309bde33960e01b8b5260048bfd5b6314f8a09f60e21b8a5260048afd5b503461030e5760a036600319011261030e57612664612be6565b61266c612bfc565b90612675612c3c565b60643590612681612c12565b6001600160a01b03811695909390861561291b5761269e3361371a565b6126a78561371a565b6126b08261374e565b604051639c4bad2960e01b81526001600160a01b03831695906020816004818a5afa9081156109d25783916128fc575b506040516337de09eb60e11b81526001600160401b03821660048201526001600160a01b038916602482015260208160448161100f60921b5afa9081156112055784916128dd575b50156111d75760405163b389e30560e01b81526001600160401b03821660048201526001600160a01b038916602482015260208160448161100f60921b5afa9081156112055784916128be575b50156111d7576040516337de09eb60e11b81526001600160401b039190911660048201526001600160a01b0391909116602482015260208160448161100f60921b5afa9081156106d157829161289f575b50156128905750946001600160401b03926127eb6001600160801b03936020986137d9565b939061283182604051996127fe8b612d54565b898b52338c8c015260018060a01b0316968760408c015216988960608201528560808201528460a0820152600554613902565b9761283b89613987565b956040519889528a8901526040880152166060860152608085015260a08401521660c0820152817ffee50869847a4d073bfd83ec655472febff62beb46febf4ec48752f0686affd160e03393a3604051908152f35b6354cfe65960e01b8152600490fd5b6128b8915060203d6020116109cb576109bd8183612db9565b5f6127c6565b6128d7915060203d6020116109cb576109bd8183612db9565b5f612775565b6128f6915060203d6020116109cb576109bd8183612db9565b5f612728565b612915915060203d602011611236576112288183612db9565b5f6126e0565b638dc30b3960e01b8152600490fd5b503461030e57602036600319011261030e5760209060ff906040906001600160a01b03612955612be6565b168152601484522054166040519015158152f35b503461030e57602036600319011261030e57612983612be6565b6001546001600160a01b031633036109ec576001600160a01b03168082526008602052604082205460ff161561099457808252600860205260408220805461ff0019166101001790557f22ab73af03f04a21e91c7923327f99279b7f5d07d9551762c39bccdf051f1fe98280a280f35b905034612b965760c0366003190112612b9657612a0e612be6565b90612a17612bfc565b612a1f612c3c565b91608435926001600160401b038416809403612b965760a4356001600160401b038111612b9657612a54903690600401612c90565b9093303303612bd7576001600160a01b03165f8181526013602052604090205490959060ff1615612bc85760055460115463a9059cbb60e01b86526001600160a01b03602091821c811660048801526001600160801b038616602488015291989091169490816044815f895af1908115612b8b575f91612ba9575b5015612b9a57601154602081901c6001600160a01b031693843b15612b96575f9663ffffffff9488946001600160801b03612b4c946040519c8d9b8c9a8b996311da526160e01b8b521660048a015260248901526064356044890152606488015216608486015260a485015260e060c485015260e4840191612e16565b03925af18015612b8b57612b76575b5060055414612b675780f35b631fd6e06160e01b8152600490fd5b612b839192505f90612db9565b5f905f612b5b565b6040513d5f823e3d90fd5b5f80fd5b6312171d8360e31b5f5260045ffd5b612bc2915060203d6020116109cb576109bd8183612db9565b5f612acf565b63793cd94b60e11b5f5260045ffd5b6314e1dbf760e11b5f5260045ffd5b600435906001600160a01b0382168203612b9657565b602435906001600160a01b0382168203612b9657565b608435906001600160a01b0382168203612b9657565b35906001600160a01b0382168203612b9657565b604435906001600160801b0382168203612b9657565b35906001600160801b0382168203612b9657565b600435906001600160401b0382168203612b9657565b35906001600160401b0382168203612b9657565b9181601f84011215612b96578235916001600160401b038311612b965760208381860195010111612b9657565b6040906003190112612b96576004356001600160a01b0381168103612b9657906024358015158103612b965790565b9181601f84011215612b96578235916001600160401b038311612b96576020808501948460051b010111612b9657565b90600182811c92168015612d4a575b6020831014612d3657565b634e487b7160e01b5f52602260045260245ffd5b91607f1691612d2b565b60c081019081106001600160401b03821117612d6f57604052565b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b03821117612d6f57604052565b604081019081106001600160401b03821117612d6f57604052565b90601f801991011681019081106001600160401b03821117612d6f57604052565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90816020910312612b9657518015158103612b965790565b908060209392818452848401375f828201840152601f01601f1916010190565b60c4356001600160401b0381168103612b965790565b60e4356001600160401b0381168103612b965790565b356001600160401b0381168103612b965790565b6001600160401b03166001600160401b038114612e935760010190565b634e487b7160e01b5f52601160045260245ffd5b600754811015612ec35760075f5260205f209060011b01905f90565b634e487b7160e01b5f52603260045260245ffd5b9190811015612ec35760051b0190565b356001600160a01b0381168103612b965790565b60065460c01c48810290808204481490151715612e935764e8d4a51000810190818111612e935764e8d4a50fff01908111612e935764e8d4a510006001600160801b0391041690565b6001600160801b0360035416620186a0026001600160801b038116908103612e935790565b60075480821015612fd6575f198101908111612e93578114612fce5760018101809111612e93576001600160401b036001612fa76201518093612ea7565b50015460081c1601906001600160401b038211612e93576001600160401b03821643109190565b506001905f90565b50505f905f90565b90816020910312612b9657516001600160401b0381168103612b965790565b3560ff81168103612b965790565b903590601e1981360301821215612b9657018035906001600160401b038211612b9657602001918136038313612b9657565b6001600160401b038111612d6f57601f01601f191660200190565b600954811015612ec35760095f5260205f2001905f90565b356001600160801b0381168103612b965790565b600b545f91600c54821461370b576064820693845f52600d60205260405f2054905f1982148015613701575b6136ec5780159384156136fa575f19935b60408051602081019182529590926001600160a01b036130e084612c28565b1660608801526020830135608088018190529960408401956001600160a01b0361310988612c28565b1660a08a0152606085019961311d8b612c52565b6001600160801b031660c08b015260808601986131398a612c52565b6001600160801b031660e08c015260a08701356101008c015260c087019761316089612c7c565b6001600160401b03166101208d015260e088019b61317d8d612c7c565b6001600160401b0316610140820152806101008a019861319d8a8c613e36565b610160840161014090526101a08401906131b692612e16565b6131c46101208d018d613e36565b848303605f19016101808601526131db9291612e16565b90604083015203601f19810182526131f39082612db9565b519020036136ec57156136d957505f52600d6020525f1960405f205560018101809111612e9357600b555b61322782612ee7565b946001600160401b0361323988612e62565b16156134fc576001600160801b0361325082613070565b166134d7575b50629896806001600160401b0361326c85612e62565b161161346f576001600160401b0361328384612e62565b1661335457505050916001600160801b035f516020613f435f395f51905f5294926132e76132e160609660018060a01b036132bd82612ee7565b168a52601460205260ff60408b20541680613332575b15958661330f575b50612ee7565b94613070565b90604051975060018060a01b0316875216602086015215604085015260018060a01b031692a3565b61332c9061332561331f8a613070565b91612e62565b9086613e67565b5f6132db565b5061334f61333f82612ee7565b61334889613070565b9086613dc3565b6132d3565b61337961336085612ee7565b9161337361336d8a613070565b95612e62565b9361300b565b9092303b15612b9657604051625d97ef60e01b81526001600160a01b03808916600483015290931660248401526001600160801b03949094166044830152606482018a90526001600160401b0393909316608482015260c060a4820152915f91839182916133eb9160c4840191612e16565b038183305af19081613430575b505f516020613f435f395f51905f5294926132e76132e1606096946001600160801b0394155f1461342957896132d3565b60016132d3565b6001600160801b03919650926132e76132e16060969461345f5f5f516020613f435f395f51905f529a98612db9565b5f999450949650505092946133f8565b505050839194506134b26134ac6001600160801b0392611ad26060966134a561331f5f516020613f435f395f51905f529a613070565b908a613e67565b92613070565b604080516001600160a01b0397881681529290911660208301525f90820152931692a3565b6134f5906134ee60018060a01b035f541691613070565b9087613dc3565b505f613256565b505093505093505061350f919350612ee7565b613517612efb565b926001600160801b0361352982613070565b166001600160801b038516116136be575b8361354761354c92613070565b6137b9565b906001600160801b0384166136a1575b6001600160a01b0361356d84612ee7565b165f52601460205260ff60405f20541680613688575b156135f9577f0f7ef08806234f85aaee43d3ba4589c3bc6d5ac3fc8edd56fc3d91cc7553bdcb926135f4906001600160a01b03906135c090612ee7565b1694604051938493849160409194936001600160801b038092606086019760018060a01b0316865216602085015216910152565b0390a2565b6001600160a01b0381165f908152600a602052604090207f5fea28d0adb7d877ae3259768f41ad6741aa1784c4475746dd931364f62e68a1936135f49161363f82612ee7565b60018060a01b03165f5260205260405f206001600160801b036136658682845416613799565b82546001600160801b03191691161790556001600160a01b03906135c090612ee7565b5061369c8261369685612ee7565b83613dc3565b613583565b5f546136b89085906001600160a01b031683613dc3565b5061355c565b6135479350806136d061354c92613070565b9450905061353a565b91505f52600d60205260405f205561321e565b626aee3160e51b5f5260045ffd5b81936130c1565b505f1981146130b0565b63d7fa91d160e01b5f5260045ffd5b6001600160a01b03165f8181526014602052604090205460ff161561373c5750565b6376ecd09f60e11b5f5260045260245ffd5b6001600160a01b03165f9081526008602052604090205460ff81161561378a5760081c60ff161561377b57565b63dec712ef60e01b5f5260045ffd5b631fcf8c4760e11b5f5260045ffd5b906001600160801b03809116911601906001600160801b038211612e9357565b906001600160801b03809116911603906001600160801b038211612e9357565b90916137e3612f44565b926001600160801b036137fd6137f7612efb565b86613799565b16906001600160801b0381169182106138f3578461381a916137b9565b6040516323b872dd60e01b81523360048201523060248201526044810192909252926001600160a01b03166020826064815f855af1918215612b8b5785926138d6575b506001600160801b038216613870575050565b5f805460405163a9059cbb60e01b81526001600160a01b0390911660048201526001600160801b0393909316602484015260209183916044918391905af18015612b8b576138bb5750565b6138d39060203d6020116109cb576109bd8183612db9565b50565b6138ee9060203d6020116109cb576109bd8183612db9565b61385d565b636ba4a1c760e01b5f5260045ffd5b6040519060a060208301935f8552600180831b038151166040850152600180831b036020820151166060850152600180831b0360408201511660808501526001600160801b0360608201511682850152600180831b0360808201511660c0850152015160e0830152610100820152610100815261398161012082612db9565b51902090565b60055560065461399f6001600160401b038216612e76565b67ffffffffffffffff199091166001600160401b0382161760065590565b3d156139e7573d906139ce8261303d565b916139dc6040519384612db9565b82523d5f602084013e565b606090565b905f8080936040516020810191825260208152613a0a604082612db9565b519071f90827f1c53a10cb7a02335b1753200029355afa613a296139bd565b9080613a4e575b613a375750565b909150602081805181010312612b96576020015190565b50602081511015613a30565b8015613aa857600c5490600b548203828111612e935760641115613a9957606482065f52600d60205260405f205560018101808211612e9357600c5590565b632764e20b60e01b5f5260045ffd5b505f1990565b602081830312612b96578051906001600160401b038211612b96570181601f82011215612b9657805190613ae18261303d565b92613aef6040519485612db9565b82845260208383010111612b9657815f9260208093018386015e8301015290565b604051613b1c81612d9e565b6001815260208101916001835260018060a01b031691825f526008602052613b5760405f2092511515839060ff801983541691151516179055565b51815461ff00191690151560081b61ff0016179055600954600160401b811015612d6f57806001613b8b9201600955613058565b81546001600160a01b0360039290921b91821b19169083901b1790556040516306fdde0360e01b81525f81600481855afa908115612b8b575f91613cb4575b506040516395d89b4160e01b81525f81600481865afa908115612b8b575f91613c9a575b5060405163e5a6b10f60e01b8152915f83600481875afa928315612b8b577f4ac4dcc08b0c26c3fb6b58c64c1392b7934b1ce6b0382a5986ea5c3de795e05393613c56936135f4925f92613c72575b50613c6490604051958695606087526060870190612dda565b908582036020870152612dda565b908382036040850152612dda565b613c64919250613c93903d805f833e613c8b8183612db9565b810190613aae565b9190613c3d565b613cae91503d805f833e613c8b8183612db9565b5f613bee565b613cc891503d805f833e613c8b8183612db9565b5f613bca565b60ff1660028114908115613ce0575090565b600391501490565b602081519101519060208110613cfc575090565b5f199060200360031b1b1690565b80158015613db3575b613da2575f9081906401000003d0199060079082908181800909086040516020810191602083526020604083015260206060830152608082015263800001e9600160ff1b0360a08201526401000003d01960c082015260c08152613d7860e082612db9565b519060055afa613d866139bd565b90158015613da7575b613da257613d9e600191613ce8565b1490565b505f90565b50602081511415613d8f565b506401000003d019811015613d13565b60405163a9059cbb60e01b81526001600160a01b0392831660048201526001600160801b03939093166024840152602091839160449183915f91165af15f9181613e15575b50613e1257505f90565b90565b613e2f91925060203d6020116109cb576109bd8183612db9565b905f613e08565b9035601e1982360301811215612b965701602081359101916001600160401b038211612b96578136038313612b9657565b909160606001600160401b037fadf6f2901dd7af2f28a594f47a925894a08d4de10609dff591a80642648775c5921693613ee26001600160801b0360405195613eaf87612d54565b60018060a01b031692838752306020880152876040880152169485848201525f60808201525f60a0820152600554613902565b9384600555600654906001600160401b03613efe818416612e76565b1680926001600160401b0319161760065560405192835260208301526040820152a356febb7b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd65042ea6dad60c26f055e80ec401b3437c854ed586a0704d305bb4e9ea4518cfa26469706673582212206272594c6fb2d17088eaee0e54a30d972758894541036808d4d846c68044eed264736f6c63430008230033a2646970667358221220bb35f1055c0c8bf0a84cd6aef6cf8599cf60c7402fa067a3e2972713d5f7715364736f6c6343000823003360808060405234601557610188908161001a8239f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63b628c33414610025575f80fd5b346100db576101e03660031901126100db5760043563ffffffff8116036100db5761004e6100df565b506100576100f6565b5061006061010d565b5060a4356001600160a01b038116036100db5760403660c31901126100db576080366101031901126100db576101a43567ffffffffffffffff81116100db576100ad903690600401610124565b50506101c4359067ffffffffffffffff82116100db576100d36020923690600401610124565b505060018152f35b5f80fd5b6024359067ffffffffffffffff821682036100db57565b6044359067ffffffffffffffff821682036100db57565b6084359067ffffffffffffffff821682036100db57565b9181601f840112156100db5782359167ffffffffffffffff83116100db57602083818601950101116100db5756fea264697066735822122087b641cd97faae24a0489f0ae4a8deb7668537d326bf024fa9f3f1151192d9d664736f6c6343000823003360a034606e57601f61060d38819003918201601f19168301916001600160401b03831184841017607257808492602094604052833981010312606e57516001600160a01b03811690819003606e57608052604051610586908161008782396080518181816047015261015e0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe6080806040526004361015610012575f80fd5b5f3560e01c90816311da52611461007a5750637b9a730114610032575f80fd5b34610076575f366003190112610076576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b5f80fd5b346100765760e0366003190112610076576004359063ffffffff8216809203610076576024356001600160a01b03811690819003610076576064356001600160a01b038116939084900361007657608435916fffffffffffffffffffffffffffffffff83168093036100765760a4359167ffffffffffffffff83168093036100765760c4359567ffffffffffffffff871161007657366023880112156100765786600401359567ffffffffffffffff871161007657366024888a010111610076575f546104f35760015f9081556390b7f6fd60e01b825260048201849052816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa9081156102f0575f91610376575b50602001516001600160a01b031633036103675760405163078097c560e41b815260048101829052602081602481335afa9081156102f0575f91610348575b50156103395760405163a9059cbb60e01b81528160048201528560248201526020816044815f885af19081156102f0575f9161030a575b50156102fb5760e45f928760209860246040519b8c9a8b998a96630a137ff560e01b88526004880152338488015260443560448801526064870152608486015260c060a48601528260c486015201848401378181018301859052601f01601f19168101030193f19081156102f0575f916102ad575b506001600160e01b03191663f5ec800b60e01b0161029e575f8055005b63be51ced360e01b5f5260045ffd5b90506020813d6020116102e8575b816102c860209383610502565b8101031261007657516001600160e01b0319811681036100765781610281565b3d91506102bb565b6040513d5f823e3d90fd5b6312171d8360e31b5f5260045ffd5b61032c915060203d602011610332575b6103248183610502565b810190610538565b8861020c565b503d61031a565b63793cd94b60e11b5f5260045ffd5b610361915060203d602011610332576103248183610502565b886101d5565b63efc26b5760e01b5f5260045ffd5b90503d805f833e6103878183610502565b8101906020818303126100765780519067ffffffffffffffff8211610076570190610140828203126100765760405191610140830183811067ffffffffffffffff8211176104df57604052805163ffffffff811681036100765783526103ef60208201610524565b602084015261040060408201610524565b604084015261041160608201610524565b606084015261042260808201610524565b608084015261043360a08201610524565b60a084015260c081015160c084015260e081015160e084015261010081015167ffffffffffffffff81168103610076576101008401526101208101519067ffffffffffffffff8211610076570181601f820112156100765780519067ffffffffffffffff82116104df57604051926104b5601f8401601f191660200185610502565b8284526020838301011161007657815f9260208093018386015e8301015261012082015288610196565b634e487b7160e01b5f52604160045260245ffd5b6336a4d21b60e11b5f5260045ffd5b90601f8019910116810190811067ffffffffffffffff8211176104df57604052565b51906001600160a01b038216820361007657565b9081602091031261007657518015158103610076579056fea2646970667358221220d69e785adfdce7e5e4870e5ae4f3188fd1782c386c2f27035fd195dfaeb15d8064736f6c63430008230033" + "zoneFactoryBytecode": "0x60808060405234610138575f805463ffffffff191660011781556003600755600680546001600160a01b03191633908117909155907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a36101a28181016001600160401b03811183821017610124578291615943833903905ff08015610119576001600160a01b03165f8181526003602052604090819020805460ff19166001179055600480546001600160a01b031916909217909155516106c1808201906001600160401b03821183831017610124576020918391615ae583393081520301905ff0801561011957600580546001600160a01b0319166001600160a01b0392909216919091179055604051615806908161013d8239f35b6040513d5f823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b5f80fdfe6080806040526004361015610012575f80fd5b5f905f3560e01c90816301b290d3146111ed575080632b7ac3f3146111c55780633cb747bf1461119d57806374134d371461118057806376628f83146111435780638da5cb5b1461111b57806390b7f6fd14610e4f578063dc6aebd014610170578063eee83499146101235763f2fde38b1461008c575f80fd5b34610120576020366003190112610120576100a5611226565b600654906001600160a01b0382169033829003610111576001600160a01b0316918215610102576001600160a01b03191682176006557f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b6349e27cff60e01b8452600484fd5b6330cd747160e01b8452600484fd5b80fd5b50346101205780600319360112610120575f1963ffffffff825416019063ffffffff821161015c5760208263ffffffff60405191168152f35b634e487b7160e01b81526011600452602490fd5b5034610bd7576020366003190112610bd7576001600160401b0360043511610bd75761018060043536036003190112610bd7576006546001600160a01b03163303610e40576101c36004356004016112cf565b6040516335ec42c960e01b81526001600160a01b03909116600482015260208160248161083f60921b5afa908115610bcc575f91610e05575b5015610df6576005546001600160a01b03166004356064015f5b610225826004356004016112e3565b9050811015610276578281610259610254610245866004356004016112e3565b6001600160a01b039491611318565b6112cf565b161461026757600101610216565b630a1eaf7360e41b5f5260045ffd5b50825f915b61028f6084600435016004356004016112e3565b905083101561030f576102b9610254846102b36084600435016004356004016112e3565b90611318565b6001600160a01b0316915f5b6102d4836004356004016112e3565b90508110156103025783816102f4610254610245876004356004016112e3565b1614610267576001016102c5565b509150916001019161027b565b6001600160a01b0361032560043560a4016112cf565b1615610de7576001600160a01b0361034160043560c4016112cf565b1615610dd8576001600160a01b0361035d60043560e4016112cf565b165f52600360205260ff60405f20541615610dc95762e4e1c05a10610dba575f549063ffffffff80831614610dab57600163ffffffff83160163ffffffff8111610d975763ffffffff1663ffffffff198316175f5560075460018101808211610d9757600755606081610bef575050604051606b60f91b6020820152602560fa1b60218201523060601b6022820152600160ff1b603682015260178152610405603782611276565b60018060a01b0390602081519101201660405161440a8082018281106001600160401b03821117610bdb5782916113c7833903905ff0918215610bcc576104506004356004016112cf565b906002602460043501351015610bd7576002604460043501351015610bd75761047e906004356004016112e3565b6104926084600435016004356004016112e3565b6005546001600160a01b031692906104ae60043560a4016112cf565b6104bc60c4600435016112cf565b6104ca60e4600435016112cf565b906104da6101446004350161133c565b926104f061016460043501600435600401611350565b9790956001600160a01b038e163b15610bd7578e9a8998604051809e819e6344ea314f60e01b835263ffffffff166004830152600160a01b60019003169060240152600435602401356105429061123c565b8c6004356024013590604401526004356044013561055f9061123c565b6044600435013560648e01526101a060848e0152610583916101a48e019190611382565b906003198c83030160a48d015261059992611382565b60c48a01989098526001600160a01b0390811660e48a0152908116610104808a0191909152911661012488015260043501356101448701526001600160401b03166101648601528484036003190161018486015281845260208401378082016020015f9052601f1990601f0116010360200181600160a01b60019003851691815a5f948591f18015610bcc57610bb7575b506001600160a01b03821603610b5b576106486004356004016112cf565b9261065760a4600435016112cf565b61066560c4600435016112cf565b6001600160401b0361067b60e4600435016112cf565b61068a6101446004350161133c565b906106a061016460043501600435600401611350565b9490956040519a6106b08c61125a565b63ffffffff8b168c526001600160a01b038a811660208e01521660408c01526106de6004356024013561123c565b6024600435013560608c01526106f96044600435013561123c565b600435604481013560808d01526001600160a01b0391821660a08d015291811660c08c01529190911660e08a01526101048101356101008a01526101240135610120890152166101408701526001600160401b038111610b43576040519161076b601f8301601f191660200184611276565b8183523682820111610b575781849260209283860137830101526101608581019190915263ffffffff848116835260016020818152604080862089518154848c01516001600160c01b031990911691909616179490921b640100000000600160c01b03169390931781559187015190820180546001600160a01b0319166001600160a01b0392909216919091178155606087015191969161080b8161123c565b6108148161123c565b815460808401516108248161123c565b61082d8161123c565b61ffff60a01b1990911660a092831b60ff60a01b161760a89190911b60ff60a81b16179091558101516002870180546001600160a01b03199081166001600160a01b039384161790915560c0830151600389018054831691841691909117905560e083015160048901805490921692169190911790556101008101516005870155610120810151600687015561014081015160078701805467ffffffffffffffff19166001600160401b03928316179055910151805195918611610b43576108f86008830154611297565b601f8111610aee575b50602090601f8711600114610a815795806008926040988692610a76575b50508160011b915f199060031b1c1916179101555b6001600160a01b03821681526002602052838120805460ff1916600117905561096060048035016112cf565b90506001600160401b0361097860a4600435016112cf565b61098660c4600435016112cf565b61099460e4600435016112cf565b906109a46101446004350161133c565b88516001600160a01b039096168652926109c36004356024013561123c565b6024600435013560208701526109de6044600435013561123c565b6004356044810135878b01526001600160a01b039182166060880152918116608087015291821660a086015261010481013560c0860152610124013560e0850152911661010083015282169063ffffffff8416907fea91a64f0124a834474483fb8d6d9f88c7ef85624a43afb0d929c9647d1122339061012090a3825163ffffffff9290921682526001600160a01b03166020820152f35b01519050888061091f565b9060088301845280842091845b601f1989168110610ad657509660089260019260409983601f19811610610abe575b505050811b01910155610934565b01515f1960f88460031b161c19169055888080610ab0565b91926020600181928685015181550194019201610a8e565b868111156109015760088301845260208420601f880160051c9060208910610b3b575b81601f9101920160051c0390845b828110610b2d575050610901565b808660019284015501610b1f565b859150610b11565b634e487b7160e01b83526041600452602483fd5b8380fd5b60405162461bcd60e51b815260206004820152602e60248201527f506f7274616c2061646472657373206d69736d61746368202d206e6f6e63652060448201526d3a3930b1b5b4b7339032b93937b960911b6064820152608490fd5b610bc49194505f90611276565b5f928461062a565b6040513d5f823e3d90fd5b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b607f8211610c415750604051606b60f91b6020820152602560fa1b60218201523060601b602282015260f89190911b6001600160f81b031916603682015260178152610c3c603782611276565b610405565b60ff8211610c98575060405160d760f81b6020820152602560fa1b60218201523060601b6022820152608160f81b603682015260f89190911b6001600160f81b031916603782015260188152610c3c603882611276565b61ffff8211610cf05750604051601b60fb1b6020820152602560fa1b60218201523060601b6022820152604160f91b603682015260f09190911b6001600160f01b031916603782015260198152610c3c603982611276565b62ffffff8211610d49575060405160d960f81b6020820152602560fa1b60218201523060601b6022820152608360f81b603682015260e89190911b6001600160e81b0319166037820152601a8152610c3c603a82611276565b604051606d60f91b6020820152602560fa1b60218201523090911b6022820152602160fa1b603682015260e09190911b6001600160e01b0319166037820152601b8152610c3c603b82611276565b634e487b7160e01b5f52601160045260245ffd5b63e558bfa360e01b5f5260045ffd5b6307099c5360e21b5f5260045ffd5b63baa3de5f60e01b5f5260045ffd5b633c9f858360e21b5f5260045ffd5b630b5eba9f60e41b5f5260045ffd5b63c1ab6dc160e01b5f5260045ffd5b90506020813d602011610e38575b81610e2060209383611276565b81010312610bd757518015158103610bd7575f6101fc565b3d9150610e13565b6330cd747160e01b5f5260045ffd5b34610bd7576020366003190112610bd75760043563ffffffff8116809103610bd7576060610160604051610e828161125a565b5f81525f60208201525f60408201525f838201525f60808201525f60a08201525f60c08201525f60e08201525f6101008201525f6101208201525f61014082015201525f52600160205260405f20604051610edc8161125a565b815463ffffffff811682526001600160a01b03602091821c8116918301918252600184015490811660408401908152919390606084019060ff9060a081901c8216610f268161123c565b835260a81c166080850190610f3a8161123c565b815260018060a01b0360028401541660a0860190815260018060a01b0360038501541660c0870190815260018060a01b036004860154169160e0880192835260058601549361010089019485526006870154956101208a0196875260086001600160401b0360078a015416986101408c01998a520198604051809a5f90805490610fc382611297565b80855291600181169081156110fd57506001146110c1575b505003610fe8908b611276565b6101608b01998a526040519b8c9b60208d525163ffffffff1660208d0152600160a01b6001900390511660408c0152600160a01b6001900390511660608b0152516110328161123c565b60808a0152516110418161123c565b60a0890152516001600160a01b0390811660c08901529051811660e08801529051166101008601525161012085015251610140840152516001600160401b0316610160830152516101808083015280516101a083018190529081906020016101c084015e8082016101c0015f9052601f1990601f01168101036101c00190f35b5f908152602081209092505b8183106110e257505081016020018e80610fdb565b80602092948385600194549201015201910190918c926110cd565b9150506020925060ff191682840152151560051b8201018e80610fdb565b34610bd7575f366003190112610bd7576006546040516001600160a01b039091168152602090f35b34610bd7576020366003190112610bd7576001600160a01b03611164611226565b165f526003602052602060ff60405f2054166040519015158152f35b34610bd7575f366003190112610bd757602060405162e4e1c08152f35b34610bd7575f366003190112610bd7576005546040516001600160a01b039091168152602090f35b34610bd7575f366003190112610bd7576004546040516001600160a01b039091168152602090f35b34610bd7576020366003190112610bd7576020906001600160a01b03611211611226565b165f526002825260ff60405f20541615158152f35b600435906001600160a01b0382168203610bd757565b6002111561124657565b634e487b7160e01b5f52602160045260245ffd5b61018081019081106001600160401b03821117610bdb57604052565b90601f801991011681019081106001600160401b03821117610bdb57604052565b90600182811c921680156112c5575b60208310146112b157565b634e487b7160e01b5f52602260045260245ffd5b91607f16916112a6565b356001600160a01b0381168103610bd75790565b903590601e1981360301821215610bd757018035906001600160401b038211610bd757602001918160051b36038313610bd757565b91908110156113285760051b0190565b634e487b7160e01b5f52603260045260245ffd5b356001600160401b0381168103610bd75790565b903590601e1981360301821215610bd757018035906001600160401b038211610bd757602001918136038313610bd757565b916020908281520191905f905b80821061139c5750505090565b90919283359060018060a01b038216809203610bd75760208160019382935201940192019061138f56fe608080604052346015576143f0908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f5f3560e01c80625d97ef14612c91578062ebfdb414612c075780630223188114612bc857806309a0a234146128e85780630d0c743e146124b55780630e18b681146124265780630e86bbdc1461239157806311dd959714612376578063267822471461234d57806327c71b50146122c75780632b7ac3f31461229e5780632c37826e146122745780632dfdf0b51461224d5780633488ce0d146121e257806337f981d81461218d578063385cadd0146120e057806339dc015d14611f775780633cb747bf14611f4e5780634256ce3814611f3057806344ea314f14611a8657806345c5d2fb14611a68578063521246c2146119db578063580bbcfc146119b15780635c1bba381461198a578063652ef10b146117b5578063748538d91461177657806375829def1461170057806378097c50146116c15780637b5c6e28146116955780637e31d2cc146116015780638081a8b2146115e357806383a146b3146115c8578063857e85f81461156c57806386f47e551461154e5780638976248d1461152257806390f595981461143c57806394ce88b01461141e578063959f47c31461134a5780639a97784b146112a75780639ecd4cc01461126f578063b01f22e414610c52578063b02690f214610c1a578063be2a63ee14610bf0578063bf8e244014610b6c578063bffa55d514610aa4578063c13a2d4b14610a2b578063c690908a14610937578063cfaabb4d1461090d578063d179978a146108e9578063e09eae1014610857578063e202d9951461082e578063e4643e85146107a1578063e471784914610783578063e84abe6914610759578063ecf79a4e14610732578063ef10b187146103e2578063f22a195e146103c4578063f490ca96146103a1578063f706cfbf14610383578063f851a4401461035a578063fb2f1206146103395763fe136c4e146102c2575f80fd5b3461033657602036600319011261033657604080916102df612ecb565b81602084516102ed816130a1565b82815201526001600160a01b0316815260086020522081519061030f826130a1565b5460ff6020818316151593848152019160081c161515815282519182525115156020820152f35b80fd5b5034610336578060031936011261033657602060065460c01c604051908152f35b50346103365780600319360112610336576001546040516001600160a01b039091168152602090f35b50346103365780600319360112610336576020604051620186a08152f35b50346103365780600319360112610336576020604051670de0b6b3a76400008152f35b50346103365780600319360112610336576020600454604051908152f35b50346103365760a03660031901126103365760043560243560ff81169081810361072e576044359060ff821680920361072a5784546001600160a01b0316330361071b5761042f906140c8565b1561070c5761043d83614104565b1561070c57836080602092604051848101903082528760408201528660608201526060815261046c84826130bc565b51902090604051918252848201526064356040820152608435606082015282805260015afa1561070157825183806401000003d019600781878181800909086040516020810191602083526020604083015260206060830152608082015263400000f4600160fe1b0360a08201526401000003d01960c082015260c081526104f560e0826130bc565b519060055afa610503613db7565b90806106f6575b156106c157610518906140e2565b60028314600182161503610697575b6040519060208201908582526040830152604082526105476060836130bc565b905190206001600160a01b0391821680159290911690821561068c575b505061067d576001600160401b0343169060405161058181613086565b83815260208101908282526040810190848252600754600160401b811015610669578060016105b392016007556131be565b9190916106555760ff916001915181550192511660ff198354161782555168ffffffffffffffff0082549160081b169068ffffffffffffffff001916179055600754905f19820191821161064157917f82b5f4090f18a082bc8156b956154bfe0319307f5e5a7e903ef33f14ad2cb17e9391608093604051938452602084015260408301526060820152a180f35b634e487b7160e01b85526011600452602485fd5b634e487b7160e01b88526004889052602488fd5b634e487b7160e01b88526041600452602488fd5b6392536faf60e01b8352600483fd5b141590505f80610564565b6401000003d019036401000003d01981111561052757634e487b7160e01b85526011600452602485fd5b60405162461bcd60e51b815260206004820152600d60248201526c1b5bd9195e1c0819985a5b1959609a1b6044820152606490fd5b50602081511461050a565b6040513d84823e3d90fd5b632f0be49b60e11b8452600484fd5b6314f8a09f60e21b8552600485fd5b8480fd5b8380fd5b503461033657806003193601126103365760206001600160801b0360035416604051908152f35b503461033657806003193601126103365760206001600160401b0360065460401c16604051908152f35b50346103365780600319360112610336576020600954604051908152f35b50346103365780600319360112610336576002546001600160a01b0381169081158015610824575b6108155782546001600160a01b0319808216841785559091166002556001600160a01b03167f9945af900637995146537e5ea7a7be455dfd08bbb46fdd79e9c3a2bad0c4eef88380a380f35b63d71402d960e01b8352600483fd5b50813314156107c9565b50346103365780600319360112610336576002546040516001600160a01b039091168152602090f35b503461033657602036600319011261033657610871612f4b565b81546001600160a01b031633036108da57600680546001600160c01b031660c083901b6001600160c01b0319161790556040516001600160401b0390911681527f66bcd750662bb66118e25a8e421ae73974634d9af2d44fb9e600d250917fe69090602090a180f35b6314f8a09f60e21b8252600482fd5b5034610336578060031936011261033657602063ffffffff60115416604051908152f35b50346103365760203660031901126103365760406020916004358152600d83522054604051908152f35b503461033657602036600319011261033657610951612ecb565b6001546001600160a01b03163303610a1c576001600160a01b0381168083526008602052604083205460ff16610a0d576040516335ec42c960e01b8152600481019190915260208160248161083f60921b5afa908115610a025783916109d3575b50156109c4576109c190613f0a565b80f35b631fcf8c4760e11b8252600482fd5b6109f5915060203d6020116109fb575b6109ed81836130bc565b810190613101565b5f6109b2565b503d6109e3565b6040513d85823e3d90fd5b632909298f60e11b8352600483fd5b637bfa4b9f60e01b8252600482fd5b5034610336576040366003190112610336576004356001600160401b038111610aa0576101406003198236030112610aa05781546001600160a01b031633036108da57601054610a9157610a8a906001601055602435906004016133cd565b8060105580f35b630b5c738160e01b8252600482fd5b5080fd5b503461033657602036600319011261033657610abe612ecb565b610ac733613b1a565b6001600160a01b038116808352600a60208181526040808620335f818152918452828220548689529484528288208183529093522080546001600160801b03191690556001600160801b0390911692610b219184916141bd565b15610b5d57602092506040518281527fffd3bbab073ab4b2d0792c270104924c14c285a153b9acddabae166395d2eb5c843392a3604051908152f35b63be51ced360e01b8352600483fd5b503461033657602036600319011261033657610b86612ecb565b81546001600160a01b03169033829003610be157600280546001600160a01b0319166001600160a01b03929092169182179055907f12cf9e4d760566fb6a3e0193406f2295fc4b53f2b60817145a61f29e470cf7d68380a380f35b6314f8a09f60e21b8352600483fd5b503461033657806003193601126103365760206001600160401b0360065460801c16604051908152f35b5034610336576020366003190112610336576020610c396004356133a1565b905460405160039290921b1c6001600160a01b03168152f35b50346103365760a036600319011261033657610c6c612ecb565b906024356001600160801b0381168103610aa0576001600160401b0360643511610aa05760a060643536036003190112610aa057610ca8612ef7565b906001600160a01b0382161561126057610cc133613a8e565b610cca82613b1a565b610cd384613b4b565b604051639c4bad2960e01b81526020816004816001600160a01b0389165afa9081156112285783610d42926020928791611233575b506040516337de09eb60e11b81526001600160401b0390911660048201526001600160a01b03909116602482015291829081906044820190565b038161100f60921b5afa908115611228578491611209575b50156111fa57610d76610d71602460643501613346565b6140c8565b156111eb57610d8a60643560040135614104565b156111eb576040610da5604460643501606435600401613354565b9050036111c057610db76044356132b2565b501561112e57610dc79084613bd6565b9060405192610dd584613057565b6001600160a01b0386811685523360208601526001600160801b03841660408087019190915290821660608601526044356080860152519360a085016001600160401b0381118682101761111a576040526064356004013585526024606435013560ff811681036111165760208601526001600160401b0360446064350135116111125736606435604481013501602301121561111257610e8160643560448101350160040135613386565b610e8e60405191826130bc565b60643560448101350160048101358083523660249190920101116111165760643560448101350160048101359060240160208301376064803560448101358101600401358301602001899052604088019290925201356001600160a01b03198116900361111257606480350135606086015260846064350135946001600160801b031986169586810361110e57926110cb8860806110426001600160401b03968660209e99847f4debdbd2891da07eb466d2025028a25aab636034cf3c941afd1025a0c590a07a9c99015260a082019081526005549060405194602093869485019750600188526060604086015260018060a01b038151168286015260018060a01b0360208201511660a08601526001600160801b0360408201511660c086015260018060a01b0360608201511660e08601520151610100840152519060c061012084015281516101408401528f60ff9083015116610160840152611004604083015160a06101808601526101e08501906130dd565b6060808401516001600160a01b0319166101a08601526080909301516001600160801b0319166101c08501529183015203601f1981018352826130bc565b51902098899760ff6110538a613d81565b956001600160801b0361106a602460643501613346565b9181611080604460643501606435600401613354565b969097506040519c60018060a01b03168d521660208c01521660408a015260443560608a01526064356004013560808a01521660a088015261016060c0880152610160870191613119565b6064803501356001600160a01b03191660e08601526101008501979097526001600160a01b031661012084015216610140820152339381900390a3604051908152f35b8780fd5b8580fd5b8680fd5b634e487b7160e01b87526041600452602487fd5b8260075460443510156111ab576111466044356131be565b5090600160443501918260443511611197576001600160401b036001818161116f6064976131be565b5094015460081c1692015460081c169063034dff0f60e01b8352604435600452602452604452fd5b634e487b7160e01b82526011600452602482fd5b637f455d3760e11b8152604435600452602490fd5b6044836111d68260643501606435600401613354565b632331a96360e11b8352600452506040602452fd5b632f0be49b60e11b8352600483fd5b6354cfe65960e01b8352600483fd5b611222915060203d6020116109fb576109ed81836130bc565b5f610d5a565b6040513d86823e3d90fd5b6112539150833d8511611259575b61124b81836130bc565b810190613327565b5f610d08565b503d611241565b638dc30b3960e01b8352600483fd5b50346103365760203660031901126103365760406001600160401b036112966004356132b2565b835191151582529091166020820152f35b5034610336576020366003190112610336576004359080604080516112cb81613086565b82815282602082015201526007548210156113385760606112eb836131be565b506001600160401b0360405161130081613086565b60ff600184549485845201549183604060208301928486168452019360081c1683526040519485525116602084015251166040820152f35b602491637f455d3760e11b8252600452fd5b50346103365780600319360112610336576040519080600e549061136d8261301f565b80855291600181169081156113f757506001146113ad575b6113a984611395818603826130bc565b6040519182916020835260208301906130dd565b0390f35b600e81525f51602061435b5f395f51905f52939250905b8082106113dd5750909150810160200161139582611385565b9192600181602092548385880101520191019092916113c4565b60ff191660208087019190915292151560051b850190920192506113959150839050611385565b50346103365780600319360112610336576020600b54604051908152f35b50346103365761144b36612fc0565b6001549091906001600160a01b031633036115135781806114d2575b6114c3576001600160a01b03168083526014602090815260408420805460ff191660ff8515151617905590917f85a68034076f99047d5db3dd6a0adaa05addad1c2d330a044b888fbf7aa47e9391905b6040519015158152a280f35b63719e350160e01b8352600483fd5b506001600160a01b0381168084526013602052604084205460ff169081156114fb575b50611467565b60115460201c6001600160a01b03161490505f6114f5565b637bfa4b9f60e01b8352600483fd5b5034610336578060031936011261033657602061153d61328d565b6001600160801b0360405191168152f35b50346103365780600319360112610336576020604051629896808152f35b5034610336576040366003190112610336576040611588612ecb565b91611591612ee1565b9260018060a01b03168152600a602052209060018060a01b03165f5260205260206001600160801b0360405f205416604051908152f35b5034610336578060031936011261033657602061153d613244565b50346103365780600319360112610336576020600c54604051908152f35b5034610336576020366003190112610336576004356002811015610aa0576001546001600160a01b03163303610a1c575f51602061437b5f395f51905f529061164981612fa2565b8061168057600160155460ff81169060ff191617176015555b61166a6131aa565b9061167a604051928392836131ee565b0390a180f35b60155460fe81169060ff191617601555611662565b503461033657806003193601126103365760206116b0613235565b604051906116bd81612fa2565b8152f35b50346103365760203660031901126103365760209060ff906040906001600160a01b036116ec612ecb565b168152601384522054166040519015158152f35b50346103365760203660031901126103365761171a612ecb565b6001546001600160a01b0316903382900361151357600f80546001600160a01b0319166001600160a01b03929092169182179055907fe5cd1c804f1c9cc6d7009e4c0fb532f0e2d8863524c3323a6b3790c3f80bf25c8380a380f35b50346103365760203660031901126103365760209060ff906040906001600160a01b036117a1612ecb565b168152600884522054166040519015158152f35b5034610336576020366003190112610336576004356001600160401b038111610aa0576117e6903690600401612f75565b8254909291906001600160a01b031633036108da576001600160401b03831161197657611814600e5461301f565b601f811161191a575b508192601f811160011461188c578083947ff4e00967b25e707df96d88676243b33be84847ef27615af8ef91290b52294fc69491611881575b508160011b905f198360031b1c191617600e555b61167a604051928392602084526020840191613119565b90508201355f611856565b600e8352601f198116935f51602061435b5f395f51905f5290845b8681106119025750827ff4e00967b25e707df96d88676243b33be84847ef27615af8ef91290b52294fc69596106118e9575b5050600181811b01600e5561186a565b8301355f19600384901b60f8161c191690555f806118d9565b909160206001819285880135815501930191016118a7565b8381111561181d57600e8352601f840160051c906020851061196e575b601f82910160051c0390835b82811061195157505061181d565b80855f51602061435b5f395f51905f526001938501015501611943565b839150611937565b634e487b7160e01b82526041600452602482fd5b5034610336578060031936011261033657546040516001600160a01b039091168152602090f35b503461033657806003193601126103365760206001600160401b0360125460a01c16604051908152f35b5034610336576020366003190112610336576004356002811015610aa0576001546001600160a01b03163303610a1c575f51602061437b5f395f51905f5290611a2381612fa2565b80611a5357600260155460ff81169060ff191617176015555b611a44613235565b61167a604051928392836131ee565b60155460fd81169060ff191617601555611a3c565b50346103365780600319360112610336576020600554604051908152f35b5034610336576101a03660031901126103365760043563ffffffff8116809103610aa057611ab2612ee1565b90604435600281101561072e5760643590600282101561072a576084356001600160401b03811161111257611aeb903690600401612fef565b93909260a4356001600160401b03811161110e57611b0d903690600401612fef565b93909260c4356001600160a01b0381168103611f2c5760e4356001600160a01b0381169290839003611f2857610104356001600160a01b03811690819003611f2457610124356001600160a01b0381169190829003611f205761016435906001600160401b0382168203611f1c57610184356001600160401b038111611f1857611b9b903690600401612f75565b969095735af1ffffffffffffffffffffffffffffffffffff193301611f08576012549560ff8760e01c16611ef857601180546001600160c01b0319169190911760209290921b640100000000600160c01b0316919091179055600180546001600160a01b03199081169290921790558e5416178d55610144356004556001600160e81b03199092161760a09190911b67ffffffffffffffff60a01b1617600160e01b1760125589611c4b84612fa2565b8315611ef0575b60ff90611c5e86612fa2565b8515611ee8575b1660ff1960155416176015556001600160401b038211611ed457611c8a600e5461301f565b601f8111611e78575b508990601f8311600114611df9575f51602061437b5f395f51905f52949392918b9183611dee575b50508160011b915f199060031b1c191617600e555b611cdf604051928392836131ee565b0390a1855b828110611d7a57505050835b828110611d0157846109c185613f0a565b6001906001600160a01b03611d1f611d1a838787613211565b613221565b1686526014602052604086208260ff19825416179055818060a01b03611d49611d1a838787613211565b167f85a68034076f99047d5db3dd6a0adaa05addad1c2d330a044b888fbf7aa47e936020604051858152a201611cf0565b6001906001600160a01b03611d93611d1a838787613211565b1688526013602052604088208260ff19825416179055818060a01b03611dbd611d1a838787613211565b167fb359c7f3988b64787c42818b9235b2a6294c092435969764520af8ca7facae146020604051858152a201611ce4565b013590505f80611cbb565b600e8b525f51602061435b5f395f51905f5291601f1984168c5b818110611e6057509160019391855f51602061437b5f395f51905f529897969410611e47575b505050811b01600e55611cd0565b01355f19600384901b60f8161c191690555f8080611e39565b91936020600181928787013581550195019201611e13565b82811115611c9357600e8b52601f830160051c9060208410611ecc575b601f82910160051c03908b5b828110611eaf575050611c93565b808d5f51602061435b5f395f51905f526001938501015501611ea1565b8b9150611e95565b634e487b7160e01b8a52604160045260248afd5b600217611c65565b506001611c52565b505062dc149f60e41b8f5260048ffd5b50631966391b60e11b8f5260048ffd5b8e80fd5b8d80fd5b8c80fd5b8b80fd5b8a80fd5b8980fd5b50346103365780600319360112610336576020600754604051908152f35b5034610336578060031936011261033657601154604051602091821c6001600160a01b03168152f35b503461033657602036600319011261033657611f91612f4b565b906007549081158015612086575b61206a5780915f19810190811161119757836001600160401b0384929116925b818310611ff057606083611fd2816131be565b509060ff600183549301541660405192835260208301526040820152f35b909182810180821161205657600181018091116120565760011c90846001600160401b03600161201f856131be565b50015460081c16116120345750915b90611fbf565b92505f1981019081111561202e57634e487b7160e01b82526011600452602482fd5b634e487b7160e01b83526011600452602483fd5b63302c66ab60e21b81526001600160401b038316600452602490fd5b50600754156120cc57600781527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c6895460081c6001600160401b0390811690841610611f9f565b634e487b7160e01b81526032600452602490fd5b5034610336576120ef36612fc0565b6001549091906001600160a01b0316330361151357818061216e575b61215f576001600160a01b03168083526013602090815260408420805460ff191660ff8515151617905590917fb359c7f3988b64787c42818b9235b2a6294c092435969764520af8ca7facae1491906114b7565b63793cd94b60e11b8352600483fd5b506001600160a01b03811683526014602052604083205460ff1661210b565b5034610336576020366003190112610336576020906040906001600160a01b036121b5612ecb565b16815260088352205460ff811690816121d4575b506040519015158152f35b60ff915060081c165f6121c9565b503461033657806003193601126103365760075490811561223e575f19820191821161222a576040612213836131be565b5060ff600182549201541682519182526020820152f35b634e487b7160e01b81526011600452602490fd5b630c322fb560e31b8152600490fd5b503461033657806003193601126103365760206001600160401b0360065416604051908152f35b503461033657806003193601126103365760206001600160401b0360035460801c16604051908152f35b50346103365780600319360112610336576012546040516001600160a01b039091168152602090f35b5034610336576020366003190112610336576122e1612ecb565b6001546001600160a01b03163303610a1c576001600160a01b03168082526008602052604082205460ff16156109c457808252600860205260408220805461ff00191690557feb225a736fbfee3f85ccb72bdf84ff0396ab358b7970e2cc351ab3e3fd92358d8280a280f35b5034610336578060031936011261033657600f546040516001600160a01b039091168152602090f35b503461033657806003193601126103365760206116b06131aa565b5034610336576020366003190112610336576004356001600160801b038116809103610aa05781546001600160a01b031633036108da57670de0b6b3a76400008111612417576020817fc62141e607d6fcbf7d11fd2b6d8e18e5ebef6d3fff8136ca98822801abbaea38926001600160801b03196003541617600355604051908152a180f35b630d62f21160e11b8252600482fd5b5034610336578060031936011261033657600f546001600160a01b03811690811580156124ab575b61249c57600180546001600160a01b031980821685179092559116600f556001600160a01b03167ff8ccb027dfcd135e000e9d45e6cc2d662578a8825d4c45b5e32e0adf67e79ec68380a380f35b63058d9a1b60e01b8352600483fd5b508133141561244e565b503461033657610160366003190112610336576124d0612f4b565b6024356001600160401b0381169182820361072e57604036604319011261072e57608036608319011261072e576101043591610124356001600160401b03811161111257612522903690600401612f75565b9094610144356001600160401b03811161110e57612544903690600401612f75565b88546001600160a01b03169190338390036128d9576044359360045485036128ca57601254906001600160401b038916976001600160401b038360a01c16891061288b57816128a9575050879443881161289a576125a188613de6565b945b851561288b576125b1613139565b98600654996001600160401b03808c60401c16911614801590612868575b801561284c575b61283d57908d96959493929163ffffffff6011541696600354996001600160401b038b60801c16995060018a01926001600160401b038411612826576001600160401b0393929184916040519b632d8a30cd60e21b8d5260048d015260248c01521660448a0152606489015216608487015260a486015260c4850152606435968760e486015260843561010486015260a4359b8c61012487015260c4356001600160401b038116809103611f185761014487015260e435916001600160401b038316809303611f185760209587956126d28f946126e59489979688976101648901526101848801526101e06101a48801526101e4870191613119565b848103600319016101c486015291613119565b03916001600160a01b03165afa90811561281b5789916127fc575b50156127ed5767ffffffffffffffff60801b9061271c90613179565b60801b16906001600160401b0360801b1916176003556004556fffffffffffffffff000000000000000061274e61314f565b60401b16916001600160401b0360801b9060801b169077ffffffffffffffffffffffffffffffff0000000000000000191617176006557f5a66941dc92cb865480c966eff640c02b1d00d544b74332fd67c6f1cbfccdf3960806127b083613e54565b936001600160401b03600354831c1693600454906001600160401b0360065460401c1691604051938452602084015260408301526060820152a380f35b6309bde33960e01b8852600488fd5b612815915060203d6020116109fb576109ed81836130bc565b5f612700565b6040513d8b823e3d90fd5b50505060248f634e487b7160e01b81526011600452fd5b633d9dffbd60e01b8e5260048efd5b5061285561314f565b6001600160401b03808c169116116125d6565b5061287161314f565b6001600160401b0380612882613139565b169116106125cf565b632705871360e01b8d5260048dfd5b632705871360e01b8c5260048cfd5b88829792111561288b5743871161288b576128c49096613de6565b946125a3565b6309bde33960e01b8b5260048bfd5b6314f8a09f60e21b8a5260048afd5b50346103365760a036600319011261033657612902612ecb565b61290a612ee1565b90612913612f21565b6064359061291f612ef7565b6001600160a01b038116959093908615612bb95761293c33613a8e565b61294585613b1a565b61294e82613b4b565b604051639c4bad2960e01b81526001600160a01b03831695906020816004818a5afa908115610a02578391612b9a575b506040516337de09eb60e11b81526001600160401b03821660048201526001600160a01b038916602482015260208160448161100f60921b5afa908115611228578491612b7b575b50156111fa5760405163b389e30560e01b81526001600160401b03821660048201526001600160a01b038916602482015260208160448161100f60921b5afa908115611228578491612b5c575b50156111fa576040516337de09eb60e11b81526001600160401b039190911660048201526001600160a01b0391909116602482015260208160448161100f60921b5afa908115610701578291612b3d575b5015612b2e5750946001600160401b0392612a896001600160801b0393602098613bd6565b9390612acf8260405199612a9c8b613057565b898b52338c8c015260018060a01b0316968760408c015216988960608201528560808201528460a0820152600554613cfc565b97612ad989613d81565b956040519889528a8901526040880152166060860152608085015260a08401521660c0820152817ffee50869847a4d073bfd83ec655472febff62beb46febf4ec48752f0686affd160e03393a3604051908152f35b6354cfe65960e01b8152600490fd5b612b56915060203d6020116109fb576109ed81836130bc565b5f612a64565b612b75915060203d6020116109fb576109ed81836130bc565b5f612a13565b612b94915060203d6020116109fb576109ed81836130bc565b5f6129c6565b612bb3915060203d6020116112595761124b81836130bc565b5f61297e565b638dc30b3960e01b8152600490fd5b50346103365760203660031901126103365760209060ff906040906001600160a01b03612bf3612ecb565b168152601484522054166040519015158152f35b503461033657602036600319011261033657612c21612ecb565b6001546001600160a01b03163303610a1c576001600160a01b03168082526008602052604082205460ff16156109c457808252600860205260408220805461ff0019166101001790557f22ab73af03f04a21e91c7923327f99279b7f5d07d9551762c39bccdf051f1fe98280a280f35b5034612e595760c0366003190112612e5957612cab612ecb565b612cb3612ee1565b612cbb612f21565b91608435926001600160401b038416809403612e595760a4356001600160401b038111612e5957612cf0903690600401612f75565b919092303303612ebc57612d026131aa565b612d0b81612fa2565b1580612e9a575b612e8b5760115460405163a9059cbb60e01b81526001600160a01b03602092831c811660048301526001600160801b038516602483015292909216939190816044815f885af1908115612e4e575f91612e6c575b5015612e5d576005549560115460018060a01b038160201c1693843b15612e59575f9663ffffffff9488946001600160801b03612ded946040519c8d9b8c9a8b996311da526160e01b8b521660048a01526024890152606435604489015260018060a01b0316606488015216608486015260a485015260e060c485015260e4840191613119565b03925af18015612e4e57612e39575b50612e05613235565b612e0e81612fa2565b159081612e2d575b50612e1e5780f35b631fd6e06160e01b8152600490fd5b9050600554145f612e16565b612e469192505f906130bc565b5f905f612dfc565b6040513d5f823e3d90fd5b5f80fd5b6312171d8360e31b5f5260045ffd5b612e85915060203d6020116109fb576109ed81836130bc565b5f612d66565b63793cd94b60e11b5f5260045ffd5b506001600160a01b0385165f9081526013602052604090205460ff1615612d12565b6314e1dbf760e11b5f5260045ffd5b600435906001600160a01b0382168203612e5957565b602435906001600160a01b0382168203612e5957565b608435906001600160a01b0382168203612e5957565b35906001600160a01b0382168203612e5957565b604435906001600160801b0382168203612e5957565b35906001600160801b0382168203612e5957565b600435906001600160401b0382168203612e5957565b35906001600160401b0382168203612e5957565b9181601f84011215612e59578235916001600160401b038311612e595760208381860195010111612e5957565b60021115612fac57565b634e487b7160e01b5f52602160045260245ffd5b6040906003190112612e59576004356001600160a01b0381168103612e5957906024358015158103612e595790565b9181601f84011215612e59578235916001600160401b038311612e59576020808501948460051b010111612e5957565b90600182811c9216801561304d575b602083101461303957565b634e487b7160e01b5f52602260045260245ffd5b91607f169161302e565b60c081019081106001600160401b0382111761307257604052565b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b0382111761307257604052565b604081019081106001600160401b0382111761307257604052565b90601f801991011681019081106001600160401b0382111761307257604052565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90816020910312612e5957518015158103612e595790565b908060209392818452848401375f828201840152601f01601f1916010190565b60c4356001600160401b0381168103612e595790565b60e4356001600160401b0381168103612e595790565b356001600160401b0381168103612e595790565b6001600160401b03166001600160401b0381146131965760010190565b634e487b7160e01b5f52601160045260245ffd5b601554600216156131b9575f90565b600190565b6007548110156131da5760075f5260205f209060011b01905f90565b634e487b7160e01b5f52603260045260245ffd5b604081019392916020919061320281612fa2565b815261320d83612fa2565b0152565b91908110156131da5760051b0190565b356001600160a01b0381168103612e595790565b601554600116156131b9575f90565b60065460c01c488102908082044814901517156131965764e8d4a510008101908181116131965764e8d4a50fff019081116131965764e8d4a510006001600160801b0391041690565b6001600160801b0360035416620186a0026001600160801b0381169081036131965790565b6007548082101561331f575f1981019081116131965781146133175760018101809111613196576001600160401b0360016132f062015180936131be565b50015460081c1601906001600160401b038211613196576001600160401b03821643109190565b506001905f90565b50505f905f90565b90816020910312612e5957516001600160401b0381168103612e595790565b3560ff81168103612e595790565b903590601e1981360301821215612e5957018035906001600160401b038211612e5957602001918136038313612e5957565b6001600160401b03811161307257601f01601f191660200190565b6009548110156131da5760095f5260205f2001905f90565b356001600160801b0381168103612e595790565b600b545f91600c548214613a7f576064820693845f52600d60205260405f2054905f1982148015613a75575b613a60578015938415613a6e575f19935b60408051602081019182529590926001600160a01b0361342984612f0d565b1660608801526020830135608088018190529960408401956001600160a01b0361345288612f0d565b1660a08a015260608501996134668b612f37565b6001600160801b031660c08b015260808601986134828a612f37565b6001600160801b031660e08c015260a08701356101008c015260c08701976134a989612f61565b6001600160401b03166101208d015260e088019b6134c68d612f61565b6001600160401b0316610140820152806101008a01986134e68a8c614230565b610160840161014090526101a08401906134ff92613119565b61350d6101208d018d614230565b848303605f19016101808601526135249291613119565b90604083015203601f198101825261353c90826130bc565b51902003613a605715613a4d57505f52600d6020525f1960405f20556001810180911161319657600b555b61357082613221565b946001600160401b0361358288613165565b1615613885576001600160801b03613599826133b9565b16613860575b50629896806001600160401b036135b585613165565b16116137f8576001600160401b036135cc84613165565b166136dd57505050916001600160801b035f51602061439b5f395f51905f52949261363161362b60609660016136006131aa565b61360981612fa2565b1480156136b5575b8061369e575b8061367c575b159586613659575b50613221565b946133b9565b90604051975060018060a01b0316875216602086015215604085015260018060a01b031692a3565b6136769061366f6136698a6133b9565b91613165565b9086614261565b5f613625565b5061369961368982613221565b613692896133b9565b90866141bd565b61361d565b506136b06136ab82613221565b61431c565b613617565b506001600160a01b036136c782613221565b168a52601360205260ff60408b20541615613611565b6137026136e985613221565b916136fc6136f68a6133b9565b95613165565b93613354565b9092303b15612e5957604051625d97ef60e01b81526001600160a01b03808916600483015290931660248401526001600160801b03949094166044830152606482018a90526001600160401b0393909316608482015260c060a4820152915f91839182916137749160c4840191613119565b038183305af190816137b9575b505f51602061439b5f395f51905f52949261363161362b606096946001600160801b0394155f146137b2578961361d565b600161361d565b6001600160801b039196509261363161362b606096946137e85f5f51602061439b5f395f51905f529a986130bc565b5f99945094965050509294613781565b5050508391945061383b6138356001600160801b0392611d1a60609661382e6136695f51602061439b5f395f51905f529a6133b9565b908a614261565b926133b9565b604080516001600160a01b0397881681529290911660208301525f90820152931692a3565b61387e9061387760018060a01b035f5416916133b9565b90876141bd565b505f61359f565b5050935050935050613898919350613221565b6138a0613244565b926001600160801b036138b2826133b9565b166001600160801b03851611613a32575b836138d06138d5926133b9565b613bb6565b906001600160801b038416613a15575b6138f16136ab84613221565b806139fc575b1561396d577f0f7ef08806234f85aaee43d3ba4589c3bc6d5ac3fc8edd56fc3d91cc7553bdcb92613968906001600160a01b039061393490613221565b1694604051938493849160409194936001600160801b038092606086019760018060a01b0316865216602085015216910152565b0390a2565b6001600160a01b0381165f908152600a602052604090207f5fea28d0adb7d877ae3259768f41ad6741aa1784c4475746dd931364f62e68a193613968916139b382613221565b60018060a01b03165f5260205260405f206001600160801b036139d98682845416613b96565b82546001600160801b03191691161790556001600160a01b039061393490613221565b50613a1082613a0a85613221565b836141bd565b6138f7565b5f54613a2c9085906001600160a01b0316836141bd565b506138e5565b6138d0935080613a446138d5926133b9565b945090506138c3565b91505f52600d60205260405f2055613567565b626aee3160e51b5f5260045ffd5b819361340a565b505f1981146133f9565b63d7fa91d160e01b5f5260045ffd5b6001613a98613235565b613aa181612fa2565b14613af657613aae6131aa565b613ab781612fa2565b1580613af9575b613af6576001600160a01b03165f8181526014602052604090205460ff1615613ae45750565b6376ecd09f60e11b5f5260045260245ffd5b50565b506001600160a01b0381165f9081526013602052604090205460ff16613abe565b613b238161431c565b15613b2b5750565b6376ecd09f60e11b5f9081526001600160a01b0391909116600452602490fd5b6001600160a01b03165f9081526008602052604090205460ff811615613b875760081c60ff1615613b7857565b63dec712ef60e01b5f5260045ffd5b631fcf8c4760e11b5f5260045ffd5b906001600160801b03809116911601906001600160801b03821161319657565b906001600160801b03809116911603906001600160801b03821161319657565b9091613be061328d565b926001600160801b03613bfa613bf4613244565b86613b96565b16906001600160801b038116918210613ced5784613c1791613bb6565b6040516323b872dd60e01b81523360048201523060248201526044810192909252926001600160a01b03166020826064815f855af1918215612e4e578592613cd0575b506001600160801b038216613c6d575050565b5f805460405163a9059cbb60e01b81526001600160a01b0390911660048201526001600160801b0393909316602484015260209183916044918391905af18015612e4e57613cb85750565b613af69060203d6020116109fb576109ed81836130bc565b613ce89060203d6020116109fb576109ed81836130bc565b613c5a565b636ba4a1c760e01b5f5260045ffd5b6040519060a060208301935f8552600180831b038151166040850152600180831b036020820151166060850152600180831b0360408201511660808501526001600160801b0360608201511682850152600180831b0360808201511660c0850152015160e08301526101008201526101008152613d7b610120826130bc565b51902090565b600555600654613d996001600160401b038216613179565b67ffffffffffffffff199091166001600160401b0382161760065590565b3d15613de1573d90613dc882613386565b91613dd660405193846130bc565b82523d5f602084013e565b606090565b905f8080936040516020810191825260208152613e046040826130bc565b519071f90827f1c53a10cb7a02335b1753200029355afa613e23613db7565b9080613e48575b613e315750565b909150602081805181010312612e59576020015190565b50602081511015613e2a565b8015613ea257600c5490600b5482038281116131965760641115613e9357606482065f52600d60205260405f20556001810180821161319657600c5590565b632764e20b60e01b5f5260045ffd5b505f1990565b602081830312612e59578051906001600160401b038211612e59570181601f82011215612e5957805190613edb82613386565b92613ee960405194856130bc565b82845260208383010111612e5957815f9260208093018386015e8301015290565b604051613f16816130a1565b6001815260208101916001835260018060a01b031691825f526008602052613f5160405f2092511515839060ff801983541691151516179055565b51815461ff00191690151560081b61ff0016179055600954600160401b81101561307257806001613f8592016009556133a1565b81546001600160a01b0360039290921b91821b19169083901b1790556040516306fdde0360e01b81525f81600481855afa908115612e4e575f916140ae575b506040516395d89b4160e01b81525f81600481865afa908115612e4e575f91614094575b5060405163e5a6b10f60e01b8152915f83600481875afa928315612e4e577f4ac4dcc08b0c26c3fb6b58c64c1392b7934b1ce6b0382a5986ea5c3de795e0539361405093613968925f9261406c575b5061405e906040519586956060875260608701906130dd565b9085820360208701526130dd565b9083820360408501526130dd565b61405e91925061408d903d805f833e61408581836130bc565b810190613ea8565b9190614037565b6140a891503d805f833e61408581836130bc565b5f613fe8565b6140c291503d805f833e61408581836130bc565b5f613fc4565b60ff16600281149081156140da575090565b600391501490565b6020815191015190602081106140f6575090565b5f199060200360031b1b1690565b801580156141ad575b61419c575f9081906401000003d0199060079082908181800909086040516020810191602083526020604083015260206060830152608082015263800001e9600160ff1b0360a08201526401000003d01960c082015260c0815261417260e0826130bc565b519060055afa614180613db7565b901580156141a1575b61419c576141986001916140e2565b1490565b505f90565b50602081511415614189565b506401000003d01981101561410d565b60405163a9059cbb60e01b81526001600160a01b0392831660048201526001600160801b03939093166024840152602091839160449183915f91165af15f918161420f575b5061420c57505f90565b90565b61422991925060203d6020116109fb576109ed81836130bc565b905f614202565b9035601e1982360301811215612e595701602081359101916001600160401b038211612e59578136038313612e5957565b909160606001600160401b037fadf6f2901dd7af2f28a594f47a925894a08d4de10609dff591a80642648775c59216936142dc6001600160801b03604051956142a987613057565b60018060a01b031692838752306020880152876040880152169485848201525f60808201525f60a0820152600554613cfc565b9384600555600654906001600160401b036142f8818416613179565b1680926001600160401b0319161760065560405192835260208301526040820152a3565b6001614326613235565b61432f81612fa2565b1490811561433b575090565b6001600160a01b03165f9081526014602052604090205460ff1691905056febb7b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd0f4479e76524cb040b512439f3e3f3b1886c696af4ba5c832092a966970ffba165042ea6dad60c26f055e80ec401b3437c854ed586a0704d305bb4e9ea4518cfa264697066735822122061fe78e858f1b08b0a9944ed99e2c8eaec3f2a93024ffa133abf623cdd3efb0c64736f6c63430008230033a26469706673582212200a2bab77fec3817b82c426d2b5a88a72bb29c8754829d482ba8ebf71b75d258564736f6c6343000823003360808060405234601557610188908161001a8239f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63b628c33414610025575f80fd5b346100db576101e03660031901126100db5760043563ffffffff8116036100db5761004e6100df565b506100576100f6565b5061006061010d565b5060a4356001600160a01b038116036100db5760403660c31901126100db576080366101031901126100db576101a43567ffffffffffffffff81116100db576100ad903690600401610124565b50506101c4359067ffffffffffffffff82116100db576100d36020923690600401610124565b505060018152f35b5f80fd5b6024359067ffffffffffffffff821682036100db57565b6044359067ffffffffffffffff821682036100db57565b6084359067ffffffffffffffff821682036100db57565b9181601f840112156100db5782359167ffffffffffffffff83116100db57602083818601950101116100db5756fea2646970667358221220c8ebad2506285f5bd9495eabd9c19af21d099cd35dc2f36ef4b813731648d78364736f6c6343000823003360a034606e57601f6106c138819003918201601f19168301916001600160401b03831184841017607257808492602094604052833981010312606e57516001600160a01b03811690819003606e5760805260405161063a908161008782396080518181816047015261015e0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe6080806040526004361015610012575f80fd5b5f3560e01c90816311da52611461007a5750637b9a730114610032575f80fd5b34610076575f366003190112610076576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b5f80fd5b346100765760e0366003190112610076576004359063ffffffff8216809203610076576024356001600160a01b03811690819003610076576064356001600160a01b038116939084900361007657608435916fffffffffffffffffffffffffffffffff83168093036100765760a4359167ffffffffffffffff83168093036100765760c4359567ffffffffffffffff871161007657366023880112156100765786600401359567ffffffffffffffff871161007657366024888a010111610076575f546105a75760015f9081556390b7f6fd60e01b825260048201849052816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa9081156102f8575f91610400575b50602001516001600160a01b031633036103f1576040516311dd959760e01b8152602081600481335afa9081156102f8575f916103b6575b5060028110156103a2571580610350575b6103415760405163a9059cbb60e01b81528160048201528560248201526020816044815f885af19081156102f8575f91610312575b50156103035760e45f928760209860246040519b8c9a8b998a96630a137ff560e01b88526004880152338488015260443560448801526064870152608486015260c060a48601528260c486015201848401378181018301859052601f01601f19168101030193f19081156102f8575f916102b5575b506001600160e01b03191663f5ec800b60e01b016102a6575f8055005b63be51ced360e01b5f5260045ffd5b90506020813d6020116102f0575b816102d0602093836105b6565b8101031261007657516001600160e01b0319811681036100765781610289565b3d91506102c3565b6040513d5f823e3d90fd5b6312171d8360e31b5f5260045ffd5b610334915060203d60201161033a575b61032c81836105b6565b8101906105ec565b88610214565b503d610322565b63793cd94b60e11b5f5260045ffd5b5060405163078097c560e41b815260048101829052602081602481335afa9081156102f8575f91610383575b50156101df565b61039c915060203d60201161033a5761032c81836105b6565b8861037c565b634e487b7160e01b5f52602160045260245ffd5b90506020813d6020116103e9575b816103d1602093836105b6565b810103126100765751600281101561007657886101ce565b3d91506103c4565b63efc26b5760e01b5f5260045ffd5b90503d805f833e61041181836105b6565b8101906020818303126100765780519067ffffffffffffffff8211610076570190610180828203126100765760405191610180830183811067ffffffffffffffff82111761059357604052805163ffffffff81168103610076578352610479602082016105d8565b602084015261048a604082016105d8565b604084015260608101516002811015610076576060840152608081015160028110156100765760808401526104c160a082016105d8565b60a08401526104d260c082016105d8565b60c08401526104e360e082016105d8565b60e084015261010081015161010084015261012081015161012084015261014081015167ffffffffffffffff81168103610076576101408401526101608101519067ffffffffffffffff8211610076570181601f820112156100765780519067ffffffffffffffff82116105935760405192610569601f8401601f1916602001856105b6565b8284526020838301011161007657815f9260208093018386015e8301015261016082015288610196565b634e487b7160e01b5f52604160045260245ffd5b6336a4d21b60e11b5f5260045ffd5b90601f8019910116810190811067ffffffffffffffff82111761059357604052565b51906001600160a01b038216820361007657565b9081602091031261007657518015158103610076579056fea2646970667358221220471da8e4c35c05f627f1e0a051af71ad740b9270fcee31857b2c244613d3330c64736f6c63430008230033" } diff --git a/crates/node/src/cli.rs b/crates/node/src/cli.rs index b1c81e54f..2ddd4205b 100644 --- a/crates/node/src/cli.rs +++ b/crates/node/src/cli.rs @@ -391,13 +391,7 @@ mod tests { #[test] fn dev_is_parsed_by_the_top_level_cli() { - let parsed = ZoneCli::try_parse_from([ - "tempo-zone", - "dev", - "--dev.zone-gateway", - "0x0000000000000000000000000000000000000001", - ]) - .unwrap(); + let parsed = ZoneCli::try_parse_from(["tempo-zone", "dev"]).unwrap(); assert!(matches!(parsed, ZoneCli::Dev(_))); } diff --git a/crates/node/src/dev.rs b/crates/node/src/dev.rs index 20ff1097d..316ecf6c1 100644 --- a/crates/node/src/dev.rs +++ b/crates/node/src/dev.rs @@ -14,7 +14,7 @@ use alloy_signer_local::PrivateKeySigner; use alloy_sol_types::SolEvent; use tempo_alloy::TempoNetwork; use tempo_contracts::precompiles::{ITIP20, PATH_USD_ADDRESS}; -use tempo_zone_contracts::{ZONE_FACTORY_ADDRESS, ZoneFactory}; +use tempo_zone_contracts::{ZONE_FACTORY_ADDRESS, ZoneAccessMode, ZoneFactory, ZoneGatewayMode}; use zone_primitives::constants::zone_chain_id; use zone_sequencer::register_encryption_key; @@ -29,9 +29,13 @@ pub struct ProvisionConfig { pub factory: Option
, /// Initial TIP-20 enabled on the portal. pub initial_token: Address, + /// Initial account allowlist enforcement mode. + pub access_mode: ZoneAccessMode, + /// Initial callback gateway registration enforcement mode. + pub gateway_mode: ZoneGatewayMode, /// Initial callback-only ZoneGateway implementations. pub zone_gateways: Vec
, - /// Initial closed-loop portal account membership. + /// Initial portal membership (required for closed mode, retained but unenforced in open mode). pub allowed_accounts: Vec
, /// Public zone RPC URL registered on the portal. pub rpc_url: String, @@ -67,10 +71,20 @@ pub async fn provision_zone(config: ProvisionConfig) -> eyre::Result {} + _ => return Err(eyre::eyre!("invalid zone access mode")), + } + match gateway_mode { + ZoneGatewayMode::Enforced | ZoneGatewayMode::Open => {} + _ => return Err(eyre::eyre!("invalid zone gateway mode")), + } let dev_address = dev_key.address(); let wallet = EthereumWallet::from(dev_key.clone()); @@ -107,6 +121,8 @@ pub async fn provision_zone(config: ProvisionConfig) -> eyre::Result ZoneAccessMode { + match self { + Self::Closed => ZoneAccessMode::Closed, + Self::Open => ZoneAccessMode::Open, + } + } + + const fn label(self) -> &'static str { + match self { + Self::Closed => "closed", + Self::Open => "open", + } + } + } + + #[derive(Clone, Copy, Debug, clap::ValueEnum)] + enum DevGatewayMode { + Enforced, + Open, + } + + impl DevGatewayMode { + const fn contract_value(self) -> ZoneGatewayMode { + match self { + Self::Enforced => ZoneGatewayMode::Enforced, + Self::Open => ZoneGatewayMode::Open, + } + } + + const fn label(self) -> &'static str { + match self { + Self::Enforced => "enforced", + Self::Open => "open", + } + } + } /// Default dev private key (account #0 of the standard `test test ... junk` mnemonic). const DEFAULT_DEV_KEY: &str = @@ -270,8 +331,20 @@ mod command { #[arg(long = "dev.token", default_value_t = PATH_USD_ADDRESS)] initial_token: Address, + /// Account allowlist enforcement mode. + #[arg(long = "dev.access-mode", value_enum, default_value_t = DevAccessMode::Open)] + access_mode: DevAccessMode, + + /// Callback gateway registration enforcement mode. + #[arg( + long = "dev.gateway-mode", + value_enum, + default_value_t = DevGatewayMode::Open + )] + gateway_mode: DevGatewayMode, + /// Callback-only ZoneGateway implementation. Repeat for legacy/replacement support. - #[arg(long = "dev.zone-gateway", required = true)] + #[arg(long = "dev.zone-gateway")] zone_gateways: Vec
, /// Additional allowed portal account. Repeat for each account. @@ -322,12 +395,10 @@ mod command { prepare_datadir(&self.datadir)?; + let allowed_accounts = self.allowed_accounts.clone(); + // Provision on a scoped runtime; the node builds its own afterwards. let provisioned = { - let mut allowed_accounts = self.allowed_accounts.clone(); - if !allowed_accounts.contains(&dev_key.address()) { - allowed_accounts.push(dev_key.address()); - } let runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() .build()?; @@ -336,8 +407,10 @@ mod command { dev_key: dev_key.clone(), factory: self.factory_address, initial_token: self.initial_token, + access_mode: self.access_mode.contract_value(), + gateway_mode: self.gateway_mode.contract_value(), zone_gateways: self.zone_gateways.clone(), - allowed_accounts, + allowed_accounts: allowed_accounts.clone(), rpc_url: format!("http://{}:{}", self.http_addr, self.http_port), }))? }; @@ -355,7 +428,10 @@ mod command { "chainId": provisioned.chain_id, "portal": format!("{}", provisioned.portal), "initialToken": format!("{}", self.initial_token), + "accessMode": self.access_mode.label(), + "gatewayMode": self.gateway_mode.label(), "zoneGateways": self.zone_gateways.iter().map(ToString::to_string).collect::>(), + "allowedAccounts": allowed_accounts.iter().map(ToString::to_string).collect::>(), "admin": format!("{}", dev_key.address()), "sequencer": format!("{}", dev_key.address()), "sequencerKey": self.dev_key, diff --git a/crates/node/src/genesis.rs b/crates/node/src/genesis.rs index 123bb87d8..c799544ce 100644 --- a/crates/node/src/genesis.rs +++ b/crates/node/src/genesis.rs @@ -24,7 +24,7 @@ const ZONE_CONFIG_ADDRESS: Address = address!("0x1c00000000000000000000000000000 /// `tempoPortal` immutable occurrences in ZoneInbox deployed bytecode. const ZONE_INBOX_PORTAL_IMMUTABLES: usize = 4; /// `tempoPortal` immutable occurrences in ZoneConfig deployed bytecode. -const ZONE_CONFIG_PORTAL_IMMUTABLES: usize = 5; +const ZONE_CONFIG_PORTAL_IMMUTABLES: usize = 7; /// Parses the bundled zone genesis template. pub fn genesis_template() -> eyre::Result { diff --git a/crates/node/src/rpc.rs b/crates/node/src/rpc.rs index b2852d616..c83571dda 100644 --- a/crates/node/src/rpc.rs +++ b/crates/node/src/rpc.rs @@ -47,7 +47,8 @@ use tokio::{ use alloy_rpc_client::{ConnectionConfig, WebSocketConfig}; use tempo_zone_contracts::{ - DepositType, TEMPO_STATE_ADDRESS, ZONE_INBOX_ADDRESS, ZONE_TOKEN_ADDRESS, ZoneInbox, ZonePortal, + DepositType, TEMPO_STATE_ADDRESS, ZONE_CONFIG_ADDRESS, ZONE_INBOX_ADDRESS, ZONE_TOKEN_ADDRESS, + ZoneConfig, ZoneInbox, ZonePortal, }; use zone_rpc::{ auth::AuthContext, @@ -319,6 +320,30 @@ impl ZoneRpc { .map_err(internal) } + async fn zone_access_mode(&self) -> Result { + if self.config.zone_portal.is_zero() { + return Ok(0); + } + + ZoneConfig::new(ZONE_CONFIG_ADDRESS, &self.zone_provider) + .accessMode() + .call() + .await + .map_err(internal) + } + + async fn zone_gateway_mode(&self) -> Result { + if self.config.zone_portal.is_zero() { + return Ok(0); + } + + ZoneConfig::new(ZONE_CONFIG_ADDRESS, &self.zone_provider) + .gatewayMode() + .call() + .await + .map_err(internal) + } + async fn enforce_authorized( &self, request: &mut TempoTransactionRequest, @@ -901,8 +926,12 @@ where Box::pin(async move { let zone_tokens = self.zone_tokens().await?; let sequencer = self.zone_sequencer().await?; + let access_mode = self.zone_access_mode().await?; + let gateway_mode = self.zone_gateway_mode().await?; to_raw(&ZoneInfoResponse { zone_id: U64::from(self.config.zone_id), + access_mode: U64::from(access_mode), + gateway_mode: U64::from(gateway_mode), zone_tokens, sequencer, chain_id: U64::from(self.config.chain_id), diff --git a/crates/node/tests/it/demo_cross_zone.rs b/crates/node/tests/it/demo_cross_zone.rs index c726eccac..f26f62674 100644 --- a/crates/node/tests/it/demo_cross_zone.rs +++ b/crates/node/tests/it/demo_cross_zone.rs @@ -1,9 +1,11 @@ //! Demo Flow 3: Cross-Zone Transfer -use crate::utils::{L1TestNode, WithdrawalArgs, ZoneAccount, ZoneTestNode, spawn_sequencer}; +use crate::utils::{ + L1TestNode, WithdrawalArgs, ZoneAccount, ZoneCreationConfig, ZoneTestNode, spawn_sequencer, +}; use alloy::primitives::U256; use tempo_precompiles::PATH_USD_ADDRESS; -use tempo_zone_contracts::ZONE_TOKEN_ADDRESS; +use tempo_zone_contracts::{ZONE_TOKEN_ADDRESS, ZoneAccessMode, ZoneGatewayMode}; /// Longer timeout for real L1 tests. const L1_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); @@ -11,7 +13,7 @@ const L1_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); /// Cross-zone transfer via SwapAndDepositRouter: /// /// 1. Start L1 dev node. -/// 2. Deploy ZoneFactory, create zone_a and zone_b, deploy SwapAndDepositRouter. +/// 2. Deploy ZoneFactory, create open zone_a and zone_b, deploy SwapAndDepositRouter. /// 3. Start both zone nodes connected to L1. /// 4. Alice deposits pathUSD into zone_a. /// 5. Spawn sequencers for both zones. @@ -45,9 +47,24 @@ async fn test_cross_zone_send() -> eyre::Result<()> { let refund_burner = l1.signer_at(5).address(); // --- Step 2: Deploy L1 infrastructure --- - let (portal_a, portal_b, router) = l1 - .deploy_two_zones_with_sequencers(seq_a_signer.clone(), seq_b_signer.clone()) + let factory = l1.deploy_zone_factory().await?; + let portal_a = l1 + .create_zone_with_admin_sequencer_and_config( + factory, + l1.dev_address(), + seq_a_signer.address(), + ZoneCreationConfig::open(), + ) + .await?; + let portal_b = l1 + .create_zone_with_admin_sequencer_and_config( + factory, + l1.dev_address(), + seq_b_signer.address(), + ZoneCreationConfig::open(), + ) .await?; + let router = l1.deploy_router(factory).await?; // --- Step 3: Start both zone nodes --- let zone_a = ZoneTestNode::start_from_l1(l1.http_url(), l1.ws_url(), portal_a).await?; @@ -55,6 +72,18 @@ async fn test_cross_zone_send() -> eyre::Result<()> { zone_a.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; zone_b.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; + zone_a + .assert_access_mode(ZoneAccessMode::Open as u8) + .await?; + zone_b + .assert_access_mode(ZoneAccessMode::Open as u8) + .await?; + zone_a + .assert_gateway_mode(ZoneGatewayMode::Open as u8) + .await?; + zone_b + .assert_gateway_mode(ZoneGatewayMode::Open as u8) + .await?; // --- Step 4: Alice deposits into zone_a --- let mut alice = ZoneAccount::from_l1_and_zone(&l1, &zone_a, portal_a); @@ -87,6 +116,16 @@ async fn test_cross_zone_send() -> eyre::Result<()> { refund_burner, // Tempo refund target if Zone B later bounces the deposit ); alice.withdraw_with(args).await?; + let callback_succeeded = l1 + .wait_for_withdrawal_processed_status( + portal_a, + router, + PATH_USD_ADDRESS, + cross_amount, + std::time::Duration::from_secs(60), + ) + .await?; + eyre::ensure!(callback_succeeded, "cross-zone router callback failed"); // --- Step 7: Verify Bob received deposit on zone_b --- let cross_timeout = std::time::Duration::from_secs(60); diff --git a/crates/node/tests/it/l1_e2e.rs b/crates/node/tests/it/l1_e2e.rs index 97110279e..19cd59eae 100644 --- a/crates/node/tests/it/l1_e2e.rs +++ b/crates/node/tests/it/l1_e2e.rs @@ -6,15 +6,19 @@ use crate::utils::{ EncryptedRouterCallbackArgs, L1TestNode, PlaintextRouterCallbackArgs, STABLECOIN_DEX_ADDRESS, - WithdrawalArgs, ZoneAccount, ZoneTestNode, spawn_sequencer, + WithdrawalArgs, ZoneAccount, ZoneCreationConfig, ZoneTestNode, spawn_sequencer, }; use alloy::{ primitives::{Address, B256, U256}, providers::Provider, }; +use eyre::WrapErr as _; use std::time::Duration; use tempo_precompiles::PATH_USD_ADDRESS; -use tempo_zone_contracts::{TEMPO_STATE_ADDRESS, TempoState, ZONE_TOKEN_ADDRESS}; +use tempo_zone_contracts::{ + TEMPO_STATE_ADDRESS, TempoState, ZONE_TOKEN_ADDRESS, ZoneAccessMode, ZoneGatewayMode, + ZonePortal, +}; use zone_node::dev::{ProvisionConfig, provision_zone}; /// Longer timeout for real L1 tests — the L1 dev node produces blocks every @@ -51,12 +55,18 @@ async fn setup_same_zone_swap_fixture() -> eyre::Result { let factory = l1.deploy_zone_factory().await?; let portal_address = l1.create_zone(factory).await?; + let zone = ZoneTestNode::start_from_l1(l1.http_url(), l1.ws_url(), portal_address).await?; + zone.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; + let router = l1 .deploy_router_with_dex(factory, STABLECOIN_DEX_ADDRESS) .await?; - - let zone = ZoneTestNode::start_from_l1(l1.http_url(), l1.ws_url(), portal_address).await?; - zone.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; + let gateway_block = l1 + .set_zone_gateway_on_portal(portal_address, router, true) + .await?; + zone.wait_for_l2_tempo_finalized(gateway_block, L1_TIMEOUT) + .await?; + zone.assert_zone_gateway(router, true).await?; l1.enable_token_on_portal(portal_address, alpha).await?; l1.enable_token_on_portal(portal_address, beta).await?; @@ -162,6 +172,8 @@ async fn test_dev_provisioner_replays_initial_token_event() -> eyre::Result<()> dev_key: l1.dev_signer(), factory: None, initial_token, + access_mode: ZoneAccessMode::Closed, + gateway_mode: ZoneGatewayMode::Enforced, zone_gateways: vec![Address::repeat_byte(0x42)], allowed_accounts: vec![dev_address], rpc_url: String::new(), @@ -275,6 +287,487 @@ async fn test_deposit_via_real_l1() -> eyre::Result<()> { Ok(()) } +/// An open zone has no account allowlist: an unlisted account can complete the full +/// L1 deposit -> L2 mint -> L2 withdrawal -> L1 release loop. +#[tokio::test(flavor = "multi_thread")] +async fn test_open_mode_unlisted_account_roundtrip() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let l1 = L1TestNode::start().await?; + let factory = l1.deploy_zone_factory().await?; + let portal_address = l1 + .create_zone_with_admin_sequencer_and_config( + factory, + l1.admin_address(), + l1.dev_address(), + ZoneCreationConfig::open_with_enforced_gateways(), + ) + .await?; + let zone = ZoneTestNode::start_from_l1(l1.http_url(), l1.ws_url(), portal_address).await?; + zone.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; + + let portal = ZonePortal::new(portal_address, l1.provider()); + let account_address = l1.user_signer().address(); + assert_eq!( + portal.accessMode().call().await?, + ZoneAccessMode::Open as u8 + ); + assert!(!portal.allowedAccount(account_address).call().await?); + zone.assert_access_mode(ZoneAccessMode::Open as u8).await?; + + let mut account = ZoneAccount::from_l1_and_zone(&l1, &zone, portal_address); + let deposit_amount = 2_000_000u128; + let withdrawal_amount = 400_000u128; + l1.fund_user(account.address(), deposit_amount * 2).await?; + account.deposit(deposit_amount, L1_TIMEOUT, &zone).await?; + + // A second arbitrary account can also deposit without a membership update. + let second_signer = l1.signer_at(3); + let mut second_account = + ZoneAccount::with_signer(second_signer.clone(), &l1, &zone, portal_address); + l1.fund_user(second_signer.address(), 500_000).await?; + second_account.deposit(200_000, L1_TIMEOUT, &zone).await?; + + // The encrypted recipient is also arbitrary; only the depositor/refund address is public. + let encryption_key = k256::SecretKey::from(l1.dev_signer().credential()); + l1.set_sequencer_encryption_key(portal_address, &encryption_key) + .await?; + let encrypted_recipient = l1.signer_at(4).address(); + account + .deposit_encrypted(300_000, encrypted_recipient, B256::ZERO, L1_TIMEOUT, &zone) + .await?; + + let _sequencer_handle = spawn_sequencer(&l1, &zone, portal_address, l1.dev_signer()).await; + let arbitrary_l1_recipient = l1.signer_at(5).address(); + let mut withdrawal = WithdrawalArgs::new(withdrawal_amount); + withdrawal.to = Some(arbitrary_l1_recipient); + account.withdraw_with(withdrawal).await?; + l1.wait_for_withdrawal_on_l1( + portal_address, + arbitrary_l1_recipient, + withdrawal_amount, + Duration::from_secs(60), + ) + .await?; + + // Open mode does not weaken callback target validation. + let router = l1.deploy_router(factory).await?; + let unregistered = WithdrawalArgs::cross_zone_via_router( + 100_000, + router, + portal_address, + PATH_USD_ADDRESS, + account.address(), + account.address(), + ); + assert!( + account.simulate_withdraw_with(unregistered).await.is_err(), + "open mode accepted an unregistered callback target" + ); + + let gateway_block = l1 + .set_zone_gateway_on_portal(portal_address, router, true) + .await?; + zone.wait_for_l2_tempo_finalized(gateway_block, L1_TIMEOUT) + .await?; + zone.assert_zone_gateway(router, true).await?; + + let callback_amount = 150_000u128; + let balance_before_callback = zone + .balance_of(ZONE_TOKEN_ADDRESS, account.address()) + .await?; + let callback = WithdrawalArgs::cross_zone_via_router( + callback_amount, + router, + portal_address, + PATH_USD_ADDRESS, + account.address(), + account.address(), + ); + account.withdraw_with(callback).await?; + let balance_after_callback_request = zone + .balance_of(ZONE_TOKEN_ADDRESS, account.address()) + .await?; + eyre::ensure!( + balance_after_callback_request + U256::from(callback_amount) <= balance_before_callback, + "callback request did not burn its withdrawal amount" + ); + let callback_succeeded = l1 + .wait_for_withdrawal_processed_status( + portal_address, + router, + PATH_USD_ADDRESS, + callback_amount, + Duration::from_secs(60), + ) + .await?; + eyre::ensure!( + callback_succeeded, + "registered open-zone router callback was processed as a failure" + ); + zone.wait_for_balance( + ZONE_TOKEN_ADDRESS, + account.address(), + balance_after_callback_request + U256::from(callback_amount), + Duration::from_secs(60), + ) + .await?; + l1.assert_withdrawal_processed_with_status( + portal_address, + router, + PATH_USD_ADDRESS, + callback_amount, + true, + ) + .await?; + + Ok(()) +} + +/// Closed mode enforces the exact configured set for the deposit caller/refund recipient +/// and for a plain withdrawal recipient. +#[tokio::test(flavor = "multi_thread")] +async fn test_closed_mode_rejects_unlisted_deposit_and_withdrawal_recipient() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let l1 = L1TestNode::start().await?; + let portal_address = l1.deploy_zone().await?; + let zone = ZoneTestNode::start_from_l1(l1.http_url(), l1.ws_url(), portal_address).await?; + zone.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; + zone.assert_access_mode(ZoneAccessMode::Closed as u8) + .await?; + + let outsider_signer = l1.signer_at(3); + let outsider = outsider_signer.address(); + let portal = ZonePortal::new(portal_address, l1.provider()); + assert!(!portal.allowedAccount(outsider).call().await?); + + let mut outsider_account = + ZoneAccount::with_signer(outsider_signer, &l1, &zone, portal_address); + l1.fund_user(outsider, 1_000_000).await?; + assert!( + outsider_account + .simulate_deposit(500_000, outsider, outsider) + .await + .is_err(), + "closed zone accepted an unlisted deposit caller/refund recipient" + ); + + let mut allowed_account = ZoneAccount::from_l1_and_zone(&l1, &zone, portal_address); + l1.fund_user(allowed_account.address(), 1_000_000).await?; + + { + use tempo_contracts::precompiles::ITIP20; + let provider = l1.provider_with_signer(l1.user_signer()); + ITIP20::new(PATH_USD_ADDRESS, &provider) + .approve(portal_address, U256::MAX) + .send() + .await? + .get_receipt() + .await?; + let portal = ZonePortal::new(portal_address, &provider); + assert!( + portal + .deposit( + PATH_USD_ADDRESS, + allowed_account.address(), + 100_000, + B256::ZERO, + outsider, + ) + .call() + .await + .is_err(), + "closed zone accepted an unlisted Tempo refund recipient" + ); + } + + let add_block = l1 + .set_allowed_account_on_portal(portal_address, outsider, true) + .await?; + zone.wait_for_l2_tempo_finalized(add_block, L1_TIMEOUT) + .await?; + zone.assert_allowed_account(outsider, true).await?; + outsider_account.deposit(500_000, L1_TIMEOUT, &zone).await?; + + let remove_block = l1 + .set_allowed_account_on_portal(portal_address, outsider, false) + .await?; + zone.wait_for_l2_tempo_finalized(remove_block, L1_TIMEOUT) + .await?; + zone.assert_allowed_account(outsider, false).await?; + assert!( + outsider_account + .simulate_deposit(100_000, outsider, outsider) + .await + .is_err(), + "closed zone accepted a new deposit after membership removal" + ); + + allowed_account.deposit(500_000, L1_TIMEOUT, &zone).await?; + let mut args = WithdrawalArgs::new(100_000); + args.to = Some(outsider); + assert!( + allowed_account.simulate_withdraw_with(args).await.is_err(), + "closed zone accepted an unlisted plain withdrawal recipient" + ); + + Ok(()) +} + +/// Account and gateway enforcement can be changed independently without rewriting either set. +#[tokio::test(flavor = "multi_thread")] +async fn test_access_and_gateway_modes_are_mutable_and_independent() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let l1 = L1TestNode::start().await?; + let factory = l1.deploy_zone_factory().await?; + let portal_address = l1.create_zone(factory).await?; + let zone = ZoneTestNode::start_from_l1(l1.http_url(), l1.ws_url(), portal_address).await?; + zone.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; + zone.assert_access_mode(ZoneAccessMode::Closed as u8) + .await?; + zone.assert_gateway_mode(ZoneGatewayMode::Enforced as u8) + .await?; + + let outsider_signer = l1.signer_at(3); + let outsider = outsider_signer.address(); + let mut outsider_account = + ZoneAccount::with_signer(outsider_signer, &l1, &zone, portal_address); + l1.fund_user(outsider, 10_000_000).await?; + assert!( + outsider_account + .simulate_deposit(5_000_000, outsider, outsider) + .await + .is_err(), + "closed access accepted an unlisted depositor" + ); + + let open_access_block = l1 + .set_access_mode_on_portal(portal_address, ZoneAccessMode::Open) + .await?; + zone.wait_for_l2_tempo_finalized(open_access_block, L1_TIMEOUT) + .await?; + zone.assert_access_mode(ZoneAccessMode::Open as u8).await?; + zone.assert_allowed_account(outsider, true).await?; + outsider_account + .deposit(5_000_000, L1_TIMEOUT, &zone) + .await?; + + let router = l1.deploy_router(factory).await?; + let callback = WithdrawalArgs::cross_zone_via_router( + 100_000, + router, + portal_address, + PATH_USD_ADDRESS, + outsider, + outsider, + ); + assert!( + outsider_account + .simulate_withdraw_with(callback.clone()) + .await + .is_err(), + "open account access disabled gateway registration enforcement" + ); + + let open_gateway_block = l1 + .set_gateway_mode_on_portal(portal_address, ZoneGatewayMode::Open) + .await?; + zone.wait_for_l2_tempo_finalized(open_gateway_block, L1_TIMEOUT) + .await?; + zone.assert_gateway_mode(ZoneGatewayMode::Open as u8) + .await?; + outsider_account + .withdraw_with(callback.clone()) + .await + .wrap_err("callback should pass after opening gateway mode")?; + + let closed_access_block = l1 + .set_access_mode_on_portal(portal_address, ZoneAccessMode::Closed) + .await?; + zone.wait_for_l2_tempo_finalized(closed_access_block, L1_TIMEOUT) + .await?; + zone.assert_access_mode(ZoneAccessMode::Closed as u8) + .await?; + assert!( + outsider_account + .simulate_deposit(100_000, outsider, outsider) + .await + .is_err(), + "re-closed access did not restore account allowlist enforcement" + ); + outsider_account + .withdraw_with(callback.clone()) + .await + .wrap_err("callback should still pass after reclosing account access")?; + + let enforced_gateway_block = l1 + .set_gateway_mode_on_portal(portal_address, ZoneGatewayMode::Enforced) + .await?; + zone.wait_for_l2_tempo_finalized(enforced_gateway_block, L1_TIMEOUT) + .await?; + assert!( + outsider_account + .simulate_withdraw_with(callback) + .await + .is_err(), + "re-enabled gateway enforcement accepted an unregistered callback target" + ); + + Ok(()) +} + +/// A plain withdrawal accepted while its recipient is allowed must still drain after +/// that recipient is revoked. It bounces to the private fallback recipient and does +/// not block the next valid withdrawal in the FIFO. +#[tokio::test(flavor = "multi_thread")] +async fn test_queued_plain_withdrawal_bounces_after_recipient_revocation() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let l1 = L1TestNode::start().await?; + let portal_address = l1.deploy_zone().await?; + let zone = ZoneTestNode::start_from_l1(l1.http_url(), l1.ws_url(), portal_address).await?; + zone.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; + + let mut account = ZoneAccount::from_l1_and_zone(&l1, &zone, portal_address); + let revoked_recipient = account.address(); + let trailing_recipient = l1.admin_address(); + let deposit_amount = 700_000u128; + let bounced_amount = 200_000u128; + let trailing_amount = 100_000u128; + l1.fund_user(account.address(), deposit_amount).await?; + account.deposit(deposit_amount, L1_TIMEOUT, &zone).await?; + + account.withdraw(bounced_amount).await?; + let mut trailing = WithdrawalArgs::new(trailing_amount); + trailing.to = Some(trailing_recipient); + account.withdraw_with(trailing).await?; + let balance_after_queue = zone + .balance_of(ZONE_TOKEN_ADDRESS, account.address()) + .await?; + let trailing_l1_before = l1.balance_of(PATH_USD_ADDRESS, trailing_recipient).await?; + + let revocation_block = l1 + .set_allowed_account_on_portal(portal_address, revoked_recipient, false) + .await?; + zone.wait_for_l2_tempo_finalized(revocation_block, L1_TIMEOUT) + .await?; + zone.assert_allowed_account(revoked_recipient, false) + .await?; + + let _sequencer = spawn_sequencer(&l1, &zone, portal_address, l1.dev_signer()).await; + zone.wait_for_balance( + ZONE_TOKEN_ADDRESS, + account.address(), + balance_after_queue + U256::from(bounced_amount), + Duration::from_secs(60), + ) + .await?; + l1.wait_for_balance( + PATH_USD_ADDRESS, + trailing_recipient, + trailing_l1_before + U256::from(trailing_amount), + Duration::from_secs(60), + ) + .await?; + l1.assert_withdrawals_processed_in_order( + portal_address, + &[ + (revoked_recipient, PATH_USD_ADDRESS, bounced_amount, false), + (trailing_recipient, PATH_USD_ADDRESS, trailing_amount, true), + ], + ) + .await?; + + Ok(()) +} + +/// A callback accepted while its target is registered must still drain if the +/// gateway is revoked before L1 processing. The callback bounces, and the next +/// plain withdrawal proves the failed head did not block the FIFO. +#[tokio::test(flavor = "multi_thread")] +async fn test_queued_callback_bounces_after_gateway_revocation() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let mut fixture = setup_same_zone_swap_fixture().await?; + let callback_amount = 200_000u128; + let trailing_amount = 100_000u128; + let trailing_recipient = fixture.l1.admin_address(); + + let callback = WithdrawalArgs::cross_zone_via_router( + callback_amount, + fixture.router, + fixture.portal_address, + PATH_USD_ADDRESS, + fixture.account.address(), + fixture.account.address(), + ); + fixture.account.withdraw_with(callback).await?; + let mut trailing = WithdrawalArgs::new(trailing_amount); + trailing.to = Some(trailing_recipient); + fixture.account.withdraw_with(trailing).await?; + let balance_after_queue = fixture + .zone + .balance_of(ZONE_TOKEN_ADDRESS, fixture.account.address()) + .await?; + let trailing_l1_before = fixture + .l1 + .balance_of(PATH_USD_ADDRESS, trailing_recipient) + .await?; + + let revocation_block = fixture + .l1 + .set_zone_gateway_on_portal(fixture.portal_address, fixture.router, false) + .await?; + fixture + .zone + .wait_for_l2_tempo_finalized(revocation_block, L1_TIMEOUT) + .await?; + fixture + .zone + .assert_zone_gateway(fixture.router, false) + .await?; + + let _sequencer = spawn_sequencer( + &fixture.l1, + &fixture.zone, + fixture.portal_address, + fixture.l1.dev_signer(), + ) + .await; + fixture + .zone + .wait_for_balance( + ZONE_TOKEN_ADDRESS, + fixture.account.address(), + balance_after_queue + U256::from(callback_amount), + Duration::from_secs(60), + ) + .await?; + fixture + .l1 + .wait_for_balance( + PATH_USD_ADDRESS, + trailing_recipient, + trailing_l1_before + U256::from(trailing_amount), + Duration::from_secs(60), + ) + .await?; + fixture + .l1 + .assert_withdrawals_processed_in_order( + fixture.portal_address, + &[ + (fixture.router, PATH_USD_ADDRESS, callback_amount, false), + (trailing_recipient, PATH_USD_ADDRESS, trailing_amount, true), + ], + ) + .await?; + + Ok(()) +} + /// Cross-zone withdrawal via the SwapAndDepositRouter: /// /// 1. Start L1 dev node. @@ -309,7 +802,7 @@ async fn test_cross_zone_withdrawal() -> eyre::Result<()> { // --- Step 2: Deploy L1 infrastructure (factory, two portals, router) --- let (portal_a, portal_b, router) = l1 - .deploy_two_zones_with_sequencers(seq_a_signer.clone(), seq_b_signer.clone()) + .deploy_two_open_zones_with_sequencers(seq_a_signer.clone(), seq_b_signer.clone()) .await?; // --- Step 3: Start both zone nodes --- @@ -318,6 +811,18 @@ async fn test_cross_zone_withdrawal() -> eyre::Result<()> { zone_a.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; zone_b.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; + zone_a + .assert_access_mode(ZoneAccessMode::Open as u8) + .await?; + zone_b + .assert_access_mode(ZoneAccessMode::Open as u8) + .await?; + zone_a + .assert_gateway_mode(ZoneGatewayMode::Open as u8) + .await?; + zone_b + .assert_gateway_mode(ZoneGatewayMode::Open as u8) + .await?; // --- Step 4: Deposit into zone_a --- let mut account_a = ZoneAccount::from_l1_and_zone(&l1, &zone_a, portal_a); @@ -334,14 +839,13 @@ async fn test_cross_zone_withdrawal() -> eyre::Result<()> { // --- Step 5: Cross-zone withdrawal: zone_a → router → zone_b --- let cross_amount: u128 = 400_000; // 0.4 pathUSD - let refund_burner_a_to_b = l1.signer_at(5).address(); let args_a_to_b = WithdrawalArgs::cross_zone_via_router( cross_amount, router, portal_b, PATH_USD_ADDRESS, account_a.address(), - refund_burner_a_to_b, + account_a.address(), ); account_a.withdraw_with(args_a_to_b).await?; @@ -377,14 +881,13 @@ async fn test_cross_zone_withdrawal() -> eyre::Result<()> { // --- Step 7: Cross-zone withdrawal: zone_b → router → zone_a --- let mut account_b = ZoneAccount::from_l1_and_zone(&l1, &zone_b, portal_b); let reverse_amount: u128 = 200_000; // 0.2 pathUSD - let refund_burner_b_to_a = l1.signer_at(6).address(); let args_b_to_a = WithdrawalArgs::cross_zone_via_router( reverse_amount, router, portal_a, PATH_USD_ADDRESS, account_b.address(), - refund_burner_b_to_a, + account_b.address(), ); account_b.withdraw_with(args_b_to_a).await?; @@ -434,7 +937,7 @@ async fn test_cross_zone_encrypted_router_tempo_refund_recipient() -> eyre::Resu let refund_burner = l1.signer_at(5).address(); let (portal_a, portal_b, router) = l1 - .deploy_two_zones_with_sequencers(seq_a_signer.clone(), seq_b_signer.clone()) + .deploy_two_open_zones_with_sequencers(seq_a_signer.clone(), seq_b_signer.clone()) .await?; let policy_id = l1.create_blacklist_policy().await?; @@ -452,6 +955,18 @@ async fn test_cross_zone_encrypted_router_tempo_refund_recipient() -> eyre::Resu zone_a.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; zone_b.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; + zone_a + .assert_access_mode(ZoneAccessMode::Open as u8) + .await?; + zone_b + .assert_access_mode(ZoneAccessMode::Open as u8) + .await?; + zone_a + .assert_gateway_mode(ZoneGatewayMode::Open as u8) + .await?; + zone_b + .assert_gateway_mode(ZoneGatewayMode::Open as u8) + .await?; let encryption_key = k256::SecretKey::from(seq_b_signer.credential()); l1.set_sequencer_encryption_key_with_signer(portal_b, &encryption_key, seq_b_signer.clone()) @@ -578,7 +1093,7 @@ async fn test_swap_and_deposit_into_same_zone() -> eyre::Result<()> { token_out: fixture.beta, target_portal: fixture.portal_address, recipient: fixture.account.address(), - tempo_refund_recipient: fixture.l1.signer_at(5).address(), + tempo_refund_recipient: fixture.account.address(), memo: B256::ZERO, min_amount_out: expected_beta, }); @@ -676,7 +1191,7 @@ async fn test_swap_and_deposit_into_same_zone_bounces_back_on_plaintext_deposit_ token_out: fixture.beta, target_portal: fixture.portal_address, recipient: fixture.account.address(), - tempo_refund_recipient: fixture.l1.signer_at(5).address(), + tempo_refund_recipient: fixture.account.address(), memo: B256::ZERO, min_amount_out: expected_beta, }); @@ -795,7 +1310,7 @@ async fn test_swap_and_deposit_into_same_zone_bounces_back_on_encrypted_deposit_ target_portal: fixture.portal_address, key_index, encrypted, - tempo_refund_recipient: fixture.l1.signer_at(5).address(), + tempo_refund_recipient: fixture.account.address(), min_amount_out: expected_beta, }); fixture @@ -1145,6 +1660,13 @@ async fn test_l1_policy_operations_and_zone_advancement() -> eyre::Result<()> { let zone = ZoneTestNode::start_from_l1(l1.http_url(), l1.ws_url(), portal_address).await?; zone.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; + let membership_block = l1 + .set_allowed_account_on_portal(portal_address, clean_user, true) + .await?; + zone.wait_for_l2_tempo_finalized(membership_block, L1_TIMEOUT) + .await?; + zone.assert_allowed_account(clean_user, true).await?; + // Zone should have produced blocks — policy changes on L1 don't break zone let zone_block = zone.provider().get_block_number().await?; assert!( @@ -1169,6 +1691,104 @@ async fn test_l1_policy_operations_and_zone_advancement() -> eyre::Result<()> { Ok(()) } +/// A plaintext deposit accepted on L1 but rejected by the zone's current token +/// policy must complete the zone -> batch -> L1 bounceback loop and refund the +/// explicitly selected Tempo recipient. +#[tokio::test(flavor = "multi_thread")] +async fn test_plaintext_deposit_policy_failure_bounces_to_tempo_refund_recipient() +-> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + use tempo_contracts::precompiles::{ITIP20, ITIP403Registry::PolicyType}; + + let l1 = L1TestNode::start().await?; + let portal_address = l1.deploy_zone().await?; + let policy_id = l1.create_blacklist_policy().await?; + l1.change_transfer_policy_id(PATH_USD_ADDRESS, policy_id) + .await?; + + let zone = ZoneTestNode::start_from_l1(l1.http_url(), l1.ws_url(), portal_address).await?; + zone.wait_for_l2_tempo_finalized(0, L1_TIMEOUT).await?; + + // Keep the recipient authorized on Tempo so the portal accepts and escrows + // the deposit, while pinning the zone policy view to the rejecting state. + let rejected_recipient = l1.admin_address(); + assert!(l1.is_authorized(policy_id, rejected_recipient).await?); + let policy_block = l1.provider().get_block_number().await?; + { + let policy_cache = zone.policy_cache(); + let mut cache = policy_cache.write(); + cache.set_policy_type(policy_id, PolicyType::BLACKLIST); + cache.set_token_policy(PATH_USD_ADDRESS, policy_block, policy_id); + cache.set_policy_status(policy_id, rejected_recipient, policy_block, true); + } + + let depositor = l1.user_signer(); + let tempo_refund_recipient = depositor.address(); + let deposit_amount = 1_000_000u128; + l1.fund_user(tempo_refund_recipient, deposit_amount).await?; + let provider = l1.provider_with_signer(depositor); + ITIP20::new(PATH_USD_ADDRESS, &provider) + .approve(portal_address, U256::MAX) + .send() + .await? + .get_receipt() + .await?; + let refund_balance_before = l1 + .balance_of(PATH_USD_ADDRESS, tempo_refund_recipient) + .await?; + + let _sequencer = spawn_sequencer(&l1, &zone, portal_address, l1.dev_signer()).await; + let portal = ZonePortal::new(portal_address, &provider); + let receipt = portal + .deposit( + PATH_USD_ADDRESS, + rejected_recipient, + deposit_amount, + B256::ZERO, + tempo_refund_recipient, + ) + .send() + .await? + .get_receipt() + .await?; + eyre::ensure!( + receipt.status(), + "plaintext L1 deposit failed before queueing" + ); + + l1.wait_for_balance( + PATH_USD_ADDRESS, + tempo_refund_recipient, + refund_balance_before, + Duration::from_secs(60), + ) + .await?; + assert_eq!( + zone.balance_of(ZONE_TOKEN_ADDRESS, rejected_recipient) + .await?, + U256::ZERO, + "policy-rejected plaintext recipient should not be minted" + ); + + let bouncebacks = portal + .DepositBounceBack_filter() + .from_block(0) + .query() + .await?; + let bounced = bouncebacks.iter().any(|(event, _)| { + event.tempoRefundRecipient == tempo_refund_recipient + && event.token == PATH_USD_ADDRESS + && event.amount + event.bouncebackFee == deposit_amount + }); + eyre::ensure!( + bounced, + "expected completed plaintext deposit bounceback event" + ); + + Ok(()) +} + /// Test that an encrypted deposit whose decrypted recipient is blacklisted /// gets bounced back to the sender on L1 instead of minting to the recipient. /// @@ -1229,6 +1849,8 @@ async fn test_encrypted_deposit_blacklisted_recipient() -> eyre::Result<()> { cache.set_policy_status(policy_id, blacklisted_recipient, l1_block, true); } + let _sequencer = spawn_sequencer(&l1, &zone, portal_address, sequencer_signer.clone()).await; + // --- Step 6: Make an encrypted deposit targeting the blacklisted recipient --- let depositor = ZoneAccount::from_l1_and_zone(&l1, &zone, portal_address); let deposit_amount: u128 = 1_000_000; diff --git a/crates/node/tests/it/private_rpc_e2e.rs b/crates/node/tests/it/private_rpc_e2e.rs index 97f199099..d82e02c30 100644 --- a/crates/node/tests/it/private_rpc_e2e.rs +++ b/crates/node/tests/it/private_rpc_e2e.rs @@ -1121,6 +1121,8 @@ async fn test_zone_metadata_methods() -> eyre::Result<()> { zone_info["result"]["zoneId"].as_str().unwrap(), format!("0x{:x}", ctx.config.zone_id), ); + assert_eq!(zone_info["result"]["accessMode"], "0x0"); + assert_eq!(zone_info["result"]["gatewayMode"], "0x0"); assert_eq!( zone_info["result"]["zoneTokens"] .as_array() @@ -1325,6 +1327,13 @@ async fn test_zone_get_deposit_status_encrypted_tempo_refund_recipient() -> eyre let tempo_refund_signer = l1.signer_at(3); let tempo_refund_recipient = tempo_refund_signer.address(); + let membership_block = l1 + .set_allowed_account_on_portal(portal_address, tempo_refund_recipient, true) + .await?; + ctx.zone + .wait_for_l2_tempo_finalized(membership_block, DEFAULT_TIMEOUT) + .await?; + let depositor = ZoneAccount::with_signer(depositor_signer.clone(), l1, &ctx.zone, portal_address); let deposit_amount: u128 = 1_000_000; diff --git a/crates/node/tests/it/utils.rs b/crates/node/tests/it/utils.rs index 9da5b8aa8..5d5821778 100644 --- a/crates/node/tests/it/utils.rs +++ b/crates/node/tests/it/utils.rs @@ -48,6 +48,10 @@ use zone_l1::{ }; use zone_node::ZoneNode; use zone_p2p::{P2pConfig, Role}; +use zone_primitives::constants::{ + ACCOUNT_ALLOWLIST_ENFORCED_FLAG, GATEWAY_ALLOWLIST_ENFORCED_FLAG, PORTAL_ACCESS_MODE_SLOT, + PORTAL_ALLOWED_ACCOUNT_SLOT, PORTAL_ZONE_GATEWAY_SLOT, +}; #[path = "../../../rpc/test-utils/auth_tokens.rs"] mod auth_tokens; @@ -131,6 +135,10 @@ fn portal_token_config_slot(token: Address) -> B256 { keccak256((token, portal_token_configs_slot).abi_encode()) } +fn portal_address_mapping_slot(account: Address, mapping_slot: B256) -> B256 { + keccak256((account, mapping_slot).abi_encode()) +} + fn enabled_deposits_active_token_config() -> B256 { let mut value = [0u8; 32]; value[30] = 1; // TokenConfig.depositsActive @@ -396,6 +404,60 @@ impl ZoneTestNode { .erased() } + /// Assert the gateway view exposed by the L2 ZoneConfig predeploy. + pub(crate) async fn assert_zone_gateway( + &self, + gateway: Address, + expected: bool, + ) -> eyre::Result<()> { + use tempo_zone_contracts::{ZONE_CONFIG_ADDRESS, ZoneConfig}; + let config = ZoneConfig::new(ZONE_CONFIG_ADDRESS, self.provider()); + eyre::ensure!( + config.isZoneGateway(gateway).call().await? == expected, + "ZoneConfig gateway state for {gateway} did not equal {expected}" + ); + Ok(()) + } + + /// Assert the current account mode exposed by the L2 ZoneConfig predeploy. + pub(crate) async fn assert_access_mode(&self, expected: u8) -> eyre::Result<()> { + use tempo_zone_contracts::{ZONE_CONFIG_ADDRESS, ZoneConfig}; + let config = ZoneConfig::new(ZONE_CONFIG_ADDRESS, self.provider()); + let actual = config.accessMode().call().await?; + eyre::ensure!( + actual == expected, + "ZoneConfig access mode {actual} did not equal {expected}" + ); + Ok(()) + } + + /// Assert the gateway mode exposed by the L2 ZoneConfig predeploy. + pub(crate) async fn assert_gateway_mode(&self, expected: u8) -> eyre::Result<()> { + use tempo_zone_contracts::{ZONE_CONFIG_ADDRESS, ZoneConfig}; + let config = ZoneConfig::new(ZONE_CONFIG_ADDRESS, self.provider()); + let actual = config.gatewayMode().call().await?; + eyre::ensure!( + actual == expected, + "ZoneConfig gateway mode {actual} did not equal {expected}" + ); + Ok(()) + } + + /// Assert the mode-aware account authorization view exposed by L2 ZoneConfig. + pub(crate) async fn assert_allowed_account( + &self, + account: Address, + expected: bool, + ) -> eyre::Result<()> { + use tempo_zone_contracts::{ZONE_CONFIG_ADDRESS, ZoneConfig}; + let config = ZoneConfig::new(ZONE_CONFIG_ADDRESS, self.provider()); + eyre::ensure!( + config.isAllowedAccount(account).call().await? == expected, + "ZoneConfig account state for {account} did not equal {expected}" + ); + Ok(()) + } + /// Returns a handle to the deposit queue for injecting synthetic L1 blocks. pub(crate) fn deposit_queue(&self) -> &DepositQueue { &self.deposit_queue @@ -941,6 +1003,44 @@ pub(crate) struct L1TestNode { _tasks: Runtime, } +/// Explicit account-access and callback-gateway configuration for a test zone. +#[derive(Clone, Debug)] +pub(crate) struct ZoneCreationConfig { + pub(crate) access_mode: tempo_zone_contracts::ZoneAccessMode, + pub(crate) gateway_mode: tempo_zone_contracts::ZoneGatewayMode, + pub(crate) allowed_accounts: Vec
, + pub(crate) zone_gateways: Vec
, +} + +impl ZoneCreationConfig { + pub(crate) fn closed(mut allowed_accounts: Vec
) -> Self { + allowed_accounts.sort_unstable(); + allowed_accounts.dedup(); + Self { + access_mode: tempo_zone_contracts::ZoneAccessMode::Closed, + gateway_mode: tempo_zone_contracts::ZoneGatewayMode::Enforced, + allowed_accounts, + zone_gateways: Vec::new(), + } + } + + pub(crate) fn open() -> Self { + Self { + access_mode: tempo_zone_contracts::ZoneAccessMode::Open, + gateway_mode: tempo_zone_contracts::ZoneGatewayMode::Open, + allowed_accounts: Vec::new(), + zone_gateways: Vec::new(), + } + } + + pub(crate) fn open_with_enforced_gateways() -> Self { + Self { + gateway_mode: tempo_zone_contracts::ZoneGatewayMode::Enforced, + ..Self::open() + } + } +} + impl L1TestNode { /// Returns the HTTP RPC URL for this L1 node. pub(crate) fn http_url(&self) -> &url::Url { @@ -1127,6 +1227,67 @@ impl L1TestNode { Ok(()) } + /// Wait for a matching withdrawal result and return its callback status. + pub(crate) async fn wait_for_withdrawal_processed_status( + &self, + portal_address: Address, + to: Address, + token: Address, + amount: u128, + timeout: Duration, + ) -> eyre::Result { + use tempo_zone_contracts::ZonePortal; + let portal = ZonePortal::new(portal_address, self.provider()); + poll_until(timeout, DEFAULT_POLL, "WithdrawalProcessed event", || { + let portal = &portal; + async move { + let events = portal + .WithdrawalProcessed_filter() + .from_block(0) + .query() + .await?; + Ok(events + .iter() + .find(|(event, _)| { + event.to == to && event.token == token && event.amount == amount + }) + .map(|(event, _)| event.callbackSuccess)) + } + }) + .await + } + + /// Assert that matching withdrawal results were emitted in FIFO order. + pub(crate) async fn assert_withdrawals_processed_in_order( + &self, + portal_address: Address, + expected: &[(Address, Address, u128, bool)], + ) -> eyre::Result<()> { + use tempo_zone_contracts::ZonePortal; + let portal = ZonePortal::new(portal_address, self.provider()); + let events = portal + .WithdrawalProcessed_filter() + .from_block(0) + .query() + .await?; + let mut expected_index = 0; + for (event, _) in events { + if let Some((to, token, amount, success)) = expected.get(expected_index) + && event.to == *to + && event.token == *token + && event.amount == *amount + && event.callbackSuccess == *success + { + expected_index += 1; + } + } + eyre::ensure!( + expected_index == expected.len(), + "expected ordered withdrawal results {expected:?}, matched only {expected_index}" + ); + Ok(()) + } + /// Returns an HTTP provider with the dev account wallet attached. pub(crate) fn dev_provider(&self) -> alloy_provider::DynProvider { ProviderBuilder::new() @@ -1146,6 +1307,17 @@ impl L1TestNode { .erased() } + /// Returns an HTTP provider with an explicit signer attached. + pub(crate) fn provider_with_signer( + &self, + signer: alloy_signer_local::PrivateKeySigner, + ) -> alloy_provider::DynProvider { + ProviderBuilder::new() + .wallet(signer) + .connect_http(self.http_url.clone()) + .erased() + } + /// Deploy the ZoneFactory and create a zone in one step. /// /// Combines [`deploy_zone_factory`](Self::deploy_zone_factory) and @@ -1311,11 +1483,17 @@ impl L1TestNode { /// /// [`admin_address`]: Self::admin_address pub(crate) async fn create_zone(&self, factory_address: Address) -> eyre::Result
{ + let config = ZoneCreationConfig::closed(vec![ + self.admin_address(), + self.dev_address(), + self.user_signer().address(), + ]); let portal = self - .create_zone_with_admin_and_sequencer( + .create_zone_with_admin_sequencer_and_config( factory_address, self.admin_address(), self.dev_address(), + config, ) .await?; // The admin is not pre-funded; give it pathUSD to pay for gas on @@ -1324,12 +1502,13 @@ impl L1TestNode { Ok(portal) } - /// Create a zone on an existing ZoneFactory with explicit admin and sequencer addresses. - pub(crate) async fn create_zone_with_admin_and_sequencer( + /// Create a zone with an exact access-mode, membership, and gateway configuration. + pub(crate) async fn create_zone_with_admin_sequencer_and_config( &self, factory_address: Address, admin: Address, sequencer: Address, + config: ZoneCreationConfig, ) -> eyre::Result
{ use tempo_precompiles::PATH_USD_ADDRESS; use tempo_zone_contracts::ZoneFactory; @@ -1351,19 +1530,14 @@ impl L1TestNode { let genesis_tempo_block_hash = keccak256(&rlp_buf); let verifier_address = factory.verifier().call().await?; - let zone_gateway = Address::repeat_byte(0xfe); - let mut allowed_accounts = vec![admin, sequencer]; - for index in 0..10 { - allowed_accounts.push(self.signer_at(index).address()); - } - allowed_accounts.sort_unstable(); - allowed_accounts.dedup(); let receipt = factory .createZone(ZoneFactory::CreateZoneParams { admin, initialToken: PATH_USD_ADDRESS, - allowedAccounts: allowed_accounts, - zoneGateways: vec![zone_gateway], + accessMode: config.access_mode, + gatewayMode: config.gateway_mode, + allowedAccounts: config.allowed_accounts, + zoneGateways: config.zone_gateways, sequencer, verifier: verifier_address, zoneParams: ZoneFactory::ZoneParams { @@ -1431,25 +1605,27 @@ impl L1TestNode { .ok_or_else(|| eyre::eyre!("SwapAndDepositRouter deployment missing contract address")) } - /// Deploy L1 infrastructure for a two-zone cross-zone test with separate sequencers. - pub(crate) async fn deploy_two_zones_with_sequencers( + /// Deploy two open zones for cross-zone routing, with separate sequencers. + pub(crate) async fn deploy_two_open_zones_with_sequencers( &self, sequencer_a: alloy_signer_local::PrivateKeySigner, sequencer_b: alloy_signer_local::PrivateKeySigner, ) -> eyre::Result<(Address, Address, Address)> { let factory = self.deploy_zone_factory().await?; let portal_a = self - .create_zone_with_admin_and_sequencer( + .create_zone_with_admin_sequencer_and_config( factory, self.dev_address(), sequencer_a.address(), + ZoneCreationConfig::open(), ) .await?; let portal_b = self - .create_zone_with_admin_and_sequencer( + .create_zone_with_admin_sequencer_and_config( factory, self.dev_address(), sequencer_b.address(), + ZoneCreationConfig::open(), ) .await?; let router = self.deploy_router(factory).await?; @@ -1515,6 +1691,134 @@ impl L1TestNode { Ok(()) } + /// Update a portal callback gateway with the default distinct admin signer. + pub(crate) async fn set_zone_gateway_on_portal( + &self, + portal_address: Address, + gateway: Address, + enabled: bool, + ) -> eyre::Result { + self.set_zone_gateway_on_portal_with_signer( + portal_address, + gateway, + enabled, + self.admin_signer(), + ) + .await + } + + /// Update account allowlist enforcement with the default portal admin. + pub(crate) async fn set_access_mode_on_portal( + &self, + portal_address: Address, + mode: tempo_zone_contracts::ZoneAccessMode, + ) -> eyre::Result { + use tempo_zone_contracts::ZonePortal; + let provider = self.admin_provider(); + let portal = ZonePortal::new(portal_address, &provider); + let receipt = portal + .setAccessMode(mode as u8) + .send() + .await? + .get_receipt() + .await?; + eyre::ensure!(receipt.status(), "setAccessMode failed"); + eyre::ensure!( + portal.accessMode().call().await? == mode as u8, + "L1 ZonePortal access mode did not update" + ); + Ok(provider.get_block_number().await?) + } + + /// Update callback gateway registration enforcement with the default portal admin. + pub(crate) async fn set_gateway_mode_on_portal( + &self, + portal_address: Address, + mode: tempo_zone_contracts::ZoneGatewayMode, + ) -> eyre::Result { + use tempo_zone_contracts::ZonePortal; + let provider = self.admin_provider(); + let portal = ZonePortal::new(portal_address, &provider); + let receipt = portal + .setGatewayMode(mode as u8) + .send() + .await? + .get_receipt() + .await?; + eyre::ensure!(receipt.status(), "setGatewayMode failed"); + eyre::ensure!( + portal.gatewayMode().call().await? == mode as u8, + "L1 ZonePortal gateway mode did not update" + ); + Ok(provider.get_block_number().await?) + } + + /// Update a portal callback gateway with the signer that owns that portal's admin role. + pub(crate) async fn set_zone_gateway_on_portal_with_signer( + &self, + portal_address: Address, + gateway: Address, + enabled: bool, + admin_signer: alloy_signer_local::PrivateKeySigner, + ) -> eyre::Result { + use tempo_zone_contracts::ZonePortal; + let provider = self.provider_with_signer(admin_signer); + let portal = ZonePortal::new(portal_address, &provider); + let receipt = portal + .setZoneGateway(gateway, enabled) + .send() + .await? + .get_receipt() + .await?; + eyre::ensure!(receipt.status(), "setZoneGateway failed"); + eyre::ensure!( + portal.zoneGateway(gateway).call().await? == enabled, + "L1 ZonePortal gateway state for {gateway} did not equal {enabled}" + ); + Ok(provider.get_block_number().await?) + } + + /// Update closed-mode account membership with the default distinct admin signer. + pub(crate) async fn set_allowed_account_on_portal( + &self, + portal_address: Address, + account: Address, + enabled: bool, + ) -> eyre::Result { + self.set_allowed_account_on_portal_with_signer( + portal_address, + account, + enabled, + self.admin_signer(), + ) + .await + } + + /// Update closed-mode account membership with an explicit portal admin signer. + pub(crate) async fn set_allowed_account_on_portal_with_signer( + &self, + portal_address: Address, + account: Address, + enabled: bool, + admin_signer: alloy_signer_local::PrivateKeySigner, + ) -> eyre::Result { + use tempo_zone_contracts::ZonePortal; + let provider = self.provider_with_signer(admin_signer); + let portal = ZonePortal::new(portal_address, &provider); + let receipt = portal + .setAllowedAccount(account, enabled) + .send() + .await? + .get_receipt() + .await?; + eyre::ensure!(receipt.status(), "setAllowedAccount failed"); + eyre::ensure!( + portal.allowedAccount(account).call().await? == enabled, + "L1 ZonePortal account state for {account} did not equal {enabled}" + ); + Ok(provider.get_block_number().await?) + } + /// Pause deposits for a token on the ZonePortal. pub(crate) async fn pause_deposits_on_portal( &self, @@ -1994,6 +2298,7 @@ where /// /// Use [`WithdrawalArgs::new`] for the common case (amount only, self-withdrawal), /// then override individual fields as needed. +#[derive(Clone)] pub(crate) struct WithdrawalArgs { pub amount: u128, pub to: Option
, @@ -2223,6 +2528,29 @@ impl ZoneAccount { self.deposit_to(self.address, amount, timeout, zone).await } + /// Simulate a plaintext deposit without submitting a transaction. + pub(crate) async fn simulate_deposit( + &self, + amount: u128, + recipient: Address, + tempo_refund_recipient: Address, + ) -> eyre::Result<()> { + use tempo_precompiles::PATH_USD_ADDRESS; + use tempo_zone_contracts::ZonePortal; + + ZonePortal::new(self.portal_address, &self.l1_provider) + .deposit( + PATH_USD_ADDRESS, + recipient, + amount, + B256::ZERO, + tempo_refund_recipient, + ) + .call() + .await?; + Ok(()) + } + /// Approve the ZonePortal to spend pathUSD on L1, then deposit to a specific recipient. /// /// Waits for the expected post-deposit balance on L2 and returns it. @@ -2472,6 +2800,30 @@ impl ZoneAccount { self.withdraw_token_with(ZONE_TOKEN_ADDRESS, args).await } + /// Simulate a withdrawal request without submitting it to the transaction pool. + /// Useful for asserting deterministic validation reverts. + pub(crate) async fn simulate_withdraw_with(&self, args: WithdrawalArgs) -> eyre::Result<()> { + use tempo_zone_contracts::{ZONE_OUTBOX_ADDRESS, ZONE_TOKEN_ADDRESS, ZoneOutbox}; + + let to = args.to.unwrap_or(self.address); + let zone_fallback_recipient = args.zone_fallback_recipient.unwrap_or(self.address); + ZoneOutbox::new(ZONE_OUTBOX_ADDRESS, &self.l2_provider) + .requestWithdrawal( + ZONE_TOKEN_ADDRESS, + to, + args.amount, + args.memo, + args.gas_limit, + zone_fallback_recipient, + args.data, + args.reveal_to, + ) + .from(self.address) + .call() + .await?; + Ok(()) + } + /// Approve the ZoneOutbox for a specific token, then request a withdrawal on L2. pub(crate) async fn withdraw_token( &mut self, @@ -2488,17 +2840,9 @@ impl ZoneAccount { token: Address, args: WithdrawalArgs, ) -> eyre::Result<()> { - use tempo_contracts::precompiles::ITIP20; use tempo_zone_contracts::{ZONE_OUTBOX_ADDRESS, ZoneOutbox}; - // Approve outbox for this token - ITIP20::new(token, &self.l2_provider) - .approve(ZONE_OUTBOX_ADDRESS, U256::MAX) - .gas(TIP20_TX_GAS) - .send() - .await? - .get_receipt() - .await?; + self.approve_outbox(token).await?; let to = args.to.unwrap_or(self.address); let zone_fallback_recipient = args.zone_fallback_recipient.unwrap_or(self.address); @@ -2528,6 +2872,22 @@ impl ZoneAccount { Ok(()) } + + /// Approve the ZoneOutbox for a token without submitting a withdrawal. + pub(crate) async fn approve_outbox(&self, token: Address) -> eyre::Result<()> { + use tempo_contracts::precompiles::ITIP20; + use tempo_zone_contracts::ZONE_OUTBOX_ADDRESS; + + let receipt = ITIP20::new(token, &self.l2_provider) + .approve(ZONE_OUTBOX_ADDRESS, U256::MAX) + .gas(TIP20_TX_GAS) + .send() + .await? + .get_receipt() + .await?; + eyre::ensure!(receipt.status(), "L2 outbox approval failed"); + Ok(()) + } } /// Spawn the zone sequencer background tasks (batch submitter + withdrawal processor). @@ -2589,6 +2949,7 @@ pub(crate) async fn start_local_zone_with_fixture( zone.l1_state_cache(), Address::ZERO, Address::ZERO, + &[l1_dev_signer().address()], seed_blocks, ); Ok((zone, fixture)) @@ -2729,6 +3090,7 @@ pub(crate) async fn start_local_p2p_pair( zone.l1_state_cache(), Address::ZERO, Address::ZERO, + &[l1_dev_signer().address()], seed_blocks, ); } @@ -2881,6 +3243,7 @@ pub(crate) fn seed_fixture_for_zone(fixture: &L1Fixture, zone: &ZoneTestNode, se zone.l1_state_cache(), Address::ZERO, Address::ZERO, + &[l1_dev_signer().address()], seed_blocks, ); } @@ -3406,7 +3769,13 @@ pub(crate) async fn start_zone_with_private_rpc() -> eyre::Result bool)). pub const PORTAL_ZONE_GATEWAY_SLOT: B256 = { let mut bytes = [0u8; 32]; diff --git a/crates/rpc/src/types.rs b/crates/rpc/src/types.rs index d8f43922b..3ebebc414 100644 --- a/crates/rpc/src/types.rs +++ b/crates/rpc/src/types.rs @@ -175,6 +175,10 @@ pub struct AuthorizationTokenInfoResponse { pub struct ZoneInfoResponse { /// The zone's numeric identifier. pub zone_id: U64, + /// Account allowlist enforcement mode (`0` closed, `1` open). + pub access_mode: U64, + /// Callback gateway enforcement mode (`0` enforced, `1` open). + pub gateway_mode: U64, /// The enabled zone token contract addresses. pub zone_tokens: Vec
, /// The active sequencer address. diff --git a/docs/ZONES.md b/docs/ZONES.md index dfb152980..e8dce5a88 100644 --- a/docs/ZONES.md +++ b/docs/ZONES.md @@ -148,6 +148,25 @@ export PRIVATE_KEY="$SEQUENCER_KEY" just create-zone my-zone ``` +The default is open account access and open callback targeting. No account or gateway +addresses are required, and the admin and sequencer are not added implicitly. + +The two controls are independent and remain mutable after creation: + +| Control | Open (default) | Restricted mode | +|---------|----------------|-----------------| +| Account access | Does not enforce account membership | `closed` enforces the account mapping for deposits, refunds, and plain withdrawals; an empty mapping denies all accounts | +| Callback targets | Does not enforce gateway registration | `enforced` requires callback targets from `ZONE_GATEWAYS` | + +Changing a mode does not clear its stored mapping. To create and start a closed zone +with enforced callback targets in one command: + +```bash +export ZONE_ALLOWED_ACCOUNTS="0x,0x" +export ZONE_GATEWAYS="0x" +just deploy-zone restricted-zone pathusd closed enforced +``` + To choose the initial TIP-20 enabled on the portal, pass it as the second positional argument: ```bash @@ -171,7 +190,9 @@ cargo run -p tempo-xtask -- create-zone \ --private-key "$SEQUENCER_KEY" ``` -`create-zone` requires the admin address explicitly. Keep the matching `ADMIN_KEY` available for admin-only portal calls such as `enable-token`, `pause-deposits`, and `resume-deposits`. +`create-zone` requires the admin address explicitly. Keep the matching `ADMIN_KEY` +available for admin-only portal calls such as changing either mode or its mapping, +enabling tokens, and pausing or resuming deposits. ### 5. Start the Zone Node @@ -626,14 +647,16 @@ Current deployment: | `ROUTER_BOUNCEBACK_RECIPIENT` | No | Optional controlled burner/stealth address for `demo-swap-and-deposit` routed deposit refunds | | `PRIVATE_RPC_MAX_AUTH_TOKEN_VALIDITY_SECS` | No | Maximum auth token validity the private RPC accepts, in seconds. The effective limit is capped at 30 days. | | `ZONE_TOKEN` | No | Default initial TIP-20 for `just create-zone` / `just deploy-zone`; defaults to `pathUSD` | +| `ZONE_ALLOWED_ACCOUNTS` | No | Comma-separated initial account membership | +| `ZONE_GATEWAYS` | No | Comma-separated initial callback-target registrations | | `ZONE_FACTORY` | No | Optional ZoneFactory override; xtasks default to the current Moderato shared deployment | ## Justfile Commands Reference | Command | Description | |---------|-------------| -| `just deploy-zone []` | One-shot: keygen → fund → create → genesis → start node | -| `just create-zone []` | Create zone on L1 + generate genesis (requires `PRIVATE_KEY`, `SEQUENCER_KEY`, and `ADMIN_KEY` or `ADMIN_ADDR`) | +| `just deploy-zone [] [open\|closed] [enforced\|open]` | One-shot: keygen → fund → create → genesis → start node | +| `just create-zone [] [open\|closed] [enforced\|open]` | Create zone on L1 + generate genesis (requires `PRIVATE_KEY`, `SEQUENCER_KEY`, and `ADMIN_KEY` or `ADMIN_ADDR`) | | `just deploy-router [dex]` | Deploy `SwapAndDepositRouter` on L1 for the zone and save it to `zone.json` | | `just zone-up [reset] [profile]` | Start the zone node. `reset=true` wipes datadir. `profile=release` for production. | | `just max-approve-portal [token]` | Approve portal to spend tokens on L1 | diff --git a/specs/ref-impls/src/interfaces/IZone.sol b/specs/ref-impls/src/interfaces/IZone.sol index d35c38c38..f3a52dba5 100644 --- a/specs/ref-impls/src/interfaces/IZone.sol +++ b/specs/ref-impls/src/interfaces/IZone.sol @@ -4,6 +4,18 @@ pragma solidity ^0.8.13; // Protocol-managed ZoneFactory precompile defined by TIP-1091. address constant ZONE_FACTORY_ADDRESS = 0x5aF2000000000000000000000000000000000000; +/// @notice Account-authorization mode enforced by a zone. +enum ZoneAccessMode { + Closed, + Open +} + +/// @notice Callback-gateway registration mode enforced by a zone. +enum ZoneGatewayMode { + Enforced, + Open +} + /// @title IZoneToken /// @notice Interface for the zone's zone token (TIP-20 with mint/burn for system) interface IZoneToken { @@ -25,6 +37,8 @@ struct ZoneInfo { uint32 zoneId; address portal; address initialToken; // first TIP-20 enabled at zone creation (additional tokens enabled via enableToken) + ZoneAccessMode accessMode; // creation-time snapshot; query the portal for the current mode + ZoneGatewayMode gatewayMode; // creation-time snapshot; query the portal for the current mode address admin; address sequencer; address verifier; @@ -354,6 +368,7 @@ interface IZoneTxContext { // slot 18: verifier (address) + genesisTempoBlockNumber (uint64) + _initialized (bool) [packed] // slot 19: zoneGateway (mapping(address => bool)) // slot 20: allowedAccount (mapping(address => bool)) +// slot 21: _enforcementFlags (uint8; bit 0 accounts, bit 1 gateways) // // These constants are the single source of truth for cross-domain reads. // ZoneConfig and ZoneInbox use them to read portal state via @@ -369,6 +384,11 @@ bytes32 constant PORTAL_ENABLED_TOKENS_SLOT = bytes32(uint256(9)); bytes32 constant PORTAL_PENDING_ADMIN_SLOT = bytes32(uint256(15)); bytes32 constant PORTAL_ZONE_GATEWAY_SLOT = bytes32(uint256(19)); bytes32 constant PORTAL_ALLOWED_ACCOUNT_SLOT = bytes32(uint256(20)); +bytes32 constant PORTAL_ENFORCEMENT_FLAGS_SLOT = bytes32(uint256(21)); +bytes32 constant PORTAL_ACCESS_MODE_SLOT = PORTAL_ENFORCEMENT_FLAGS_SLOT; +bytes32 constant PORTAL_GATEWAY_MODE_SLOT = PORTAL_ENFORCEMENT_FLAGS_SLOT; +uint8 constant ACCOUNT_ALLOWLIST_ENFORCED_FLAG = 1 << 0; +uint8 constant GATEWAY_ALLOWLIST_ENFORCED_FLAG = 1 << 1; /// @title IVerifier /// @notice Interface for zone proof/attestation verification @@ -422,7 +442,9 @@ interface IZoneFactory { struct CreateZoneParams { address initialToken; // first TIP-20 to enable (admin can enable more later) - address[] allowedAccounts; // initial closed-loop account allowlist + ZoneAccessMode accessMode; // initial account allowlist enforcement mode + ZoneGatewayMode gatewayMode; // initial callback gateway enforcement mode + address[] allowedAccounts; // initial account allowlist (retained while access is open) address[] zoneGateways; // initial withdrawal-and-call implementations address admin; address sequencer; @@ -435,6 +457,8 @@ interface IZoneFactory { uint32 indexed zoneId, address indexed portal, address initialToken, + ZoneAccessMode accessMode, + ZoneGatewayMode gatewayMode, address admin, address sequencer, address verifier, @@ -617,12 +641,15 @@ interface IZonePortal { /// @notice Emitted when the sequencer updates the zone's public RPC endpoint event RpcUrlUpdated(string rpcUrl); - /// @notice Emitted when the admin enables or disables a callback-only ZoneGateway. + /// @notice Emitted when a callback-only ZoneGateway is initialized, enabled, or disabled. event ZoneGatewayUpdated(address indexed gateway, bool enabled); - /// @notice Emitted when the admin enables or disables a closed-loop account. + /// @notice Emitted when an account membership is initialized, enabled, or disabled. event AllowedAccountUpdated(address indexed account, bool enabled); + /// @notice Emitted when the independently mutable enforcement modes are initialized or updated. + event EnforcementModesUpdated(ZoneAccessMode accessMode, ZoneGatewayMode gatewayMode); + error NotSequencer(); error NotAdmin(); error NotFactory(); @@ -657,6 +684,8 @@ interface IZonePortal { function initialize( uint32 zoneId, address initialToken, + ZoneAccessMode accessMode, + ZoneGatewayMode gatewayMode, address[] calldata allowedAccounts, address[] calldata zoneGateways, address messenger, @@ -683,6 +712,18 @@ interface IZonePortal { /// @notice Fixed callback messenger assigned during portal initialization. function messenger() external view returns (address); + /// @notice Current account allowlist enforcement mode. + function accessMode() external view returns (ZoneAccessMode); + + /// @notice Change account allowlist enforcement. Only callable by the admin. + function setAccessMode(ZoneAccessMode newMode) external; + + /// @notice Current callback gateway enforcement mode. + function gatewayMode() external view returns (ZoneGatewayMode); + + /// @notice Change callback gateway enforcement. Only callable by the admin. + function setGatewayMode(ZoneGatewayMode newMode) external; + function zoneGateway(address gateway) external view returns (bool); /// @notice Enable or disable a callback-only gateway. Only callable by the admin. @@ -690,7 +731,7 @@ interface IZonePortal { function allowedAccount(address account) external view returns (bool); - /// @notice Enable or disable a closed-loop account. Only callable by the admin. + /// @notice Enable or disable an account membership. Only callable by the admin. function setAllowedAccount(address account, bool enabled) external; function sequencer() external view returns (address); @@ -1266,7 +1307,14 @@ interface IZoneConfig { /// @notice Check if a token is enabled by reading from L1 ZonePortal function isEnabledToken(address token) external view returns (bool); - /// @notice Check closed-loop account membership by reading from L1 ZonePortal. + /// @notice Read the account allowlist enforcement mode from L1 ZonePortal. + function accessMode() external view returns (ZoneAccessMode); + + /// @notice Read the callback gateway enforcement mode from L1 ZonePortal. + function gatewayMode() external view returns (ZoneGatewayMode); + + /// @notice Check whether an account is authorized under the zone's access mode. + /// @dev Returns true for every account in open mode and checks membership in closed mode. function isAllowedAccount(address account) external view returns (bool); /// @notice Check whether an address is a registered callback-only ZoneGateway. diff --git a/specs/ref-impls/src/tempo/ZoneFactory.sol b/specs/ref-impls/src/tempo/ZoneFactory.sol index da6a1cfb6..aff62923b 100644 --- a/specs/ref-impls/src/tempo/ZoneFactory.sol +++ b/specs/ref-impls/src/tempo/ZoneFactory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import { IZoneFactory, ZoneInfo } from "../interfaces/IZone.sol"; +import { IZoneFactory, ZoneAccessMode, ZoneInfo } from "../interfaces/IZone.sol"; import { Verifier } from "./Verifier.sol"; import { ZoneMessenger } from "./ZoneMessenger.sol"; import { ZonePortal } from "./ZonePortal.sol"; @@ -64,10 +64,6 @@ contract ZoneFactory is IZoneFactory { if (!ITIP20Factory(StdPrecompiles.TIP20_FACTORY_ADDRESS).isTIP20(params.initialToken)) { revert InvalidToken(); } - if (params.zoneGateways.length == 0 || params.allowedAccounts.length == 0) { - revert InvalidClosedLoopConfig(); - } - for (uint256 i; i < params.allowedAccounts.length; ++i) { address account = params.allowedAccounts[i]; if (account == _messenger) { @@ -103,6 +99,8 @@ contract ZoneFactory is IZoneFactory { portalContract.initialize( zoneId, params.initialToken, + params.accessMode, + params.gatewayMode, params.allowedAccounts, params.zoneGateways, _messenger, @@ -123,6 +121,8 @@ contract ZoneFactory is IZoneFactory { zoneId: zoneId, portal: portal, initialToken: params.initialToken, + accessMode: params.accessMode, + gatewayMode: params.gatewayMode, admin: params.admin, sequencer: params.sequencer, verifier: params.verifier, @@ -138,6 +138,8 @@ contract ZoneFactory is IZoneFactory { zoneId, portal, params.initialToken, + params.accessMode, + params.gatewayMode, params.admin, params.sequencer, params.verifier, diff --git a/specs/ref-impls/src/tempo/ZoneMessenger.sol b/specs/ref-impls/src/tempo/ZoneMessenger.sol index 8b4eea1d9..26ab7d94f 100644 --- a/specs/ref-impls/src/tempo/ZoneMessenger.sol +++ b/specs/ref-impls/src/tempo/ZoneMessenger.sol @@ -6,6 +6,7 @@ import { IZoneFactory, IZoneMessenger, IZonePortal, + ZoneGatewayMode, ZoneInfo } from "../interfaces/IZone.sol"; import { ITIP20 } from "tempo-std/interfaces/ITIP20.sol"; @@ -50,7 +51,12 @@ contract ZoneMessenger is IZoneMessenger { ZoneInfo memory zone = zoneFactory.zones(zoneId); if (zone.portal != msg.sender) revert UnauthorizedPortal(); - if (!IZonePortal(msg.sender).zoneGateway(target)) revert InvalidCallbackTarget(); + if ( + IZonePortal(msg.sender).gatewayMode() == ZoneGatewayMode.Enforced + && !IZonePortal(msg.sender).zoneGateway(target) + ) { + revert InvalidCallbackTarget(); + } if (!ITIP20(token).transfer(target, amount)) { revert TransferFailed(); diff --git a/specs/ref-impls/src/tempo/ZonePortal.sol b/specs/ref-impls/src/tempo/ZonePortal.sol index 390d6bd59..33233e35e 100644 --- a/specs/ref-impls/src/tempo/ZonePortal.sol +++ b/specs/ref-impls/src/tempo/ZonePortal.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.13; import { + ACCOUNT_ALLOWLIST_ENFORCED_FLAG, BlockTransition, Deposit, DepositQueueTransition, @@ -10,6 +11,7 @@ import { EncryptedDeposit, EncryptedDepositPayload, EncryptionKeyEntry, + GATEWAY_ALLOWLIST_ENFORCED_FLAG, IVerifier, IZoneMessenger, IZonePortal, @@ -17,7 +19,9 @@ import { QueuedDeposit, TokenConfig, Withdrawal, - ZONE_FACTORY_ADDRESS + ZONE_FACTORY_ADDRESS, + ZoneAccessMode, + ZoneGatewayMode } from "../interfaces/IZone.sol"; import { getBlockHash } from "../libraries/BlockHashHistory.sol"; import { DepositQueueLib } from "../libraries/DepositQueueLib.sol"; @@ -136,13 +140,15 @@ contract ZonePortal is IZonePortal { address public verifier; uint64 public genesisTempoBlockNumber; bool internal _initialized; - /// @notice Callback-only ZoneGateway implementations accepted by this portal. mapping(address => bool) public zoneGateway; /// @notice Shared admin-managed account allowlist for all portal flows. mapping(address => bool) public allowedAccount; + /// @dev Dedicated packed mode slot. Zero is the common open/open configuration. + uint8 internal _enforcementFlags; + /*////////////////////////////////////////////////////////////// INITIALIZATION //////////////////////////////////////////////////////////////*/ @@ -150,6 +156,8 @@ contract ZonePortal is IZonePortal { function initialize( uint32 _zoneId, address _initialToken, + ZoneAccessMode _accessMode, + ZoneGatewayMode _gatewayMode, address[] calldata _allowedAccounts, address[] calldata _zoneGateways, address _messenger, @@ -173,14 +181,18 @@ contract ZonePortal is IZonePortal { verifier = _verifier; blockHash = _genesisBlockHash; genesisTempoBlockNumber = _genesisTempoBlockNumber; + _enforcementFlags = _encodeEnforcementFlags(_accessMode, _gatewayMode); rpcUrl = _rpcUrl; + emit EnforcementModesUpdated(_accessMode, _gatewayMode); for (uint256 i; i < _zoneGateways.length; ++i) { zoneGateway[_zoneGateways[i]] = true; + emit ZoneGatewayUpdated(_zoneGateways[i], true); } for (uint256 i; i < _allowedAccounts.length; ++i) { allowedAccount[_allowedAccounts[i]] = true; + emit AllowedAccountUpdated(_allowedAccounts[i], true); } // Enable the initial token. The admin may enable additional TIP-20s later. @@ -292,6 +304,52 @@ contract ZonePortal is IZonePortal { emit ZoneGatewayUpdated(gateway, enabled); } + /// @notice Enable or disable account allowlist enforcement without discarding membership. + function setAccessMode(ZoneAccessMode newMode) external onlyAdmin { + if (newMode == ZoneAccessMode.Closed) { + _enforcementFlags |= ACCOUNT_ALLOWLIST_ENFORCED_FLAG; + } else { + _enforcementFlags &= ~ACCOUNT_ALLOWLIST_ENFORCED_FLAG; + } + emit EnforcementModesUpdated(newMode, gatewayMode()); + } + + /// @notice Enable or disable callback gateway registration enforcement. + function setGatewayMode(ZoneGatewayMode newMode) external onlyAdmin { + if (newMode == ZoneGatewayMode.Enforced) { + _enforcementFlags |= GATEWAY_ALLOWLIST_ENFORCED_FLAG; + } else { + _enforcementFlags &= ~GATEWAY_ALLOWLIST_ENFORCED_FLAG; + } + emit EnforcementModesUpdated(accessMode(), newMode); + } + + /// @notice Return whether account allowlist enforcement is closed or open. + function accessMode() public view returns (ZoneAccessMode) { + return (_enforcementFlags & ACCOUNT_ALLOWLIST_ENFORCED_FLAG) != 0 + ? ZoneAccessMode.Closed + : ZoneAccessMode.Open; + } + + /// @notice Return whether callback gateway registration is enforced or open. + function gatewayMode() public view returns (ZoneGatewayMode) { + return (_enforcementFlags & GATEWAY_ALLOWLIST_ENFORCED_FLAG) != 0 + ? ZoneGatewayMode.Enforced + : ZoneGatewayMode.Open; + } + + function _encodeEnforcementFlags( + ZoneAccessMode accountMode, + ZoneGatewayMode callbackMode + ) + internal + pure + returns (uint8 flags) + { + if (accountMode == ZoneAccessMode.Closed) flags |= ACCOUNT_ALLOWLIST_ENFORCED_FLAG; + if (callbackMode == ZoneGatewayMode.Enforced) flags |= GATEWAY_ALLOWLIST_ENFORCED_FLAG; + } + /// @notice Enable or disable an account across deposits, refunds, and plain withdrawals. /// @dev Accounts cannot be an active gateway or the fixed messenger. function setAllowedAccount(address account, bool enabled) external onlyAdmin { @@ -552,9 +610,19 @@ contract ZonePortal is IZonePortal { } function _requireAllowed(address account) internal view { + if (!_isAllowed(account)) revert AccountNotAllowed(account); + } + + function _requireAllowedDepositor(address account) internal view { + if (accessMode() == ZoneAccessMode.Open) return; + if (gatewayMode() == ZoneGatewayMode.Enforced && zoneGateway[account]) return; if (!allowedAccount[account]) revert AccountNotAllowed(account); } + function _isAllowed(address account) internal view returns (bool) { + return accessMode() == ZoneAccessMode.Open || allowedAccount[account]; + } + function _validateDepositPolicy( address _token, address to, @@ -582,6 +650,7 @@ contract ZonePortal is IZonePortal { internal returns (uint128 fee, uint128 netAmount) { + // FIXME(closed-loop-fees): Fix sequencer fees to zero and enforce onchain. fee = calculateDepositFee(); uint128 bouncebackFee = calculateBouncebackFee(); if (amount < fee + bouncebackFee) revert DepositTooSmall(); @@ -621,7 +690,8 @@ contract ZonePortal is IZonePortal { returns (bytes32 newCurrentDepositQueueHash) { if (tempoRefundRecipient == address(0)) revert InvalidBouncebackRecipient(); - _requireAllowed(msg.sender); + // An enforced, registered gateway is independently authorized to return callback funds. + _requireAllowedDepositor(msg.sender); _requireAllowed(tempoRefundRecipient); _validateDepositsActive(_token); @@ -676,8 +746,8 @@ contract ZonePortal is IZonePortal { returns (bytes32 newCurrentDepositQueueHash) { if (tempoRefundRecipient == address(0)) revert InvalidBouncebackRecipient(); - // Gateways may deposit callback returns without also being allowed accounts. - if (!zoneGateway[msg.sender]) _requireAllowed(msg.sender); + // Enforced gateways may deposit callback returns without also being allowed accounts. + _requireAllowedDepositor(msg.sender); _requireAllowed(tempoRefundRecipient); _validateDepositsActive(_token); @@ -792,9 +862,10 @@ contract ZonePortal is IZonePortal { bool success; if (withdrawal.gasLimit == 0) { - // Re-check current membership without reverting so an in-flight withdrawal to a - // revoked account bounces without blocking the FIFO. - success = allowedAccount[withdrawal.to] + // Re-check current roles without reverting so an in-flight withdrawal to a revoked + // account or newly registered gateway bounces without blocking the FIFO. + success = (gatewayMode() == ZoneGatewayMode.Open || !zoneGateway[withdrawal.to]) + && _isAllowed(withdrawal.to) && _tryTransfer(_token, withdrawal.to, withdrawal.amount); } else { // Isolate callback effects so failure can be caught without reverting the dequeue. @@ -833,19 +904,29 @@ contract ZonePortal is IZonePortal { external onlySelf { - if (!zoneGateway[target]) revert InvalidCallbackTarget(); - bytes32 depositQueueHashBefore = currentDepositQueueHash; - + if (gatewayMode() == ZoneGatewayMode.Enforced && !zoneGateway[target]) { + revert InvalidCallbackTarget(); + } if (!ITIP20(token).transfer(messenger, amount)) { revert TransferFailed(); } + bytes32 depositQueueHashBefore = currentDepositQueueHash; + IZoneMessenger(messenger) .relayMessage(zoneId, token, senderTag, target, amount, gasLimit, data); - // This proves only that some deposit was appended. Callback data is opaque; the configured - // gateway is trusted to allow only deposit/redeem and deposit the result back into the zone. - if (currentDepositQueueHash == depositQueueHashBefore) revert CallbackDidNotReturnToZone(); + // In closed access, this proves only that some deposit was appended to this portal; it does + // not bind that deposit to the callback's token, amount, or recipient. Callback data is + // opaque, so an enforced gateway is trusted to constrain the operation and return the + // intended result. Open access imposes no source-deposit invariant: callback value may go + // to another zone or leave the zone system entirely. + if ( + accessMode() == ZoneAccessMode.Closed + && currentDepositQueueHash == depositQueueHashBefore + ) { + revert CallbackDidNotReturnToZone(); + } } function _processDepositBounceBack(Withdrawal calldata withdrawal) internal { @@ -863,7 +944,7 @@ contract ZonePortal is IZonePortal { } bool success = - allowedAccount[withdrawal.to] && _tryTransfer(_token, withdrawal.to, refundAmount); + _isAllowed(withdrawal.to) && _tryTransfer(_token, withdrawal.to, refundAmount); if (success) { emit DepositBounceBack(withdrawal.to, _token, refundAmount, bouncebackFee); diff --git a/specs/ref-impls/src/zone/ZoneConfig.sol b/specs/ref-impls/src/zone/ZoneConfig.sol index f9632fe35..a7e4391ad 100644 --- a/specs/ref-impls/src/zone/ZoneConfig.sol +++ b/specs/ref-impls/src/zone/ZoneConfig.sol @@ -2,14 +2,20 @@ pragma solidity ^0.8.13; import { + ACCOUNT_ALLOWLIST_ENFORCED_FLAG, + GATEWAY_ALLOWLIST_ENFORCED_FLAG, ITempoState, IZoneConfig, + PORTAL_ACCESS_MODE_SLOT, PORTAL_ALLOWED_ACCOUNT_SLOT, PORTAL_ENCRYPTION_KEYS_SLOT, + PORTAL_GATEWAY_MODE_SLOT, PORTAL_PENDING_SEQUENCER_SLOT, PORTAL_SEQUENCER_SLOT, PORTAL_TOKEN_CONFIGS_SLOT, - PORTAL_ZONE_GATEWAY_SLOT + PORTAL_ZONE_GATEWAY_SLOT, + ZoneAccessMode, + ZoneGatewayMode } from "../interfaces/IZone.sol"; /// @title ZoneConfig @@ -131,8 +137,25 @@ contract ZoneConfig is IZoneConfig { return uint8(uint256(value) & 0xff) != 0; } - /// @notice Check account membership in the portal's admin-managed closed-loop allowlist. + /// @notice Read account allowlist enforcement from the dedicated packed mode slot. + function accessMode() public view returns (ZoneAccessMode) { + bytes32 value = tempoState.readTempoStorageSlot(tempoPortal, PORTAL_ACCESS_MODE_SLOT); + return (uint8(uint256(value)) & ACCOUNT_ALLOWLIST_ENFORCED_FLAG) != 0 + ? ZoneAccessMode.Closed + : ZoneAccessMode.Open; + } + + /// @notice Read callback gateway enforcement from the dedicated packed mode slot. + function gatewayMode() public view returns (ZoneGatewayMode) { + bytes32 value = tempoState.readTempoStorageSlot(tempoPortal, PORTAL_GATEWAY_MODE_SLOT); + return (uint8(uint256(value)) & GATEWAY_ALLOWLIST_ENFORCED_FLAG) != 0 + ? ZoneGatewayMode.Enforced + : ZoneGatewayMode.Open; + } + + /// @notice Check whether an account is authorized under the portal's access mode. function isAllowedAccount(address account) external view returns (bool) { + if (accessMode() == ZoneAccessMode.Open) return true; bytes32 accountSlot = keccak256(abi.encode(account, PORTAL_ALLOWED_ACCOUNT_SLOT)); return uint256(tempoState.readTempoStorageSlot(tempoPortal, accountSlot)) != 0; } diff --git a/specs/ref-impls/src/zone/ZoneOutbox.sol b/specs/ref-impls/src/zone/ZoneOutbox.sol index 7494ba5a5..31507b15c 100644 --- a/specs/ref-impls/src/zone/ZoneOutbox.sol +++ b/specs/ref-impls/src/zone/ZoneOutbox.sol @@ -12,7 +12,8 @@ import { PendingWithdrawal, Withdrawal, ZONE_INBOX, - ZONE_TX_CONTEXT + ZONE_TX_CONTEXT, + ZoneGatewayMode } from "../interfaces/IZone.sol"; import { Secp256k1Lib } from "../libraries/Secp256k1Lib.sol"; @@ -258,11 +259,15 @@ contract ZoneOutbox is IZoneOutbox { } if (gasLimit == 0) { - if (config.isZoneGateway(to)) revert IZonePortal.InvalidCallbackTarget(); + if (config.gatewayMode() == ZoneGatewayMode.Enforced && config.isZoneGateway(to)) { + revert IZonePortal.InvalidCallbackTarget(); + } if (!config.isAllowedAccount(to)) revert IZonePortal.AccountNotAllowed(to); } else { _validateGasLimit(gasLimit); - if (!config.isZoneGateway(to)) revert IZonePortal.InvalidCallbackTarget(); + if (config.gatewayMode() == ZoneGatewayMode.Enforced && !config.isZoneGateway(to)) { + revert IZonePortal.InvalidCallbackTarget(); + } } _validateRevealTo(revealTo); diff --git a/specs/ref-impls/storage-layouts/ZonePortal.json b/specs/ref-impls/storage-layouts/ZonePortal.json index c312784ea..447a790df 100644 --- a/specs/ref-impls/storage-layouts/ZonePortal.json +++ b/specs/ref-impls/storage-layouts/ZonePortal.json @@ -1,7 +1,7 @@ { "storage": [ { - "astId": 5089, + "astId": 5201, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "sequencer", "offset": 0, @@ -9,7 +9,7 @@ "type": "t_address" }, { - "astId": 5092, + "astId": 5204, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "admin", "offset": 0, @@ -17,7 +17,7 @@ "type": "t_address" }, { - "astId": 5095, + "astId": 5207, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "pendingSequencer", "offset": 0, @@ -25,7 +25,7 @@ "type": "t_address" }, { - "astId": 5098, + "astId": 5210, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "zoneGasRate", "offset": 0, @@ -33,7 +33,7 @@ "type": "t_uint128" }, { - "astId": 5100, + "astId": 5212, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "withdrawalBatchIndex", "offset": 16, @@ -41,7 +41,7 @@ "type": "t_uint64" }, { - "astId": 5102, + "astId": 5214, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "blockHash", "offset": 0, @@ -49,7 +49,7 @@ "type": "t_bytes32" }, { - "astId": 5105, + "astId": 5217, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "currentDepositQueueHash", "offset": 0, @@ -57,7 +57,7 @@ "type": "t_bytes32" }, { - "astId": 5108, + "astId": 5220, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "depositCount", "offset": 0, @@ -65,7 +65,7 @@ "type": "t_uint64" }, { - "astId": 5111, + "astId": 5223, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "lastProcessedDepositNumber", "offset": 8, @@ -73,7 +73,7 @@ "type": "t_uint64" }, { - "astId": 5114, + "astId": 5226, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "lastSyncedTempoBlockNumber", "offset": 16, @@ -81,7 +81,7 @@ "type": "t_uint64" }, { - "astId": 5117, + "astId": 5229, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "bouncebackGas", "offset": 24, @@ -89,23 +89,23 @@ "type": "t_uint64" }, { - "astId": 5122, + "astId": 5234, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "_encryptionKeys", "offset": 0, "slot": "7", - "type": "t_array(t_struct(EncryptionKeyEntry)2600_storage)dyn_storage" + "type": "t_array(t_struct(EncryptionKeyEntry)2614_storage)dyn_storage" }, { - "astId": 5128, + "astId": 5240, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "_tokenConfigs", "offset": 0, "slot": "8", - "type": "t_mapping(t_address,t_struct(TokenConfig)3039_storage)" + "type": "t_mapping(t_address,t_struct(TokenConfig)3090_storage)" }, { - "astId": 5132, + "astId": 5244, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "_enabledTokens", "offset": 0, @@ -113,7 +113,7 @@ "type": "t_array(t_address)dyn_storage" }, { - "astId": 5139, + "astId": 5251, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "refunds", "offset": 0, @@ -121,15 +121,15 @@ "type": "t_mapping(t_address,t_mapping(t_address,t_uint128))" }, { - "astId": 5143, + "astId": 5255, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "_withdrawalQueue", "offset": 0, "slot": "11", - "type": "t_struct(WithdrawalQueue)4790_storage" + "type": "t_struct(WithdrawalQueue)4898_storage" }, { - "astId": 5146, + "astId": 5258, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "rpcUrl", "offset": 0, @@ -137,7 +137,7 @@ "type": "t_string_storage" }, { - "astId": 5149, + "astId": 5261, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "pendingAdmin", "offset": 0, @@ -145,7 +145,7 @@ "type": "t_address" }, { - "astId": 5152, + "astId": 5264, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "_withdrawalReentrancyStatus", "offset": 0, @@ -153,7 +153,7 @@ "type": "t_uint256" }, { - "astId": 5155, + "astId": 5267, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "zoneId", "offset": 0, @@ -161,7 +161,7 @@ "type": "t_uint32" }, { - "astId": 5158, + "astId": 5270, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "messenger", "offset": 4, @@ -169,7 +169,7 @@ "type": "t_address" }, { - "astId": 5160, + "astId": 5272, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "verifier", "offset": 0, @@ -177,7 +177,7 @@ "type": "t_address" }, { - "astId": 5162, + "astId": 5274, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "genesisTempoBlockNumber", "offset": 20, @@ -185,7 +185,7 @@ "type": "t_uint64" }, { - "astId": 5164, + "astId": 5276, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "_initialized", "offset": 28, @@ -193,7 +193,7 @@ "type": "t_bool" }, { - "astId": 5169, + "astId": 5281, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "zoneGateway", "offset": 0, @@ -201,12 +201,20 @@ "type": "t_mapping(t_address,t_bool)" }, { - "astId": 5174, + "astId": 5286, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "allowedAccount", "offset": 0, "slot": "20", "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 5289, + "contract": "src/tempo/ZonePortal.sol:ZonePortal", + "label": "_enforcementFlags", + "offset": 0, + "slot": "21", + "type": "t_uint8" } ], "types": { @@ -221,11 +229,11 @@ "numberOfBytes": "32", "base": "t_address" }, - "t_array(t_struct(EncryptionKeyEntry)2600_storage)dyn_storage": { + "t_array(t_struct(EncryptionKeyEntry)2614_storage)dyn_storage": { "encoding": "dynamic_array", "label": "struct EncryptionKeyEntry[]", "numberOfBytes": "32", - "base": "t_struct(EncryptionKeyEntry)2600_storage" + "base": "t_struct(EncryptionKeyEntry)2614_storage" }, "t_bool": { "encoding": "inplace", @@ -251,12 +259,12 @@ "numberOfBytes": "32", "value": "t_mapping(t_address,t_uint128)" }, - "t_mapping(t_address,t_struct(TokenConfig)3039_storage)": { + "t_mapping(t_address,t_struct(TokenConfig)3090_storage)": { "encoding": "mapping", "key": "t_address", "label": "mapping(address => struct TokenConfig)", "numberOfBytes": "32", - "value": "t_struct(TokenConfig)3039_storage" + "value": "t_struct(TokenConfig)3090_storage" }, "t_mapping(t_address,t_uint128)": { "encoding": "mapping", @@ -277,13 +285,13 @@ "label": "string", "numberOfBytes": "32" }, - "t_struct(EncryptionKeyEntry)2600_storage": { + "t_struct(EncryptionKeyEntry)2614_storage": { "encoding": "inplace", "label": "struct EncryptionKeyEntry", "numberOfBytes": "64", "members": [ { - "astId": 2595, + "astId": 2609, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "x", "offset": 0, @@ -291,7 +299,7 @@ "type": "t_bytes32" }, { - "astId": 2597, + "astId": 2611, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "yParity", "offset": 0, @@ -299,7 +307,7 @@ "type": "t_uint8" }, { - "astId": 2599, + "astId": 2613, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "activationBlock", "offset": 1, @@ -308,13 +316,13 @@ } ] }, - "t_struct(TokenConfig)3039_storage": { + "t_struct(TokenConfig)3090_storage": { "encoding": "inplace", "label": "struct TokenConfig", "numberOfBytes": "32", "members": [ { - "astId": 3036, + "astId": 3087, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "enabled", "offset": 0, @@ -322,7 +330,7 @@ "type": "t_bool" }, { - "astId": 3038, + "astId": 3089, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "depositsActive", "offset": 1, @@ -331,13 +339,13 @@ } ] }, - "t_struct(WithdrawalQueue)4790_storage": { + "t_struct(WithdrawalQueue)4898_storage": { "encoding": "inplace", "label": "struct WithdrawalQueue", "numberOfBytes": "96", "members": [ { - "astId": 4783, + "astId": 4891, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "head", "offset": 0, @@ -345,7 +353,7 @@ "type": "t_uint256" }, { - "astId": 4785, + "astId": 4893, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "tail", "offset": 0, @@ -353,7 +361,7 @@ "type": "t_uint256" }, { - "astId": 4789, + "astId": 4897, "contract": "src/tempo/ZonePortal.sol:ZonePortal", "label": "slots", "offset": 0, diff --git a/specs/ref-impls/test/tempo/ZoneBridge.t.sol b/specs/ref-impls/test/tempo/ZoneBridge.t.sol index a1d9776be..41a0d7588 100644 --- a/specs/ref-impls/test/tempo/ZoneBridge.t.sol +++ b/specs/ref-impls/test/tempo/ZoneBridge.t.sol @@ -25,6 +25,8 @@ import { Withdrawal, ZONE_INBOX, ZONE_OUTBOX, + ZoneAccessMode, + ZoneGatewayMode, ZoneInfo, ZoneParams } from "../../src/interfaces/IZone.sol"; @@ -189,6 +191,8 @@ contract ZoneBridgeTest is BaseTest { l1Portal.initialize( 1, // zoneId address(l2ZoneToken), // initialToken = MockZoneToken (NOT pathUSD) + ZoneAccessMode.Closed, + ZoneGatewayMode.Enforced, bridgeAccounts, _zoneGateways(), address(messengerContract), diff --git a/specs/ref-impls/test/tempo/ZoneFactory.t.sol b/specs/ref-impls/test/tempo/ZoneFactory.t.sol index 646236649..e3210efc3 100644 --- a/specs/ref-impls/test/tempo/ZoneFactory.t.sol +++ b/specs/ref-impls/test/tempo/ZoneFactory.t.sol @@ -1,7 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import { IZoneFactory, ZoneInfo, ZoneParams } from "../../src/interfaces/IZone.sol"; +import { + IZoneFactory, + IZonePortal, + ZoneAccessMode, + ZoneGatewayMode, + ZoneInfo, + ZoneParams +} from "../../src/interfaces/IZone.sol"; import { ZoneFactory } from "../../src/tempo/ZoneFactory.sol"; import { ZonePortal } from "../../src/tempo/ZonePortal.sol"; import { BaseTest } from "../BaseTest.t.sol"; @@ -28,6 +35,8 @@ contract ZoneFactoryTest is BaseTest { function test_createZone_success() public { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -52,6 +61,7 @@ contract ZoneFactoryTest is BaseTest { assertEq(info.zoneId, 1); assertEq(info.portal, portal); assertEq(info.initialToken, address(pathUSD)); + assertEq(uint8(info.accessMode), uint8(ZoneAccessMode.Closed)); assertEq(info.admin, admin); assertEq(info.sequencer, sequencer); assertEq(info.verifier, zoneFactory.verifier()); @@ -68,6 +78,8 @@ contract ZoneFactoryTest is BaseTest { gateways[1] = replacement; IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: gateways, admin: admin, @@ -86,12 +98,67 @@ contract ZoneFactoryTest is BaseTest { assertTrue(ZonePortal(portal).zoneGateway(replacement)); } + function test_createZone_openModeAllowsEmptyAccountsAndGateways() public { + IZoneFactory.CreateZoneParams memory params = _defaultParams(); + params.accessMode = ZoneAccessMode.Open; + params.allowedAccounts = new address[](0); + params.zoneGateways = new address[](0); + + (uint32 zoneId, address portal) = zoneFactory.createZone(params); + + assertEq(uint8(zoneFactory.zones(zoneId).accessMode), uint8(ZoneAccessMode.Open)); + assertEq(uint8(ZonePortal(portal).accessMode()), uint8(ZoneAccessMode.Open)); + assertFalse(ZonePortal(portal).allowedAccount(alice)); + assertFalse(ZonePortal(portal).zoneGateway(address(zoneGateway))); + + vm.prank(admin); + ZonePortal(portal).setAllowedAccount(alice, true); + assertTrue(ZonePortal(portal).allowedAccount(alice)); + } + + function test_createZone_closedModeAllowsEmptyGateways() public { + IZoneFactory.CreateZoneParams memory params = _defaultParams(); + params.zoneGateways = new address[](0); + + (, address portal) = zoneFactory.createZone(params); + + assertEq(uint8(ZonePortal(portal).accessMode()), uint8(ZoneAccessMode.Closed)); + assertTrue(ZonePortal(portal).allowedAccount(alice)); + assertFalse(ZonePortal(portal).zoneGateway(address(zoneGateway))); + } + + function test_createZone_closedModeAllowsEmptyAccounts() public { + IZoneFactory.CreateZoneParams memory params = _defaultParams(); + params.allowedAccounts = new address[](0); + + (, address portal) = zoneFactory.createZone(params); + + assertEq(uint8(ZonePortal(portal).accessMode()), uint8(ZoneAccessMode.Closed)); + assertFalse(ZonePortal(portal).allowedAccount(alice)); + + vm.prank(admin); + ZonePortal(portal).setAllowedAccount(alice, true); + assertTrue(ZonePortal(portal).allowedAccount(alice)); + } + + function test_createZone_openModeRetainsConfiguredAccountsForLaterClosure() public { + IZoneFactory.CreateZoneParams memory params = _defaultParams(); + params.accessMode = ZoneAccessMode.Open; + + (, address portal) = zoneFactory.createZone(params); + + assertEq(uint8(ZonePortal(portal).accessMode()), uint8(ZoneAccessMode.Open)); + assertTrue(ZonePortal(portal).allowedAccount(alice)); + } + function test_createZone_revertsWhenGatewayIsAllowedAccount() public { address[] memory accounts = _closedLoopAccounts(); address[] memory gateways = new address[](1); gateways[0] = accounts[0]; IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: accounts, zoneGateways: gateways, admin: admin, @@ -114,6 +181,8 @@ contract ZoneFactoryTest is BaseTest { accounts[0] = zoneFactory.messenger(); IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: accounts, zoneGateways: _zoneGateways(), admin: admin, @@ -140,6 +209,8 @@ contract ZoneFactoryTest is BaseTest { gateways[1] = address(zoneGateway); IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: accounts, zoneGateways: gateways, admin: admin, @@ -164,6 +235,8 @@ contract ZoneFactoryTest is BaseTest { accounts[0] = address(0); IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: accounts, zoneGateways: _zoneGateways(), admin: admin, @@ -190,6 +263,8 @@ contract ZoneFactoryTest is BaseTest { gateways[0] = address(0); IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: gateways, admin: admin, @@ -214,6 +289,8 @@ contract ZoneFactoryTest is BaseTest { function test_createZone_revertsForNonOwner() public { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -255,6 +332,8 @@ contract ZoneFactoryTest is BaseTest { function test_createZone_usesSharedMessenger() public { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -282,6 +361,8 @@ contract ZoneFactoryTest is BaseTest { function test_createZone_multipleZones() public { IZoneFactory.CreateZoneParams memory params1 = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -300,6 +381,8 @@ contract ZoneFactoryTest is BaseTest { address secondSequencer = alice; IZoneFactory.CreateZoneParams memory params2 = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -333,6 +416,8 @@ contract ZoneFactoryTest is BaseTest { function test_createZone_emitsEvent() public { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -346,6 +431,18 @@ contract ZoneFactoryTest is BaseTest { rpcUrl: "" }); + address expectedPortal = vm.computeCreateAddress(address(zoneFactory), 3); + vm.expectEmit(false, false, false, true, expectedPortal); + emit IZonePortal.EnforcementModesUpdated(params.accessMode, params.gatewayMode); + for (uint256 i; i < params.zoneGateways.length; ++i) { + vm.expectEmit(true, false, false, true, expectedPortal); + emit IZonePortal.ZoneGatewayUpdated(params.zoneGateways[i], true); + } + for (uint256 i; i < params.allowedAccounts.length; ++i) { + vm.expectEmit(true, false, false, true, expectedPortal); + emit IZonePortal.AllowedAccountUpdated(params.allowedAccounts[i], true); + } + // Record logs and verify ZoneCreated event was emitted vm.recordLogs(); (uint32 zoneId, address portal) = zoneFactory.createZone(params); @@ -357,7 +454,7 @@ contract ZoneFactoryTest is BaseTest { if ( logs[i].topics[0] == keccak256( - "ZoneCreated(uint32,address,address,address,address,address,bytes32,bytes32,uint64)" + "ZoneCreated(uint32,address,address,uint8,uint8,address,address,address,bytes32,bytes32,uint64)" ) ) { found = true; @@ -381,6 +478,8 @@ contract ZoneFactoryTest is BaseTest { function test_createZone_revertsOnInvalidToken_zeroAddress() public { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(0), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -404,6 +503,8 @@ contract ZoneFactoryTest is BaseTest { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: notTip20, + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -424,6 +525,8 @@ contract ZoneFactoryTest is BaseTest { function test_createZone_revertsOnInvalidToken_eoa() public { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: alice, // EOA, not a contract + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -448,6 +551,8 @@ contract ZoneFactoryTest is BaseTest { function test_createZone_revertsOnInvalidAdmin_zeroAddress() public { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: address(0), @@ -472,6 +577,8 @@ contract ZoneFactoryTest is BaseTest { function test_createZone_revertsOnInvalidSequencer_zeroAddress() public { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -496,6 +603,8 @@ contract ZoneFactoryTest is BaseTest { function test_createZone_revertsOnInvalidVerifier() public { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -541,6 +650,8 @@ contract ZoneFactoryTest is BaseTest { function _defaultParams() internal view returns (IZoneFactory.CreateZoneParams memory) { return IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -593,6 +704,7 @@ contract ZoneFactoryTest is BaseTest { assertEq(pc.messenger(), zoneFactory.messenger()); assertEq(pc.blockHash(), p.zoneParams.genesisBlockHash); assertEq(pc.genesisTempoBlockNumber(), p.zoneParams.genesisTempoBlockNumber); + assertEq(uint8(pc.accessMode()), uint8(p.accessMode)); assertEq(pc.rpcUrl(), p.rpcUrl); assertTrue(pc.isTokenEnabled(address(pathUSD))); } diff --git a/specs/ref-impls/test/tempo/ZoneIntegration.t.sol b/specs/ref-impls/test/tempo/ZoneIntegration.t.sol index 95a857dfa..fd3b4b0c5 100644 --- a/specs/ref-impls/test/tempo/ZoneIntegration.t.sol +++ b/specs/ref-impls/test/tempo/ZoneIntegration.t.sol @@ -15,6 +15,8 @@ import { PORTAL_CURRENT_DEPOSIT_QUEUE_HASH_SLOT, QueuedDeposit, Withdrawal, + ZoneAccessMode, + ZoneGatewayMode, ZoneInfo, ZoneParams } from "../../src/interfaces/IZone.sol"; @@ -153,6 +155,8 @@ contract ZoneIntegrationTest is BaseTest { l1Portal.initialize( 1, address(l2ZoneToken), + ZoneAccessMode.Closed, + ZoneGatewayMode.Enforced, _closedLoopAccounts(), _zoneGateways(), address(messengerContract), diff --git a/specs/ref-impls/test/tempo/ZoneMessenger.t.sol b/specs/ref-impls/test/tempo/ZoneMessenger.t.sol index 312fe055b..9fcab9a0c 100644 --- a/specs/ref-impls/test/tempo/ZoneMessenger.t.sol +++ b/specs/ref-impls/test/tempo/ZoneMessenger.t.sol @@ -5,6 +5,7 @@ import { IWithdrawalReceiver, IZonePortal, MAX_WITHDRAWAL_CALLBACK_GAS, + ZoneGatewayMode, ZoneInfo } from "../../src/interfaces/IZone.sol"; import { ZoneMessenger } from "../../src/tempo/ZoneMessenger.sol"; @@ -98,6 +99,11 @@ contract ZoneMessengerTest is BaseTest { messengerFactory.setPortal(OTHER_ZONE_ID, otherPortal); messenger = new ZoneMessenger(address(messengerFactory)); + vm.mockCall( + portal, + abi.encodeWithSelector(IZonePortal.gatewayMode.selector), + abi.encode(ZoneGatewayMode.Enforced) + ); zoneToken = new MockZoneToken("Zone USD", "zUSD"); zoneToken.setMinter(address(this), true); } diff --git a/specs/ref-impls/test/tempo/ZonePortal.t.sol b/specs/ref-impls/test/tempo/ZonePortal.t.sol index fe836f09d..7af5bf9c7 100644 --- a/specs/ref-impls/test/tempo/ZonePortal.t.sol +++ b/specs/ref-impls/test/tempo/ZonePortal.t.sol @@ -22,12 +22,15 @@ import { PORTAL_ALLOWED_ACCOUNT_SLOT, PORTAL_CURRENT_DEPOSIT_QUEUE_HASH_SLOT, PORTAL_ENCRYPTION_KEYS_SLOT, + PORTAL_ENFORCEMENT_FLAGS_SLOT, PORTAL_PENDING_ADMIN_SLOT, PORTAL_PENDING_SEQUENCER_SLOT, PORTAL_SEQUENCER_SLOT, PORTAL_ZONE_GATEWAY_SLOT, Withdrawal, ZONE_FACTORY_ADDRESS, + ZoneAccessMode, + ZoneGatewayMode, ZoneInfo, ZoneParams } from "../../src/interfaces/IZone.sol"; @@ -221,6 +224,8 @@ contract ZonePortalProxyStorageTest is Test { .initialize( 1, initialToken, + ZoneAccessMode.Closed, + ZoneGatewayMode.Enforced, _proxyAccounts(1), _proxyGateways(messengerA), messengerA, @@ -265,6 +270,8 @@ contract ZonePortalProxyStorageTest is Test { .initialize( id, initialToken, + ZoneAccessMode.Closed, + ZoneGatewayMode.Enforced, _proxyAccounts(id), _proxyGateways(portalMessenger), portalMessenger, @@ -328,6 +335,8 @@ contract ZonePortalTest is BaseTest { // Create a zone IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -641,6 +650,71 @@ contract ZonePortalTest is BaseTest { portal.setZoneGateway(makeAddr("replacement gateway"), true); } + function test_setAccessMode_opensAndReclosesWithoutDiscardingMembership() public { + address outsider = makeAddr("mutable mode outsider"); + address stagedAccount = makeAddr("staged account"); + + vm.prank(outsider); + vm.expectRevert(abi.encodeWithSelector(IZonePortal.AccountNotAllowed.selector, outsider)); + portal.deposit(address(pathUSD), outsider, 1, bytes32(0), outsider); + + vm.expectEmit(false, false, false, true); + emit IZonePortal.EnforcementModesUpdated(ZoneAccessMode.Open, ZoneGatewayMode.Enforced); + vm.prank(admin); + portal.setAccessMode(ZoneAccessMode.Open); + + vm.prank(admin); + portal.setAllowedAccount(stagedAccount, true); + + vm.prank(pathUSDAdmin); + pathUSD.mint(outsider, 2); + vm.startPrank(outsider); + pathUSD.approve(address(portal), 2); + portal.deposit(address(pathUSD), outsider, 1, bytes32(0), outsider); + vm.stopPrank(); + + vm.prank(admin); + portal.setAccessMode(ZoneAccessMode.Closed); + + assertEq(uint8(portal.accessMode()), uint8(ZoneAccessMode.Closed)); + assertTrue(portal.allowedAccount(stagedAccount)); + vm.prank(outsider); + vm.expectRevert(abi.encodeWithSelector(IZonePortal.AccountNotAllowed.selector, outsider)); + portal.deposit(address(pathUSD), outsider, 1, bytes32(0), outsider); + } + + function test_setGatewayMode_makesGatewayMembershipInertWhileOpen() public { + address gateway = makeAddr("mutable mode gateway"); + + vm.prank(admin); + portal.setZoneGateway(gateway, true); + vm.prank(pathUSDAdmin); + pathUSD.mint(gateway, 2); + vm.startPrank(gateway); + pathUSD.approve(address(portal), 2); + portal.deposit(address(pathUSD), alice, 1, bytes32(0), alice); + vm.stopPrank(); + + vm.expectEmit(false, false, false, true); + emit IZonePortal.EnforcementModesUpdated(ZoneAccessMode.Closed, ZoneGatewayMode.Open); + vm.prank(admin); + portal.setGatewayMode(ZoneGatewayMode.Open); + + assertTrue(portal.zoneGateway(gateway)); + vm.prank(gateway); + vm.expectRevert(abi.encodeWithSelector(IZonePortal.AccountNotAllowed.selector, gateway)); + portal.deposit(address(pathUSD), alice, 1, bytes32(0), alice); + } + + function test_setModes_revertIfNotAdmin() public { + vm.startPrank(alice); + vm.expectRevert(IZonePortal.NotAdmin.selector); + portal.setAccessMode(ZoneAccessMode.Open); + vm.expectRevert(IZonePortal.NotAdmin.selector); + portal.setGatewayMode(ZoneGatewayMode.Open); + vm.stopPrank(); + } + function test_setAllowedAccount_enablesAndDisablesMembership() public { address account = makeAddr("managed account"); @@ -3065,6 +3139,7 @@ contract ZonePortalTest is BaseTest { /// slot 18: verifier + genesisTempoBlockNumber + _initialized [packed] /// slot 19: zoneGateway mapping /// slot 20: allowedAccount mapping + /// slot 21: account/gateway enforcement flags [packed] function test_storageLayout_slotPositions() public { // --- Slot 0: sequencer --- bytes32 slot0 = vm.load(address(portal), bytes32(uint256(0))); @@ -3139,7 +3214,7 @@ contract ZonePortalTest is BaseTest { "slot 17: messenger mismatch" ); - // --- Slot 18: verifier (address) + genesisTempoBlockNumber (uint64) packed --- + // --- Slot 18: verifier + genesisTempoBlockNumber + initialized packed --- bytes32 slot18 = vm.load(address(portal), bytes32(uint256(18))); assertEq(address(uint160(uint256(slot18))), portal.verifier(), "slot 18: verifier mismatch"); assertEq( @@ -3155,6 +3230,9 @@ contract ZonePortalTest is BaseTest { bytes32 accountSlot = keccak256(abi.encode(alice, uint256(PORTAL_ALLOWED_ACCOUNT_SLOT))); assertEq(uint256(vm.load(address(portal), accountSlot)), 1, "slot 20: account mismatch"); + + bytes32 modeSlot = vm.load(address(portal), PORTAL_ENFORCEMENT_FLAGS_SLOT); + assertEq(uint8(uint256(modeSlot)), 3, "slot 21: enforcement flags mismatch"); } /// @notice Verify that the _encryptionKeys dynamic array uses the expected slot layout. diff --git a/specs/ref-impls/test/tempo/ZonePortalGasLimit.t.sol b/specs/ref-impls/test/tempo/ZonePortalGasLimit.t.sol index bcc1833f0..45462b8a8 100644 --- a/specs/ref-impls/test/tempo/ZonePortalGasLimit.t.sol +++ b/specs/ref-impls/test/tempo/ZonePortalGasLimit.t.sol @@ -1,7 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import { IZonePortal, Withdrawal, ZONE_FACTORY_ADDRESS } from "../../src/interfaces/IZone.sol"; +import { + IZonePortal, + Withdrawal, + ZONE_FACTORY_ADDRESS, + ZoneAccessMode, + ZoneGatewayMode +} from "../../src/interfaces/IZone.sol"; import { EMPTY_SENTINEL } from "../../src/libraries/WithdrawalQueueLib.sol"; import { ZonePortal } from "../../src/tempo/ZonePortal.sol"; import { Test } from "forge-std/Test.sol"; @@ -63,6 +69,8 @@ contract ZonePortalGasLimitTest is Test { portal.initialize( 1, address(token), + ZoneAccessMode.Closed, + ZoneGatewayMode.Enforced, allowed, gateways, address(0x400), diff --git a/specs/ref-impls/test/zone/ZoneConfig.t.sol b/specs/ref-impls/test/zone/ZoneConfig.t.sol index 067e3c273..8da58f746 100644 --- a/specs/ref-impls/test/zone/ZoneConfig.t.sol +++ b/specs/ref-impls/test/zone/ZoneConfig.t.sol @@ -2,15 +2,20 @@ pragma solidity ^0.8.13; import { + ACCOUNT_ALLOWLIST_ENFORCED_FLAG, + GATEWAY_ALLOWLIST_ENFORCED_FLAG, IZoneConfig, IZoneFactory, IZonePortal, + PORTAL_ACCESS_MODE_SLOT, PORTAL_ALLOWED_ACCOUNT_SLOT, PORTAL_ENCRYPTION_KEYS_SLOT, PORTAL_PENDING_SEQUENCER_SLOT, PORTAL_SEQUENCER_SLOT, PORTAL_TOKEN_CONFIGS_SLOT, PORTAL_ZONE_GATEWAY_SLOT, + ZoneAccessMode, + ZoneGatewayMode, ZoneParams } from "../../src/interfaces/IZone.sol"; import { ZoneFactory } from "../../src/tempo/ZoneFactory.sol"; @@ -39,6 +44,8 @@ contract ZoneConfigTest is BaseTest { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), + accessMode: ZoneAccessMode.Closed, + gatewayMode: ZoneGatewayMode.Enforced, allowedAccounts: _closedLoopAccounts(), zoneGateways: _zoneGateways(), admin: admin, @@ -60,6 +67,7 @@ contract ZoneConfigTest is BaseTest { _syncPortalSlot(PORTAL_SEQUENCER_SLOT); _syncPortalSlot(PORTAL_PENDING_SEQUENCER_SLOT); + _syncPortalSlot(PORTAL_ACCESS_MODE_SLOT); _syncTokenConfig(address(pathUSD)); _syncAllowedAccount(alice); _syncZoneGateway(address(zoneGateway)); @@ -133,12 +141,41 @@ contract ZoneConfigTest is BaseTest { } function test_closedLoopMembershipAndGatewayAreIndependent() public view { + assertEq(uint8(config.accessMode()), uint8(ZoneAccessMode.Closed)); + assertEq(uint8(config.gatewayMode()), uint8(ZoneGatewayMode.Enforced)); assertTrue(config.isAllowedAccount(alice)); assertFalse(config.isZoneGateway(alice)); assertTrue(config.isZoneGateway(address(zoneGateway))); assertFalse(config.isAllowedAccount(address(zoneGateway))); } + function test_openModeBypassesAccountMembershipButNotGatewayState() public { + tempoState.setMockStorageValue( + address(portal), + PORTAL_ACCESS_MODE_SLOT, + bytes32(uint256(GATEWAY_ALLOWLIST_ENFORCED_FLAG)) + ); + + address outsider = makeAddr("open mode outsider"); + assertEq(uint8(config.accessMode()), uint8(ZoneAccessMode.Open)); + assertTrue(config.isAllowedAccount(outsider)); + assertTrue(config.isAllowedAccount(address(zoneGateway))); + assertTrue(config.isZoneGateway(address(zoneGateway))); + assertFalse(config.isZoneGateway(outsider)); + } + + function test_gatewayModeIsIndependentFromAccessMode() public { + tempoState.setMockStorageValue( + address(portal), + PORTAL_ACCESS_MODE_SLOT, + bytes32(uint256(ACCOUNT_ALLOWLIST_ENFORCED_FLAG)) + ); + + assertEq(uint8(config.accessMode()), uint8(ZoneAccessMode.Closed)); + assertEq(uint8(config.gatewayMode()), uint8(ZoneGatewayMode.Open)); + assertTrue(config.isZoneGateway(address(zoneGateway))); + } + /// @notice Verifies reading the sequencer encryption key reverts before any key is set. function test_sequencerEncryptionKey_revertsWhenNoneSet() public { vm.expectRevert(IZoneConfig.NoEncryptionKeySet.selector); diff --git a/specs/ref-impls/test/zone/ZoneInbox.t.sol b/specs/ref-impls/test/zone/ZoneInbox.t.sol index d976cd52f..d17789dec 100644 --- a/specs/ref-impls/test/zone/ZoneInbox.t.sol +++ b/specs/ref-impls/test/zone/ZoneInbox.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.13; import { + ACCOUNT_ALLOWLIST_ENFORCED_FLAG, AES_GCM_DECRYPT, CHAUM_PEDERSEN_VERIFY, ChaumPedersenProof, @@ -18,6 +19,7 @@ import { IZoneInbox, IZoneOutbox, IZonePortal, + PORTAL_ACCESS_MODE_SLOT, PORTAL_CURRENT_DEPOSIT_QUEUE_HASH_SLOT, PORTAL_ENCRYPTION_KEYS_SLOT, QueuedDeposit, @@ -74,6 +76,9 @@ contract ZoneInboxTest is Test { tempoState.setMockStorageValue( mockPortal, bytes32(uint256(0)), bytes32(uint256(uint160(sequencer))) ); + tempoState.setMockStorageValue( + mockPortal, PORTAL_ACCESS_MODE_SLOT, bytes32(uint256(ACCOUNT_ALLOWLIST_ENFORCED_FLAG)) + ); tempoState.setMockAccountAllowed(mockPortal, alice, true); tempoState.setMockAccountAllowed(mockPortal, bob, true); tempoState.setMockAccountAllowed(mockPortal, address(0x500), true); diff --git a/specs/ref-impls/test/zone/ZoneOutbox.t.sol b/specs/ref-impls/test/zone/ZoneOutbox.t.sol index 5c6a96746..4177b8259 100644 --- a/specs/ref-impls/test/zone/ZoneOutbox.t.sol +++ b/specs/ref-impls/test/zone/ZoneOutbox.t.sol @@ -2,14 +2,19 @@ pragma solidity ^0.8.13; import { + ACCOUNT_ALLOWLIST_ENFORCED_FLAG, EncryptedDepositPayload, + GATEWAY_ALLOWLIST_ENFORCED_FLAG, IZoneOutbox, IZonePortal, LastBatch, + PORTAL_ACCESS_MODE_SLOT, PendingWithdrawal, Withdrawal, ZONE_INBOX, - ZONE_TX_CONTEXT + ZONE_TX_CONTEXT, + ZoneAccessMode, + ZoneGatewayMode } from "../../src/interfaces/IZone.sol"; import { EMPTY_SENTINEL } from "../../src/libraries/WithdrawalQueueLib.sol"; import { ZoneConfig } from "../../src/zone/ZoneConfig.sol"; @@ -67,6 +72,7 @@ contract ZoneOutboxTest is Test { tempoState.setMockAccountAllowed(mockPortal, bob, true); tempoState.setMockAccountAllowed(mockPortal, charlie, true); tempoState.setMockZoneGateway(mockPortal, callbackTarget, true); + _setModes(ZoneAccessMode.Closed, ZoneGatewayMode.Enforced); inbox = new ZoneInbox(address(config), mockPortal, address(tempoState)); outbox = new ZoneOutbox(address(config)); @@ -85,6 +91,13 @@ contract ZoneOutboxTest is Test { return keccak256(abi.encodePacked(sender, txContext.txHashFor(txSequence))); } + function _setModes(ZoneAccessMode accessMode, ZoneGatewayMode gatewayMode) internal { + uint8 flags; + if (accessMode == ZoneAccessMode.Closed) flags |= ACCOUNT_ALLOWLIST_ENFORCED_FLAG; + if (gatewayMode == ZoneGatewayMode.Enforced) flags |= GATEWAY_ALLOWLIST_ENFORCED_FLAG; + tempoState.setMockStorageValue(mockPortal, PORTAL_ACCESS_MODE_SLOT, bytes32(uint256(flags))); + } + function _withdrawal( uint256 txSequence, address sender, @@ -295,6 +308,34 @@ contract ZoneOutboxTest is Test { assertEq(disabledToken.balanceOf(alice), 1000e6); } + function test_requestWithdrawal_openAccessStillEnforcesGatewayRegistration() public { + address outsider = address(0x999); + _setModes(ZoneAccessMode.Open, ZoneGatewayMode.Enforced); + + vm.startPrank(alice); + zoneToken.approve(address(outbox), 1000e6); + outbox.requestWithdrawal(address(zoneToken), outsider, 500e6, bytes32(0), 0, alice, ""); + vm.expectRevert(IZonePortal.InvalidCallbackTarget.selector); + outbox.requestWithdrawal(address(zoneToken), outsider, 500e6, bytes32(0), 1, alice, ""); + vm.stopPrank(); + } + + function test_requestWithdrawal_openGatewayStillEnforcesAccountAllowlist() public { + address outsider = address(0x999); + _setModes(ZoneAccessMode.Closed, ZoneGatewayMode.Open); + tempoState.setMockAccountAllowed(mockPortal, callbackTarget, true); + + vm.startPrank(alice); + zoneToken.approve(address(outbox), 1500e6); + outbox.requestWithdrawal(address(zoneToken), outsider, 500e6, bytes32(0), 1, alice, ""); + outbox.requestWithdrawal( + address(zoneToken), callbackTarget, 500e6, bytes32(0), 0, alice, "" + ); + vm.expectRevert(abi.encodeWithSelector(IZonePortal.AccountNotAllowed.selector, outsider)); + outbox.requestWithdrawal(address(zoneToken), outsider, 500e6, bytes32(0), 0, alice, ""); + vm.stopPrank(); + } + function test_requestWithdrawal_revertsOnInvalidCurrentTxHash() public { ZeroTxContext zeroTxContext = new ZeroTxContext(); vm.etch(ZONE_TX_CONTEXT, address(zeroTxContext).code); diff --git a/specs/spec.md b/specs/spec.md index 629d83a88..97c53cd09 100644 --- a/specs/spec.md +++ b/specs/spec.md @@ -98,7 +98,7 @@ A Tempo Zone is a private execution environment anchored to Tempo. Inside a zone, balances, transfers, and transaction history are invisible to block explorers, indexers, and other users. Each zone is operated by a dedicated sequencer that is the sole block producer, settling back to Tempo through a proof-agnostic verification system. -Funds enter a zone through deposits on Tempo, where they are locked in the portal. The zone mints equivalent tokens, and users transact privately with balances and transaction history hidden behind authenticated RPC access and execution-level controls. When users withdraw, tokens are burned on the zone and released from the portal on Tempo. Proofs guarantee that the sequencer executed every transaction correctly and cannot forge state transitions. Closed-loop zones restrict deposits, refunds, and plain withdrawals to configured accounts. Callback withdrawals may target only registered ZoneGateway implementations and must synchronously return funds to the same zone. +Funds enter a zone through deposits on Tempo, where they are locked in the portal. The zone mints equivalent tokens, and users transact privately with balances and transaction history hidden behind authenticated RPC access and execution-level controls. When users withdraw, tokens are burned on the zone and released from the portal on Tempo. Proofs guarantee that the sequencer executed every transaction correctly and cannot forge state transitions. Each portal has two independent, admin-mutable modes: `ZoneAccessMode` controls account allowlist enforcement for deposits, refunds, and plain withdrawals, while `ZoneGatewayMode` controls callback target registration. Opening either mode disables only its corresponding checks without deleting the stored mapping. This document specifies the zone protocol: deployment, sequencer operations, deposits, execution, the private RPC interface, the proving system, batch submission, withdrawals, precompiles, contract interfaces, and the network upgrade process. @@ -118,8 +118,8 @@ This document specifies the zone protocol: deployment, sequencer operations, dep | TIP-20 | Tempo's fungible token standard. | | TIP-403 | Tempo's compliance registry. Issuers attach transfer policies (whitelists, blacklists) to TIP-20 tokens. | | Predeploy | A system contract deployed at a fixed address on the zone at genesis. | -| Allowed account | An address currently enabled in the portal's admin-managed closed-loop membership mapping. It may initiate deposits and receive Tempo-side refunds and plain withdrawals. | -| ZoneGateway | A callback-only Tempo contract registered in a portal's separate gateway mapping. A gateway is never an allowed plain-withdrawal recipient. | +| Allowed account | An address enabled in the portal's admin-managed membership mapping. Membership is enforced while `ZoneAccessMode.Closed` and retained but inactive while `ZoneAccessMode.Open`. | +| ZoneGateway | A Tempo callback contract registered in a portal's separate gateway mapping. Registration is enforced while `ZoneGatewayMode.Enforced` and retained but inactive while `ZoneGatewayMode.Open`. |
@@ -129,7 +129,7 @@ Each zone is operated by a **sequencer** that collects transactions, produces bl On the Tempo side, an onchain **verifier** contract validates that each batch was executed correctly. The verifier is abstracted behind a minimal interface (`IVerifier`) and is proof-agnostic. Any proving backend (ZK, TEE, or otherwise) can implement the interface. The portal does not care how the proof was produced. -On Tempo, each zone has a **portal** that locks deposited tokens. Only allowed accounts may initiate deposits, and refund recipients must also be allowed. Plaintext and encrypted deposit recipients are zone addresses and need not be allowed Tempo accounts. The portal locks the tokens and appends the deposit to a queue. The sequencer observes the deposit, advances the zone's view of Tempo, and mints equivalent tokens on the zone. +On Tempo, each zone has a **portal** that locks deposited tokens. In closed access mode, only allowed accounts may initiate deposits and refund recipients must also be allowed; open access mode skips both membership checks. Plaintext and encrypted deposit recipients are zone addresses and need not be allowed Tempo accounts. The portal locks the tokens and appends the deposit to a queue. The sequencer observes the deposit, advances the zone's view of Tempo, and mints equivalent tokens on the zone. Users transact on the zone privately. Balances, transfers, and transaction history are only visible to the account holder and the sequencer. The zone does not post transaction data, and data availability is entrusted to the sequencer. The sequencer has full visibility into zone activity. Privacy protects against public observers on Tempo, not against the sequencer. @@ -205,6 +205,8 @@ The following table lists every privileged action and the role authorized to inv | `resumeDeposits(token)` | [`ZonePortal`](#izoneportal) | **admin** | | `setZoneGateway(gateway, enabled)` | [`ZonePortal`](#izoneportal) | **admin** | | `setAllowedAccount(account, enabled)` | [`ZonePortal`](#izoneportal) | **admin** | +| `setAccessMode(mode)` | [`ZonePortal`](#izoneportal) | **admin** | +| `setGatewayMode(mode)` | [`ZonePortal`](#izoneportal) | **admin** | | `transferAdmin(newAdmin)` | [`ZonePortal`](#izoneportal) | **admin** | | `acceptAdmin()` | [`ZonePortal`](#izoneportal) | **pending admin** | | `transferSequencer(newSequencer)` | [`ZonePortal`](#izoneportal) | **sequencer** | @@ -237,8 +239,10 @@ A zone is created via `ZoneFactory.createZone(...)` on Tempo with the following | Parameter | Description | |-----------|-------------| | `initialToken` | The first TIP-20 token to enable. The admin can enable additional tokens later. | -| `allowedAccounts` | Non-empty initial membership. Duplicate entries are accepted and have the same effect as one entry. Members MUST NOT be the messenger. Membership applies to deposit callers, Tempo refund recipients, and plain withdrawal destinations. | -| `zoneGateways` | Non-empty initial set of callback-only gateway contracts. Duplicate entries are accepted and have the same effect as one entry. Gateways MUST NOT also be allowed accounts. The admin may later enable a replacement while retaining a legacy gateway during migration. | +| `accessMode` | Initial account enforcement mode: `Closed` checks `allowedAccounts`; `Open` skips account membership checks. The admin may change it later. | +| `gatewayMode` | Initial callback enforcement mode: `Enforced` checks `zoneGateways`; `Open` accepts arbitrary callback targets. The admin may change it later. | +| `allowedAccounts` | Optional initial membership. An empty closed mapping denies all accounts until the admin adds one; open mode may pre-stage entries for a later close. Duplicate entries have the same effect as one entry. Members MUST NOT be the messenger. | +| `zoneGateways` | Optional initial callback gateway set. Duplicate entries have the same effect as one entry. Gateways MUST NOT also be allowed accounts. Entries are retained while gateway mode is open. | | `admin` | The address that holds the admin role for the zone (token enablement, deposit pause/resume). MUST NOT be the zero address. May be the same as `sequencer`. See [Access Control](#access-control). | | `sequencer` | The address that will operate the zone (block production, batch submission, withdrawal processing). | | `verifier` | The `IVerifier` contract used to validate batch proofs. | @@ -264,7 +268,7 @@ A single [`ZoneFactory`](#izonefactory) on Tempo creates zones and maintains the |----------|---------| | [`ZonePortal`](#izoneportal) | Locks deposited tokens, accepts batch submissions, verifies proofs, and processes withdrawals. Manages the token registry and deposit/withdrawal queues. | -The factory's shared `ZoneMessenger` is fixed when each portal is initialized. It is separated from the portal so callback code does not execute with the fund-owning portal as `msg.sender`. Gateway implementations are managed with `setZoneGateway(gateway, enabled)`. Closed-loop membership is managed with `setAllowedAccount(account, enabled)`. An allowed account cannot also be an active ZoneGateway or the messenger. +The factory's shared `ZoneMessenger` is fixed when each portal is initialized. It is separated from the portal so callback code does not execute with the fund-owning portal as `msg.sender`. Gateway implementations are managed with `setZoneGateway(gateway, enabled)`, and account membership is managed with `setAllowedAccount(account, enabled)` regardless of the current modes. `setAccessMode` and `setGatewayMode` activate or deactivate the corresponding mapping atomically without clearing it. An allowed account cannot also be an active ZoneGateway or the messenger. Account and gateway membership is evaluated when each portal or zone-side action executes. Revoked in-flight destinations and gateways bounce back, while revoked refund recipients have funds parked until membership is restored. @@ -280,7 +284,7 @@ Each zone has five system contracts deployed at genesis at fixed addresses: | [`ZoneConfig`](#izoneconfig) | `0x1c00...0003` | Central configuration. Reads the sequencer address and token registry from Tempo via `TempoState`. | | `ZoneTxContext` | `0x1c00...0005` | Provides the current transaction hash to system contracts (used by `ZoneOutbox` for `senderTag` computation). | -`ZoneConfig` reads the sequencer address, token registry, allowed-account mapping, and ZoneGateway mapping from the portal on Tempo via `TempoState` storage reads, making Tempo the single source of truth for zone configuration. See [Tempo State Reads](#tempo-state-reads) for details. +`ZoneConfig` reads the sequencer address, token registry, both enforcement modes, the allowed-account mapping, and the ZoneGateway mapping from the portal on Tempo via `TempoState` storage reads, making Tempo the single source of truth for zone configuration. See [Tempo State Reads](#tempo-state-reads) for details. ### Zone Token Model @@ -356,9 +360,9 @@ Deposits move TIP-20 tokens from Tempo into a zone. The user deposits on Tempo, ### Regular Deposits -An allowed account deposits by calling `deposit(token, to, amount, memo, tempoRefundRecipient)` on the portal. The portal: +An account deposits by calling `deposit(token, to, amount, memo, tempoRefundRecipient)` on the portal. The portal: -1. Requires `msg.sender` and `tempoRefundRecipient` to be allowed accounts. The zone recipient `to` need not be allowed. +1. In closed access mode, requires `msg.sender` and `tempoRefundRecipient` to be allowed accounts. An enforced, registered ZoneGateway may deposit callback returns without account membership. Open access mode skips these membership checks. The zone recipient `to` need not be allowed. 2. Validates the token is enabled and deposits are active, requires `tempoRefundRecipient != address(0)`, validates `to` against the token's TIP-403 recipient and mint-recipient policies, and validates `tempoRefundRecipient` against the token's TIP-403 recipient policy. 3. Computes `depositFee` from `zoneGasRate` and checks `amount >= depositFee + currentBouncebackFee`, where `currentBouncebackFee = ceil(bouncebackGas * block.basefee / 1e12)` (reverts `DepositTooSmall` otherwise). This prevents obvious dust deposits that could not pay for an immediate Tempo refund when bounce-back gas is configured. 4. Transfers `amount` from the user into the portal. @@ -424,7 +428,7 @@ The encryption scheme is ECIES with secp256k1: 1. The user generates an ephemeral keypair and derives a shared secret via ECDH with the sequencer's published encryption key. 2. The user derives an AES-256 key from the shared secret using HKDF-SHA256. 3. The user encrypts `(to || memo || padding)` with AES-256-GCM, producing ciphertext, a nonce, and an authentication tag. -4. The user calls `depositEncrypted(token, amount, keyIndex, encryptedPayload, tempoRefundRecipient)` on the portal, where `keyIndex` references which encryption key they encrypted to (see [Encryption Key Management](#encryption-key-management)), and `tempoRefundRecipient` is the Tempo address that receives a refund if zone-side processing fails (see [Deposit Failures and Bounce-Back](#deposit-failures-and-bounce-back)). The caller and bounce-back recipient MUST be allowed accounts. A registered ZoneGateway is the sole exception to the caller rule: it may call `depositEncrypted` to complete an atomic callback withdrawal, but it still must supply an allowed bounce-back recipient. The decrypted `to` address is not checked against Tempo closed-loop membership. `depositEncrypted` also enforces its fee, encryption-key, payload-shape, and TIP-403 checks. +4. The user calls `depositEncrypted(token, amount, keyIndex, encryptedPayload, tempoRefundRecipient)` on the portal, where `keyIndex` references which encryption key they encrypted to (see [Encryption Key Management](#encryption-key-management)), and `tempoRefundRecipient` is the Tempo address that receives a refund if zone-side processing fails (see [Deposit Failures and Bounce-Back](#deposit-failures-and-bounce-back)). Closed access mode applies the same caller and bounce-back membership rules as a regular deposit; an enforced, registered ZoneGateway is the caller exception. Open access mode skips membership checks. The decrypted `to` address is not checked against Tempo membership. `depositEncrypted` also enforces its fee, encryption-key, payload-shape, and TIP-403 checks. Before queue insertion, the portal also validates encrypted-payload shape. `depositEncrypted` reverts `InvalidEphemeralPubkey` if the ephemeral public key parity is not `0x02` or `0x03`, or if the X coordinate is not a valid secp256k1 X coordinate. It reverts `InvalidCiphertextLength(actual, expected)` unless `ciphertext.length == 64`, the fixed plaintext size for `(to, memo, padding)`. These are Tempo-side deposit-time reverts: no queue entry is created and no zone-side bounce-back is needed. @@ -609,7 +613,7 @@ flowchart LR ### Withdrawal Request -A user withdraws by calling `requestWithdrawal(token, to, amount, memo, gasLimit, zoneFallbackRecipient, data, revealTo)` on the `ZoneOutbox`. The user must first approve the outbox to spend `amount + fee` of the token. The token must be enabled, and the `zoneFallbackRecipient` must be non-zero but need not be a Tempo allowed account. For a plain withdrawal (`gasLimit == 0`), `to` must be an allowed account and MUST NOT be a registered ZoneGateway. For a callback withdrawal (`gasLimit > 0`), `to` must be a registered ZoneGateway; it need not and MUST NOT be an allowed account. +A user withdraws by calling `requestWithdrawal(token, to, amount, memo, gasLimit, zoneFallbackRecipient, data, revealTo)` on the `ZoneOutbox`. The user must first approve the outbox to spend `amount + fee` of the token. The token must be enabled, and the `zoneFallbackRecipient` must be non-zero but need not be a Tempo allowed account. For a plain withdrawal (`gasLimit == 0`), closed access mode requires `to` membership; open access mode does not. Enforced gateway mode prevents registered gateways from receiving plain withdrawals and requires callback targets (`gasLimit > 0`) to be registered. Open gateway mode skips both gateway-mapping checks. Withdrawal requests are bounded before they enter the pending queue. `gasLimit` must be less than or equal to `MAX_WITHDRAWAL_GAS_LIMIT` or the request reverts with `GasLimitTooHigh`; `data.length` must be less than or equal to `MAX_CALLBACK_DATA_SIZE` (1,024 bytes) or the request reverts with `CallbackDataTooLarge`; and `revealTo`, when non-empty, must be a valid 33-byte compressed secp256k1 public key or the request reverts with `InvalidRevealTo`. The outbox reads the current zone transaction hash from `ZoneTxContext`; if it is zero, the request reverts with `InvalidCurrentTxHash`, because the transaction hash is part of the authenticated-withdrawal sender tag. @@ -660,19 +664,19 @@ The sequencer processes withdrawals on Tempo by calling `processWithdrawal(withd The portal dequeues before executing the withdrawal, then independently requires `withdrawal.token` to be enabled. Failed callbacks roll back in an external self-call and become bounce-backs, so the dequeue remains committed and cannot block the FIFO. If `remainingQueue` is zero (last item in the slot), processing sets the slot to `EMPTY_SENTINEL` and advances `head`; otherwise it updates the slot to `remainingQueue`. -For a plain withdrawal (`gasLimit == 0`), the portal requires `to` to be an allowed account and not a registered ZoneGateway, then transfers directly. A failed transfer creates a withdrawal bounce-back deposit for the Zone-local `zoneFallbackRecipient`. +For a plain withdrawal (`gasLimit == 0`), the portal rechecks the current modes and their corresponding mappings before transferring directly. A failed transfer or a destination invalidated by a mode or membership change creates a withdrawal bounce-back deposit for the Zone-local `zoneFallbackRecipient`. ### Withdrawal Callbacks -For withdrawals with `gasLimit > 0`, `to` must be present in the portal's `zoneGateway[address]` mapping. The withdrawal queue hash is verified and dequeued by `ZonePortal.processWithdrawal` before the callback reaches the messenger. The portal snapshots `currentDepositQueueHash`, transfers exactly `amount` to its fixed `ZoneMessenger`, and asks the messenger to relay the callback. The messenger authenticates the source portal through `ZoneFactory`, independently confirms `to` against that portal's gateway mapping, transfers the funds to the gateway, invokes `onWithdrawalReceived`, and requires the expected selector. +For withdrawals with `gasLimit > 0`, enforced gateway mode requires `to` in the portal's `zoneGateway[address]` mapping; open gateway mode accepts any target. The withdrawal queue hash is verified and dequeued by `ZonePortal.processWithdrawal` before the callback reaches the messenger. The portal snapshots `currentDepositQueueHash`, transfers exactly `amount` to its fixed `ZoneMessenger`, and asks the messenger to relay the callback. The messenger authenticates the source portal through `ZoneFactory`, independently applies the current gateway mode, transfers the funds to the target, invokes `onWithdrawalReceived`, and requires the expected selector. -The self-call requires `currentDepositQueueHash` to change, proving that a deposit was synchronously appended to the source zone. This does not bind the deposit's token or amount, so soundness rests on the configured `ZoneGateway`. Any callback failure rolls back the self-call and enqueues a bounce-back while advancing the withdrawal FIFO. +Closed access mode requires `currentDepositQueueHash` to change, proving only that some deposit was synchronously appended to the source zone. It does not bind that deposit to the callback's token, amount, or recipient; an enforced gateway is trusted to constrain the operation and return the intended result. Open access mode imposes no source-deposit invariant: callback value may enter another zone or leave the zone system entirely. Any callback failure rolls back the self-call and enqueues a bounce-back while advancing the withdrawal FIFO. -Callback data is opaque to the zone protocol. The configured ZoneGateway is trusted to allow only deposit and redeem flows and to synchronously deposit the result back into the source zone. +Callback data is opaque to the zone protocol. In enforced gateway mode, registered ZoneGateways are trusted to constrain callback behavior. In open gateway mode, arbitrary callback targets are permitted and no gateway-specific trust assumption is imposed by the protocol. An over-limit callback withdrawal also bounces back and advances the queue. -The configured gateway is expected to constrain callbacks to synchronous vault deposit with encrypted receipt-token return and synchronous vault redeem with encrypted asset return. The reference implementation contains only a test mock; production gateway/vault token-conversion behavior is outside this repository. Assets may leave the closed loop only through a plain portal withdrawal to an allowed account, never through a callback. +For closed access mode, a successful callback must synchronously append a deposit to the source zone, subject to the limitation above. Open access mode deliberately permits arbitrary open-loop routing, including callbacks that deposit into another zone or do not deposit into any zone. The reference implementation contains only test routing implementations; production gateway/vault token-conversion behavior is outside this repository. ### Withdrawal Failures and Bounce-Back @@ -729,7 +733,7 @@ For callback withdrawals, `IWithdrawalReceiver.onWithdrawalReceived` receives th ### Zone-to-Zone Transfers -Closed-loop callback withdrawals cannot target another zone portal, router, DEX, staking contract, or arbitrary receiver. A transfer to another zone must first exit through a plain withdrawal to an allowed Tempo account and then enter the destination zone through its independently authorized deposit path. There is no direct callback-based zone-to-zone escape from the closed loop. +Closed access mode requires source queue advancement but cannot prove that the callback value itself returned. Open access mode permits direct callback-based routing without source queue advancement, whether into another zone or outside the zone system. Gateway registration remains independently enforced unless the admin also selects open gateway mode.
@@ -953,7 +957,7 @@ The zone exposes three methods under the `zone_` namespace: | Method | Access | Description | |--------|--------|-------------| | `zone_getAuthorizationTokenInfo` | Any authenticated | Returns the authenticated account address and token expiry | -| `zone_getZoneInfo` | Any authenticated | Returns `zoneId`, `zoneTokens`, `sequencer`, `chainId` | +| `zone_getZoneInfo` | Any authenticated | Returns `zoneId`, `zoneTokens`, `sequencer`, `chainId`, `accessMode`, and `gatewayMode` | | `zone_getDepositStatus(tempoBlockNumber)` | Scoped | Returns deposit processing status for the given Tempo block, filtered to deposits where the caller is the sender or recipient | There are no state-changing methods via authorization token. Withdrawals require a signed transaction submitted via `eth_sendRawTransaction`. @@ -1572,6 +1576,8 @@ struct ZoneInfo { uint32 zoneId; address portal; address initialToken; + ZoneAccessMode accessMode; + ZoneGatewayMode gatewayMode; address admin; address sequencer; address verifier; @@ -1597,8 +1603,13 @@ struct LastBatch { ```solidity interface IZoneFactory { + enum ZoneAccessMode { Closed, Open } + enum ZoneGatewayMode { Enforced, Open } + struct CreateZoneParams { address initialToken; + ZoneAccessMode accessMode; + ZoneGatewayMode gatewayMode; address[] allowedAccounts; address[] zoneGateways; address admin; @@ -1610,7 +1621,8 @@ interface IZoneFactory { event ZoneCreated( uint32 indexed zoneId, address indexed portal, - address initialToken, address admin, address sequencer, address verifier, + address initialToken, ZoneAccessMode accessMode, ZoneGatewayMode gatewayMode, + address admin, address sequencer, address verifier, bytes32 genesisBlockHash, bytes32 genesisTempoBlockHash, uint64 genesisTempoBlockNumber ); @@ -1698,6 +1710,7 @@ interface IZonePortal { event DepositsResumed(address indexed token); event ZoneGatewayUpdated(address indexed gateway, bool enabled); event AllowedAccountUpdated(address indexed account, bool enabled); + event EnforcementModesUpdated(ZoneAccessMode accessMode, ZoneGatewayMode gatewayMode); error NotSequencer(); error NotAdmin(); @@ -1739,7 +1752,11 @@ interface IZonePortal { function enabledTokenCount() external view returns (uint256); function enabledTokenAt(uint256 index) external view returns (address); - // Closed-loop configuration + // Access and callback configuration + function accessMode() external view returns (ZoneAccessMode); + function setAccessMode(ZoneAccessMode newMode) external; // admin-only + function gatewayMode() external view returns (ZoneGatewayMode); + function setGatewayMode(ZoneGatewayMode newMode) external; // admin-only function allowedAccount(address account) external view returns (bool); function setAllowedAccount(address account, bool enabled) external; // admin-only function zoneGateway(address gateway) external view returns (bool); @@ -1751,12 +1768,12 @@ interface IZonePortal { function setRpcUrl(string calldata rpcUrl) external; // sequencer-only // Deposits - /// @dev Caller and `tempoRefundRecipient` must be allowed accounts. `to` is a zone address. + /// @dev Closed access requires caller and refund-recipient membership. `to` is a zone address. function deposit( address token, address to, uint128 amount, bytes32 memo, address tempoRefundRecipient ) external returns (bytes32 newCurrentDepositQueueHash); - /// @dev Caller and `tempoRefundRecipient` must be allowed accounts, except that a - /// registered ZoneGateway may call this function for a synchronous callback return. + /// @dev Closed access requires caller and refund-recipient membership, except that an + /// enforced, registered ZoneGateway may make a synchronous callback return. /// The encrypted zone recipient need not be an allowed Tempo account. function depositEncrypted( address token, uint128 amount, uint256 keyIndex, @@ -2038,13 +2055,15 @@ interface IZoneConfig { function sequencer() external view returns (address); function isSequencer(address account) external view returns (bool); function isEnabledToken(address token) external view returns (bool); + function accessMode() external view returns (ZoneAccessMode); + function gatewayMode() external view returns (ZoneGatewayMode); function isAllowedAccount(address account) external view returns (bool); function isZoneGateway(address gateway) external view returns (bool); function sequencerEncryptionKey() external view returns (bytes32 x, uint8 yParity); } ``` -Reads the sequencer address, token registry, account membership, gateway membership, and encryption key from the portal on Tempo via `TempoState` storage reads. +Reads the sequencer address, token registry, enforcement modes, account membership, gateway membership, and encryption key from the portal on Tempo via `TempoState` storage reads. ### TIP-403 Registry diff --git a/xtask/src/create_zone.rs b/xtask/src/create_zone.rs index bb3c180fb..ba4845f21 100644 --- a/xtask/src/create_zone.rs +++ b/xtask/src/create_zone.rs @@ -19,6 +19,16 @@ use zone_primitives::constants::zone_chain_id; use crate::zone_utils::MODERATO_ZONE_FACTORY; sol! { + enum ZoneAccessMode { + Closed, + Open, + } + + enum ZoneGatewayMode { + Enforced, + Open, + } + struct ZoneParams { bytes32 genesisBlockHash; bytes32 genesisTempoBlockHash; @@ -27,6 +37,8 @@ sol! { struct CreateZoneParams { address initialToken; + ZoneAccessMode accessMode; + ZoneGatewayMode gatewayMode; address[] allowedAccounts; address[] zoneGateways; address admin; @@ -42,6 +54,8 @@ sol! { uint32 indexed zoneId, address indexed portal, address initialToken, + ZoneAccessMode accessMode, + ZoneGatewayMode gatewayMode, address admin, address sequencer, address verifier, @@ -56,6 +70,50 @@ sol! { } } +#[derive(Clone, Copy, Debug, clap::ValueEnum)] +enum ZoneAccessModeArg { + Closed, + Open, +} + +impl ZoneAccessModeArg { + const fn contract_value(self) -> ZoneAccessMode { + match self { + Self::Closed => ZoneAccessMode::Closed, + Self::Open => ZoneAccessMode::Open, + } + } + + const fn label(self) -> &'static str { + match self { + Self::Closed => "closed", + Self::Open => "open", + } + } +} + +#[derive(Clone, Copy, Debug, clap::ValueEnum)] +enum ZoneGatewayModeArg { + Enforced, + Open, +} + +impl ZoneGatewayModeArg { + const fn contract_value(self) -> ZoneGatewayMode { + match self { + Self::Enforced => ZoneGatewayMode::Enforced, + Self::Open => ZoneGatewayMode::Open, + } + } + + const fn label(self) -> &'static str { + match self { + Self::Enforced => "enforced", + Self::Open => "open", + } + } +} + #[derive(Debug, clap::Parser)] pub(crate) struct CreateZone { /// Output directory where genesis.json will be written. @@ -74,13 +132,21 @@ pub(crate) struct CreateZone { #[arg(long, default_value_t = address!("0x20C0000000000000000000000000000000000000"))] initial_token: Address, + /// Initial account allowlist enforcement mode. Membership is retained while open. + #[arg(long, value_enum, default_value_t = ZoneAccessModeArg::Open)] + access_mode: ZoneAccessModeArg, + + /// Initial callback gateway registration enforcement mode. + #[arg(long, value_enum, default_value_t = ZoneGatewayModeArg::Open)] + gateway_mode: ZoneGatewayModeArg, + /// Callback-only ZoneGateway implementation. Repeat to support legacy and replacement gateways. - #[arg(long = "zone-gateway", required = true)] + #[arg(long = "zone-gateway")] zone_gateways: Vec
, /// Allowed plain-withdrawal/deposit account. Repeat for each member. /// Zone gateways are configured separately and must not be included. - #[arg(long = "allowed-account", required = true)] + #[arg(long = "allowed-account")] allowed_accounts: Vec
, /// Sequencer address that will operate the zone. @@ -152,6 +218,8 @@ impl CreateZone { let params = CreateZoneParams { initialToken: self.initial_token, + accessMode: self.access_mode.contract_value(), + gatewayMode: self.gateway_mode.contract_value(), allowedAccounts: self.allowed_accounts.clone(), zoneGateways: self.zone_gateways.clone(), admin: self.admin, @@ -227,6 +295,8 @@ impl CreateZone { "portal": format!("{portal}"), "messenger": format!("{messenger}"), "initialToken": format!("{}", self.initial_token), + "accessMode": self.access_mode.label(), + "gatewayMode": self.gateway_mode.label(), "zoneGateways": self.zone_gateways.iter().map(ToString::to_string).collect::>(), "allowedAccounts": self.allowed_accounts.iter().map(ToString::to_string).collect::>(), "admin": format!("{}", self.admin), @@ -248,6 +318,8 @@ impl CreateZone { println!(" Portal: {portal}"); println!(" Messenger: {messenger}"); println!(" Initial Token: {}", self.initial_token); + println!(" Access Mode: {}", self.access_mode.label()); + println!(" Gateway Mode: {}", self.gateway_mode.label()); println!(" Admin: {}", self.admin); println!(" Sequencer: {}", self.sequencer); println!(" ZoneFactory: {}", self.zone_factory);