Contract
0x5e0e6f3bb713b69c395502a89b4368995b276cd5
- Address
- 0x5e0e6f3bb713b69c395502a89b4368995b276cd5
- Kind
- verified contract FinalAssetRegistry
- Balance
- 0 vETH
- Nonce
- 1
- Code
- 33,519 bytes codehash 0x60c9aaedf634fed7a5101bb8945650c6994621af71bd3bda483b6bcb4f0a50f6
account tree
- Tree
- 1 · accounts
- Present
- no leaf
- Key
- 0x02f0e4eb18190210234a21671271fefdc138c9302d9e700d968f3bf24223df02
- Live root
- 0xeae723253d5f6a608807aa960f2b55066f9694cd06953d238148b49b400dce61
This address holds no leaf in the account tree. Every Final Wallet — service identities included — has one, so an absent leaf means an ordinary account rather than a wallet.
source verified
- Contract
- FinalAssetRegistry exact match · immutables masked
- Compiler
- v0.8.33+commit.64118f21
- Optimizer
- enabled · 200 runs
- EVM version
- prague
- Verified
- 2026-09-10T07:10:05.198Z
- Provenance
- preverify-final-chain (forge artifact, bytecode compared against live code)
contracts/finalchain/FinalAssetRegistry.sol
// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy and operate this asset registry as part
// of a Final DeFi Protocol chain, and may publish entries to it under the
// quorum the chain recognises.
// 2. Integrators, indexers, and node operators may read the asset set, its
// per-chain parameters, and the roots it publishes, as part of their
// integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
// deployment of a Fork of this asset registry or a competing asset or
// risk-parameter plane derived from it without permission prior to the
// Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";
import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalStateTrees, IChainSource} from "./FinalStateTrees.sol";
import {FinalPlaneSweep} from "./FinalPlaneSweep.sol";
/**
* @title FinalAssetRegistry
* @notice Which networks we settle on, and which assets exist on them. On
* Final Chain, as state — not as a list in a process.
*
* ## What this replaces
*
* Three hardcoded rosters, each authoritative for something and none of them a
* record anyone could prove:
*
* | was | where | how it failed |
* |---|---|---|
* | the supported chain set | `chainRegistry.js` + a JS Merkle fold | the root a chain verified against was folded in `log2(n)`; the chain's own verifier is `verifyTaggedSortedProof`. Two implementations of one construction. |
* | the vAsset roster | `buildAssetRegistry(records)` over a caller-supplied array | whoever called it decided the set. "The publisher should pass the right list" is not a control. |
* | the PHI morphable roster | `PHI_MORPH_ASSETS`, an env var | unset on every environment, so five publishers idled; set wrong, and they would have priced against pools nobody checked. |
* | the monitored fee assets | `MONITORED_ASSETS`, a literal with MAINNET defaults | applied silently on Sepolia, where those pool addresses have no code, so the test fleet sampled reserves that could never exist. |
*
* Each was a different shape of the same mistake: a set that decides what the
* protocol will accept, held somewhere that cannot be proven, versioned or
* audited, and readable only by the process that happened to hold it.
*
* ## The tree IS the roster
*
* An entry here is state. It is seeded once, mutated by `add` and `remove`
* under a quorum, and every mutation writes tree 6 in the same transaction —
* so there is no window in which the record and the root disagree, and no
* second copy for them to disagree with.
*
* `remove` is a tombstone, not a deletion, and that is deliberate twice over.
* `FinalStateTrees` gives every key a PERMANENT slot on first write, so a
* deletion is not available to implement. It is also the wrong thing to want:
* a consumer asking "is this asset supported?" needs a provable NO, and an
* absent leaf proves nothing — it is indistinguishable from a leaf that was
* never published, from a chain that is behind, and from a proof built against
* the wrong epoch. `enabled=false` is a leaf, and a leaf can be proven.
*
* ## Two trees, and they are not redundant
*
* **Tree 6 holds the rows.** One leaf per chain and per asset, carrying every
* attribute a consumer needs. That is what makes membership provable on a chain
* that cannot read this one.
*
* **Tree 5 holds the two registry roots** that `FinalSettlement.syncChain` and
* `registerAsset` verify against. Those are not tree 6's root: they are roots
* over the chain leaves alone and the asset leaves alone, in the sorted-pair
* tagged shape `FinalMerkle.verifyTaggedSortedProof` runs — a different
* construction from tree 6's fixed depth-20 slotted tree, over a different leaf
* set. Publishing tree 6's root as a registry root would produce a value that
* verifies nothing, with both sides internally consistent.
*
* They are computed HERE, on chain, for the reason every other root on this
* chain is: the alternative is a second implementation in JavaScript, and a
* divergence between two folders of one construction presents as a proof that
* verifies nowhere with nothing pointing at the cause.
*
* ## Tree 6 is ALL of the protocol's configuration
*
* Eight key kinds. Kinds 0–2 are the rosters (chains, assets, assets on chains),
* kind 3 is every numeric policy, kind 7 is a chain's execution terms (its
* lanes and fee-quote bounds), kind 8 is the builder set a chain's bundles may
* be offered to (flavour, endpoint hash, capability bits — the tree attests the
* transport, the endpoint URL and its key stay in the service's own
* configuration), and kinds 4 and 5 are the price plane's
* wiring: one row per price VENUE with that venue's own ticker and, for a DEX
* pool, every TWAP term of that pool; and one row per DEX protocol deployment
* per chain, so a pool is verified against its factory by a chain read and the
* treasury's router is a reference rather than an address of its own. A chain
* row is the complete protocol description of a chain — base asset, finality
* rule, block timer, multicall, gas model. With this, no service reads an
* environment variable for anything but its own RPC and keys; the RPC stays
* off the tree by rule, because where one operator's fleet reads a chain is
* not a protocol fact and tree 6 is public state.
*
* ## Gas
*
* Re-folding both registries on every mutation is O(n log n) hashes. That is
* affordable because this is our own chain, the cadence is "rarely", and the
* sets are tens of entries. Do not carry the pattern to a chain where a fold is
* paid by a user.
*/
contract FinalAssetRegistry is IChainSource, FinalPlaneSweep {
// ------------------------------------------------------------- constants
/// @dev Chain-registry leaf space. Byte-for-byte what `FinalSettlement`
/// hashes, because a root computed under a different domain verifies
/// nowhere and the mismatch is invisible until a proof is spent.
bytes32 internal constant DOMAIN_CHAIN_LEAF = keccak256("FINAL_CHAIN_REGISTRY_LEAF_v01");
/// @dev Asset-registry leaf space. Disjoint from the chain space so a chain
/// record can never be replayed as an asset record.
bytes32 internal constant DOMAIN_ASSET_LEAF = keccak256("FINAL_ASSET_REGISTRY_LEAF_v01");
/// @dev Tree 6 key and leaf spaces. Separate from the registry spaces
/// above: tree 6 answers "is this supported", the registries answer "what
/// are its attributes", and one leaf must not satisfy the other's proof.
bytes32 internal constant DOMAIN_ALLOWLIST_KEY = keccak256("FINAL_ALLOWLIST_KEY_v01");
/// @dev Domain tag for an allowlist leaf. Any change here invalidates every proof already published against
/// tree 6, so it is versioned rather than edited.
bytes32 internal constant DOMAIN_ALLOWLIST_LEAF = keccak256("FINAL_ALLOWLIST_LEAF_v01");
/// @dev Tree 5 key and leaf spaces.
bytes32 internal constant DOMAIN_REGISTRY_ROOT_KEY = keccak256("FINAL_REGISTRY_ROOT_KEY_v01");
/// @dev Domain tag for a registry-root leaf, versioned on the same rule as the allowlist tag.
bytes32 internal constant DOMAIN_REGISTRY_ROOT_LEAF = keccak256("FINAL_REGISTRY_ROOT_LEAF_v01");
/// @dev Action tag for the one-shot seeding call, kept distinct from the mutation tag so a seeding approval
/// can never be replayed as an ordinary mutation.
bytes32 internal constant ACTION_SEED = keccak256("FinalAssetRegistry.seed.v01");
/// @dev Action tag for an ordinary mutation.
bytes32 internal constant ACTION_MUTATE = keccak256("FinalAssetRegistry.mutate.v01");
/// @dev A DISTINCT action, sharing `mutate`'s nonce. Distinct so a batch
/// approved for one door cannot be replayed through the other; shared
/// nonce so the two write paths are totally ordered against each other
/// rather than each advancing a counter the other cannot see.
bytes32 internal constant ACTION_SET_POLICY = keccak256("FinalAssetRegistry.setPolicy.v01");
/// @dev Action tag for a per-chain execution-terms write.
bytes32 internal constant ACTION_SET_CHAIN_TERMS = keccak256("FinalAssetRegistry.setChainTerms.v01");
/// @dev Action tag for a per-chain builder-row write (kind 8).
bytes32 internal constant ACTION_SET_BUILDERS = keccak256("FinalAssetRegistry.setBuilders.v01");
/// @dev Kinds 4 and 5, same arrangement: their own door, the shared nonce.
bytes32 internal constant ACTION_SET_SOURCES = keccak256("FinalAssetRegistry.setSources.v01");
/// @dev Tree index carrying the settlement set.
uint8 internal constant TREE_SETTLEMENT = 5;
/// @dev Tree index carrying the allowlist.
uint8 internal constant TREE_ALLOWLIST = 6;
/// @dev The branch every registry row lives in — `FinalStateTrees.BRANCH_MAIN`,
/// pinned by test. Branch 0 of both trees is the configuration branch.
uint8 internal constant BRANCH_MAIN_ID = 1;
/// @dev Which registry a tree-5 leaf is the root of.
uint8 internal constant REGISTRY_CHAIN = 0;
/// @dev Registry selector for the ASSET root, the counterpart of `REGISTRY_CHAIN`.
uint8 internal constant REGISTRY_ASSET = 1;
/// @dev Byte-for-byte `FinalSettlement`'s. A chain reference derived under
/// a different domain here would name a chain nothing else can find.
bytes32 internal constant DOMAIN_CHAIN_REF = keccak256("FINAL_CHAIN_REF_v01");
/// @notice CAIP namespace hash for EVM chains. A chain in this namespace must declare `VM_EVM`.
bytes32 public constant CAIP_NAMESPACE_EIP155 = keccak256("eip155");
// ----------------------------------------------------------------- types
/// @notice What an asset is FOR. An asset can be more than one.
///
/// @dev A bitfield rather than an enum, because the three uses are
/// independent and an asset commonly has two of them. Modelling it as an
/// enum forced a "BOTH" member the day the second combination appeared,
/// and a third use makes that combinatorial.
///
/// - `USE_SETTLE` — bridgeable; may be issued as a vAsset.
/// - `USE_MORPH` — may be morphed by the PHI ledger.
/// - `USE_FEE` — accepted as a gateway fee token and priced by the
/// oracle. This is the roster `MONITORED_ASSETS` was.
/// - `USE_REFILL` — autosold to base crypto.
/// - `USE_SETTLE_DENIED` — excluded from bridging.
///
/// Scope is not uniform. `USE_MORPH` and `USE_SETTLE_DENIED` are properties
/// of the ASSET and live on the global row; `USE_FEE` and `USE_REFILL`
/// differ by network and live on the per-chain row. `GLOBAL_USE_MASK` and
/// `CHAIN_USE_MASK` are enforced, so a bit cannot be set where nothing would
/// read it.
uint8 internal constant USE_SETTLE = 1;
/// @notice `uses` bit: the asset may back a morph.
uint8 internal constant USE_MORPH = 2;
/// @notice `uses` bit: the asset may pay fees.
uint8 internal constant USE_FEE = 4;
/// @dev Autosold to base crypto on this chain. **Separate from `USE_FEE` on
/// purpose**: accepting an asset as payment and dumping it for base
/// crypto are different decisions, and they stop matching the first
/// time we accept a fee token we do not want to sell.
uint8 internal constant USE_REFILL = 8;
/// @dev Excluded from bridging. A DENY bit, not an allow bit — the asset
/// registry is default-allow minus exclusions, so absence means
/// bridgeable and only an explicit leaf can say otherwise. An absent
/// leaf proves nothing, which is why exclusion has to be written down
/// rather than inferred from a missing row.
uint8 internal constant USE_SETTLE_DENIED = 16;
/// @dev Bits meaningful on the GLOBAL asset row (kind 1). Properties of the
/// asset itself, true wherever it is.
uint8 internal constant GLOBAL_USE_MASK = USE_SETTLE | USE_MORPH | USE_SETTLE_DENIED;
/// @dev Bits meaningful on the PER-CHAIN row (kind 2). Properties of the
/// asset ON a network.
uint8 internal constant CHAIN_USE_MASK = USE_FEE | USE_REFILL;
/// @notice `ChainEntry.role` values.
uint8 public constant CHAIN_ROLE_FULL = 0;
/// @notice `ChainEntry.role`: observed only — read and priced, never settled on.
uint8 public constant CHAIN_ROLE_OBSERVED = 1;
/// @notice Bounds of `AssetChainEntry.maxLeveragePct` when set — the same
/// numbers as `FinalStateRecords.MIN_LEVERAGE_PCT` / `MAX_LEVERAGE_PCT`
/// (a cap outside the records' own bound would never bind).
uint16 public constant LEVERAGE_CAP_MIN_PCT = 100;
/// @notice Upper bound of `AssetChainEntry.maxLeveragePct` when set.
uint16 public constant LEVERAGE_CAP_MAX_PCT = 500;
/// @notice How a chain's finality is decided. Zero is unset.
uint8 public constant FINALITY_TAG_FINALIZED = 1;
/// @notice Finality by confirmation depth.
uint8 public constant FINALITY_CONFIRMATIONS = 2;
/// @notice Finality by settlement on the chain's parent.
uint8 public constant FINALITY_L2_SETTLED = 3;
/// @notice How a chain prices gas. Zero is unset.
uint8 public constant GAS_MODEL_EIP1559 = 1;
/// @notice Legacy single gas price.
uint8 public constant GAS_MODEL_LEGACY = 2;
/// @notice Execution gas plus a separate parent-chain data fee.
uint8 public constant GAS_MODEL_L2_WITH_L1_FEE = 3;
/// @dev `vmKind` values. Zero is refused on an enabled row: a chain whose
/// execution model nobody named is one every consumer would guess at,
/// and the guesses would all be "EVM" right up until the first chain
/// that is not. Extend by number; never renumber.
uint8 public constant VM_EVM = 1;
/// @notice Execution model: SVM.
uint8 public constant VM_SVM = 2;
/// @notice Execution model: Move.
uint8 public constant VM_MOVE = 3;
/// @notice DEX protocol families a kind-5 row may describe. Zero is unset.
/// @dev The READ SHAPE of a pool — v2 reserves, v3 `slot0` + `observe`, v4
/// StateView — comes from this, never from the pool row.
uint8 public constant PROTOCOL_UNISWAP_V2 = 1;
/// @notice DEX family: Uniswap v3 — concentrated liquidity, priced from `slot0` and `observe`.
uint8 public constant PROTOCOL_UNISWAP_V3 = 2;
/// @notice DEX family: Uniswap v4 — priced through the state view.
uint8 public constant PROTOCOL_UNISWAP_V4 = 3;
/// @notice DEX family: Velodrome.
uint8 public constant PROTOCOL_VELODROME = 4;
/// @notice DEX family: Curve.
uint8 public constant PROTOCOL_CURVE = 5;
/// @notice The tree-4 refresh a roster row gets by default, and the one a
/// morph or fee asset gets. Milliseconds.
/// @dev A stale price on a morphable or fee asset is a free option against
/// the collateral or the float, so those refresh every second and are
/// refused after three of their own ticks. Everything else keeps the
/// round.
uint32 public constant PRICE_CADENCE_DEFAULT_MS = 10_000;
/// @notice Default staleness bound: a price older than this is refused.
uint32 public constant PRICE_MAX_AGE_DEFAULT_MS = 120_000;
/// @notice Fast cadence, for an asset that backs a morph or pays fees.
uint32 public constant PRICE_CADENCE_FAST_MS = 1_000;
/// @notice Fast staleness bound — three of its own ticks, so a stalled publisher is caught immediately.
uint32 public constant PRICE_MAX_AGE_FAST_MS = 3_000;
/// @notice One network we settle on — the complete protocol description of
/// a chain.
///
/// @dev Everything a service needs to READ a chain correctly is here, so
/// none of it is a per-process setting: base asset, finality rule, the
/// block timer, multicall, the gas model. Only the RPC endpoint stays out,
/// by rule — it is where one operator's fleet reads the chain, resolved by
/// convention from `chainRef`, not a fact about the chain.
struct ChainEntry {
/// @dev CAIP-style reference, not an EIP-155 id — the registry spans
/// non-EVM namespaces and an integer chain id cannot name those.
bytes32 chainRef;
/// @dev The two halves `chainRef` is the hash OF, carried so the entry
/// is self-describing.
///
/// A hash cannot be inverted, so a consumer holding only
/// `chainRef` cannot learn which chain it names — it can only
/// re-hash candidates from a list it already has, which is the
/// list this registry exists to replace. Carrying the preimage is
/// what makes "read the chain set from the tree" a complete answer
/// instead of a lookup that still needs the old array.
///
/// Checked on write: `chainRefFor(namespace, reference)` must
/// equal `chainRef`. An entry claiming a reference it does not
/// hash to would send every consumer to the wrong chain with a
/// correct-looking proof.
bytes32 caipNamespace;
/// @dev The reference half of the CAIP pair. Checked on write together with `caipNamespace`: the two must
/// hash to `chainRef`, or the entry would send every consumer to the wrong chain under a correct proof.
bytes32 caipReference;
/// @dev `FinalSettlement` on that chain, widened to 32 bytes.
bytes32 settlement;
/// @dev Matches `FinalSettlement.AccountSpace`.
uint8 accountSpace;
/// @dev Asset id of the gas / base asset — what fees are priced in.
/// Zero while the chain's assets are not yet seeded: the chain row
/// has to exist before an asset can name it as origin.
bytes32 nativeAsset;
/// @dev Asset id of its wrapped form — the pool leg.
bytes32 wrappedNative;
/// @dev `FINALITY_*`.
uint8 finalityKind;
/// @dev Confirmations for `FINALITY_CONFIRMATIONS`; zero for a tag rule.
uint64 finalityParam;
/// @dev The chain's block timer — the cadence of everything read per
/// block. Every DEX / TWAP source on the chain is read once per
/// block, the window → blocks conversion uses it, and the RPC
/// health probe paces on it.
uint32 blockTimeMs;
/// @dev Read the gas price every N blocks (1 = every block).
uint16 gasReadBlocks;
/// @dev `eth_feeHistory` window the gas quote is composed over.
uint16 gasHistoryBlocks;
/// @dev Multicall3 on that chain, widened.
bytes32 multicall;
/// @dev `GAS_MODEL_*`.
uint8 gasModel;
/// @dev For an L2: the chain whose L1 fee component applies, else zero.
bytes32 l1ChainRef;
/// @dev `FinalGateway` on that chain, widened to 32 bytes. On EVM
/// chains it is today the same CREATE2 address everywhere, which
/// is exactly the assumption a non-EVM chain breaks — so the
/// registry carries it per chain and a consumer reads it here
/// rather than deriving it. Zero while the chain's gateway is not
/// yet deployed, same rule as `settlement`.
bytes32 gateway;
/// @dev `VM_*` — the chain's execution model. What a consumer branches
/// on to pick an adapter; `caipNamespace` names the namespace and
/// this names the machine, and on eip155 they must agree (VM_EVM).
uint8 vmKind;
/// @dev False retires the chain. A retired row is kept rather than deleted, because an absent leaf proves
/// nothing and a consumer must be able to prove that a chain was withdrawn.
bool enabled;
/// @dev Monotonic per entry. What makes a stale proof refusable rather
/// than merely old, and what a consumer's ring compares against.
uint64 epoch;
/// @dev The height the fleet's resyncs start from on this chain: nothing
/// of ours exists below it, so no window is scanned below it.
/// Zero means unknown, and the scan starts from the cursor.
uint64 startHeight;
/// @dev `CHAIN_ROLE_FULL` (0): settlement, accounts, fees — `settlement`
/// required. `CHAIN_ROLE_OBSERVED` (1): the fleet READS it — gas per
/// block time, marks — but settles nothing there and needs no
/// contracts of ours on it — a chain the oracles price without any
/// wallet deployment.
uint8 role;
/// @dev The address that pays for execution on this chain — the fee
/// lane — widened to 32 bytes so a non-EVM chain fits.
///
/// Zero means `gateway` IS the fee lane, which is the ordinary
/// case: on every chain we have deployed, `FinalGateway`'s
/// paymaster module holds the float and settles the fee. The word
/// is written only where the two differ, so a consumer resolves
/// the fee lane as `paymaster == 0 ? gateway : paymaster` and a
/// chain row that predates the field answers the same address it
/// always did rather than a zero a client would misread as "no
/// fee lane here".
bytes32 paymaster;
}
/// @notice One asset, with every attribute a remote chain cannot read.
///
/// @dev `decimals`, `name` and `symbol` are here for the reason
/// `FinalSettlement.AssetLeaf` carries them: the token lives somewhere
/// else, so a chain registering it cannot check them, and a
/// caller-supplied `decimals` on a permissionless entrypoint is a
/// mint-multiplier attack no on-chain check could catch.
///
/// The venue an asset is priced against is NOT here any more: it is a
/// kind-4 row per venue, because the median needs several and each has its
/// own ticker and terms.
struct AssetEntry {
/// @dev The chain the asset is native to — where its real balance is custodied.
bytes32 originChainRef;
/// @dev The asset's identifier on its origin chain, widened to 32 bytes so a non-EVM token fits.
bytes32 originToken;
uint8 decimals;
string name;
string symbol;
/// @dev `USE_*` bitfield.
uint8 uses;
/// @dev How often this asset's tree-4 row is republished, and how old a
/// price may be before the banded agreement refuses it. Properties
/// of the asset, inherited by its kind-4 venues, so no service
/// decides on its own how fresh a price is. Zero on write selects
/// the default for the asset's uses.
uint32 priceCadenceMs;
/// @dev How old this asset's price may be before the banded agreement refuses it. Zero on write selects the
/// default for the asset's uses.
uint32 maxAgeMs;
/// @dev False retires the asset, as a tombstone rather than a deletion.
bool enabled;
/// @dev Monotonic per entry, so a stale proof is refusable rather than merely old.
uint64 epoch;
}
/// @notice How a price venue is reached.
///
/// @dev An enum and not a bool, because "DEX or CEX" was the question until
/// the first aggregator, and a third answer should not need a second
/// field. `None` is the zero value so an unset venue is legible rather
/// than reading as a DEX at address zero.
enum VenueKind {
None,
Dex,
Cex,
Aggregator
}
/// @notice One asset's policy on ONE chain.
///
/// @dev `uses` carries only `CHAIN_USE_MASK` bits; the global row carries
/// the rest. The venue that used to sit here is a kind-4 row.
struct AssetChainEntry {
/// @dev The asset this row is about.
bytes32 assetId;
/// @dev The chain this row is about. One asset has one row per chain it is configured on.
bytes32 chainRef;
/// @dev `USE_FEE` and/or `USE_REFILL`. Other bits are refused.
uint8 uses;
/// @dev False retires this asset's configuration on this chain, leaving the global row untouched.
bool enabled;
/// @dev Monotonic per entry.
uint64 epoch;
/// @dev Leverage ceiling for morphs on THIS asset on THIS chain, in
/// percent (300 = 3×); 0 = no per-chain cap, the records' global
/// bound alone applies. Enforced by `FinalStateRecords.setPhiAccounts`
/// through `leverageCapPct`, so leverage is bounded per asset AND
/// per chain rather than once globally.
uint16 maxLeveragePct;
/// @dev The asset's own contract ON THIS CHAIN, widened to 32 bytes.
///
/// The global row carries `originToken`, which is the address on
/// the chain the asset is NATIVE to and is the wrong address
/// everywhere else. A client acting on the asset here needs the
/// one deployed here, and deriving it is not possible for a
/// bridged token whose address nobody controls. Zero means the
/// asset has no contract of its own on this chain — the native
/// gas asset, or an asset reached only as its vAsset.
bytes32 token;
/// @dev The morph contract for this asset on this chain, widened.
///
/// Zero means the asset is not morphable here, which is the state
/// of every asset whose global row lacks `USE_MORPH` and of a
/// morphable asset on a chain where the contract is not deployed
/// yet. A consumer must treat the two as one answer: no address,
/// no morph.
bytes32 morph;
/// @dev The `FinalVAsset` clone standing in for this asset on this
/// chain, widened. Zero on the asset's ORIGIN chain, where the
/// real token is custodied and no stand-in exists, and on any
/// chain the clone has not been issued on yet.
bytes32 vAsset;
}
/// @notice One price source for one asset — kind 4.
///
/// @dev One row per VENUE, because the median wants several and every DEX
/// pool has its own right TWAP terms: a deep mainnet pool tolerates a long
/// window, a thin L2 pool needs a shorter one and a liquidity floor. All
/// of it was a roster file, a set of constants and fourteen environment
/// variables; now it is a leaf, and the median reads every enabled row for
/// the asset. `symbol` is the ticker THIS venue uses, so an alias
/// (WETH-vs-ETH) is a field rather than a code path.
struct PriceSourceEntry {
/// @dev The asset this venue prices.
bytes32 assetId;
/// @dev Zero for a CEX row — a CEX is on no chain.
bytes32 chainRef;
/// @dev Distinguishes rows for one asset on one chain.
bytes32 venueId;
/// @dev `VenueKind`.
uint8 venueKind;
/// @dev The pool address, widened, or the CEX id.
bytes32 venue;
/// @dev The kind-5 deployment a DEX pool is verified against
/// (`factory.getPool(token0, token1, fee) == venue`). Zero on a CEX.
bytes32 protocolId;
/// @dev The ticker this venue quotes the asset under.
bytes32 symbol;
/// @dev What the venue quotes against — an asset id.
bytes32 quoteAsset;
/// @dev Weight in the composition, basis points of the total.
uint16 weight;
// DEX rows only — the TWAP terms of THIS pool. Zero on a CEX row.
/// @dev The pool's token ordering. `slot0` always prices token0 in
/// token1, and reading it the wrong way round is a well-formed
/// wrong price at full weight.
bytes32 baseToken;
/// @dev The pool's other side. Together with `baseToken` it fixes which direction of the pair this row
/// quotes.
bytes32 quoteToken;
/// @dev Whether the base asset is the pool's token0. Reading the ordering the wrong way round produces a
/// well-formed but inverted price at full weight, which is why it is stored rather than inferred.
bool baseIsToken0;
/// @dev The window `observe` / the cumulative pair spans, and the
/// shortest window still accepted before the row reports
/// unavailable rather than a price.
uint32 twapWindowSeconds;
/// @dev The shortest window still accepted. Below it the row reports unavailable rather than a price, so a
/// freshly deployed pool contributes nothing instead of contributing a thin one.
uint32 twapMinWindowSeconds;
/// @dev Active liquidity below which the row is skipped — a v3 pool's
/// depth is its in-range L, not its balances.
uint128 minLiquidity;
/// @dev Spot-vs-TWAP spread above which the row is refused as manipulated.
uint16 maxSpotDeviationBps;
/// @dev False retires this venue from the composition.
bool enabled;
/// @dev Monotonic per entry.
uint64 epoch;
}
/// @notice One DEX protocol's deployment on one chain — kind 5.
///
/// @dev What reading or verifying a pool needs and a pool row cannot carry:
/// the factory that proves a pool address is genuine, the quoter for a
/// quote, the router the treasury swaps through, the position manager.
/// Per protocol per chain, so the addresses hardcoded in the oracle and
/// treasury workers go, and the treasury's swap target becomes a reference
/// to `router` here — still gated on the gateway's own allowlist, because
/// the tree names it and the contract gates it.
struct DexProtocolEntry {
/// @dev The chain this deployment is on.
bytes32 chainRef;
/// @dev Which protocol family this row describes, from the `PROTOCOL_*` set.
bytes32 protocolId;
/// @dev `PROTOCOL_*`.
uint8 protocolKind;
/// @dev The family's factory, widened. A DEX price row is verified against it, so a pool that the factory
/// does not vouch for cannot enter the composition.
bytes32 factory;
/// @dev The family's quoter, where it has one. Zero when the family does not.
bytes32 quoter;
bytes32 router;
/// @dev The family's position manager, where it has one. Zero when the family does not.
bytes32 positionManager;
/// @dev False retires this deployment.
bool enabled;
/// @dev Monotonic per entry.
uint64 epoch;
}
// ----------------------------------------------------------------- state
/// @notice The identity registry this contract resolves quorum members through.
/// @dev Immutable: the registry is what decides who may publish here, so a rotatable pointer would make
/// the quorum only as strong as whoever could re-point it.
FinalIdentityRegistry public immutable registry;
/// @notice The state trees this registry publishes its roots into.
/// @dev Immutable for the same reason as `registry` — a re-pointable tree would let published state be
/// redirected to a tree nothing else reads.
FinalStateTrees public immutable trees;
/// @dev Registrar-quorum action, verified by the registry with this
/// contract as the verifying contract.
bytes32 public constant ACTION_CONFIGURE = keccak256("FINAL_ASSET_REGISTRY_CONFIGURE_v01");
/// @dev Bootstrap admin, cleared by `seal`. Mirrors the registry's window.
address public admin;
/// @dev Which role may mutate, and how many approvals it takes.
uint256 public publisherRole;
/// @dev How many approvals from `publisherRole` a mutation takes.
uint256 public threshold;
/// @dev Replay domain for mutations. Bound into every digest.
uint64 public nonce;
/// @dev Insertion-ordered chain references, so the set can be enumerated and folded deterministically. The
/// fold order is part of the published root, so entries are appended and never reordered.
bytes32[] internal _chainRefs;
/**
* @notice One global policy scalar — kind 3.
*
* @dev The fourth key kind, for the numbers that are POLICY and are not a
* property of a chain or an asset: a treasury swap ceiling, a subsidy
* budget. They lived in environment variables, which makes a limit
* something an operator can change alone, silently, per revision — and a
* limit nobody had to agree to is not a limit, it is a default.
*
* A scalar rather than a typed field per parameter, because the alternative
* is a contract change for every new bound, and a contract change is the
* one thing that must not be the price of tightening a limit. The units are
* the caller's: `TREASURY_SWAP_MAX_WEI_PER_TICK` is wei, and the name says
* so. A consumer that reads the wrong parameter reads a number of the wrong
* magnitude, which is why the reader is one shared module and not a
* `getUint` at each call site.
*
* `enabled` is a tombstone. A retired bound must prove it was retired: an
* absent leaf proves nothing, and a consumer that treats absence as
* "unlimited" is exactly the failure a policy row exists to prevent.
*/
struct PolicyEntry {
/// @dev Identifier of the bound this row carries.
bytes32 paramId;
/// @dev The bound's value, in whatever unit `paramId` names. The shared reader is what knows the unit.
uint256 value;
bool enabled;
/// @dev Monotonic per entry.
uint64 epoch;
}
/// @notice Per-chain EXECUTION TERMS — kind 7. What a leg on this chain
/// executes under and what its fee quote is bounded by. Operator-
/// tunable: a newer record (epoch) supersedes, never a deploy. The
/// default lane and every other lane available on a chain live in
/// the tree and change there, per chain, without a redeploy. A zero
/// numeric field means "the fee schedule's default".
struct ChainTermsEntry {
/// @dev The chain these execution terms apply to.
bytes32 chainRef;
/// @dev The lane a leg on this chain takes unless the intent names
/// another AVAILABLE one. Lane ids and their meaning are the
/// intent plane's own table; lane 0 is the relayer-staged
/// post-quantum envelope through the chain's gateway.
uint8 defaultLane;
/// @dev Bitmask of the lanes available on this chain (bit i = lane i);
/// must include `defaultLane`. Any bit is storable — a new lane is a
/// record, not a registry redeploy.
uint16 lanes;
/// @dev How long a fee quote for this chain stays valid, seconds.
uint32 quoteWindowSeconds;
/// @dev Per-chain float premium on top of cost, basis points.
uint16 floatPremiumBps;
/// @dev Admission floor for a leg on this chain, USD micros.
uint64 admissionFloorUsdMicros;
/// @dev Legs one intent may carry on this chain; 0 = no per-chain cap.
uint16 maxLegsPerIntent;
/// @dev False retires the terms. A retired bound must prove it was retired, so the row stays as a tombstone.
bool enabled;
/// @dev Monotonic per entry.
uint64 epoch;
}
/// @notice One BUILDER a chain's bundles may be offered to — kind 8. The
/// execution-path schema's phase 2 (2026-09-08): which builders,
/// relays and sequencer private lanes a chain submits to is
/// protocol configuration and lives here, per chain, like every
/// other execution term. Retires the named builder set of the
/// kind-3 row `CHAIN_BUILDER_SET_<chainId>` and the env lists.
/// @dev The endpoint URL is NOT on the tree: it does not fit a word, it is
/// public-but-mutable, and a service's builder key sits beside it in
/// its own configuration. The tree carries `keccak256(bytes(url))`, so
/// the service can prove the endpoint it was given is the one the
/// quorum meant and refuse any other — the attested-transport path.
/// `flavour` is the API shape (a code fact, numbered by the shared
/// table `BUILDER_FLAVOUR_*`); `capabilities` are the builder's own
/// properties as bits (`BUILDER_CAP_*`) — sponsorship is a builder's
/// property, never a chain-level term (ruled 2026-09-05: nothing on an
/// execution chain is sponsored, so the bit is a statement about the
/// builder's API, not a lane).
struct BuilderEntry {
/// @dev The chain whose blocks this builder builds.
bytes32 chainRef;
/// @dev `keccak256(bytes(name))` — the builder's stable name (`flashbots`, `titan`, `op-rollup-boost`).
bytes32 builderId;
/// @dev API flavour, `BUILDER_FLAVOUR_*`. Zero is refused on an enabled row.
uint8 flavour;
/// @dev `keccak256(bytes(endpointUrl))` of the endpoint the service must be configured with. Zero is refused on an enabled row.
bytes32 endpointHash;
/// @dev Capability bits, `BUILDER_CAP_*`.
uint16 capabilities;
/// @dev False retires the builder; the row stays as a tombstone.
bool enabled;
/// @dev Monotonic per entry.
uint64 epoch;
}
/// @dev Kind-8 flavour numbers — the wire form of `ofa/builderProfiles.js BUILDER_API_FLAVORS` keys.
uint8 internal constant BUILDER_FLAVOUR_FLASHBOTS = 1;
uint8 internal constant BUILDER_FLAVOUR_BUILDERNET = 2;
uint8 internal constant BUILDER_FLAVOUR_TITAN = 3;
uint8 internal constant BUILDER_FLAVOUR_BEAVER = 4;
uint8 internal constant BUILDER_FLAVOUR_BLOXROUTE = 5;
uint8 internal constant BUILDER_FLAVOUR_BLOCKBEELDER = 6;
uint8 internal constant BUILDER_FLAVOUR_RSYNC = 7;
uint8 internal constant BUILDER_FLAVOUR_JETBLDR = 8;
uint8 internal constant BUILDER_FLAVOUR_BTCS = 9;
uint8 internal constant BUILDER_FLAVOUR_TBUILDER = 10;
uint8 internal constant BUILDER_FLAVOUR_GENERIC = 11;
/// @dev A rollup sequencer's private lane (rollup-boost / builder endpoint on OP Mainnet, Base, Unichain).
uint8 internal constant BUILDER_FLAVOUR_ROLLUP_BOOST = 12;
/// @dev Kind-8 capability bits.
uint16 internal constant BUILDER_CAP_PRIVATE_TX = 1;
uint16 internal constant BUILDER_CAP_REFUND = 2;
uint16 internal constant BUILDER_CAP_CANCEL = 4;
uint16 internal constant BUILDER_CAP_SPONSORS = 8;
uint16 internal constant BUILDER_CAP_ATTESTED = 16;
uint16 internal constant BUILDER_CAP_AGGREGATOR = 32;
/// @dev Chain reference to its entry.
mapping(bytes32 chainRef => ChainEntry) internal _chains;
/// @dev Membership set for chains, so a zero-valued entry is distinguishable from an absent one.
mapping(bytes32 chainRef => bool) internal _chainKnown;
/// @dev Insertion-ordered asset ids, folded in this order into the published asset root.
bytes32[] internal _assetIds;
/// @dev Asset id to its entry.
mapping(bytes32 assetId => AssetEntry) internal _assets;
/// @dev Membership set for assets.
mapping(bytes32 assetId => bool) internal _assetKnown;
/// @dev Per-chain policy and venue wiring, `assetId -> chainRef -> entry`.
/// Sparse on purpose: an asset with no row on a chain is simply not a
/// fee or refill asset there, which is the common case.
mapping(bytes32 assetId => mapping(bytes32 chainRef => AssetChainEntry)) internal _assetChains;
/// @dev Kind 3 — a global policy scalar, keyed by a name.
mapping(bytes32 paramId => PolicyEntry) internal _policy;
/// @dev Membership set for policy bounds.
mapping(bytes32 paramId => bool) internal _policyKnown;
/// @dev Insertion-ordered policy ids.
bytes32[] internal _policyIds;
/// @dev Chain reference to its execution terms.
mapping(bytes32 chainRef => ChainTermsEntry) internal _chainTerms;
/// @dev Kind 8 — builders per chain, `chainRef -> builderId -> entry`.
mapping(bytes32 chainRef => mapping(bytes32 builderId => BuilderEntry)) internal _builders;
/// @dev Per-chain list of the builder ids written on it, insertion order (tombstones included).
mapping(bytes32 chainRef => bytes32[]) internal _builderIdsOn;
/// @dev Kind 4 — price sources, keyed by their tree-6 key so one mapping
/// serves rows on a chain and CEX rows alike; `_sourceKeys` is the
/// iteration order and `_sourceKeysFor[assetId]` the per-asset view.
mapping(bytes32 key => PriceSourceEntry) internal _sources;
/// @dev Membership set for price-source rows, keyed by the composite of asset, chain and venue.
mapping(bytes32 key => bool) internal _sourceKnown;
/// @dev Insertion-ordered price-source keys.
bytes32[] internal _sourceKeys;
/// @dev Per-asset list of its price-source keys, so a median can be taken without scanning every row.
mapping(bytes32 assetId => bytes32[]) internal _sourceKeysFor;
/// @dev Kind 5 — DEX protocol deployments, `chainRef -> protocolId -> entry`.
mapping(bytes32 chainRef => mapping(bytes32 protocolId => DexProtocolEntry)) internal _protocols;
/// @dev Per-chain list of the DEX deployments configured on it.
mapping(bytes32 chainRef => bytes32[]) internal _protocolIdsOn;
/// @notice The roots `FinalSettlement` consumes. Recomputed on every write.
bytes32 public chainRegistryRoot;
/// @notice Root of the published asset set, republished on every mutation.
/// @dev Copied onto each execution chain, so two chains cannot disagree about which assets exist.
bytes32 public assetRegistryRoot;
/// @notice Bumped on every mutation; published beside each root so a
/// consumer can tell which moment a proof was built against.
uint64 public registryEpoch;
// ---------------------------------------------------------------- events
/// @notice The publisher role and threshold were configured.
/// @param role Role whose members may mutate this registry.
/// @param threshold Approvals a mutation requires.
event RegistryConfigured(uint256 role, uint256 threshold);
/// @notice A chain entry was written or retired.
/// @param chainRef The chain.
/// @param enabled Whether the entry is live after this write.
/// @param epoch The entry's new epoch.
event ChainSet(bytes32 indexed chainRef, bool enabled, uint64 epoch);
/// @notice An asset entry was written or retired.
/// @param assetId The asset.
/// @param uses The asset's global use bits after this write.
/// @param enabled Whether the entry is live after this write.
/// @param epoch The entry's new epoch.
event AssetSet(bytes32 indexed assetId, uint8 uses, bool enabled, uint64 epoch);
/// @notice One asset's per-chain configuration was written or retired.
/// @param assetId The asset.
/// @param chainRef The chain it was configured on.
/// @param uses The per-chain use bits after this write.
/// @param enabled Whether the row is live after this write.
/// @param epoch The row's new epoch.
event AssetChainSet(
bytes32 indexed assetId, bytes32 indexed chainRef, uint8 uses, bool enabled, uint64 epoch
);
/// @notice A policy bound was written or retired.
/// @param paramId The bound.
/// @param value Its new value.
/// @param enabled Whether the bound is live after this write.
/// @param epoch The row's new epoch.
event PolicySet(bytes32 indexed paramId, uint256 value, bool enabled, uint64 epoch);
/// @notice A chain's execution terms were written.
/// @param chainRef The chain.
/// @param defaultLane The lane a leg takes unless the intent names another available one.
/// @param lanes Bitmask of lanes available on this chain.
/// @param quoteWindowSeconds How long a fee quote for this chain stays valid.
/// @param floatPremiumBps Per-chain float premium on top of cost.
/// @param epoch The row's new epoch.
event ChainTermsSet(
bytes32 indexed chainRef, uint8 defaultLane, uint16 lanes, uint32 quoteWindowSeconds, uint16 floatPremiumBps, uint64 epoch
);
/// @notice A builder row was written or retired.
/// @param chainRef The chain.
/// @param builderId `keccak256(bytes(name))`.
/// @param flavour The API flavour.
/// @param endpointHash `keccak256(bytes(endpointUrl))` the service must match.
/// @param capabilities The builder's capability bits.
/// @param enabled Whether the builder is live after this write.
/// @param epoch The row's new epoch.
event BuilderSet(
bytes32 indexed chainRef, bytes32 indexed builderId, uint8 flavour, bytes32 endpointHash, uint16 capabilities, bool enabled, uint64 epoch
);
/// @notice A price venue was written or retired.
/// @param assetId The asset priced.
/// @param chainRef The chain the venue is on; zero for an off-chain venue.
/// @param venueId Distinguishes venues for one asset on one chain.
/// @param enabled Whether the venue is live after this write.
/// @param epoch The row's new epoch.
event PriceSourceSet(
bytes32 indexed assetId, bytes32 indexed chainRef, bytes32 indexed venueId, bool enabled, uint64 epoch
);
/// @notice A DEX deployment was written or retired.
/// @param chainRef The chain.
/// @param protocolId The protocol family.
/// @param enabled Whether the deployment is live after this write.
/// @param epoch The row's new epoch.
event DexProtocolSet(bytes32 indexed chainRef, bytes32 indexed protocolId, bool enabled, uint64 epoch);
/// @notice Both roots were republished after a mutation.
/// @dev The pair is emitted together because a consumer verifying one against the other's epoch would be
/// verifying against a moment that never existed.
/// @param chainRoot Root of the chain set.
/// @param assetRoot Root of the asset set.
/// @param epoch The registry epoch both roots were folded at.
event RootsPublished(bytes32 chainRoot, bytes32 assetRoot, uint64 epoch);
/// @notice The bootstrap admin was cleared, permanently. Every later mutation requires the quorum.
event Sealed();
// ---------------------------------------------------------------- errors
/// @notice Thrown when a bootstrap-only entrypoint is reached by anyone but the admin.
/// @param caller The rejected caller.
error NotAdmin(address caller);
/// @notice The default lane is not among the chain's available lanes.
error LaneNotAvailable(bytes32 chainRef, uint8 defaultLane, uint16 lanes);
/// @notice The epoch can be seeded only into a registry that holds nothing.
error NotFresh();
/// @notice A fresh registry took over the previous registry's epoch.
event EpochSeeded(uint64 epoch);
/// @notice Thrown when a bootstrap-only entrypoint is reached after sealing.
/// @dev Sealing is one-way by design: a re-openable bootstrap window is not a bootstrap window.
error AlreadySealed();
/// @notice Thrown when the requested threshold exceeds the number of live role members.
/// @dev Refused up front, because a threshold nobody can reach would freeze the registry with no way back.
/// @param live Members currently holding the role.
/// @param asked The threshold requested.
error ThresholdUnreachable(uint256 live, uint256 asked);
/// @notice Thrown when a mutation is attempted before the publisher role and threshold are configured.
error NotConfigured();
/// @notice Thrown when a chain that was never registered is referenced.
/// @param chainRef The unknown chain.
error UnknownChain(bytes32 chainRef);
/// @notice Thrown when an asset that was never registered is referenced.
/// @param assetId The unknown asset.
error UnknownAsset(bytes32 assetId);
/// @notice Thrown when a batch call carries no entries.
/// @dev Refused rather than treated as a no-op, so an empty batch cannot silently burn a quorum nonce.
error EmptyBatch();
/// @notice Thrown when a row names a chain that has no entry.
/// @param chainRef The missing chain.
error ChainNotRegistered(bytes32 chainRef);
/// @notice Thrown when a chain entry's CAIP pair does not hash to the reference it claims.
/// @dev The check that keeps a self-describing entry honest; without it a correct-looking proof would name
/// the wrong chain.
/// @param claimed The reference the entry states.
/// @param derived The reference its CAIP pair actually hashes to.
error ChainRefMismatch(bytes32 claimed, bytes32 derived);
/// @notice Thrown when a row names an asset that has no entry.
/// @param assetId The missing asset.
error AssetNotRegistered(bytes32 assetId);
/// @dev A use bit set in the wrong scope — a global bit on a per-chain row
/// or the reverse. Refused rather than stored, because a bit nothing
/// reads still looks accepted to the operator who set it.
error UseBitOutOfScope(uint8 uses);
/// @dev A kind-4 row is malformed for its venue kind: a CEX row on a chain
/// or with a protocol, a DEX row with no chain, no pool, no protocol,
/// or a protocol the chain has no row for, a zero weight, a zero window.
error InvalidPriceSource(bytes32 assetId, bytes32 chainRef, bytes32 venueId);
/// @dev A kind-5 row names no protocol, no factory, or an unknown kind.
error InvalidDexProtocol(bytes32 chainRef, bytes32 protocolId);
/// @dev A kind-8 row is enabled with no id, no flavour or no endpoint hash.
error InvalidBuilder(bytes32 chainRef, bytes32 builderId);
/// @notice Thrown when a chain's execution model contradicts its CAIP namespace.
/// @dev A chain in the EVM namespace must declare the EVM model; disagreeing would send consumers to the
/// wrong adapter with a valid proof.
/// @param chainRef The chain.
/// @param vmKind The rejected execution model.
error VmKindMismatch(bytes32 chainRef, uint8 vmKind);
/// @dev A chain row's protocol description is incomplete for an enabled
/// chain: no block timer, no finality rule, no gas model.
error IncompleteChainDescription(bytes32 chainRef);
/// @notice `role` is not one of the `CHAIN_ROLE_*` values.
error InvalidChainRole(bytes32 chainRef, uint8 role);
/// @notice An enabled FULL chain named no settlement contract.
error SettlementRequired(bytes32 chainRef);
/// @notice A per-chain leverage cap outside `[LEVERAGE_CAP_MIN_PCT, LEVERAGE_CAP_MAX_PCT]`.
error LeverageCapOutOfRange(bytes32 assetId, bytes32 chainRef, uint16 maxLeveragePct);
/// @notice An address word on a row for an EVM chain carries more than 20
/// significant bytes.
/// @dev The address fields are `bytes32` so a non-EVM chain fits, and on
/// an EVM chain that width is exactly the room for a mistake: a word
/// whose upper 12 bytes are not zero is not an address, and every
/// consumer widening it back with `address(uint160(word))` would
/// silently TRUNCATE to a different, well-formed address. There is no
/// later check — the truncated address looks correct everywhere — so
/// it is refused at the one place the full word is still visible.
/// @param chainRef The EVM chain the row is on.
/// @param field The field name, as written in the struct.
/// @param word The rejected word.
error NotAnEvmAddress(bytes32 chainRef, string field, bytes32 word);
// ----------------------------------------------------------- constructor
/**
* @dev The precompile probe is the point of having a constructor. A
* registry deployed where ML-DSA cannot be verified would accept no quorum
* it was ever given, and the first symptom would be a roster nobody can
* change.
*/
constructor(FinalIdentityRegistry registry_, FinalStateTrees trees_, address admin_) {
FinalChainPrecompiles.assertAvailable();
registry = registry_;
trees = trees_;
admin = admin_;
}
// ------------------------------------------------------------- bootstrap
/**
* @notice Set which role may mutate the registry, and how many approvals.
* @dev Bootstrap only. A threshold above the live member count is refused
* rather than stored: that is not a strict quorum, it is a registry that
* reverts on every write with the revert naming the threshold rather than
* the roster.
*/
function configure(
uint256 role,
uint256 k,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
// The admin alone while this contract's window is open; the sealed
// `ROLE_REGISTRAR` quorum afterwards, exactly as on the registry and
// the trees. Before the quorum path existed, `seal()` froze this
// configuration forever — a publisher set that could never re-threshold.
if (msg.sender != admin) {
registry.requireRegistrarQuorum(
ACTION_CONFIGURE, keccak256(abi.encode(role, k)), anchorBlock, approvals
);
}
if (k != 0) {
uint256 live = registry.liveMemberCount(role);
if (live < k) revert ThresholdUnreachable(live, k);
}
publisherRole = role;
threshold = k;
emit RegistryConfigured(role, k);
}
/// @notice Close the bootstrap window. One way.
function seal() external {
if (msg.sender != admin) revert NotAdmin(msg.sender);
admin = address(0);
emit Sealed();
}
/**
* @notice Take over the previous registry's `registryEpoch`, so the first
* rows this registry publishes carry an epoch every consumer's
* ring already accepts as newer. A redeploy does NOT wipe: this
* is how a fresh contract is brought up already ahead of the
* consumers rather than behind them. Bootstrap admin only, and
* only while this registry is still empty.
*/
function seedEpoch(uint64 epoch_) external {
if (msg.sender != admin) revert NotAdmin(msg.sender);
if (registryEpoch != 0 || _chainRefs.length != 0 || _assetIds.length != 0) revert NotFresh();
registryEpoch = epoch_;
emit EpochSeeded(epoch_);
}
// -------------------------------------------------------------- mutation
/**
* @notice Add or update chains and assets, and remove them by disabling.
*
* @dev One entrypoint for add, update and remove, because they are the same
* write. A separate `remove` would be a second path to the same storage
* with its own quorum check to get wrong, and "removed" here is a field
* rather than an absence.
*
* The digest binds the nonce AND the full batch. Binding only the batch
* would make an approval to enable an asset an approval to re-enable it at
* any later block — which, for a roster that gates settlement, is the whole
* attack.
*
* An asset whose `originChainRef` names a chain this registry does not hold
* is refused. The asset registry's default-allow is bounded by the chain
* set's default-deny, and that only holds if the reference resolves.
*
* The address words a row carries — `paymaster` on a chain row, `token` /
* `morph` / `vAsset` on a per-chain row — are refused when the chain is in
* the EVM namespace and the word does not fit in twenty bytes
* (`NotAnEvmAddress`). Zero is always accepted: it is how each of them says
* "nothing here", and the fields are optional by design.
*/
function mutate(
ChainEntry[] calldata chainUpdates,
AssetEntry[] calldata assetUpdates,
AssetChainEntry[] calldata assetChainUpdates,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
uint256 k = threshold;
if (k == 0) revert NotConfigured();
if (chainUpdates.length == 0 && assetUpdates.length == 0 && assetChainUpdates.length == 0) {
revert EmptyBatch();
}
uint64 n = nonce;
FinalPqQuorum.require_(
registry,
approvals,
FinalPqQuorum.digest(
address(this),
ACTION_MUTATE,
anchorBlock,
keccak256(abi.encode(n, chainUpdates, assetUpdates, assetChainUpdates))
),
publisherRole,
k,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
false
);
nonce = n + 1;
uint64 e = registryEpoch + 1;
registryEpoch = e;
// One extra slot per kind-2 row: the fee-asset cadence rule below may
// republish that row's kind-1 leaf in the same write. Unused slots are
// trimmed before the writer call.
uint256 rows = chainUpdates.length + assetUpdates.length + 2 * assetChainUpdates.length;
bytes32[] memory keys = new bytes32[](rows);
bytes32[] memory leaves = new bytes32[](rows);
uint256 w;
for (uint256 i = 0; i < chainUpdates.length; i++) {
ChainEntry calldata c = chainUpdates[i];
// The reference must hash to the ref it is filed under. Unchecked,
// an entry could name Ethereum and carry Polygon's preimage — every
// proof would verify and every consumer would go to the wrong
// chain, which is the failure a hash-only entry cannot even have
// because it cannot be read at all.
if (chainRefFor(c.caipNamespace, c.caipReference) != c.chainRef) {
revert ChainRefMismatch(c.chainRef, chainRefFor(c.caipNamespace, c.caipReference));
}
// An enabled chain must be readable: the oracles pace on the block
// timer, the settlement lane waits on the finality rule, the fee
// quote needs the gas model, and every adapter branches on the VM
// kind. A tombstone may leave them zero.
if (c.enabled && (c.blockTimeMs == 0 || c.finalityKind == 0 || c.gasModel == 0 || c.vmKind == 0)) {
revert IncompleteChainDescription(c.chainRef);
}
if (c.role > CHAIN_ROLE_OBSERVED) revert InvalidChainRole(c.chainRef, c.role);
// A FULL chain settles; an OBSERVED one is only read, so it may
// name no contracts of ours at all.
if (c.enabled && c.role == CHAIN_ROLE_FULL && c.settlement == bytes32(0)) {
revert SettlementRequired(c.chainRef);
}
// The namespace and the machine must agree where the namespace
// decides it: an eip155 row claiming any other VM would send every
// adapter to the wrong codepath with a self-consistent row.
if (c.caipNamespace == keccak256("eip155") && c.vmKind != VM_EVM) {
revert VmKindMismatch(c.chainRef, c.vmKind);
}
// The fee lane, where the row names one of its own. Widened like
// every other address here, so it is checked like every other one.
if (c.caipNamespace == CAIP_NAMESPACE_EIP155) {
_requireEvmWord(c.chainRef, "paymaster", c.paymaster);
}
if (c.l1ChainRef != bytes32(0) && !_chainKnown[c.l1ChainRef]) revert ChainNotRegistered(c.l1ChainRef);
if (!_chainKnown[c.chainRef]) {
_chainKnown[c.chainRef] = true;
_chainRefs.push(c.chainRef);
}
// Calldata straight into storage, the epoch stamped there, the leaf
// hashed from storage: no memory copy of the whole row.
ChainEntry storage sc = _chains[c.chainRef];
sc.chainRef = c.chainRef;
sc.caipNamespace = c.caipNamespace;
sc.caipReference = c.caipReference;
sc.settlement = c.settlement;
sc.accountSpace = c.accountSpace;
sc.nativeAsset = c.nativeAsset;
sc.wrappedNative = c.wrappedNative;
sc.finalityKind = c.finalityKind;
sc.finalityParam = c.finalityParam;
sc.blockTimeMs = c.blockTimeMs;
sc.gasReadBlocks = c.gasReadBlocks;
sc.gasHistoryBlocks = c.gasHistoryBlocks;
sc.multicall = c.multicall;
sc.gasModel = c.gasModel;
sc.l1ChainRef = c.l1ChainRef;
sc.gateway = c.gateway;
sc.vmKind = c.vmKind;
sc.enabled = c.enabled;
sc.epoch = e;
sc.startHeight = c.startHeight;
sc.role = c.role;
sc.paymaster = c.paymaster;
keys[w] = allowlistKeyForChain(c.chainRef);
leaves[w] = _allowlistLeafForChain(sc);
w++;
emit ChainSet(c.chainRef, c.enabled, e);
}
for (uint256 i = 0; i < assetUpdates.length; i++) {
AssetEntry memory a = assetUpdates[i];
if (!_chainKnown[a.originChainRef]) revert ChainNotRegistered(a.originChainRef);
if (a.uses & ~GLOBAL_USE_MASK != 0) revert UseBitOutOfScope(a.uses);
// The cadence is a property of the asset's USES, chosen here rather
// than by each publisher: a morphable asset is what liquidations
// mark against, so it refreshes every second whether or not the
// operator remembered to say so. A fee asset is per chain (kind 2),
// which is why the kind-2 loop below applies the same rule.
if (a.priceCadenceMs == 0) {
a.priceCadenceMs = (a.uses & USE_MORPH) != 0 ? PRICE_CADENCE_FAST_MS : PRICE_CADENCE_DEFAULT_MS;
}
if (a.maxAgeMs == 0) {
a.maxAgeMs = (a.uses & USE_MORPH) != 0 ? PRICE_MAX_AGE_FAST_MS : PRICE_MAX_AGE_DEFAULT_MS;
}
a.epoch = e;
bytes32 id = assetIdFor(a.originChainRef, a.originToken);
if (!_assetKnown[id]) {
_assetKnown[id] = true;
_assetIds.push(id);
}
_assets[id] = a;
keys[w] = allowlistKeyForAsset(id);
leaves[w] = _allowlistLeafForAsset(id, a);
w++;
emit AssetSet(id, a.uses, a.enabled, e);
}
for (uint256 i = 0; i < assetChainUpdates.length; i++) {
AssetChainEntry memory ac = assetChainUpdates[i];
// Both halves must exist. A per-chain row naming an asset the
// registry does not know, or a chain it does not settle on, is a
// policy statement about nothing — and it would sit in the tree
// looking authoritative.
if (!_chainKnown[ac.chainRef]) revert ChainNotRegistered(ac.chainRef);
if (!_assetKnown[ac.assetId]) revert AssetNotRegistered(ac.assetId);
// One scope per bit. A global bit arriving on a per-chain row would
// be stored and never read, which is worse than a refusal: the
// operator sees it accepted.
if (ac.uses & ~CHAIN_USE_MASK != 0) revert UseBitOutOfScope(ac.uses);
if (
ac.maxLeveragePct != 0
&& (ac.maxLeveragePct < LEVERAGE_CAP_MIN_PCT || ac.maxLeveragePct > LEVERAGE_CAP_MAX_PCT)
) revert LeverageCapOutOfRange(ac.assetId, ac.chainRef, ac.maxLeveragePct);
// The row's addresses are addresses ON ITS CHAIN, so the width
// rule comes from the chain's namespace, which the registry
// already holds — the row does not carry one and must not, or two
// rows on one chain could disagree about how wide its addresses
// are.
if (_chains[ac.chainRef].caipNamespace == CAIP_NAMESPACE_EIP155) {
_requireEvmWord(ac.chainRef, "token", ac.token);
_requireEvmWord(ac.chainRef, "morph", ac.morph);
_requireEvmWord(ac.chainRef, "vAsset", ac.vAsset);
}
ac.epoch = e;
_assetChains[ac.assetId][ac.chainRef] = ac;
keys[w] = allowlistKeyForAssetOnChain(ac.assetId, ac.chainRef);
leaves[w] = _allowlistLeafForAssetOnChain(ac);
w++;
emit AssetChainSet(ac.assetId, ac.chainRef, ac.uses, ac.enabled, e);
// A fee asset anywhere is a fast asset everywhere: the paymaster
// converts at its price, so the global row's cadence tightens the
// first time a chain accepts it as a fee token. Written as its own
// leaf so the kind-1 proof moves with the fact.
if (ac.enabled && (ac.uses & USE_FEE) != 0) {
AssetEntry storage ga = _assets[ac.assetId];
if (ga.priceCadenceMs > PRICE_CADENCE_FAST_MS || ga.maxAgeMs > PRICE_MAX_AGE_FAST_MS) {
ga.priceCadenceMs = PRICE_CADENCE_FAST_MS;
ga.maxAgeMs = PRICE_MAX_AGE_FAST_MS;
ga.epoch = e;
keys[w] = allowlistKeyForAsset(ac.assetId);
leaves[w] = _allowlistLeafForAsset(ac.assetId, ga);
w++;
emit AssetSet(ac.assetId, ga.uses, ga.enabled, e);
}
}
}
// Trim the unused tail so the writer sees exactly the rows written.
assembly ("memory-safe") {
mstore(keys, w)
mstore(leaves, w)
}
trees.setLeavesAsWriter(TREE_ALLOWLIST, BRANCH_MAIN_ID, keys, leaves);
_republishRoots(e);
}
/// @dev Refuses a widened address that cannot be one on an EVM chain.
/// Zero passes: it is the "not deployed here" value every address
/// field on these rows uses, and refusing it would make the fields
/// mandatory rather than optional.
/// @param chainRef The EVM chain the word is an address on.
/// @param field The field name, carried into the revert so an operator
/// reading a failed batch knows which of four words was wrong.
/// @param word The word to check.
function _requireEvmWord(bytes32 chainRef, string memory field, bytes32 word) private pure {
if (uint256(word) > type(uint160).max) revert NotAnEvmAddress(chainRef, field, word);
}
/**
* @notice Set or retire price sources (kind 4) and DEX protocol deployments
* (kind 5).
*
* @dev Its own door for the reasons `setPolicy` is: `mutate`'s digest binds
* its three arrays, and neither kind is copied to other chains, so the
* registry roots in tree 5 must not move for them. Shared nonce, distinct
* action.
*
* Protocols are applied before sources within the batch, so a pool and the
* deployment it is verified against can land in one write.
*/
function setSources(
DexProtocolEntry[] calldata protocolUpdates,
PriceSourceEntry[] calldata sourceUpdates,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
uint256 k = threshold;
if (k == 0) revert NotConfigured();
if (protocolUpdates.length == 0 && sourceUpdates.length == 0) revert EmptyBatch();
uint64 n = nonce;
FinalPqQuorum.require_(
registry,
approvals,
FinalPqQuorum.digest(
address(this),
ACTION_SET_SOURCES,
anchorBlock,
keccak256(abi.encode(n, protocolUpdates, sourceUpdates))
),
publisherRole,
k,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
false
);
nonce = n + 1;
uint64 e = registryEpoch + 1;
registryEpoch = e;
uint256 rows = protocolUpdates.length + sourceUpdates.length;
bytes32[] memory keys = new bytes32[](rows);
bytes32[] memory leaves = new bytes32[](rows);
uint256 w;
for (uint256 i = 0; i < protocolUpdates.length; i++) {
DexProtocolEntry calldata p = protocolUpdates[i];
if (!_chainKnown[p.chainRef]) revert ChainNotRegistered(p.chainRef);
if (p.protocolId == bytes32(0) || (p.enabled && (p.protocolKind == 0 || p.factory == bytes32(0)))) {
revert InvalidDexProtocol(p.chainRef, p.protocolId);
}
DexProtocolEntry storage sp = _protocols[p.chainRef][p.protocolId];
if (sp.epoch == 0) _protocolIdsOn[p.chainRef].push(p.protocolId);
sp.chainRef = p.chainRef;
sp.protocolId = p.protocolId;
sp.protocolKind = p.protocolKind;
sp.factory = p.factory;
sp.quoter = p.quoter;
sp.router = p.router;
sp.positionManager = p.positionManager;
sp.enabled = p.enabled;
sp.epoch = e;
keys[w] = allowlistKeyForProtocol(p.chainRef, p.protocolId);
leaves[w] = _allowlistLeafForProtocol(sp);
w++;
emit DexProtocolSet(p.chainRef, p.protocolId, p.enabled, e);
}
for (uint256 i = 0; i < sourceUpdates.length; i++) {
PriceSourceEntry calldata s = sourceUpdates[i];
if (!_assetKnown[s.assetId]) revert AssetNotRegistered(s.assetId);
_assertPriceSource(s);
bytes32 key = allowlistKeyForSource(s.assetId, s.chainRef, s.venueId);
if (!_sourceKnown[key]) {
_sourceKnown[key] = true;
_sourceKeys.push(key);
_sourceKeysFor[s.assetId].push(key);
}
PriceSourceEntry storage ss = _sources[key];
ss.assetId = s.assetId;
ss.chainRef = s.chainRef;
ss.venueId = s.venueId;
ss.venueKind = s.venueKind;
ss.venue = s.venue;
ss.protocolId = s.protocolId;
ss.symbol = s.symbol;
ss.quoteAsset = s.quoteAsset;
ss.weight = s.weight;
ss.baseToken = s.baseToken;
ss.quoteToken = s.quoteToken;
ss.baseIsToken0 = s.baseIsToken0;
ss.twapWindowSeconds = s.twapWindowSeconds;
ss.twapMinWindowSeconds = s.twapMinWindowSeconds;
ss.minLiquidity = s.minLiquidity;
ss.maxSpotDeviationBps = s.maxSpotDeviationBps;
ss.enabled = s.enabled;
ss.epoch = e;
keys[w] = key;
leaves[w] = _allowlistLeafForSource(ss);
w++;
emit PriceSourceSet(s.assetId, s.chainRef, s.venueId, s.enabled, e);
}
trees.setLeavesAsWriter(TREE_ALLOWLIST, BRANCH_MAIN_ID, keys, leaves);
}
/// @dev The shape rules of a kind-4 row, by venue kind. A tombstone
/// (`enabled == false`) only needs its identity.
function _assertPriceSource(PriceSourceEntry calldata s) private view {
if (s.venueId == bytes32(0)) revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
if (!s.enabled) return;
if (s.weight == 0 || s.venue == bytes32(0)) revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
if (s.venueKind == uint8(VenueKind.Cex)) {
// A CEX is on no chain and verifies against no factory.
if (s.chainRef != bytes32(0) || s.protocolId != bytes32(0)) {
revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
}
return;
}
if (s.venueKind != uint8(VenueKind.Dex) && s.venueKind != uint8(VenueKind.Aggregator)) {
revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
}
// A pool is on a chain the registry knows, verified against a
// deployment that chain has a row for, with a real window.
if (!_chainKnown[s.chainRef]) revert ChainNotRegistered(s.chainRef);
DexProtocolEntry storage p = _protocols[s.chainRef][s.protocolId];
if (s.protocolId == bytes32(0) || !p.enabled) revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
if (s.baseToken == bytes32(0) || s.quoteToken == bytes32(0)) {
revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
}
if (s.twapWindowSeconds == 0 || s.twapMinWindowSeconds == 0 || s.twapMinWindowSeconds > s.twapWindowSeconds) {
revert InvalidPriceSource(s.assetId, s.chainRef, s.venueId);
}
}
/**
* @notice Set or retire global policy scalars — tree 6, kind 3.
*
* @dev Deliberately NOT part of `mutate`. Two reasons, and the second is
* the load-bearing one:
*
* - `mutate`'s digest binds its three arrays, so widening it would
* invalidate every approval shape already in use for no gain here; and
* - policy scalars are not copied to other chains. `mutate` ends by
* republishing the chain and asset roots into tree 5, which is what
* `syncChain` copies. A swap ceiling has no business advancing that
* root: every chain would see a new registry epoch to catch up to, for a
* number none of them read.
*
* The nonce is shared with `mutate` so the two paths are totally ordered,
* and the action is distinct so an approval for one cannot be replayed
* through the other.
*/
function setPolicy(
PolicyEntry[] calldata updates,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
uint256 k = threshold;
if (k == 0) revert NotConfigured();
if (updates.length == 0) revert EmptyBatch();
uint64 n = nonce;
FinalPqQuorum.require_(
registry,
approvals,
FinalPqQuorum.digest(
address(this), ACTION_SET_POLICY, anchorBlock, keccak256(abi.encode(n, updates))
),
publisherRole,
k,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
false
);
nonce = n + 1;
uint64 e = registryEpoch + 1;
registryEpoch = e;
bytes32[] memory keys = new bytes32[](updates.length);
bytes32[] memory leaves = new bytes32[](updates.length);
for (uint256 i = 0; i < updates.length; i++) {
PolicyEntry memory p = updates[i];
p.epoch = e;
if (!_policyKnown[p.paramId]) {
_policyKnown[p.paramId] = true;
_policyIds.push(p.paramId);
}
_policy[p.paramId] = p;
keys[i] = allowlistKeyForPolicy(p.paramId);
leaves[i] = _allowlistLeafForPolicy(p);
emit PolicySet(p.paramId, p.value, p.enabled, e);
}
trees.setLeavesAsWriter(TREE_ALLOWLIST, BRANCH_MAIN_ID, keys, leaves);
}
/**
* @notice Publish per-chain execution terms (kind 7) — the default lane,
* the available lanes and the fee-quote bounds a leg on that chain
* runs under. The same quorum as every other row; a newer epoch
* supersedes the old record, which is how these change.
*/
function setChainTerms(
ChainTermsEntry[] calldata updates,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
uint256 k = threshold;
if (k == 0) revert NotConfigured();
if (updates.length == 0) revert EmptyBatch();
uint64 n = nonce;
FinalPqQuorum.require_(
registry,
approvals,
FinalPqQuorum.digest(
address(this), ACTION_SET_CHAIN_TERMS, anchorBlock, keccak256(abi.encode(n, updates))
),
publisherRole,
k,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
false
);
nonce = n + 1;
uint64 e = registryEpoch + 1;
registryEpoch = e;
bytes32[] memory keys = new bytes32[](updates.length);
bytes32[] memory leaves = new bytes32[](updates.length);
for (uint256 i = 0; i < updates.length; i++) {
ChainTermsEntry memory t = updates[i];
if (!_chainKnown[t.chainRef]) revert ChainNotRegistered(t.chainRef);
if (t.lanes & (uint16(1) << t.defaultLane) == 0) revert LaneNotAvailable(t.chainRef, t.defaultLane, t.lanes);
t.epoch = e;
_chainTerms[t.chainRef] = t;
keys[i] = allowlistKeyForChainTerms(t.chainRef);
leaves[i] = _allowlistLeafForChainTerms(t);
emit ChainTermsSet(t.chainRef, t.defaultLane, t.lanes, t.quoteWindowSeconds, t.floatPremiumBps, e);
}
trees.setLeavesAsWriter(TREE_ALLOWLIST, BRANCH_MAIN_ID, keys, leaves);
}
/**
* @notice Publish per-chain builder rows (kind 8) — which builders, relays
* and sequencer private lanes a chain's bundles may be offered to,
* each with its API flavour, the hash of the endpoint the service
* must be configured with, and its capability bits. The same quorum
* as every other row; a newer epoch supersedes; `enabled == false`
* retires a builder and leaves its tombstone.
*/
function setBuilders(
BuilderEntry[] calldata updates,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
uint256 k = threshold;
if (k == 0) revert NotConfigured();
if (updates.length == 0) revert EmptyBatch();
uint64 n = nonce;
FinalPqQuorum.require_(
registry,
approvals,
FinalPqQuorum.digest(
address(this), ACTION_SET_BUILDERS, anchorBlock, keccak256(abi.encode(n, updates))
),
publisherRole,
k,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
false
);
nonce = n + 1;
uint64 e = registryEpoch + 1;
registryEpoch = e;
bytes32[] memory keys = new bytes32[](updates.length);
bytes32[] memory leaves = new bytes32[](updates.length);
for (uint256 i = 0; i < updates.length; i++) {
BuilderEntry memory b = updates[i];
if (!_chainKnown[b.chainRef]) revert ChainNotRegistered(b.chainRef);
if (b.builderId == bytes32(0) || (b.enabled && (b.flavour == 0 || b.endpointHash == bytes32(0)))) {
revert InvalidBuilder(b.chainRef, b.builderId);
}
b.epoch = e;
if (_builders[b.chainRef][b.builderId].epoch == 0) _builderIdsOn[b.chainRef].push(b.builderId);
_builders[b.chainRef][b.builderId] = b;
keys[i] = allowlistKeyForBuilder(b.chainRef, b.builderId);
leaves[i] = _allowlistLeafForBuilder(b);
emit BuilderSet(b.chainRef, b.builderId, b.flavour, b.endpointHash, b.capabilities, b.enabled, e);
}
trees.setLeavesAsWriter(TREE_ALLOWLIST, BRANCH_MAIN_ID, keys, leaves);
}
/**
* @dev Recompute both registry roots and write them into tree 5.
*
* Both are folded over the ENABLED entries only. A disabled entry is a
* tombstone in tree 6, where a consumer needs to prove the negative; it is
* not a member of the registry a settlement contract copies from, and
* including it would make `syncChain` able to copy a revoked chain.
*/
function _republishRoots(uint64 e) private {
chainRegistryRoot = _foldChains();
assetRegistryRoot = _foldAssets();
bytes32[] memory keys = new bytes32[](2);
bytes32[] memory leaves = new bytes32[](2);
keys[0] = registryRootKey(REGISTRY_CHAIN);
leaves[0] = keccak256(
abi.encode(DOMAIN_REGISTRY_ROOT_LEAF, REGISTRY_CHAIN, chainRegistryRoot, e)
);
keys[1] = registryRootKey(REGISTRY_ASSET);
leaves[1] = keccak256(
abi.encode(DOMAIN_REGISTRY_ROOT_LEAF, REGISTRY_ASSET, assetRegistryRoot, e)
);
trees.setLeavesAsWriter(TREE_SETTLEMENT, BRANCH_MAIN_ID, keys, leaves);
emit RootsPublished(chainRegistryRoot, assetRegistryRoot, e);
}
// ------------------------------------------------------------------ keys
/// @dev `assetIdFor` as `FinalSettlement` derives it. Not a stored field:
/// a record carrying its own id could disagree with its own contents.
function assetIdFor(bytes32 originChainRef, bytes32 originToken) public pure returns (bytes32) {
return keccak256(abi.encode(originChainRef, originToken));
}
/// @notice Allowlist key for a chain entry.
/// @param chainRef The chain.
/// @return The tree-6 key its leaf is stored under.
function allowlistKeyForChain(bytes32 chainRef) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(0), chainRef));
}
/// @notice Allowlist key for a global asset entry.
/// @param assetId The asset.
/// @return The tree-6 key its leaf is stored under.
function allowlistKeyForAsset(bytes32 assetId) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(1), assetId));
}
/// @notice One asset's wiring on one chain — kind 2.
///
/// @dev The third key kind, because paymaster policy and venue integration
/// differ by network while morphability and bridging do not. There is
/// no inheritance from the global row: a bit is meaningful in exactly
/// one scope, so a consumer never has to implement a fallback rule that
/// another consumer might implement differently.
/// @notice One asset's per-chain wiring, or a zeroed entry if it has none.
function assetOnChain(bytes32 assetId, bytes32 chainRef)
external
view
returns (AssetChainEntry memory)
{
return _assetChains[assetId][chainRef];
}
/// @notice The morph leverage ceiling for `assetId` on EVM chain `chainId`,
/// in percent; 0 when the asset has no per-chain cap there.
/// `ILeverageCapSource` for `FinalStateRecords`.
function leverageCapPct(bytes32 assetId, uint64 chainId) external view returns (uint16) {
return _assetChains[assetId][evmChainRef(chainId)].maxLeveragePct;
}
/// @notice Allowlist key for one asset's row on one chain.
/// @dev Derived from both ids, so a per-chain row can never collide with the asset's global row.
/// @param assetId The asset.
/// @param chainRef The chain.
/// @return The tree-6 key that row's leaf is stored under.
function allowlistKeyForAssetOnChain(bytes32 assetId, bytes32 chainRef)
public
pure
returns (bytes32)
{
return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(2), assetId, chainRef));
}
/// @notice One global policy scalar's tree-6 key — kind 3.
function allowlistKeyForPolicy(bytes32 paramId) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(3), paramId));
}
/// @notice Kind 7: the chain's execution terms.
function allowlistKeyForChainTerms(bytes32 chainRef) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(7), chainRef));
}
/// @notice Kind 8: one builder row of a chain.
function allowlistKeyForBuilder(bytes32 chainRef, bytes32 builderId) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(8), chainRef, builderId));
}
/// @notice One price source's tree-6 key — kind 4.
function allowlistKeyForSource(bytes32 assetId, bytes32 chainRef, bytes32 venueId)
public
pure
returns (bytes32)
{
return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(4), assetId, chainRef, venueId));
}
/// @notice One DEX protocol deployment's tree-6 key — kind 5.
function allowlistKeyForProtocol(bytes32 chainRef, bytes32 protocolId) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_ALLOWLIST_KEY, uint8(5), chainRef, protocolId));
}
/// @notice Key under which a published registry root is stored.
/// @param which `REGISTRY_CHAIN` or `REGISTRY_ASSET`.
/// @return The tree key for that root.
function registryRootKey(uint8 which) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_REGISTRY_ROOT_KEY, which));
}
// ----------------------------------------------------------- leaf hashes
/// @dev Kind 0: the complete chain description. Pinned against the
/// backend's `chainLeafHash` by test.
///
/// Field order IS the leaf, so a field is appended at the tail and
/// never inserted: an insertion moves every word after it and every
/// published proof over this row stops verifying at once, while both
/// sides still look internally consistent.
/// @param c The chain row, read from storage after the epoch is stamped.
/// @return The leaf.
function _allowlistLeafForChain(ChainEntry storage c) private view returns (bytes32) {
return keccak256(
abi.encode(
DOMAIN_ALLOWLIST_LEAF, uint8(0), c.chainRef, c.caipNamespace, c.caipReference,
c.settlement, c.accountSpace,
c.nativeAsset, c.wrappedNative, c.finalityKind, c.finalityParam,
c.blockTimeMs, c.gasReadBlocks, c.gasHistoryBlocks, c.multicall, c.gasModel, c.l1ChainRef,
c.gateway, c.vmKind,
c.enabled, c.epoch,
c.startHeight, c.role, c.paymaster
)
);
}
/// @dev Kind 1, v2: `pool` / `poolQuote` moved to kind 4; the tree-4
/// cadence and staleness joined.
function _allowlistLeafForAsset(bytes32 id, AssetEntry memory a) private pure returns (bytes32) {
return keccak256(
abi.encode(
DOMAIN_ALLOWLIST_LEAF, uint8(1), id, a.originChainRef, a.originToken,
a.decimals, keccak256(bytes(a.name)), keccak256(bytes(a.symbol)),
a.uses, a.priceCadenceMs, a.maxAgeMs, a.enabled, a.epoch
)
);
}
/// @dev Kind 2: this asset's policy and its addresses on one chain. The
/// venue is a kind-4 row.
///
/// The three address words are appended at the tail under the same
/// rule the chain leaf follows — order is the leaf, so a field is
/// never inserted.
/// @param e The per-chain row, with the epoch already stamped.
/// @return The leaf.
function _allowlistLeafForAssetOnChain(AssetChainEntry memory e) private pure returns (bytes32) {
return keccak256(
abi.encode(
DOMAIN_ALLOWLIST_LEAF,
uint8(2),
e.assetId,
e.chainRef,
e.uses,
e.enabled,
e.epoch,
e.maxLeveragePct,
e.token,
e.morph,
e.vAsset
)
);
}
/// @dev Folds a policy row into its allowlist leaf. The field order here is part of the published root and
/// must match every off-chain producer word for word.
/// @param p The policy row.
/// @return The leaf.
function _allowlistLeafForPolicy(PolicyEntry memory p) private pure returns (bytes32) {
return keccak256(
abi.encode(DOMAIN_ALLOWLIST_LEAF, uint8(3), p.paramId, p.value, p.enabled, p.epoch)
);
}
/// @dev Folds a chain-terms row into its allowlist leaf, under the same word-for-word rule.
/// @param t The terms row.
/// @return The leaf.
function _allowlistLeafForChainTerms(ChainTermsEntry memory t) private pure returns (bytes32) {
return keccak256(
abi.encode(
DOMAIN_ALLOWLIST_LEAF, uint8(7), t.chainRef, t.defaultLane, t.lanes, t.quoteWindowSeconds,
t.floatPremiumBps, t.admissionFloorUsdMicros, t.maxLegsPerIntent, t.enabled, t.epoch
)
);
}
/// @dev Folds a builder row into its allowlist leaf, under the same word-for-word rule.
/// @param b The builder row.
/// @return The leaf.
function _allowlistLeafForBuilder(BuilderEntry memory b) private pure returns (bytes32) {
return keccak256(
abi.encode(
DOMAIN_ALLOWLIST_LEAF, uint8(8), b.chainRef, b.builderId, b.flavour, b.endpointHash, b.capabilities,
b.enabled, b.epoch
)
);
}
/// @dev Kind 4: identity, venue, composition weight, then every TWAP term
/// of the pool. Encoded in two halves because one `abi.encode` over
/// twenty words is the same bytes and this reads as the leaf it is.
function _allowlistLeafForSource(PriceSourceEntry storage s) private view returns (bytes32) {
return keccak256(
bytes.concat(
abi.encode(
DOMAIN_ALLOWLIST_LEAF, uint8(4), s.assetId, s.chainRef, s.venueId, s.venueKind, s.venue,
s.protocolId, s.symbol, s.quoteAsset, s.weight
),
abi.encode(
s.baseToken, s.quoteToken, s.baseIsToken0, s.twapWindowSeconds, s.twapMinWindowSeconds,
s.minLiquidity, s.maxSpotDeviationBps, s.enabled, s.epoch
)
)
);
}
/// @dev Kind 5.
function _allowlistLeafForProtocol(DexProtocolEntry storage p) private view returns (bytes32) {
return keccak256(
abi.encode(
DOMAIN_ALLOWLIST_LEAF, uint8(5), p.chainRef, p.protocolId, p.protocolKind,
p.factory, p.quoter, p.router, p.positionManager, p.enabled, p.epoch
)
);
}
/// @dev `FinalSettlement.ChainLeaf`, field for field and in order.
function chainLeafHash(ChainEntry memory c) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_CHAIN_LEAF, c.chainRef, c.settlement, c.accountSpace, c.epoch));
}
/// @dev `FinalSettlement.assetLeafHash`, byte for byte.
///
/// `name` and `symbol` are HASHED, because that is what the consumer does.
/// This encoded them as raw strings — with a comment asserting the
/// opposite — so `assetRegistryRoot` was folded over leaves no
/// `registerAsset` proof could verify against on any settlement chain.
/// Both sides were internally consistent and no proof verified anywhere,
/// while the root on the publishing chain looked perfectly healthy.
/// `AssetLeafParity.t.sol` pins the two against each other.
///
/// Not the same encoding as `_allowlistLeafForAsset`, deliberately: tree 6
/// commits to the full registry entry, this commits to the six fields
/// `FinalSettlement` copies.
function assetLeafHash(AssetEntry memory a) public pure returns (bytes32) {
return keccak256(
abi.encode(
DOMAIN_ASSET_LEAF, a.originChainRef, a.originToken,
a.decimals, keccak256(bytes(a.name)), keccak256(bytes(a.symbol)), a.epoch
)
);
}
// -------------------------------------------------------------- the fold
//
// Sorted-pair, tagged, promoting an odd node — `FinalMerkle`'s shape, which
// is what `FinalSettlement.verifyTaggedSortedProof` runs. Not tree 6's
// shape, and the two must not be confused: a fixed depth-20 slotted tree
// over the same leaves lands on a different root while both sides look
// right.
/// @dev Folds the chain set into its root, in insertion order. The order is part of the root, so a sorted
/// rebuild produces a different tree that nothing can prove against.
/// @return The chain root.
function _foldChains() private view returns (bytes32) {
uint256 n = _chainRefs.length;
bytes32[] memory hashes = new bytes32[](n);
uint256 m;
for (uint256 i = 0; i < n; i++) {
ChainEntry memory c = _chains[_chainRefs[i]];
if (!c.enabled) continue;
hashes[m++] = chainLeafHash(c);
}
return _fold(hashes, m);
}
/// @dev Folds the asset set into its root, under the same insertion-order rule.
/// @return The asset root.
function _foldAssets() private view returns (bytes32) {
uint256 n = _assetIds.length;
bytes32[] memory hashes = new bytes32[](n);
uint256 m;
for (uint256 i = 0; i < n; i++) {
AssetEntry memory a = _assets[_assetIds[i]];
if (!a.enabled) continue;
hashes[m++] = assetLeafHash(a);
}
return _fold(hashes, m);
}
/**
* @dev Fold `m` leaves of `hashes` into a sorted-pair tagged root.
*
* Leaves are sorted first, so the root is a function of the SET rather than
* of insertion order — two registries holding the same entries must publish
* the same root however they got there.
*
* An empty set folds to zero rather than to a hash of nothing. A consumer
* comparing against zero can tell "no registry published" from "a registry
* that happens to be empty"; a hash cannot be distinguished from a real
* root without knowing the construction.
*/
function _fold(bytes32[] memory hashes, uint256 m) private pure returns (bytes32) {
if (m == 0) return bytes32(0);
// Insertion sort. `m` is tens of entries and the alternative is a
// quicksort's worst case on an adversarially ordered set, which here
// would be a set an operator chose.
for (uint256 i = 1; i < m; i++) {
bytes32 x = hashes[i];
uint256 j = i;
while (j > 0 && hashes[j - 1] > x) {
hashes[j] = hashes[j - 1];
j--;
}
hashes[j] = x;
}
// Tag every leaf once, then pair upward.
for (uint256 i = 0; i < m; i++) {
hashes[i] = keccak256(abi.encodePacked(bytes1(0x00), hashes[i]));
}
uint256 len = m;
while (len > 1) {
uint256 w;
for (uint256 i = 0; i < len; i += 2) {
if (i + 1 == len) {
// Odd node promoted, not paired with itself: hashing a node
// with its own value makes a one-element layer collide with
// a two-element layer holding it twice.
hashes[w++] = hashes[i];
continue;
}
(bytes32 lo, bytes32 hi) =
hashes[i] < hashes[i + 1] ? (hashes[i], hashes[i + 1]) : (hashes[i + 1], hashes[i]);
hashes[w++] = keccak256(abi.encodePacked(bytes1(0x01), lo, hi));
}
len = w;
}
return hashes[0];
}
// ----------------------------------------------------------------- views
/// @notice One policy scalar. `enabled == false` is a RETIRED bound, and a
/// never-written one is zeroed — which is why the flag is returned and not
/// inferred from the value. Zero is a legitimate ceiling.
function policyOf(bytes32 paramId) external view returns (PolicyEntry memory) {
return _policy[paramId];
}
/// @notice Number of policy bounds configured.
/// @return The count.
function policyCount() external view returns (uint256) {
return _policyIds.length;
}
/// @notice Reads one policy bound by index.
/// @dev Index order is insertion order and is stable, so paging over it cannot skip or repeat a row.
/// @param i Index into the policy list.
/// @return The policy row.
function policyAt(uint256 i) external view returns (PolicyEntry memory) {
return _policy[_policyIds[i]];
}
/// @notice A chain's execution terms, or a zeroed entry when none were published.
function chainTermsOf(bytes32 chainRef) external view returns (ChainTermsEntry memory) {
return _chainTerms[chainRef];
}
/// @notice One builder row of a chain, or a zeroed entry when never written.
function builderOf(bytes32 chainRef, bytes32 builderId) external view returns (BuilderEntry memory) {
return _builders[chainRef][builderId];
}
/// @notice The builder ids ever written on a chain, insertion order, tombstones included.
function builderIdsOn(bytes32 chainRef) external view returns (bytes32[] memory) {
return _builderIdsOn[chainRef];
}
/// @notice Every builder row of a chain, insertion order, tombstones included — the
/// reader filters `enabled`. One call per chain at adoption.
function buildersOn(bytes32 chainRef) external view returns (BuilderEntry[] memory out) {
bytes32[] storage ids = _builderIdsOn[chainRef];
out = new BuilderEntry[](ids.length);
for (uint256 i = 0; i < ids.length; i++) {
out[i] = _builders[chainRef][ids[i]];
}
}
/// @notice Number of chains registered.
/// @return The count.
function chainCount() external view returns (uint256) {
return _chainRefs.length;
}
/// @notice Number of assets registered.
/// @return The count.
function assetCount() external view returns (uint256) {
return _assetIds.length;
}
/// @notice Reads one chain entry by index, in insertion order.
/// @param i Index into the chain list.
/// @return The chain entry.
function chainAt(uint256 i) external view returns (ChainEntry memory) {
return _chains[_chainRefs[i]];
}
/// @notice Reads one asset entry by index, in insertion order.
/// @param i Index into the asset list.
/// @return The asset entry.
function assetAt(uint256 i) external view returns (AssetEntry memory) {
return _assets[_assetIds[i]];
}
/// @notice Reads a chain entry by reference.
/// @dev Returns a zeroed entry for an unknown chain; use the membership probe to tell the two apart.
/// @param chainRef The chain.
/// @return The chain entry.
function chainOf(bytes32 chainRef) external view returns (ChainEntry memory) {
if (!_chainKnown[chainRef]) revert UnknownChain(chainRef);
return _chains[chainRef];
}
/// @notice Reads an asset entry by id.
/// @dev Returns a zeroed entry for an unknown asset; use the membership probe to tell the two apart.
/// @param assetId The asset.
/// @return The asset entry.
function assetOf(bytes32 assetId) external view returns (AssetEntry memory) {
if (!_assetKnown[assetId]) revert UnknownAsset(assetId);
return _assets[assetId];
}
/**
* @notice Every enabled asset carrying `use`.
* @dev The roster a publisher reads instead of an environment variable. It
* is a view over state rather than a list handed to a process, so two
* publishers cannot disagree about what the set is.
*/
function assetsFor(uint8 use) external view returns (AssetEntry[] memory out) {
uint256 n = _assetIds.length;
AssetEntry[] memory buf = new AssetEntry[](n);
uint256 m;
for (uint256 i = 0; i < n; i++) {
AssetEntry memory a = _assets[_assetIds[i]];
if (a.enabled && (a.uses & use) != 0) buf[m++] = a;
}
out = new AssetEntry[](m);
for (uint256 i = 0; i < m; i++) out[i] = buf[i];
}
/**
* @notice Every asset carrying `use` ON `chainRef`, with its venue wiring.
*
* @dev The per-chain counterpart to `assetsFor`, and the roster a paymaster
* actually needs: `USE_FEE` and `USE_REFILL` live only on the per-chain row,
* so asking `assetsFor(USE_FEE)` would return nothing however many chains
* accept the asset. Both the global row and the per-chain row must be
* enabled — a globally revoked asset is revoked everywhere, and leaving that
* to each consumer to remember is how a disabled asset stays spendable on
* one chain.
*/
function assetsOnChainFor(bytes32 chainRef, uint8 use)
external
view
returns (AssetChainEntry[] memory out)
{
uint256 n = _assetIds.length;
AssetChainEntry[] memory buf = new AssetChainEntry[](n);
uint256 m;
for (uint256 i = 0; i < n; i++) {
bytes32 assetId = _assetIds[i];
if (!_assets[assetId].enabled) continue;
AssetChainEntry memory ac = _assetChains[assetId][chainRef];
if (ac.enabled && (ac.uses & use) != 0) buf[m++] = ac;
}
out = new AssetChainEntry[](m);
for (uint256 i = 0; i < m; i++) out[i] = buf[i];
}
/// @notice `keccak(DOMAIN_CHAIN_REF, namespace, reference)`, mirroring
/// `FinalSettlement.chainRefFor` byte for byte.
function chainRefFor(bytes32 namespace, bytes32 caipRef) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_CHAIN_REF, namespace, caipRef));
}
/// @notice The chain reference for an EVM chain id.
function evmChainRef(uint256 chainId) public pure returns (bytes32) {
return chainRefFor(CAIP_NAMESPACE_EIP155, bytes32(chainId));
}
/// @notice Every enabled chain's reference — what `FinalStateTrees.syncIdentities`
/// builds a service identity's `deployedChains` table from, and
/// what a reader enumerates the chain set by (`chainOf` per ref).
/// @dev Refs rather than rows: an array of the full row is an ABI encoder
/// this contract has no room for, and every reader multicalls anyway.
function enabledChainRefs() external view override returns (bytes32[] memory out) {
uint256 n = _chainRefs.length;
bytes32[] memory buf = new bytes32[](n);
uint256 m;
for (uint256 i = 0; i < n; i++) {
if (_chains[_chainRefs[i]].enabled) buf[m++] = _chainRefs[i];
}
out = new bytes32[](m);
for (uint256 i = 0; i < m; i++) out[i] = buf[i];
}
// ------------------------------------------------------ kinds 4 and 5, views
/// @notice One price source, or a zeroed entry if it has none.
function priceSourceOf(bytes32 assetId, bytes32 chainRef, bytes32 venueId)
external
view
returns (PriceSourceEntry memory)
{
return _sources[allowlistKeyForSource(assetId, chainRef, venueId)];
}
/// @notice One price source by its tree-6 key.
function priceSourceByKey(bytes32 key) external view returns (PriceSourceEntry memory) {
return _sources[key];
}
/**
* @notice The tree-6 keys of every price-source row for an asset, tombstones
* included — the roster the median reads, one venue per key, with
* the venue's own ticker and terms behind `priceSourceByKey`.
* @dev Keys rather than rows, for the encoder's sake; a reader multicalls
* the rows and drops `enabled == false` and rows of a disabled asset.
*/
function priceSourceKeysFor(bytes32 assetId) external view returns (bytes32[] memory) {
return _sourceKeysFor[assetId];
}
/// @notice Every price-source key ever written, in write order.
function priceSourceKeys() external view returns (bytes32[] memory) {
return _sourceKeys;
}
/// @notice One DEX protocol deployment, or a zeroed entry if the chain has none.
function dexProtocolOf(bytes32 chainRef, bytes32 protocolId) external view returns (DexProtocolEntry memory) {
return _protocols[chainRef][protocolId];
}
/// @notice Every DEX protocol id ever written for a chain — what can be
/// read there, behind `dexProtocolOf`. Replaces a literal set of
/// chain ids in the oracle.
function dexProtocolIdsOn(bytes32 chainRef) external view returns (bytes32[] memory) {
return _protocolIdsOn[chainRef];
}
// ------------------------------------------------------------------ sweep
/// @dev This contract's configuration gate reads the membership registry it
/// was constructed against, so the sweep authority reads the same one.
function _sweepRegistry() internal view override returns (FinalIdentityRegistry) {
return registry;
}
/// @dev Nothing is reserved because nothing is owed: this contract has no
/// payable entrypoint and no custody line — it records, it does not hold.
/// Anything it carries arrived by accident and is sweepable in full.
/// @dev The local `admin` first — the same address this contract's own
/// configuration gate accepts ahead of the registrar quorum — then the
/// plane rule. Zero once sealed, and `msg.sender` can never be zero, so the
/// leg closes with the window it belongs to.
function _requireSweepAuthority() internal view override {
if (admin != address(0) && msg.sender == admin) return;
super._requireSweepAuthority();
}
/// @dev The local admin, and the proven authority that called. The registry's
/// bootstrap admin is not named here because this contract answers to its
/// own admin during the window and to the registrar roster after it.
function _sweepDestinations() internal view override returns (address, address) {
return (admin, msg.sender);
}
}
contracts/finalchain/FinalCertificate.sol
// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may link against and call this certificate reader,
// and may encode certificates that it accepts, as part of the Final DeFi
// Protocol.
// 2. Operators, integrators, and end users may have their certificates parsed,
// self-checked, and verified through any Final DeFi surface that links it.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
// deployment of a Fork of this certificate reader or a competing identity
// certificate format derived from it without permission prior to the
// Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalChainTime} from "./FinalChainTime.sol";
/**
* @title Final Certificate
* @notice Reads a Final Certificate on chain and self-checks it, so a certificate's keys can never be
* anything other than the keys it declares.
* @dev Deployed only as part of this project's own reth-based state plane, and only on the reth-based chains
* that carry the precompiles it calls: SHA3-256 at `0x0202`, ML-DSA-87 at `0x0204` and
* SLH-DSA-SHAKE-256s at `0x0205`, each address being that primitive's FIPS number. The contracts it is
* linked into probe those precompiles at construction and refuse to exist where they are absent, so
* this library never runs somewhere its verdicts would be meaningless. It takes part in no CREATE2
* derivation, and nothing outside this directory imports it.
*
* The SHA3 precompile is not a convenience: the certificate format hashes with FIPS-202 SHA3 and the
* EVM's `keccak256` is a DIFFERENT function, so a digest computed with the wrong one matches no
* certificate any issuer ever wrote.
*
* ## Why the chain parses this at all
*
* The alternative is taking the TBS bytes and the public keys as separate arguments and deriving
* `certHash` from the bytes. That looks like verification and is not: nothing compares the keys to the
* certificate, so a registrar could bind any certificate to any keypair, the registry would hold a key
* the certificate does not contain, and every signature that key produced would verify against a
* certificate that never authorised it.
*
* So the keys are read OUT of the certificate. There is one input, and no pair of arguments that can
* disagree.
*
* Gas is deliberately not a design constraint on the chain this runs on and must not be optimised for.
* Parsing and re-hashing on chain costs more than trusting a parse done elsewhere and buys a verdict
* that is re-derivable from public state, which is the trade this whole plane is built on.
*
* ## The key-identifier check
*
* A certificate declares `SubjectKeyId` as the SHA3-256 digest of its `PublicKeyBlock`. Having parsed
* that block, {parse} recomputes the digest and compares. The field sits inside the TBS, so it is
* covered by the issuer's signatures — which makes the check a statement about what the issuer
* attested, not merely about internal consistency of bytes the caller supplied.
*
* ## Deploy-linked, not inlined
*
* {parseLive}, {parseRecovery}, {parseCa} and {verifyIssuerSignatures} are `external`, so the identity
* registry calls them across a link boundary rather than carrying them in its own bytecode, which it
* has no room for. The link target is fixed at deployment: a linked library is code, not a pointer
* anyone can move afterwards.
*
* ## What this library deliberately does not do
*
* It does not verify an issuer's signatures over the TBS as part of parsing, and it does not walk a
* certificate chain to the root. On the registration path there is nothing to walk — a chain-attested
* certificate is admitted by this chain against pinned issuer constants and the holder's own proof of
* possession, so an issuer signature is not what makes it valid. {verifyIssuerSignatures} is here for
* callers verifying an off-chain issuance, and it verifies exactly what it is handed.
*
* It also does not check an encapsulation key's length or structure. Those are checked where they are
* REGISTERED, by the precompiles that own the answer, because two checks of one thing in two shapes is
* how one of them ends up weaker and nobody notices which.
*/
library FinalCertificate {
/// @notice The four magic bytes every certificate opens with, `"PQCF"`.
uint32 internal constant MAGIC = 0x50514346;
/// @notice The current wire generation, which encoders write.
/// @dev A generation this parser does not know fails to parse rather than being reinterpreted: the
/// folded key commitment, and therefore every wallet address, derives from this exact layout, so a
/// layout read under the wrong generation would produce a self-consistent digest that matches
/// nothing.
uint32 internal constant VERSION = 2;
/// @notice The previous wire generation, still accepted on parse.
/// @dev Reading an older artifact is not the same as admitting it. Whether such a certificate may be
/// REGISTERED is settled at admission, by the holder's proof of possession and the chain-issuer
/// pins, rather than by refusing to decode it.
uint32 internal constant VERSION_V4 = 1;
/// @notice The institution identity extension, which carries an issuer's legal name, registration
/// number and jurisdiction.
uint16 internal constant EXT_INSTITUTION = 0x0102;
/// @notice ML-KEM-1024 (FIPS 203), the lattice half of the encapsulation pair.
/// @dev Algorithm identifiers ARE the FIPS numbers, in one space shared by signatures and encapsulation
/// — the same identifiers the quorum wire format uses, and the numbers the precompile addresses end
/// in. One space rather than two means an identifier can never be read against the wrong table.
uint16 internal constant ALG_ML_KEM_1024 = 0x0003;
/// @notice ML-DSA-87 (FIPS 204). Transaction class.
uint16 internal constant ALG_ML_DSA_87 = 0x0004;
/// @notice SLH-DSA-SHAKE-256s (FIPS 205). Access class, and the seal.
uint16 internal constant ALG_SLH_DSA_SHAKE_256S = 0x0005;
/// @notice FN-DSA (FIPS 206). Reserved: there is no implementation behind it and it is never accepted in
/// a slot.
uint16 internal constant ALG_FN_DSA = 0x0006;
/// @notice HQC-5 (FIPS 207), the code-based half of the encapsulation pair.
uint16 internal constant ALG_HQC_5 = 0x0007;
/// @notice Certificate signing, for both of an issuer's keys.
/// @dev Says which key to verify WITH; it grants nothing on its own — capability to issue comes from the
/// depth pair.
uint16 internal constant PURPOSE_CERT_SIGNING = 0x0004;
/// @notice The live stage's transaction-class slot, ML-DSA-87.
/// @dev A wallet holds four slots in two stages of two, and a certificate carries ONE stage, never all
/// four. The stage is what is issued, rotated and revoked as a unit, and a holder presenting a live
/// certificate presents both of that stage's keys or neither — splitting them per slot would let
/// half a stage be presented as if it were whole.
/// @dev This applies to services exactly as it applies to a user's wallet. A co-signer is a Final
/// Wallet: same four slots, same split, same algorithms. There is no second kind of identity in
/// this system.
uint16 internal constant PURPOSE_ACTIVE_TX = 0x0010;
/// @notice The live stage's access-class slot, SLH-DSA-SHAKE-256s.
uint16 internal constant PURPOSE_ACTIVE_ACCESS = 0x0011;
/// @notice The recovery stage's transaction-class slot, ML-DSA-87.
uint16 internal constant PURPOSE_RECOVERY_TX = 0x0012;
/// @notice The recovery stage's access-class slot, SLH-DSA-SHAKE-256s.
uint16 internal constant PURPOSE_RECOVERY_ACCESS = 0x0013;
/// @notice The live stage's encapsulation slot.
/// @dev Each stage's encapsulation pair is resolved alongside its signing pair, and the identity
/// registry stores both halves, so a sender can encapsulate to a registered party without a second
/// lookup somewhere less authoritative. Both halves sit under ONE purpose and are told apart by
/// algorithm, which is why the key loop matches on the `(purpose, algorithm)` pair.
uint16 internal constant PURPOSE_ACTIVE_KEM = 0x0014;
/// @notice The recovery stage's encapsulation slot, carrying the same two algorithms.
uint16 internal constant PURPOSE_RECOVERY_KEM = 0x0015;
/// @notice The seal purpose: a second SLH-DSA-SHAKE-256s key that co-signs execution-class quorum
/// decisions.
/// @dev Distinct from the access key, and carried by SERVICE certificates only — a user's wallet never
/// seals. Optional in the format, so a certificate without it parses unchanged.
/// @dev Outside the folded key commitment: a seal is operational, rotated by issuing a new live
/// certificate, and it must not move a wallet address it plays no part in deriving.
uint16 internal constant PURPOSE_ACTIVE_SEAL = 0x0016;
/// @notice A sentinel purpose no certificate can carry.
/// @dev Lets {parse} be told "this stage has no encapsulation slot" without a second boolean argument.
/// `0xffff` is outside the purpose registry and is reserved by being used here.
uint16 internal constant NO_KEM_PURPOSE = 0xffff;
/// @notice Nanoseconds per millisecond, the conversion from a certificate's validity fields to this
/// chain's clock.
/// @dev A certificate stamps validity in NANOseconds and this chain's clock is MILLIseconds, so the
/// parser divides by 1e6 on the way in and nothing downstream ever compares across units. Getting
/// the divisor wrong does not fail loudly: it shifts every window by three orders of magnitude, so
/// every certificate reads as already valid, including one issued for the future.
uint64 internal constant NS_PER_MILLISECOND = FinalChainTime.NS_PER_MILLISECOND;
/**
* @title Parsed
* @notice What the chain keeps out of one certificate.
* @dev Every field is read OUT of the TBS. Nothing here can be supplied alongside the bytes, which is
* what makes it impossible for a caller to bind a certificate to material the certificate does not
* contain.
*/
struct Parsed {
/// `SHA3-256` of the TBS bytes: the certificate's own identity, and the handle revocation is keyed
/// on.
bytes32 certHash;
/// The certificate's 32-byte serial. A serial is per certificate SET, so the two stages of one
/// wallet share it and two stages that disagree are two different wallets.
bytes32 serial;
/// keccak256 of the issuer-name bytes, for the chain-issuer pin: a chain-attested certificate
/// carries the chain's own constant issuer name, and the registry compares one hash rather than two
/// strings.
bytes32 issuerDnHash;
/// The subject-name bytes verbatim. Kept whole rather than hashed because the jurisdiction rule
/// reads its country component at issuer registration.
bytes subjectDn;
/// The institution extension's VALUE, when present; empty otherwise. Issuer registration parses
/// the declared jurisdiction out of it and requires it to match the subject name's country.
bytes institutionExt;
/// SHA3-256 of the ISSUER's public key block. Zero-length — and so
/// `bytes32(0)` here — for exactly one certificate in the hierarchy,
/// which is what terminates chain validation.
bytes32 authorityKeyId;
/// SHA3-256 of this certificate's own public key block. The child's
/// `authorityKeyId` must equal it, which is what links the two.
bytes32 subjectKeyId;
/// Position on the delegation axis; 0 is the chain's own root.
uint8 depth;
/// Deepest level this key may issue to. `== depth` means it signs no certificates at all, which is
/// every end entity. The pair is immutable per certificate, which is why consumers discriminate
/// record kinds by it rather than by a role bit.
uint8 maxDelegationDepth;
/// MILLISECONDS, converted from the schema's nanoseconds — this chain's clock.
uint64 notBefore;
/// Milliseconds. Zero means never expires, which the schema allows.
uint64 notAfter;
/// The stage's transaction-class key. ML-DSA-87 — spending, and every
/// high-cadence protocol action.
bytes transactionKey;
/// The stage's access-class key. SLH-DSA-SHAKE-256s — identity,
/// rotation, recovery-pair promotion. A different hardness assumption,
/// so a lattice break leaves the key that governs identity standing.
bytes accessKey;
/// The stage's ML-KEM-1024 encapsulation key. Empty on a CA, which has
/// no encapsulation stage, and on any v4 certificate issued without
/// one — see `parse` for why that is tolerated rather than refused.
bytes kemMlKem;
/// The stage's HQC-5 encapsulation key. Carried under the SAME purpose
/// as the lattice half and distinguished only by algorithm, which is
/// why the parser matches on the `(purpose, algorithm)` pair.
bytes kemHqc;
/// The service's seal key (`PURPOSE_ACTIVE_SEAL`, SLH-DSA-SHAKE-256s).
/// Empty on every certificate that does not carry one — a user wallet,
/// a recovery stage, a CA.
bytes sealKey;
/// Where the TBS ends, so a caller holding the whole certificate can
/// find the `SignatureBlock` without parsing forward again.
uint256 tbsLength;
}
/// @notice The bytes do not open with the certificate magic, so they are not a certificate at all.
/// @param got The four bytes that were present.
error BadMagic(uint32 got);
/// @notice The wire generation is one this parser does not read.
/// @param got The generation the certificate declares.
error BadVersion(uint32 got);
/// @notice The TBS ends before a field the parser was about to read.
/// @param needed The offset the read required.
/// @param got The length actually supplied.
error Truncated(uint256 needed, uint256 got);
/// @notice The recomputed key-block digest does not equal the one the certificate declares, so the keys
/// present are not the keys the issuer attested.
/// @param derived The digest recomputed from the key block.
/// @param declared The digest the certificate carries.
error SubjectKeyIdMismatch(bytes32 derived, bytes32 declared);
/// @notice A stage is missing a key it must carry, or carries half of a pair that is issued whole.
/// @param purpose The purpose whose slot is unfilled.
error MissingSlot(uint16 purpose);
/// @notice A slot carries a key of the wrong scheme. It would verify cryptographically and mean
/// something else entirely, which is exactly what splitting the classes exists to prevent.
/// @param purpose The slot's purpose.
/// @param algorithm The algorithm identifier that was present.
error WrongAlgorithmForSlot(uint16 purpose, uint16 algorithm);
/// @notice Two key entries share one `(purpose, algorithm)` pair, so one would silently shadow the
/// other.
/// @param purpose The repeated purpose.
/// @param algorithm The repeated algorithm identifier.
error DuplicateKey(uint16 purpose, uint16 algorithm);
/// @notice The key entries are not in ascending `(purpose, algorithm)` order. The schema requires that
/// order so `certHash` is reproducible across implementations.
error KeysNotSorted();
/// @notice A signing key whose length is not the one its algorithm defines.
/// @param algorithm The algorithm identifier the entry declares.
/// @param length The key length that was present.
error BadKeyLength(uint16 algorithm, uint256 length);
/// @notice A delegation bound shallower than the certificate's own depth, which admits nothing.
/// @param depth The certificate's position on the delegation axis.
/// @param maxDelegationDepth The deepest level it claims to issue to.
error InvalidDepth(uint8 depth, uint8 maxDelegationDepth);
/// @notice A certificate that expires no later than it begins.
/// @param notBefore The declared start, in the schema's nanoseconds.
/// @param notAfter The declared end, in the schema's nanoseconds.
error ValidityInverted(uint64 notBefore, uint64 notAfter);
/**
* @notice Parse and self-check a `TBSCertificate`.
* @dev Checking for a CAPABILITY rather than a type is the certificate schema's own rule, and the reason
* there is no type field to check instead. Passing the LIVE purposes to a recovery certificate
* finds neither key and reverts — which is what stops a recovery certificate being registered as a
* live one and handing the recovery pair everyday authority.
*
* Self-check means the declared `SubjectKeyId` is recomputed from the key block that follows it and
* compared. That field is inside the TBS and therefore covered by the issuer's signatures, so the
* comparison turns "these bytes decode" into "the issuer attested these exact keys". Doing it on
* chain costs one precompile call and buys a verdict any reader can recompute; gas is not a design
* constraint on the chain this runs on, and must not be traded for a check that would then have to
* be taken on trust from whichever process ran it.
*
* A stage is issued as a unit, so both of a stage's signing keys must be present, and its
* encapsulation pair must be present in full or absent in full.
* @param tbs the TBS bytes, verbatim. Not the whole certificate.
* @param txPurpose the transaction-class purpose this stage should carry.
* @param accessPurpose the access-class purpose for the same stage.
* @param kemPurpose the encapsulation purpose for the same stage, or {NO_KEM_PURPOSE} for a stage that
* has none.
* @return out The parsed certificate: digest, serial, names, key identifiers, depth pair, validity
* window, and every key slot the stage carries.
*/
function parse(bytes calldata tbs, uint16 txPurpose, uint16 accessPurpose, uint16 kemPurpose)
internal
view
returns (Parsed memory out)
{
_need(tbs, 58);
if (uint32(bytes4(tbs[0:4])) != MAGIC) revert BadMagic(uint32(bytes4(tbs[0:4])));
// Both live wire generations parse. An artifact issued under the older one is read rather than
// refused; whether it may be ADMITTED is a separate question, settled at registration by the
// holder's proof of possession and the chain-issuer pins.
uint32 wireVersion = uint32(bytes4(tbs[4:8]));
if (wireVersion != VERSION && wireVersion != VERSION_V4) revert BadVersion(wireVersion);
out.certHash = FinalChainPrecompiles.sha3_256(tbs);
out.serial = bytes32(tbs[8:40]);
out.depth = uint8(tbs[40]);
out.maxDelegationDepth = uint8(tbs[41]);
uint64 notBeforeNs = uint64(bytes8(tbs[42:50]));
uint64 notAfterNs = uint64(bytes8(tbs[50:58]));
if (out.maxDelegationDepth < out.depth) {
revert InvalidDepth(out.depth, out.maxDelegationDepth);
}
if (notAfterNs != 0 && notAfterNs <= notBeforeNs) {
revert ValidityInverted(notBeforeNs, notAfterNs);
}
out.notBefore = notBeforeNs / NS_PER_MILLISECOND;
out.notAfter = notAfterNs == 0 ? 0 : notAfterNs / NS_PER_MILLISECOND;
// Four length-prefixed fields: IssuerDN, SubjectDN, AuthorityKeyId,
// SubjectKeyId. Every field before them is fixed width, which is the
// whole reason the schema orders them this way.
uint256 p = 58;
uint256 issuerDnLen;
(p, issuerDnLen) = _skipLengthPrefixed(tbs, p);
out.issuerDnHash = keccak256(tbs[p - issuerDnLen:p]);
uint256 subjectDnLen;
(p, subjectDnLen) = _skipLengthPrefixed(tbs, p);
out.subjectDn = tbs[p - subjectDnLen:p];
uint256 akidLen;
(p, akidLen) = _skipLengthPrefixed(tbs, p);
out.authorityKeyId = _bytes32At(tbs, p - akidLen, akidLen);
uint256 skidLen;
(p, skidLen) = _skipLengthPrefixed(tbs, p);
uint256 skidStart = p - skidLen;
_need(tbs, p + 2);
uint16 keyCount = uint16(bytes2(tbs[p:p + 2]));
p += 2;
// AFTER the count word. `SubjectKeyId` is SHA3-256 of the KeyEntry
// array alone — `encodeTbs` writes `PublicKeyCount` as its own field and
// `encodePublicKeyBlock` returns only the entries. Hashing the count in
// produces a digest that is self-consistent and matches no certificate
// any issuer ever wrote.
uint256 blockStart = p;
uint32 previousSort = 0;
for (uint256 i = 0; i < keyCount; i++) {
_need(tbs, p + 8);
uint16 alg = uint16(bytes2(tbs[p:p + 2]));
uint16 purpose = uint16(bytes2(tbs[p + 2:p + 4]));
uint32 keyLen = uint32(bytes4(tbs[p + 4:p + 8]));
p += 8;
_need(tbs, p + keyLen);
// Ascending by (purpose, algorithm), duplicates invalid. The schema
// requires the order so `certHash` is reproducible across
// implementations; enforcing it here also means a second entry for
// one slot cannot quietly shadow the first.
uint32 sortKey = (uint32(purpose) << 16) | uint32(alg);
if (i > 0) {
if (sortKey == previousSort) revert DuplicateKey(purpose, alg);
if (sortKey < previousSort) revert KeysNotSorted();
}
previousSort = sortKey;
// The algorithm is pinned per CLASS, not merely recorded. A
// transaction slot carrying an access-class key would verify
// cryptographically and mean something entirely different — an
// identity key must never authorize a transaction, or splitting the
// classes buys nothing.
// Matched on the PAIR, not on the purpose alone. A CA carries two
// keys under one purpose (`0x0004`) distinguished only by
// algorithm, so matching on purpose first would find the first of
// them twice and the second never.
if (purpose == txPurpose && alg == ALG_ML_DSA_87) {
if (keyLen != FinalChainPrecompiles.ML_DSA_87_PUBLIC_KEY_LEN) {
revert BadKeyLength(alg, keyLen);
}
out.transactionKey = tbs[p:p + keyLen];
} else if (purpose == accessPurpose && alg == ALG_SLH_DSA_SHAKE_256S) {
if (keyLen != FinalChainPrecompiles.SLH_DSA_SHAKE_256S_PUBLIC_KEY_LEN) {
revert BadKeyLength(alg, keyLen);
}
out.accessKey = tbs[p:p + keyLen];
} else if (purpose == kemPurpose && alg == ALG_ML_KEM_1024) {
out.kemMlKem = tbs[p:p + keyLen];
} else if (purpose == kemPurpose && alg == ALG_HQC_5) {
out.kemHqc = tbs[p:p + keyLen];
} else if (purpose == PURPOSE_ACTIVE_SEAL && alg == ALG_SLH_DSA_SHAKE_256S) {
if (keyLen != FinalChainPrecompiles.SLH_DSA_SHAKE_256S_PUBLIC_KEY_LEN) {
revert BadKeyLength(alg, keyLen);
}
out.sealKey = tbs[p:p + keyLen];
} else if (purpose == PURPOSE_ACTIVE_SEAL) {
// The seal is hash-based by definition — it exists to stand on
// the OTHER assumption from the transaction key it co-signs
// with. A lattice seal would be two signatures on one bet.
revert WrongAlgorithmForSlot(purpose, alg);
} else if (purpose == txPurpose || purpose == accessPurpose) {
// A slot the caller asked for, carrying the wrong scheme. It
// would verify cryptographically and mean something else
// entirely — an identity key must never authorize a
// transaction, or splitting the classes buys nothing.
revert WrongAlgorithmForSlot(purpose, alg);
} else if (purpose == kemPurpose) {
// Same rule for the encapsulation slot. A third KEM appearing
// under this purpose is a hybrid whose second family nobody
// agreed on, and admitting it silently is how a pair becomes a
// trio that one reader honours and another ignores.
revert WrongAlgorithmForSlot(purpose, alg);
}
// NO length check on the KEM keys here, and that is deliberate.
// The signing slots are checked against a constant because the
// parser's own callers depend on the length; an encapsulation key
// is checked by `0x0203` / `0x0207` at the moment it is REGISTERED,
// where the answer is a well-formedness verdict rather than a
// parse failure. Two checks of the same thing in two shapes is how
// one of them ends up weaker and nobody notices which.
p += keyLen;
}
// `SubjectKeyId` is SHA3-256 of the KeyEntry array, count word
// EXCLUDED — `blockStart` is taken after the count is consumed, for the
// reason given where it is set. Recomputing it is what turns "these
// bytes decode" into "the CA signed these exact keys"; the field is
// inside the TBS, so it is covered by the signatures.
out.subjectKeyId = FinalChainPrecompiles.sha3_256(tbs[blockStart:p]);
bytes32 declared = _bytes32At(tbs, skidStart, skidLen);
if (out.subjectKeyId != declared) revert SubjectKeyIdMismatch(out.subjectKeyId, declared);
// Both or neither. A stage is issued as a unit, so a certificate
// carrying one of its two keys is not a partial certificate — it is a
// certificate for a stage that does not exist.
if (out.transactionKey.length == 0) revert MissingSlot(txPurpose);
if (out.accessKey.length == 0) revert MissingSlot(accessPurpose);
// The encapsulation pair is both-or-neither for the same reason, and
// the reason is louder here: a hybrid quietly reduced to one family is
// identical on the wire, so a certificate carrying only the lattice
// half would seal successfully and silently drop the code-based hedge.
// Neither is the CA case and the pre-v4 case, both legitimate.
if ((out.kemMlKem.length == 0) != (out.kemHqc.length == 0)) {
revert MissingSlot(kemPurpose);
}
_need(tbs, p + 2);
uint16 extCount = uint16(bytes2(tbs[p:p + 2]));
p += 2;
for (uint256 i = 0; i < extCount; i++) {
_need(tbs, p + 7);
uint16 extType = uint16(bytes2(tbs[p:p + 2]));
uint32 valueLen = uint32(bytes4(tbs[p + 3:p + 7]));
p += 7;
_need(tbs, p + valueLen);
// The Institution extension's VALUE, kept for the issuer
// profile's jurisdiction rule. Everything else is skipped as
// before — extensions are structural to certHash, semantic to
// whichever consumer knows them.
if (extType == EXT_INSTITUTION) out.institutionExt = tbs[p:p + valueLen];
p += valueLen;
}
out.tbsLength = p;
}
/// @notice Parse a LIVE-stage certificate: the live transaction and access keys.
/// @dev `external`, like the other three entry points below. The identity registry sits against the
/// deployed-code ceiling and this parser is its single largest inlined dependency, so the four doors
/// it calls are DEPLOY-LINKED: the library is one more contract in the state plane's fixed deploy
/// order, and its address is baked immutably into the registry's bytecode. A linked library is code,
/// not a key — nothing can repoint it after deployment, so the split costs a call boundary and no
/// trust.
/// @param tbs The TBS bytes, verbatim.
/// @return The parsed and self-checked certificate.
function parseLive(bytes calldata tbs) external view returns (Parsed memory) {
return parse(tbs, PURPOSE_ACTIVE_TX, PURPOSE_ACTIVE_ACCESS, PURPOSE_ACTIVE_KEM);
}
/// @notice Parse a RECOVERY-stage certificate.
/// @dev The recovery pair authorizes rotating the wallet's own credentials and NOTHING else. Acting as a
/// guardian is an ordinary action for that account and uses the live access key, so keeping the two
/// stages in separate certificates is what makes that boundary something a verifier can see.
/// @param tbs The TBS bytes, verbatim.
/// @return The parsed and self-checked certificate.
function parseRecovery(bytes calldata tbs) external view returns (Parsed memory) {
return parse(tbs, PURPOSE_RECOVERY_TX, PURPOSE_RECOVERY_ACCESS, PURPOSE_RECOVERY_KEM);
}
/// @notice Parse a certificate authority's certificate, whose two keys are both cert-signing.
/// @dev Both classes resolve to the same purpose, which is why {parse} matches on the
/// `(purpose, algorithm)` PAIR: an authority carries two keys under one purpose and matching on the
/// purpose alone would find the first of them twice and the second never.
/// @dev No encapsulation purpose. An authority signs and is never sealed to, so {NO_KEM_PURPOSE} is
/// passed as a value the key loop can never match. An authority certificate carrying encapsulation
/// keys would parse them into slots the registry then discards, which is a shape worth refusing to
/// have at all.
/// @param tbs The TBS bytes, verbatim.
/// @return The parsed and self-checked certificate.
function parseCa(bytes calldata tbs) external view returns (Parsed memory) {
return parse(tbs, PURPOSE_CERT_SIGNING, PURPOSE_CERT_SIGNING, NO_KEM_PURPOSE);
}
/**
* @notice Verify an issuer's dual signature over a TBS.
* @dev Both must verify, not either. Two signatures under two different hardness assumptions is the
* entire reason a certificate carries two, and accepting one would collapse that to whichever
* family breaks first.
*
* Provided for callers that verify an off-chain issuance against keys they already trust. The
* caller supplies the issuer's keys, so it is the caller's job to have taken them from a registered
* record rather than from its own calldata — a key handed in with the signature proves nothing.
* @param tbs The signed TBS bytes.
* @param issuerMlDsaKey The issuer's registered ML-DSA-87 cert-signing key.
* @param issuerSlhDsaKey The issuer's registered SLH-DSA-SHAKE-256s cert-signing key.
* @param mlDsaSignature The lattice signature over `tbs`.
* @param slhDsaSignature The hash-based signature over `tbs`.
* @return Whether both signatures verify.
*/
function verifyIssuerSignatures(
bytes memory tbs,
bytes memory issuerMlDsaKey,
bytes memory issuerSlhDsaKey,
bytes memory mlDsaSignature,
bytes memory slhDsaSignature
) external view returns (bool) {
return FinalChainPrecompiles.verifyMlDsa87(issuerMlDsaKey, tbs, mlDsaSignature)
&& FinalChainPrecompiles.verifySlhDsa(issuerSlhDsaKey, tbs, slhDsaSignature);
}
/// @notice Refuse a TBS that is shorter than the parser is about to read.
/// @dev Called before every read rather than once at the top, because the layout is variable-length: a
/// certificate can be well-formed up to its key block and truncated inside it, and a parser that
/// only checked the fixed header would read whatever calldata followed.
/// @param tbs The TBS bytes.
/// @param upto The offset the next read needs to be valid.
function _need(bytes calldata tbs, uint256 upto) private pure {
if (tbs.length < upto) revert Truncated(upto, tbs.length);
}
/// @notice Step over one four-byte-length-prefixed field and report where it was.
/// @dev Bounds-checks the prefix before reading it and the value before returning, so a truncated
/// certificate cannot make the cursor run past the end of calldata. The caller recovers the value's
/// slice as `tbs[next - length:next]`.
/// @param tbs The TBS bytes.
/// @param p Offset of the length prefix.
/// @return next Offset just past the field's value.
/// @return length The field's declared length.
function _skipLengthPrefixed(bytes calldata tbs, uint256 p)
private
pure
returns (uint256 next, uint256 length)
{
_need(tbs, p + 4);
length = uint32(bytes4(tbs[p:p + 4]));
next = p + 4 + length;
_need(tbs, next);
}
/// @notice Read a key identifier out of the TBS as one word.
/// @dev Answers `bytes32(0)` for any length other than 32 rather than reverting. A key identifier that
/// is not 32 bytes is not a SHA3-256 digest, so it cannot match the value it is compared against,
/// and the comparison at the call site produces the correct refusal with no separate error to
/// define. The one legitimate short case is a zero-length authority key identifier, which the
/// caller must reject on its own terms.
/// @param tbs The TBS bytes.
/// @param start Offset of the field's value.
/// @param length The field's declared length.
/// @return The 32-byte value, or zero when the field is not 32 bytes long.
function _bytes32At(bytes calldata tbs, uint256 start, uint256 length)
private
pure
returns (bytes32)
{
// A SubjectKeyId that is not 32 bytes is not a SHA3-256 digest, so it
// cannot match and the comparison will fail — which is the correct
// outcome and needs no separate error.
if (length != 32) return bytes32(0);
return bytes32(tbs[start:start + 32]);
}
}
contracts/finalchain/FinalChainPrecompiles.sol
// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may link this library into contracts deployed on a
// Final DeFi Protocol chain in order to reach that chain's hash and
// post-quantum signature-verification precompiles.
// 2. Integrators, node operators, and auditors may use it to reproduce and
// independently re-verify any verdict those precompiles produced, as part of
// their integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
// deployment of a Fork of this library or a competing state plane derived
// from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
/**
* @title Final Chain Precompiles
* @notice The three primitives Final Chain adds to the EVM, and the only
* supported way to reach them.
*
* @dev **These exist ONLY on Final Chain (chain id 48359).** They are provided
* by this chain's own node binary, and
* nothing at these addresses on Ethereum, Optimism or any other chain will
* answer. A contract that calls them must be one that only ever runs here;
* `assertAvailable` below is the cheap way to fail loudly rather than treat an
* empty return as a verified signature.
*
* The addresses are the FIPS numbers, which is the whole allocation rule —
* there is no local registry to consult and no way for two implementations to
* disagree about where a primitive lives:
*
* | address | primitive | FIPS |
* |---|---|---|
* | `0x…0202` | SHA3-256 | 202 |
* | `0x…0203` | ML-KEM-1024 key validation | 203 |
* | `0x…0204` | ML-DSA-87 verify | 204 |
* | `0x…0205` | SLH-DSA-SHAKE-256s verify | 205 |
* | `0x…0207` | HQC-5 key validation | 207 |
*
* The two KEM addresses VALIDATE keys and do nothing else, for one reason:
* encapsulation is a SENDER operation and decapsulation needs the secret key,
* so neither belongs on a chain at all. Checking that a registered public key
* is well-formed is hardening rather than a dependency, and nothing in this
* system waits on it.
*
* HQC's number is 207. It had none when the KEM pair was chosen, which was the
* one thing separating it from ML-KEM here — a primitive with no standard
* number has no address under this rule, and inventing one would have been a
* local convention masquerading as the global one.
*
* **No AEAD precompile, at any number.** The chain must never be able to
* decrypt an intent, and checking a revealed body against its commitment is a
* hash compare that `0x0202` already serves.
*
* ## Why this library refuses to take a public key from its caller
*
* It does take one — the primitives are pure functions and cannot do otherwise.
* The rule lives one level up, in `FinalPqQuorum`: a key passed as an argument
* proves nothing, because anyone holding a keypair can produce a valid
* signature under it. Only a key read from `FinalIdentityRegistry` is evidence
* about WHO signed. Every call site here must be able to answer "where did this
* key come from" with "storage", never "calldata".
*
* ## `success` is not the answer
*
* A `staticcall` to a verifier returns two things and both matter. `success`
* false means the call was malformed — usually a length bug in the caller — and
* `success` true with a zero word means the signature did not verify. The
* helpers below collapse both to `false` for the caller's convenience, which is
* safe in that direction and only in that direction: treating a failed call as
* a valid signature would be the whole security of the system.
*/
library FinalChainPrecompiles {
/// @notice SHA3-256 (FIPS 202). NOT `keccak256`, which is the
/// pre-standardisation padding and produces a different digest.
address internal constant SHA3_256 = address(0x0202);
/// @notice ML-DSA-87 verification (FIPS 204). Transaction-class keys.
address internal constant ML_DSA_87 = address(0x0204);
/// @notice SLH-DSA-SHAKE-256s verification (FIPS 205). Access-class keys.
address internal constant SLH_DSA_SHAKE_256S = address(0x0205);
/// @notice ML-KEM-1024 encapsulation-key validation (FIPS 203).
/// @dev VALIDATES; it does not encapsulate. Runs FIPS 203 §7.2's own
/// encapsulation-key check — the type check and the modulus check — and
/// nothing else. Encapsulation is a sender operation and decapsulation
/// needs the secret key, so neither belongs on a chain.
address internal constant ML_KEM_1024 = address(0x0203);
/// @notice HQC-5 public-key validation (FIPS 207).
/// @dev Structural only: the length, and the three padding bits the
/// encoding leaves beyond `n = 57637`. HQC has no cheap key-validity
/// predicate and this does not pretend to one.
address internal constant HQC_5 = address(0x0207);
/// @notice ML-DSA-87 public key length. Round-3 Dilithium5 shares it.
uint256 internal constant ML_DSA_87_PUBLIC_KEY_LEN = 2592;
/// @notice ML-DSA-87 signature length. Round-3 Dilithium5 is 4595.
uint256 internal constant ML_DSA_87_SIGNATURE_LEN = 4627;
/// @notice SLH-DSA-SHAKE-256s public key length (`PK.seed ‖ PK.root`).
uint256 internal constant SLH_DSA_SHAKE_256S_PUBLIC_KEY_LEN = 64;
/// @notice SLH-DSA-SHAKE-256s signature length. The `f` set is 49,856.
uint256 internal constant SLH_DSA_SHAKE_256S_SIGNATURE_LEN = 29792;
/// @notice Thrown when a precompile is absent, i.e. this is not Final Chain
/// or the node is stock reth rather than `final-reth`.
error PrecompileUnavailable(address precompile);
/**
* @notice Reverts unless all five precompiles answer.
* @dev Call this from a constructor. A contract whose security rests on PQ
* verification must not deploy onto a chain that cannot perform it — the
* failure mode otherwise is a quorum that reaches threshold with zero valid
* signatures, discovered at the worst possible moment.
*
* The probe is SHA3-256 of the empty string, whose value is a published
* FIPS 202 constant. It cannot be produced by an address with no code
* (which returns empty) nor by `keccak256` (which gives a different digest
* for the same input), so it distinguishes "the right precompile" from both
* "nothing here" and "the wrong hash function".
*/
function assertAvailable() internal view {
bytes32 expected = 0xa7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a;
(bool ok, bytes memory out) = SHA3_256.staticcall("");
if (!ok || out.length != 32 || bytes32(out) != expected) {
revert PrecompileUnavailable(SHA3_256);
}
// The two signature verifiers are probed by shape rather than by a
// known-answer vector: a KAT here would put a 29,792-byte signature in
// this contract's bytecode. A deliberately short input is a
// *precompile error* by contract, so a FAILED call is the pass and a
// silent success would mean something else is answering at the address.
_probeRejectsShortInput(ML_DSA_87);
_probeRejectsShortInput(SLH_DSA_SHAKE_256S);
// The two KEM validators are probed the other way round, because they
// are total by contract: a wrong length is a malformed KEY, which is
// the question being asked, so they ANSWER rather than error. A
// one-byte input must therefore come back as a well-formed `false`, and
// a failed call means nothing is there.
_probeAnswersFalse(ML_KEM_1024);
_probeAnswersFalse(HQC_5);
}
/**
* @dev A short input must make the precompile ERROR. The gas budget is the
* whole subtlety.
*
* A reverting CONTRACT refunds the gas it did not use. A precompile that
* returns an error consumes **everything forwarded to it** — and Solidity
* forwards 63/64 of what is left by default. Two such probes in a
* constructor therefore burn all but 1/4096 of the deployment's gas, and
* the deploy fails with no revert data at all.
*
* That is not hypothetical: it is what happened the first time this ran
* against a real `final-reth`, and no Foundry test could have caught it.
* A mocked precompile is a contract, and a contract's `require` hands the
* gas back.
*
* 5,000 is generous for a call that fails on a length check before any
* cryptography runs, and small enough that both probes together are noise
* against a deployment.
*/
function _probeRejectsShortInput(address precompile) private view {
bool ok;
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore8(ptr, 0x00)
ok := staticcall(5000, precompile, ptr, 0x01, 0x00, 0x00)
}
if (ok) revert PrecompileUnavailable(precompile);
}
/**
* @dev A one-byte input must come back as a well-formed zero word.
*
* The inverse of `_probeRejectsShortInput`, and the inversion is the point:
* these two precompiles are TOTAL. Every byte string has an answer to "is
* this a well-formed key", and for one byte the answer is no. A precompile
* that errored here would be one that treats a malformed key as a caller
* bug, which is the opposite of what a registry wants.
*
* Gas is bounded for the same reason as the other probe — an erroring
* precompile consumes everything forwarded — even though the pass case
* returns normally and refunds.
*/
function _probeAnswersFalse(address precompile) private view {
bool ok;
bytes32 answer;
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore8(ptr, 0x00)
ok := staticcall(5000, precompile, ptr, 0x01, ptr, 0x20)
answer := mload(ptr)
}
if (!ok || answer != bytes32(0)) revert PrecompileUnavailable(precompile);
}
/**
* @notice Is `encapsulationKey` a well-formed ML-KEM-1024 key?
*
* @dev The check a registry owes a sender. A malformed encapsulation key
* stored on chain is an account whose intents cannot be sealed, and the
* discovery happens at the first attempt to seal one — on the hybrid path,
* as a pair silently reduced to one family, which is the failure with no
* error attached.
*
* False rather than reverting on any shape, including the wrong length,
* because the caller is asking a question and every input has an answer.
*/
function isWellFormedMlKem1024(bytes memory encapsulationKey) internal view returns (bool) {
return _validatesKey(ML_KEM_1024, encapsulationKey);
}
/// @notice Is `publicKey` a well-formed HQC-5 key?
/// @dev Structural, and honestly partial — see the precompile. It catches a
/// truncated key, a key from the wrong parameter set, and a tail carrying
/// smuggled bytes, which are the three ways this goes wrong in practice.
function isWellFormedHqc5(bytes memory publicKey) internal view returns (bool) {
return _validatesKey(HQC_5, publicKey);
}
/// @dev A failed CALL is not a false answer. It means nothing is at the
/// address — this is not Final Chain, or the node is stock reth — and
/// reading it as "the key is malformed" would silently disable the check on
/// exactly the deployment where it cannot run.
function _validatesKey(address precompile, bytes memory key) private view returns (bool) {
(bool ok, bytes memory out) = precompile.staticcall(key);
if (!ok || out.length != 32) revert PrecompileUnavailable(precompile);
return bytes32(out) != bytes32(0);
}
/// @notice FIPS 202 SHA3-256 over `data`.
/// @dev The certificate schema hashes `TBSCertificate`, `SubjectKeyId` and
/// `AuthorityKeyId` with this, so it is the only function that can check a
/// `certHash` against the bytes it claims to summarise.
function sha3_256(bytes memory data) internal view returns (bytes32 digest) {
(bool ok, bytes memory out) = SHA3_256.staticcall(data);
if (!ok || out.length != 32) revert PrecompileUnavailable(SHA3_256);
digest = bytes32(out);
}
/// @notice Verify an ML-DSA-87 signature. False on any failure, including
/// a malformed call.
function verifyMlDsa87(bytes memory publicKey, bytes memory message, bytes memory signature)
internal
view
returns (bool)
{
if (
publicKey.length != ML_DSA_87_PUBLIC_KEY_LEN
|| signature.length != ML_DSA_87_SIGNATURE_LEN
) return false;
return _verify(ML_DSA_87, publicKey, signature, message);
}
/// @notice Verify an SLH-DSA-SHAKE-256s signature. False on any failure.
function verifySlhDsa(bytes memory publicKey, bytes memory message, bytes memory signature)
internal
view
returns (bool)
{
if (
publicKey.length != SLH_DSA_SHAKE_256S_PUBLIC_KEY_LEN
|| signature.length != SLH_DSA_SHAKE_256S_SIGNATURE_LEN
) return false;
return _verify(SLH_DSA_SHAKE_256S, publicKey, signature, message);
}
/// @dev `publicKey ‖ signature ‖ message`, in that order. Both fixed-length
/// fields come first so the message is unambiguously the remainder — the
/// same reason the precompile takes no length prefix.
function _verify(
address precompile,
bytes memory publicKey,
bytes memory signature,
bytes memory message
) private view returns (bool) {
(bool ok, bytes memory out) =
precompile.staticcall(abi.encodePacked(publicKey, signature, message));
return ok && out.length == 32 && bytes32(out) != bytes32(0);
}
}
contracts/finalchain/FinalChainTime.sol
// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may link this time library into contracts deployed on
// a Final DeFi Protocol chain, and may read its constants to interpret the
// timestamps and durations that chain publishes.
// 2. Integrators, indexers, and operators may use it to convert between this
// chain's clock and the units their own systems keep, as part of their
// integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
// deployment of a Fork of this library or a competing state plane derived
// from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
/**
* @title Final Chain Time
* @notice **On this chain, `block.timestamp` is MILLISECONDS, not seconds.**
* @dev Every other EVM chain stamps seconds. This one cannot. It mints a block every 100 ms, and the protocol
* requires block timestamps to strictly increase, so a second-denominated clock would exhaust its distinct
* values ten times over per second. Milliseconds is the deliberate consequence, and it is a property of the
* CHAIN itself rather than of any contract here — nothing in this library can change it, and nothing deployed
* beside this library may assume otherwise.
*
* Every duration and every instant on this chain is therefore in milliseconds. This library exists so that fact
* is stated in one place and converted in one place, instead of being assumed independently everywhere a
* deadline or a delay is written.
*
* ## The naming rule, which is a safety rule
*
* A field or constant carrying a duration or an instant on this chain ends in `Ms`. This is not decoration. A
* delay field named for seconds while holding milliseconds elapses a thousand times too fast: a one-day
* recovery delay would mature in about eighty-six seconds, and a two-year dormancy threshold in under a day.
* Those delays are the whole of what stands between a stolen credential and an account, so a name that states
* the wrong unit is not a cosmetic defect — it is the defect, wearing a disguise. `Seconds`-suffixed names do
* not appear in this directory and must not be introduced.
*
* A test harness is not a check on this. Standard EVM tooling stamps `block.timestamp` in seconds, so a suite
* can agree with the contracts under test and both be wrong about the chain they deploy to. The unit has to be
* carried by the names.
*
* Solidity's `hours` and `days` suffixes remain the clearest way to write a duration, so durations are written
* as `24 hours * MS_PER_SECOND` rather than as a bare literal: the intent stays readable and the unit stays
* explicit at the point of use.
*/
library FinalChainTime {
/// @notice Milliseconds per second — the whole conversion between this chain's clock and ordinary time,
/// named once.
/// @dev Multiply a `seconds`-denominated Solidity duration literal by this to express it in this chain's
/// units. It is deliberately the only place the factor appears.
uint64 internal constant MS_PER_SECOND = 1_000;
/// @notice Nanoseconds per millisecond — the divisor for values that arrive stamped in nanoseconds.
/// @dev The certificate schema stamps validity windows in nanoseconds, so a certificate converts DOWN to
/// this chain's clock. Dividing rather than multiplying is the direction that cannot overflow, and it
/// truncates toward the past, which for a validity window is the conservative rounding.
uint64 internal constant NS_PER_MILLISECOND = 1_000_000;
/// @notice This chain's current time, in milliseconds.
/// @dev A function rather than a bare `block.timestamp` read so the unit is visible at every call site.
/// It performs no arithmetic and exists purely so that reading the clock is self-describing, where
/// `block.timestamp` on this chain is silently a thousand times what a reader would assume.
/// @return nowInMs The current block's timestamp, in milliseconds.
function nowMs() internal view returns (uint64) {
return uint64(block.timestamp);
}
}
contracts/finalchain/FinalIdentityRegistry.sol
// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy this identity registry as part of a Final
// DeFi Protocol state plane, and may register, rotate, and revoke identity
// records in it under the authority this contract enforces.
// 2. Operators, integrators, and end users may read the certificates, public
// keys, role bits, and signer bindings it holds, and may call its views to
// resolve an identity, a sender, or a quorum roster.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
// deployment of a Fork of this identity registry or a competing certificate
// authority derived from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
import {FinalCertificate} from "./FinalCertificate.sol";
import {FinalChainTime} from "./FinalChainTime.sol";
import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalSweep} from "../utils/FinalSweep.sol";
/// @dev Commitment space for one stage's encapsulation pair.
/// Byte-equal to `FinalWalletFactory.DOMAIN_KEM_BUNDLE` and to the certificate issuer's own preimage
/// constant. Three independent derivations of one word: a mismatch in any of them is a certificate that
/// verifies nowhere, so the value is pinned by test against the other two rather than imported.
bytes32 constant DOMAIN_KEM_BUNDLE = keccak256("FINAL_KEM_BUNDLE_v01");
/// @dev Commitment space for the identity tree's wallet leaf.
/// Byte-equal to `IdentityRootModule.DOMAIN_IDENTITY_LEAF` on every execution chain. Restated rather
/// than imported because that module lives on other chains and no import would make the two one value; a
/// cross-contract parity test pins the pair. The spelling is FROZEN: the premined certificates were mined
/// against this exact constant, and the leaf it derives is the `certHash` inside a wallet's address
/// derivation, so changing a byte here moves addresses that already exist.
bytes32 constant DOMAIN_IDENTITY_LEAF = keccak256("FINAL_IDENTITY_LEAF_PQ_v01");
/// @dev Commitment space for the identity tree's ISSUER leaf.
/// An issuer projects under its own domain — `DOMAIN_ISSUER_LEAF ‖ certHash ‖ version ‖
/// issuerTreeRoot` — so an issuer record is stapleable for offline licence verification while the distinct
/// domain keeps it out of wallet admission: an execution chain's gateway folds with the wallet domain, so an
/// issuer leaf can never satisfy an identity-certificate check there. `issuerTreeRoot` is a RESERVED word,
/// zero until an issuer's own certificate-tree anchor is wired — the only clean path to offline licence
/// revocation, since fixed-depth insertion-ordered state trees cannot prove non-inclusion.
bytes32 constant DOMAIN_ISSUER_LEAF = keccak256("FINAL_ISSUER_LEAF_v01");
/// @dev The issuer name every chain-attested certificate carries, as a keccak digest.
/// The chain is the issuer but holds no keypair, so a chain-attested certificate carries this named
/// value in its issuer field: required by the wire format, verifying nothing on its own, and covered by
/// `certHash`. The name is deliberately environment-agnostic and jurisdiction-silent — the issuer is the
/// worldwide network rather than a legal entity, and an environment-specific name would fork `certHash` per
/// environment. Compared as a hash rather than as a string, so the check costs one word.
bytes32 constant CHAIN_ISSUER_DN_HASH = keccak256("CN=Final Chain,O=Final DeFi");
/// @dev The authority key identifier every chain-attested certificate names.
/// `SHA3-256(utf8("FINAL_CHAIN_AUTHORITY_v01"))` — a DOMAIN constant rather than the digest of a key,
/// because the chain issues certificates and holds no public key block to hash. Precomputed rather than
/// derived at construction: the harness the unit tests run under does not implement the real SHA3 function,
/// and the literal is pinned by test against a reference implementation. A zero-length authority key
/// identifier is reserved and is admitted nowhere.
bytes32 constant CHAIN_AUTHORITY_KEY_ID =
0x9a6a5d8139ad2d28957698330aaa691017dba7dc80eb7cbec585239fb680bbab;
/**
* @title Identity Leaf Sink
* @notice The identity tree's projection door on the state-trees contract.
* @dev A narrow interface rather than an import, because the trees contract imports THIS file — the
* dependency runs that way, and this is the one call that runs the other. Declaring the single method
* here keeps the cycle away from the compiler without duplicating either contract's surface.
*/
interface IIdentityLeafSink {
/// @notice Recompute and store the identity-tree leaf for each named account.
/// @dev Called inside the same transaction as every identity mutation, so an execution chain's admission
/// set sees a registration, rotation or revocation the moment this chain does. The leaf VALUE is
/// derived by the trees contract from the registry's post-mutation state, so the caller supplies
/// accounts and never a leaf.
/// @param accounts The accounts whose leaves are stale.
function syncIdentityLeaves(address[] calldata accounts) external;
}
/**
* @title Revocation Recorder
* @notice The revocation log's recording door.
* @dev Same narrow-interface reasoning as the leaf sink above. `recorded` is read first, so a fingerprint
* somebody already recorded through the log's permissionless door cannot revert the registry mutation
* that feeds it.
*/
interface IRevocationRecorder {
/// @notice Fold a permanently retired signer fingerprint into the revocation log.
/// @dev The log applies its own permanence gate, reading this registry back; the call states nothing the
/// registry has not already decided.
/// @param signerId The fingerprint that has lost standing for good.
function record(bytes32 signerId) external;
/// @notice Whether the log already holds `signerId`.
/// @param signerId The fingerprint to look up.
/// @return Whether a leaf for it exists.
function recorded(bytes32 signerId) external view returns (bool);
}
/**
* @title Final Identity Registry
* @notice Who every party in the system is, on chain: one record per party, carrying its certificate and its
* actual public keys.
* @dev Every service, every co-signer, every certificate authority and every operator has one record here.
* The record holds the party's public keys in full rather than commitments to them, and this contract is
* the certificate authority as well as the roster.
*
* ## Where this runs
*
* Only on this project's own reth-based chains. Verification happens inside precompiles that exist
* nowhere else: SHA3-256 at `0x0202`, ML-DSA-87 at `0x0204` and SLH-DSA-SHAKE-256s at `0x0205`, each
* address being that primitive's FIPS number. The constructor probes them and refuses to deploy where
* they are absent, so a registry of keys the chain cannot check never comes into existence. This
* contract takes part in no CREATE2 derivation — its address is per chain, and nothing derives an
* address from it — and nothing outside this directory imports it.
*
* Gas is deliberately NOT a design constraint on that chain and must not be optimised for. Where a
* choice below trades gas for a verdict that is re-derivable from public state, the verdict wins: a
* signature checked in a precompile is a fact anyone can recompute, where the same check run in a
* library by whichever process happened to hold the keys is only a claim.
*
* ## Keys are read from STORAGE, never from calldata
*
* A commitment would be a quarter of the storage and would be enough to CHECK a key someone hands you.
* It is not enough to VERIFY A SIGNATURE, because verification needs the key itself — and a key that
* arrives in calldata proves nothing, since anyone holding a keypair can produce a valid signature under
* it. A quorum built on caller-supplied keys is a quorum of one: whoever built the calldata.
*
* So the keys live here in full. `FinalPqQuorum` resolves a member through this registry and reads that
* member's key from this registry's storage, and "which key is co-signer three" has exactly one answer,
* in exactly one place. That is the load-bearing rule of every quorum on the chain, not an optimisation.
*
* ## The certificate is the record, not a pointer to one
*
* `certHash` is `SHA3-256(TBSCertificate)`: the certificate's own identity, and the handle revocation is
* keyed on. {registerWallet} and {registerIssuer} take the certificate's TBS bytes and read everything
* out of them — the digest, the serial, the key identifiers, the depth pair, the validity window and
* every public key. Neither takes a key argument, so no two arguments can disagree and no registrar can
* bind a certificate to a keypair that certificate does not contain.
*
* ## The root is the first record here, not a self-signed file
*
* This chain is the only root certificate authority, and the root is pinned as an entry in this registry
* rather than distributed as a self-signed certificate somebody has to install. Chain validation
* terminates here BY IDENTITY. Everything registered after the root is verified on chain, inside the
* precompiles, against what this registry already holds: the holder's own two signatures over the
* admission digest, the pinned chain-issuer constants, and — for a nested issuer — lineage to a
* registered parent whose depth admits it. There is no path by which a key enters this registry
* unattested; a registrar cannot register anything else.
*
* ## Roles are a bitmask
*
* One party is legitimately several things: a co-signer that also publishes, an operator that is also a
* guardian. A single enum would force either duplicate records for one key, which is two sources of
* truth about one party, or a role hierarchy nobody agrees on. A mask has neither problem, and a quorum
* asks whether an account CARRIES a capability rather than whether it IS a type.
*
* ## Membership is hybrid-gated
*
* Who is in this registry, and with which roles, is the root of every quorum on the chain, so it is the
* one thing no single key may decide. Once bootstrap is sealed, every membership mutation — register,
* roles, revoke, a hash-based signing key, the registrar threshold itself — and every state-plane
* configuration change routed through {requireRegistrarQuorum} takes a `ROLE_REGISTRAR` quorum whose
* approvals carry BOTH families: the ML-DSA-87 vote and the SLH-DSA seal. A lattice break cannot then
* rewrite the roster, and neither can a hash-function break; only both at once.
*
* The bootstrap window is the only exception. While it is open the bootstrap admin writes alone, because
* every roster has to be installed by someone before it can install itself. {sealBootstrap} closes it
* irreversibly, and refuses to close it onto a registrar quorum that cannot be met.
*
* ## The sender is not the account
*
* Transactions on this chain are signed by ML-DSA-87, and the node derives `msg.sender` from the key as
* `keccak256(0x04 ‖ publicKey)[12:]`. That address pays gas and holds no authority. {accountOfSender}
* binds it to the identity whose live transaction key it derives from, so a `msg.sender` gate anywhere
* on this chain asks {senderHasRole} and resolves to the identity — and a key rotation moves the binding
* instead of the roster.
*
* ## What this contract deliberately does not do
*
* It never un-revokes: a revoked certificate is finished, and reversing that would reopen every past
* verification. It never enumerates a mapping inside a mutation — the registrars supply the chain list a
* revocation touches, and a fingerprint an incomplete list missed stays permanently recordable through
* the revocation log's own permissionless door. It holds no funds, exposes no payable entrypoint, and
* reserves nothing against a sweep. And it grants no capability by parsing one: a certificate says which
* keys a party holds, `roles` says what the party may do, and the two arrive as different arguments on
* purpose.
*/
contract FinalIdentityRegistry is FinalSweep {
// ---------------------------------------------------------------- roles
/// @notice May co-sign account-state rounds (tree 1).
uint256 public constant ROLE_ACCOUNT_COSIGNER = 1 << 0;
/// @notice May co-sign MMR / bundle-log advances.
uint256 public constant ROLE_MMR_COSIGNER = 1 << 1;
/// @notice May publish PHI ledger state (tree 2).
uint256 public constant ROLE_PHI_PUBLISHER = 1 << 2;
/// @notice May publish vAsset state (tree 3).
uint256 public constant ROLE_VASSET_PUBLISHER = 1 << 3;
/// @notice May publish oracle data (tree 4).
uint256 public constant ROLE_ORACLE_PUBLISHER = 1 << 4;
/// @notice May publish settlement / asset registry roots (trees 5 and 6).
uint256 public constant ROLE_REGISTRY_PUBLISHER = 1 << 5;
/// @notice May act as a wallet guardian.
uint256 public constant ROLE_GUARDIAN = 1 << 6;
/// @notice May submit transactions on behalf of the protocol.
uint256 public constant ROLE_RELAYER = 1 << 7;
/// @notice May register and revoke identities once bootstrap is sealed.
uint256 public constant ROLE_REGISTRAR = 1 << 8;
/// @notice A certificate authority — the root, or an intermediate under it.
uint256 public constant ROLE_CERTIFICATE_AUTHORITY = 1 << 9;
/// @notice May co-sign `FinalSettlementLog` appends — the cross-chain
/// settlement quorum, the same members whose LMS keys satisfy the
/// execution chains' settlement set. A role of its own rather than a
/// second use of `ROLE_REGISTRY_PUBLISHER`: the registries (trees 5/6)
/// change on listing cadence and settlement leaves release custody, and
/// one role for both would put the value plane behind the listing roster.
uint256 public constant ROLE_SETTLEMENT_COSIGNER = 1 << 10;
// ----------------------------------------------------- action domains
/// @notice Action domain for registering or rotating a wallet identity.
/// @dev One domain per membership mutation, so an approval to grant a role can never be replayed as one
/// to revoke. This registry is its own verifying contract for all of these, and the digest also
/// binds a per-contract counter, so an approval authorises exactly one action once.
bytes32 public constant DOMAIN_REGISTER_WALLET = keccak256("FINAL_REGISTRY_REGISTER_WALLET_v01");
/// @notice Action domain for registering or rotating an issuer.
bytes32 public constant DOMAIN_REGISTER_ISSUER = keccak256("FINAL_REGISTRY_REGISTER_ISSUER_v01");
/// @notice The admission proof-of-possession digest domain.
/// @dev The HOLDER signs `keccak256(abi.encode(domain, chainid, registry, certHash, recoveryCertHash,
/// gateNonce))` with the live transaction key (ML-DSA-87) AND the live access key
/// (SLH-DSA-SHAKE-256s) — both families, in the admission transaction, verified by the precompiles.
/// Possession lives in the TRANSACTION, never in the artifact, so holding a copy of somebody's
/// public certificate admits nothing.
bytes32 public constant DOMAIN_IDENTITY_ADMISSION = keccak256("FINAL_IDENTITY_ADMISSION_v01");
/// @notice Action domain for root-plane global certificate revocation, by handle.
bytes32 public constant DOMAIN_REVOKE_CERTIFICATE =
keccak256("FINAL_REGISTRY_REVOKE_CERTIFICATE_v01");
/// @notice Digest domain for an issuer revoking a certificate it signed off chain.
/// @dev Signed by the issuer's own registered cert-signing keys rather than approved by a quorum, and
/// bound to the issuer's own gate nonce, so one issuer's revocations cannot be replayed as
/// another's.
bytes32 public constant DOMAIN_ISSUER_CERT_REVOCATION =
keccak256("FINAL_ISSUER_CERT_REVOCATION_v01");
/// @notice Action domain for recording an account's hash-based signing key.
bytes32 public constant DOMAIN_REGISTER_LMS_KEY = keccak256("FINAL_REGISTRY_REGISTER_LMS_KEY_v01");
/// @notice Action domain for replacing an identity's capability bitmask.
bytes32 public constant DOMAIN_SET_ROLES = keccak256("FINAL_REGISTRY_SET_ROLES_v01");
/// @notice Action domain for retiring an identity.
bytes32 public constant DOMAIN_REVOKE = keccak256("FINAL_REGISTRY_REVOKE_v01");
/// @notice Action domain for moving the registrar threshold itself.
bytes32 public constant DOMAIN_SET_REGISTRAR_THRESHOLD =
keccak256("FINAL_REGISTRY_SET_REGISTRAR_THRESHOLD_v01");
/// @notice The algorithm identifier the sender derivation is domain-separated by.
/// @dev ML-DSA-87, FIPS 204 — the only algorithm this chain's transaction envelope admits. Prefixing it
/// means a key of another family can never derive the same sender address.
uint8 private constant ENVELOPE_ALG_ML_DSA_87 = 4;
// ------------------------------------------------------------- storage
/**
* @title Identity
* @notice One party's on-chain identity.
* @dev `version` increments on every mutation, and that increment is what a rotation IS: the record is
* replaced rather than appended to, and the version is how a reader on another chain knows which of
* two copies it has seen is newer.
*/
struct Identity {
/// SHA3-256 of the LIVE certificate's TBS bytes. The revocation handle.
bytes32 certHash;
/// SHA3-256 of the RECOVERY certificate's TBS bytes.
bytes32 recoveryCertHash;
/// The certificate's 32-byte serial, `16 B entropy ‖ 16 B counter`.
bytes32 serial;
/// SHA3-256 of this certificate's public key block. A child names it in
/// its own `AuthorityKeyId`, which is how the chain links the two.
bytes32 subjectKeyId;
/// Capability bitmask. Zero for a registered-but-idle party.
uint256 roles;
/// Position on the delegation axis; 0 is the Final Chain root.
uint8 depth;
/// Deepest level this key may issue to. `== depth` means it signs no
/// certificates at all, which is every end entity.
uint8 maxDelegationDepth;
/// Milliseconds since the epoch, on this chain's clock. The certificate schema stamps validity in
/// nanoseconds and the parser converts on the way in, so nothing here ever compares across units.
uint64 notBefore;
/// Milliseconds since the epoch, or 0 for "never expires" — which the certificate schema allows and
/// personal identity certificates use. The bound is exclusive.
uint64 notAfter;
/// Monotonic. A rotation that does not advance it is refused.
uint64 version;
/// Set by `revoke`. Never unset: a revoked certificate is finished, and
/// an un-revoke would make every past verification re-openable.
bool revoked;
/// Distinguishes "no record" from "a record whose fields are all zero".
bool registered;
}
/**
* @title Lms Key
* @notice A hash-based (LMS) signing key held by a registered account.
* @dev The execution chains' quorums verify LMS rather than ML-DSA, because those chains have no
* post-quantum precompiles and check a keccak hash chain instead. Those keys are the authority over
* the post-quantum anchor, and therefore over post-quantum execution — which makes "who holds this
* fingerprint?" a question the state plane has to be able to answer, exactly as it answers it for
* every other key.
*
* Recorded against an account that is ALREADY registered, so an LMS key is a capability of a known
* identity rather than a standalone credential. It inherits that identity's revocation: a revoked
* account's signer is a revoked signer, with nothing extra to remember to do.
*/
struct LmsKey {
/// `I`, hashed into every step of the signature.
bytes16 keyId;
/// Merkle tree height. Bound into the fingerprint, because the leaf
/// commits to node `2^h + q` and a signer who could vary it could vary
/// the numbering.
uint8 height;
/// `T[1]`, the LMS public key.
bytes32 root;
/// Monotonic. A rotation that does not advance it is refused, so a
/// replayed registration cannot reinstate a superseded key.
uint64 version;
/// Distinguishes "no key" from "a key whose fields are all zero".
bool registered;
}
/// @notice The hash-based (LMS) signing key an account holds, per chain.
/// @dev One slot per account AND chain. A single-use hash-based counter is a complete defence only while
/// the key it names signs for ONE chain, so the roster is stored the way it is armed: the same
/// operator is a different signer on every chain, and a rotation on one says nothing about another.
mapping(address account => mapping(uint64 chainId => LmsKey)) private _lmsKey;
/**
* @title Lms Binding
* @notice What a signer fingerprint is bound to: the account holding it and the chain it signs for.
* @dev Two fields in one slot, deliberately. This contract sits within a few bytes of the deployed-code
* ceiling, so anything added to this surface has to pay for itself in bytecode first — which is why
* checks that no authority consults, such as refusing a zero chain identifier, are left to the
* publisher off chain rather than spent here.
*/
struct LmsBinding {
/// The account that registered the fingerprint. Zero means no account ever did.
address account;
/// The chain that registration was for. Zero alongside a zero account, for a fingerprint never
/// registered.
uint64 chainId;
}
/// @notice Which account a signer fingerprint belongs to, and which chain it signs for.
/// @dev The lookup the whole LMS record exists for: an execution chain's roster names fingerprints and
/// nothing else, so without this the keys behind those names are unattributable. Written once at
/// registration and left in place when the key is superseded, because attribution is history — a
/// signature made under a retired key was still made by that operator.
///
/// The chain it names is what selects the slot {lmsSignerIsLive} resolves the fingerprint against.
mapping(bytes32 signerId => LmsBinding) private _lmsBinding;
/// @notice The identity record for an account.
mapping(address account => Identity) private _identity;
/// @notice The live transaction key, ML-DSA-87: spending, and every high-cadence protocol action.
/// @dev All four key slots are stored in FULL rather than as commitments, because the precompiles verify
/// against a KEY and a key that arrived in calldata proves nothing about who signed. This is the
/// rule every quorum on this chain rests on.
/// @dev A certificate authority has two keys rather than four, and they live in the two active slots.
/// One storage shape rather than two, because every reader would otherwise have to know which kind
/// of party it was looking at before it could look.
mapping(address account => bytes) private _activeTransactionKey;
/// @notice The live access key, SLH-DSA-SHAKE-256s: identity, rotation and guardianship.
mapping(address account => bytes) private _activeAccessKey;
/// @notice The pre-committed recovery transaction key, ML-DSA-87. Empty for a certificate authority.
mapping(address account => bytes) private _recoveryTransactionKey;
/// @notice The pre-committed recovery access key, SLH-DSA-SHAKE-256s. Empty for a certificate
/// authority.
mapping(address account => bytes) private _recoveryAccessKey;
/// @notice The seal key: a service's second SLH-DSA-SHAKE-256s key, which co-signs execution-class
/// quorum decisions.
/// @dev Empty for every identity whose certificate carries no seal slot, which is every user wallet and
/// every certificate authority. An identity with no seal can never contribute to a sealed quorum,
/// so {sealableMemberCount} counts this rather than counting role bits.
mapping(address account => bytes) private _activeSealKey;
/// @notice The live stage's ML-KEM-1024 encapsulation key, the lattice half of the pair.
/// @dev Two algorithms per stage — ML-KEM-1024 and HQC-5 — so a break in either family leaves the other
/// standing, the same reasoning that pairs the two signature families. The pair is written and
/// cleared together, so an account holds both or neither.
/// @dev Stored as the RAW keys, like the signing keys, because a registry that held only commitments
/// could not answer "encapsulate to this party" without a second lookup somewhere less
/// authoritative.
mapping(address account => bytes) private _activeKemMlKem;
/// @notice The live stage's HQC-5 encapsulation key, the code-based half of the pair.
mapping(address account => bytes) private _activeKemHqc;
/// @notice The recovery stage's ML-KEM-1024 encapsulation key. Empty when the account has no recovery
/// stage.
mapping(address account => bytes) private _recoveryKemMlKem;
/// @notice The recovery stage's HQC-5 encapsulation key. Empty when the account has no recovery stage.
mapping(address account => bytes) private _recoveryKemHqc;
/// @notice Reverse index. A certificate identifies exactly one account, so
/// presenting a `certHash` is enough to find who it belongs to.
mapping(bytes32 certHash => address account) public accountOfCertificate;
/// @notice Revocation by certificate, independent of the account record.
/// A certificate stays revoked even if its account is later re-registered
/// under a new one.
mapping(bytes32 certHash => bool) public certificateRevoked;
/// @notice Who revoked a certificate through the ISSUER half of the lane.
/// Scoped by the verifier: the entry binds only when the recorded revoker
/// is the certificate's own issuer. Never gates registration.
mapping(bytes32 certHash => address) public certificateRevokedBy;
/// @notice Every registered account, in registration order. Small by
/// construction — this is services and co-signers, not wallets.
address[] private _accounts;
/// @notice Bootstrap authority. Zero once `sealBootstrap` has run.
address public bootstrapAdmin;
/// @notice Whether registration still accepts the bootstrap admin.
bool public bootstrapSealed;
/// @notice Where identity mutations project the tree-8 leaf, same-tx.
/// Zero only before {wireStatePlane} — the deploy tooling wires it before
/// the first registration, and the projection is skipped while unset so
/// the wiring transaction itself can be ordered freely in the bootstrap
/// window.
address public stateTrees;
/// @notice Where the PERMANENT standing losses — revocation and LMS-key
/// supersession — are recorded, same-tx. Zero only before {wireStatePlane}.
address public revocationLog;
/// @notice Sealed `ROLE_REGISTRAR` approvals a membership mutation needs.
/// @dev Zero until set, and bootstrap cannot be sealed while it is zero or
/// unreachable: a registry sealed behind a threshold nobody can meet is a
/// registry nobody can ever write to again.
uint256 public registrarThreshold;
/// @notice Replay counter per verifying contract — this registry for its
/// own mutations, each state-plane contract for its configuration. Bound
/// into every registrar digest, so an approval is for exactly one action.
mapping(address caller => uint64) private _gateNonce;
/// @notice The identity a Final Chain sender belongs to. See the contract
/// notes: a sender is derived from the `activeTransaction` key and is not
/// the account.
mapping(address sender => address account) public accountOfSender;
// -------------------------------------------------------------- events
/// @notice An identity was registered, or an existing one rotated onto a new certificate set.
/// @param account The identity written.
/// @param certHash The live certificate's handle.
/// @param roles The capability bitmask now in force.
/// @param version The record's monotonic version.
event IdentityRegistered(
address indexed account, bytes32 indexed certHash, uint256 roles, uint64 version
);
/// @notice An identity's capability bitmask was replaced.
/// @param account The identity whose roles changed.
/// @param previousRoles The mask before the change.
/// @param newRoles The mask now in force.
event IdentityRolesChanged(address indexed account, uint256 previousRoles, uint256 newRoles);
/// @notice An account's hash-based signing key for one chain was recorded or rotated.
/// @param account The identity that holds the key.
/// @param signerId The fingerprint an execution chain's roster names.
/// @param chainId The chain the key is armed for.
/// @param keyId The LMS key identifier.
/// @param height The Merkle tree height.
/// @param root The LMS public key.
/// @param version The lineage counter for this account and chain.
event LmsKeyRegistered(
address indexed account,
bytes32 indexed signerId,
uint64 indexed chainId,
bytes16 keyId,
uint8 height,
bytes32 root,
uint64 version
);
/// @notice An identity was retired. Irreversible, and its roles are cleared in the same transaction.
/// @param account The identity that was revoked.
/// @param certHash The certificate it held at the time.
event IdentityRevoked(address indexed account, bytes32 indexed certHash);
/// @notice One revocation-lane entry.
/// @param certHash The certificate that was revoked.
/// @param revoker Zero for a root-plane revocation, the issuing identity for an issuer's own.
event CertificateRevoked(bytes32 indexed certHash, address indexed revoker);
/// @notice The bootstrap window closed. After this there is no single-caller write path left.
/// @param sealedBy The bootstrap admin that closed it, immediately before being cleared.
event BootstrapSealed(address indexed sealedBy);
/// @notice The one-shot state-plane wiring landed. Emitted at most once in this contract's lifetime.
/// @param stateTrees The state-trees contract that owns the identity tree.
/// @param revocationLog The append-only log of retired signer fingerprints.
event StatePlaneWired(address stateTrees, address revocationLog);
/// @notice The number of sealed registrar approvals a membership mutation needs was set.
/// @param threshold The new threshold.
event RegistrarThresholdSet(uint256 threshold);
/// @notice A registrar quorum authorized an action.
/// @param verifyingContract The contract the approvals were collected for, and whose counter was burned.
/// @param actionDomain The action domain the approvals bound.
/// @param nonce The counter value the approvals were made over; the next action needs the next one.
/// @param valid How many approvals verified.
event RegistrarQuorumApproved(
address indexed verifyingContract, bytes32 indexed actionDomain, uint64 nonce, uint256 valid
);
// -------------------------------------------------------------- errors
/// @notice The caller holds none of the authority the entry point requires.
/// @param caller The address that called.
error NotAuthorized(address caller);
/// @notice The bootstrap window is already closed. Closing it is irreversible.
error BootstrapAlreadySealed();
/// @notice No record claims this account, or a zero address was offered as one.
/// @param account The address that was named.
error UnknownAccount(address account);
/// @notice A certificate's encapsulation key failed the chain's own well-formedness check.
/// @dev Names the algorithm, because the pair is stored together and "one of these two" is not an
/// actionable answer.
/// @param account The account being registered.
/// @param algorithmId The algorithm whose key was malformed.
error MalformedEncapsulationKey(address account, uint16 algorithmId);
/// @notice The certificate is already bound to a different account. One certificate identifies exactly
/// one party.
/// @param certHash The certificate's handle.
/// @param boundTo The account that already holds it.
error CertificateAlreadyBound(bytes32 certHash, address boundTo);
/// @notice The certificate has been revoked, or the account's own certificate has. Revocation is never
/// undone, so this is terminal for that handle.
/// @param certHash The revoked certificate's handle.
error CertificateIsRevoked(bytes32 certHash);
/// @notice A registration or rotation did not advance the record's version. Monotonicity is what stops a
/// replayed transaction reinstating credentials their holder has moved off.
/// @param current The version on record.
/// @param offered The version the caller presented.
error VersionNotNewer(uint64 current, uint64 offered);
/// @notice The named account does not carry `ROLE_CERTIFICATE_AUTHORITY`, or does not currently stand.
/// @param issuer The account that was named.
error IssuerNotACertificateAuthority(address issuer);
/// @notice The named parent has reached its own delegation bound and may issue nothing further.
/// @param issuer The parent account.
/// @param depth The parent's depth.
/// @param maxDelegationDepth The deepest level the parent may issue to.
error IssuerMayNotSign(address issuer, uint8 depth, uint8 maxDelegationDepth);
/// @notice A certificate sits at a depth its lineage does not put it at. Levels cannot be skipped,
/// because skipping one is how an issuer escapes its own delegation bound.
/// @param got The depth the certificate declares.
/// @param want The depth its lineage requires.
error WrongDepth(uint8 got, uint8 want);
/// @notice A child certificate claims a deeper delegation bound than the parent that admits it.
/// @param child The child's `maxDelegationDepth`.
/// @param issuer The parent's `maxDelegationDepth`.
error DelegationWidened(uint8 child, uint8 issuer);
/// @notice The certificate names an authority key that is not its declared parent's subject key.
/// @param got The authority key identifier the certificate carries.
/// @param want The parent's subject key identifier.
error AuthorityKeyIdMismatch(bytes32 got, bytes32 want);
/// @notice The live and recovery certificates carry different serials, so they describe two different
/// certificate sets rather than two stages of one.
/// @param liveSerial The live certificate's serial.
/// @param recoverySerial The recovery certificate's serial.
error StagesDisagree(bytes32 liveSerial, bytes32 recoverySerial);
/// @notice An LMS tree height outside 1 through 24, the range the verifier admits.
/// @param height The height offered.
error LmsHeightOutOfRange(uint8 height);
/// @notice A zero LMS root commits to no tree and is refused.
error LmsRootIsZero();
/// @notice This signer fingerprint already belongs to a different account.
/// @param signerId The fingerprint offered.
/// @param boundTo The account that already holds it.
error LmsKeyAlreadyBound(bytes32 signerId, address boundTo);
/// @notice Two identities cannot share a transaction key: the sender it derives would be attributable to
/// both.
/// @param sender The derived sender address.
/// @param boundTo The account that already claims it.
error SenderAlreadyBound(address sender, address boundTo);
/// @notice Fewer registrars able to seal than the threshold asks for.
/// @param sealable How many standing registrars hold a seal key.
/// @param threshold How many approvals a membership mutation needs.
error RegistrarThresholdUnreachable(uint256 sealable, uint256 threshold);
/// @notice A zero registrar threshold was offered, or a quorum was demanded before one was set. A zero
/// threshold is a registry with no authority behind its membership.
error RegistrarThresholdIsZero();
/// @notice {wireStatePlane} has already run. Both pointers are trust topology and are written once.
error StatePlaneAlreadyWired();
/// @notice {wireStatePlane} was handed a zero address for the trees or for the revocation log.
error ZeroStatePlane();
/// @notice The holder's proof of possession did not verify: one family failed, or the digest was built
/// over the wrong nonce.
/// @param account The account the admission was for.
error AdmissionProofInvalid(address account);
/// @notice The certificate does not name the chain's authority key, so it is not chain-attested.
/// @param authorityKeyId The authority key identifier that was presented.
error NotChainAttested(bytes32 authorityKeyId);
/// @notice The certificate's issuer name is not the chain's own.
/// @param issuerDnHash The digest of the name that was presented.
error WrongIssuerDn(bytes32 issuerDnHash);
/// @notice A chain-attested end entity sits at depth 1 with `maxDelegationDepth == depth`; anything else
/// is not an end entity.
/// @param depth The certificate's position on the delegation axis.
/// @param maxDelegationDepth The deepest level it may issue to.
error NotAnEndEntity(uint8 depth, uint8 maxDelegationDepth);
/// @notice An issuer that cannot sign is an end entity wearing an issuer profile, and belongs in
/// {registerWallet}.
/// @param depth The certificate's position on the delegation axis.
/// @param maxDelegationDepth The deepest level it may issue to.
error IssuerCannotSign(uint8 depth, uint8 maxDelegationDepth);
/// @notice A registered issuer's certificate never expires.
/// @dev Expiry is the passive half of an issuer's lifecycle, so a zero `NotAfter` is refused here even
/// though the certificate schema allows one for an end entity.
error IssuerMustExpire();
/// @notice An issuer validity window past {MAX_ISSUER_VALIDITY_MS}.
/// @param notBefore The certificate's start, in this chain's milliseconds.
/// @param notAfter The certificate's end, in this chain's milliseconds.
error IssuerValidityTooLong(uint64 notBefore, uint64 notAfter);
/// @notice An institution registration whose subject name carries no ISO 3166 country component, or
/// whose institution extension is too short to hold one.
/// @dev Only the trust root is jurisdiction-silent; a registered institution names where it answers for
/// itself.
error JurisdictionMissing();
/// @notice The subject name's country and the institution extension's `jurisdiction` field disagree, or
/// the extension's jurisdiction is not a two-byte country code.
error JurisdictionMismatch();
// --------------------------------------------------------- constructor
/**
* @notice Deploy the registry with a bootstrap registrar in place.
* @dev The precompile probe is the point of the constructor. This contract is meaningless on a chain
* that cannot verify post-quantum signatures, and deploying it there would produce a registry full
* of keys nothing on that chain can check — so it refuses to exist where the precompiles are
* absent rather than existing and being trusted.
*
* The admin is the whole authority until {sealBootstrap} runs, because every roster has to be
* installed by someone before it can install itself.
* @param admin The bootstrap registrar. Genesis names the chain deployer.
*/
constructor(address admin) {
FinalChainPrecompiles.assertAvailable();
bootstrapAdmin = admin;
}
// ----------------------------------------------------------- authority
/**
* @notice The authority gate on every membership mutation this registry performs.
* @dev Bootstrap is a real window, not a formality: every roster in this system has to be installed by
* someone before it can install itself, and a design that pretends otherwise ends up with a roster
* that cannot be brought into existence at all. It is closed by {sealBootstrap}, irreversibly.
*
* While the window is open the admin writes alone. Once it is closed there is no single-caller path
* left — not for a registrar, not for anyone — and every mutation goes through the sealed registrar
* quorum, whose approvals carry both signature families.
* @param actionDomain One of the `DOMAIN_*` constants naming the mutation.
* @param payloadDigest The mutation's own arguments, folded.
* @param anchorBlock The block the registrars read the roster at. Ignored while bootstrap is open.
* @param approvals The sealed registrar quorum. Empty while bootstrap is open.
*/
function _requireMembershipAuthority(
bytes32 actionDomain,
bytes32 payloadDigest,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) private {
if (!bootstrapSealed && msg.sender == bootstrapAdmin) return;
_requireRegistrarQuorum(address(this), actionDomain, payloadDigest, anchorBlock, approvals);
}
/**
* @notice The sealed registrar quorum, for the other contracts in the state plane.
* @dev `msg.sender` — the calling contract — is the verifying contract the digest binds and the counter
* it burns, so an approval collected for one contract's configuration cannot be spent on another's.
* The caller decides its own bootstrap exemption before calling; this function knows no caller's
* admin and applies none.
*
* Anyone may SUBMIT such a transaction. Authority is the approvals, not the sender, which is the
* whole point of a quorum.
* @param actionDomain The caller's own action domain for the change being authorised.
* @param payloadDigest The change's arguments, folded by the caller.
* @param anchorBlock The block the registrars read the roster at.
* @param approvals The registrar approvals, each carrying both families.
*/
function requireRegistrarQuorum(
bytes32 actionDomain,
bytes32 payloadDigest,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireRegistrarQuorum(msg.sender, actionDomain, payloadDigest, anchorBlock, approvals);
}
/// @notice Burn one gate nonce and require a sealed registrar quorum over the action.
/// @dev The digest is `FinalPqQuorum.digest(verifyingContract, actionDomain, anchorBlock,
/// keccak256(abi.encode(nonce, payloadDigest)))`. The counter is burned BEFORE verification, so an
/// approval set is spent whether or not it turns out to be sufficient.
///
/// The seal is required rather than optional: membership is the hybrid class, and an approval
/// carrying only the lattice vote is not an approval here.
/// @param verifyingContract The contract the approvals are for, and whose counter is burned.
/// @param actionDomain One of the `DOMAIN_*` constants, so an approval to grant cannot be replayed to
/// revoke.
/// @param payloadDigest The action's own arguments, folded.
/// @param anchorBlock The block the registrars read the roster at.
/// @param approvals The registrar approvals, each carrying both families.
function _requireRegistrarQuorum(
address verifyingContract,
bytes32 actionDomain,
bytes32 payloadDigest,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) private {
if (registrarThreshold == 0) revert RegistrarThresholdIsZero();
uint64 nonce = _gateNonce[verifyingContract];
_gateNonce[verifyingContract] = nonce + 1;
bytes32 quorumDigest = FinalPqQuorum.digest(
verifyingContract, actionDomain, anchorBlock, keccak256(abi.encode(nonce, payloadDigest))
);
uint256 valid = FinalPqQuorum.require_(
this,
approvals,
quorumDigest,
ROLE_REGISTRAR,
registrarThreshold,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
true
);
emit RegistrarQuorumApproved(verifyingContract, actionDomain, nonce, valid);
}
/**
* @notice Set how many sealed registrar approvals a membership mutation needs.
* @dev The bootstrap admin while the window is open; the current registrar quorum afterwards, so a
* registrar set that grows or shrinks can move the threshold to match itself.
*
* Refuses a threshold the sealable registrars cannot meet, and refuses zero. Both are a registry
* that can never be written to again, and the way that presents is every membership mutation
* reverting forever with nothing naming the threshold as the cause.
* @param threshold How many sealed approvals a mutation needs. Must be reachable and non-zero.
* @param anchorBlock The block the registrars read the roster at. Ignored while bootstrap is open.
* @param approvals The sealed registrar quorum. Empty while bootstrap is open.
*/
function setRegistrarThreshold(
uint256 threshold,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireMembershipAuthority(
DOMAIN_SET_REGISTRAR_THRESHOLD, keccak256(abi.encode(threshold)), anchorBlock, approvals
);
if (threshold == 0) revert RegistrarThresholdIsZero();
uint256 sealable = sealableMemberCount(ROLE_REGISTRAR);
if (sealable < threshold) revert RegistrarThresholdUnreachable(sealable, threshold);
registrarThreshold = threshold;
emit RegistrarThresholdSet(threshold);
}
/// @notice The replay counter the next registrar approval for `caller` must be made over.
/// @dev One counter per verifying contract, so an approval collected for one contract's configuration
/// cannot be spent on another's. A caller reads this to build the digest its registrars will sign.
/// @param caller The verifying contract the approvals will name — this registry for its own mutations.
/// @return The value the next approval must bind.
function gateNonceOf(address caller) external view returns (uint64) {
return _gateNonce[caller];
}
// -------------------------------------------------------- LMS signers
/**
* @notice The roster identity of an LMS public key.
* @dev Byte-identical to `FinalRootAuthority.signerId` on the execution chains. Restated rather than
* imported because the two live on different chains and no import would make them one value —
* which is precisely why a test pins them together. A drift here would make every lookup miss while
* looking perfectly well-formed.
*
* The height is bound into the fingerprint as well as the root, because a leaf commits to a node
* number derived from it, so a signer free to vary the height could vary the numbering.
* @param keyId The LMS key identifier.
* @param height The Merkle tree height.
* @param root The LMS public key.
* @return The fingerprint an execution chain's roster names.
*/
function lmsSignerId(bytes16 keyId, uint8 height, bytes32 root) public pure returns (bytes32) {
return keccak256(abi.encode(keyId, height, root));
}
/**
* @notice Record the hash-based (LMS) signing key an already-registered account holds for one chain.
* @dev Membership-gated, like every other write here.
*
* Deliberately NOT a certificate: an LMS key is a capability of an existing identity, not an
* identity of its own. Binding it to an account means it inherits that account's revocation, so
* retiring a compromised operator is one action rather than one action per key they hold.
*
* A rotation records the SUPERSEDED fingerprint into the revocation log in the same transaction, so
* the execution chains' suspension lane never depends on someone noticing. The superseded
* fingerprint is left BOUND to this account rather than cleared, because attribution is history.
*
* A zero `chainId` is a tooling mistake rather than an attack — the slot it occupies is
* self-consistent and no authority consults it — so the publisher refuses it off chain and this
* contract spends no bytecode on the check.
* @param account Must already be registered and not revoked.
* @param chainId The execution chain this key is armed for.
* @param keyId The LMS key identifier, hashed into every step of a signature under it.
* @param height The Merkle tree height, 1 through 24.
* @param root The LMS public key. Zero commits to no tree and is refused.
* @param version Strictly increasing per account and chain. A rotation that does not advance it is
* refused, so a replayed registration cannot reinstate a key the operator has moved off.
* @param anchorBlock The block the registrars read the roster at. Ignored while bootstrap is open.
* @param approvals The sealed registrar quorum. Empty while bootstrap is open.
*/
function registerLmsKey(
address account,
uint64 chainId,
bytes16 keyId,
uint8 height,
bytes32 root,
uint64 version,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireMembershipAuthority(
DOMAIN_REGISTER_LMS_KEY,
keccak256(abi.encode(account, chainId, keyId, height, root, version)),
anchorBlock,
approvals
);
Identity storage id = _identity[account];
if (!id.registered) revert UnknownAccount(account);
if (id.revoked) revert CertificateIsRevoked(id.certHash);
// A zero chain id is a tooling mistake, not an attack: the slot it
// would occupy is self-consistent and no authority consults it. The
// publisher refuses it; EIP-170 pressure keeps the check off-chain.
if (height == 0 || height > 24) revert LmsHeightOutOfRange(height);
if (root == bytes32(0)) revert LmsRootIsZero();
// Version lineage is PER account and chain: the same operator is a different signer on every chain,
// so one chain starting at version 1 says nothing about another already being at version 3.
LmsKey storage existing = _lmsKey[account][chainId];
// An empty slot holds version 0, so this alone also refuses a version-0
// registration — versions start at 1.
if (version <= existing.version) {
revert VersionNotNewer(existing.version, version);
}
bytes32 signerId = lmsSignerId(keyId, height, root);
address boundTo = _lmsBinding[signerId].account;
if (boundTo != address(0) && boundTo != account) {
revert LmsKeyAlreadyBound(signerId, boundTo);
}
// The fingerprint being superseded, captured before the slot moves —
// `existing` is a storage pointer and reads the NEW key afterwards.
bytes32 superseded = existing.registered
? lmsSignerId(existing.keyId, existing.height, existing.root)
: bytes32(0);
// The superseded fingerprint is left bound to this account rather than
// cleared. It is history: a signature made under the old key was made
// by this operator, and a lookup that stopped resolving would make that
// unprovable after the fact.
_lmsKey[account][chainId] = LmsKey(keyId, height, root, version, true);
_lmsBinding[signerId] = LmsBinding(account, chainId);
emit LmsKeyRegistered(account, signerId, chainId, keyId, height, root, version);
// Supersession is a PERMANENT transition — the old fingerprint stops
// being this slot's current key and nothing re-registers it (a
// re-registration of the same material is the same fingerprint, which
// the guard below leaves alone). Recorded same-tx so the execution
// chains' suspension lane never depends on someone noticing.
if (superseded != bytes32(0) && superseded != signerId) {
_recordRevokedSigner(superseded);
}
_projectIdentity(account);
}
/// @notice The LMS key an account holds for one chain, if any.
/// @dev Keyed per account AND per chain, because a single-use hash-based counter is only complete while
/// the key it names signs for one chain. `registered` is the field to branch on; the zero struct
/// means no key rather than a key of zeroes.
/// @param account The identity to read.
/// @param chainId The chain the key is armed for.
/// @return The stored key, copied to memory.
function lmsKeyOf(address account, uint64 chainId) external view returns (LmsKey memory) {
return _lmsKey[account][chainId];
}
/// @notice What a fingerprint is bound to: the account that registered it and the chain it signs for.
/// @dev The binding survives supersession, because attribution is history: a signature made under a
/// retired key was still made by that operator, and a lookup that stopped resolving would make that
/// unprovable after the fact. Standing is a separate question, answered by {lmsSignerIsLive}.
///
/// The revocation log's permanence gate reads this to find the slot a fingerprint belongs to; that
/// slot's current key is what separates a superseded fingerprint, which is permanent and
/// recordable, from a merely lapsed one, which renewal undoes.
/// @param signerId The fingerprint to resolve.
/// @return account The account that registered it, or zero for a fingerprint never registered.
/// @return chainId The chain that registration was for, or zero alongside a zero account.
function lmsBindingOf(bytes32 signerId) external view returns (address account, uint64 chainId) {
LmsBinding storage binding = _lmsBinding[signerId];
return (binding.account, binding.chainId);
}
/**
* @notice Whether a signer fingerprint is held by a standing, unrevoked account.
* @dev The question a verifier actually has. An execution chain's authority roster names fingerprints
* and learns nothing else about them, so without this the keys behind those names are
* unanswerable from the state plane.
*
* Standing is asked through {isActive} rather than by spelling the conditions out again, because a
* second spelling is how two answers drift: an expired identity already holds no role, and a signer
* lookup that disagreed would leave a roster satisfiable by an operator the rest of the registry
* has stopped honouring.
*
* Live means the CURRENT key of the fingerprint's own account-and-chain slot, not merely one this
* account ever held. A superseded fingerprint stays attributable but stops being live, and a
* rotation on one chain says nothing about the same operator's key on another.
* @param signerId The fingerprint an authority roster names.
* @return live Whether the fingerprint is that slot's current key and the account still stands.
* @return account The account the fingerprint is bound to, or zero when none ever registered it.
*/
function lmsSignerIsLive(bytes32 signerId) external view returns (bool live, address account) {
LmsBinding storage binding = _lmsBinding[signerId];
account = binding.account;
if (account == address(0)) return (false, address(0));
// `isActive`, not a registered/revoked pair spelled out here. The
// certificate validity window is part of standing: an expired identity
// already holds no role, and a signer lookup that disagreed would leave
// a roster satisfiable by an operator the rest of the registry has
// stopped honouring. Spelling the condition out a second time is how
// the two drift apart.
if (!isActive(account)) return (false, account);
// The CURRENT key of the fingerprint's own (account, chain) slot, not
// merely one this account ever held: a superseded fingerprint stays
// attributable but stops being live, and a rotation on one chain says
// nothing about the same operator's key on another.
LmsKey storage k = _lmsKey[account][binding.chainId];
live = k.registered && lmsSignerId(k.keyId, k.height, k.root) == signerId;
}
/// @notice Close the bootstrap window. Irreversible.
/// @dev Refuses while the registrar quorum is unset or unreachable, because sealing then would leave a
/// registry nobody can ever write to again — including to fix the threshold that locked it. The
/// count is of registrars that can SEAL: a certificate authority carrying the registrar role is
/// registered from a certificate with no seal slot and can never contribute an approval, so
/// counting role bits alone would seal onto a quorum that looks reachable and is not.
///
/// Clears the admin as well as setting the flag, so no single-caller path survives the seal.
function sealBootstrap() external {
if (msg.sender != bootstrapAdmin) revert NotAuthorized(msg.sender);
if (bootstrapSealed) revert BootstrapAlreadySealed();
if (registrarThreshold == 0) revert RegistrarThresholdIsZero();
uint256 sealable = sealableMemberCount(ROLE_REGISTRAR);
if (sealable < registrarThreshold) {
revert RegistrarThresholdUnreachable(sealable, registrarThreshold);
}
bootstrapSealed = true;
bootstrapAdmin = address(0);
emit BootstrapSealed(msg.sender);
}
// ------------------------------------------------- state-plane wiring
/**
* @notice Wire the state trees and the revocation log, once, inside the bootstrap window.
* @dev One-shot because both pointers are TRUST TOPOLOGY: the trees pointer decides where the
* wallet-creation admission set is written, and the log pointer decides where permanent standing
* losses are recorded. A re-wireable pointer would be a key over both.
*
* It cannot be a constructor argument, because both of those contracts take THIS registry as one of
* theirs. The deploy tooling calls it in the same nonce-fixed block that deploys them, before any
* identity is registered, which is why the projection is silently skipped while the pointers are
* zero rather than reverting.
* @param stateTrees_ The state-trees contract that owns tree 8. Zero is refused.
* @param revocationLog_ The append-only log of retired signer fingerprints. Zero is refused.
*/
function wireStatePlane(address stateTrees_, address revocationLog_) external {
if (bootstrapSealed || msg.sender != bootstrapAdmin) revert NotAuthorized(msg.sender);
if (stateTrees != address(0) || revocationLog != address(0)) revert StatePlaneAlreadyWired();
if (stateTrees_ == address(0) || revocationLog_ == address(0)) revert ZeroStatePlane();
stateTrees = stateTrees_;
revocationLog = revocationLog_;
emit StatePlaneWired(stateTrees_, revocationLog_);
}
/// @notice Refresh `account`'s tree-8 leaf in the state trees, same transaction.
/// @dev Skipped while the plane is unwired, which is a bootstrap-window state the deploy tooling closes
/// before the first registration, and never otherwise. The leaf VALUE is derived by the trees
/// contract from this registry's post-mutation state, so there is nothing here to get wrong beyond
/// forgetting to call it — which is why every mutation calls it, including the one that cannot
/// change the leaf.
/// @param account The identity whose leaf is stale.
function _projectIdentity(address account) private {
address trees = stateTrees;
if (trees == address(0)) return;
address[] memory one = new address[](1);
one[0] = account;
IIdentityLeafSink(trees).syncIdentityLeaves(one);
}
/// @notice Record a permanently retired signer fingerprint into the revocation log, same transaction.
/// @dev Skipped while the log is unwired, and skipped when somebody already recorded the fingerprint
/// through the log's permissionless door — the log refuses a duplicate, and a membership mutation
/// must not be revertible by a stranger who front-ran its bookkeeping.
/// @param signerId The fingerprint that has lost standing for good.
function _recordRevokedSigner(bytes32 signerId) private {
address log = revocationLog;
if (log == address(0)) return;
if (IRevocationRecorder(log).recorded(signerId)) return;
IRevocationRecorder(log).record(signerId);
}
// -------------------------------------------------------- registration
/**
* @title Admission Proof
* @notice The holder's proof of possession at admission: both live-stage families over the admission
* digest.
* @dev There is no root keypair and no issuer signature on this path. The chain admits, and the two
* signatures presented at creation are the HOLDER's, verified by the precompiles inside the same
* transaction that writes the record. Possession lives in the TRANSACTION, never in the artifact:
* a public certificate is a document anyone may hold, so presenting one proves nothing.
*/
struct AdmissionProof {
/// The holder's ML-DSA-87 signature under the live TRANSACTION key, over the admission digest.
bytes mlDsaSignature;
/// The holder's SLH-DSA-SHAKE-256s signature under the live ACCESS key, over the same digest. Two
/// families over one message, so neither a lattice break nor a hash-function break alone admits an
/// identity.
bytes slhDsaSignature;
}
/**
* @notice Register or rotate a Final Wallet identity from its two public certificates.
* @dev **Both stages, together.** A wallet has four keys in two stages and the recovery pair is
* PRE-COMMITTED — written at wallet initialization from the same certificate set that determined
* the wallet's address, which is why enabling post-quantum mode later takes no key arguments. The
* two certificates must share a serial: a serial is per certificate SET, so two stages that
* disagree about it are two different wallets.
*
* **Chain-attested means pinned, per stage:** the chain's issuer name and authority key, depth
* exactly 1 so the certificate hangs directly under the chain, and `maxDelegationDepth == depth` so
* the holder issues nothing. That immutable pair is what {identityTreeLeafOf} discriminates record
* kinds by.
*
* Issuance authority is the registrar quorum and possession is the holder's own proof; there is no
* root keypair anywhere and no certificate-authority signature over this admission.
* @param account The wallet address the certificate set derives.
* @param liveTbs The live certificate's TBS bytes: the live transaction and access keys.
* @param recoveryTbs The recovery certificate's TBS bytes: the pre-committed recovery pair.
* @param proof The holder's two signatures over the admission digest — the live transaction key
* (ML-DSA-87) and the live access key (SLH-DSA-SHAKE-256s), both verified in the precompiles
* inside this transaction.
* @param roles Capability bitmask. The one thing the certificates do not say, because capability is this
* system's decision rather than the certificate's.
* @param version Monotonic. A rotation that does not advance it is refused.
* @param anchorBlock The block the registrars read the roster at. Ignored while bootstrap is open.
* @param approvals The sealed registrar quorum. Empty while bootstrap is open. The digest binds the
* account, both certificates' bytes, the roles and the version.
* @return certHash The handle the live certificate is now known by.
*/
function registerWallet(
address account,
bytes calldata liveTbs,
bytes calldata recoveryTbs,
AdmissionProof calldata proof,
uint256 roles,
uint64 version,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external returns (bytes32 certHash) {
// Read BEFORE the authority check: the quorum path burns this counter
// inside `_requireRegistrarQuorum`, and the proof must bind the value
// the round was built over. The bootstrap path burns it explicitly in
// `_requireAdmissionProof`, so an admission is one-shot in both regimes.
uint64 admissionNonce = _gateNonce[address(this)];
_requireMembershipAuthority(
DOMAIN_REGISTER_WALLET,
keccak256(
abi.encode(account, keccak256(liveTbs), keccak256(recoveryTbs), roles, version)
),
anchorBlock,
approvals
);
FinalCertificate.Parsed memory l = FinalCertificate.parseLive(liveTbs);
FinalCertificate.Parsed memory r = FinalCertificate.parseRecovery(recoveryTbs);
if (l.serial != r.serial) revert StagesDisagree(l.serial, r.serial);
_requireChainAttestedEndEntity(l);
_requireChainAttestedEndEntity(r);
_requireAdmissionProof(account, l, r.certHash, proof, admissionNonce);
certHash = l.certHash;
_write(account, l, r, roles, version, false);
}
/**
* @notice Register or rotate an ISSUER: a third party, or one of this system's own intermediates, that
* signs certificates off chain with the keys registered here.
* @dev Admission is chain-native like any identity — the registrar quorum authorises, and the holder's
* own proof of possession establishes that the party controls the keys it is claiming. The
* delegation rules survive as LINEAGE: a nested issuer's depth, delegation bound and
* `AuthorityKeyId` must chain to its registered parent. No parent signs anything; this chain's
* admission IS the issuance.
*
* A registered issuer always expires, and its window is bounded by {MAX_ISSUER_VALIDITY_MS}.
*
* An institution must carry its real ISO 3166 country in its subject name, matching the
* `jurisdiction` field of its institution extension. That is enforced at the door because a
* verifier's legal recourse starts with knowing where an issuer answers for itself.
*
* `ROLE_CERTIFICATE_AUTHORITY` is added to whatever `roles` asks for, rather than being required in
* it: the capability is what this entry point means, so it cannot be forgotten in an argument.
* @param account The issuer's account on this chain.
* @param tbs The issuer certificate's TBS bytes: two cert-signing keys, ML-DSA-87 and
* SLH-DSA-SHAKE-256s, and no recovery stage — renewing an issuer is re-issuing, a governance act
* rather than a key rotation.
* @param parent The registered parent issuer for a nested intermediate; zero for an issuer hanging
* directly under the chain.
* @param proof The issuer's own two cert-signing keys over the admission digest. The recovery-handle
* slot in that digest is zero, because there is no recovery stage to bind.
* @param roles Capability bitmask, over and above the certificate-authority bit this call adds.
* @param version Monotonic. A rotation that does not advance it is refused.
* @param anchorBlock The block the registrars read the roster at. Ignored while bootstrap is open.
* @param approvals The sealed registrar quorum. Empty while bootstrap is open. The digest binds the
* account, the certificate bytes, the parent, the roles and the version.
* @return certHash The handle the registered certificate is now known by.
*/
function registerIssuer(
address account,
bytes calldata tbs,
address parent,
AdmissionProof calldata proof,
uint256 roles,
uint64 version,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external returns (bytes32 certHash) {
uint64 admissionNonce = _gateNonce[address(this)];
_requireMembershipAuthority(
DOMAIN_REGISTER_ISSUER,
keccak256(abi.encode(account, keccak256(tbs), parent, roles, version)),
anchorBlock,
approvals
);
FinalCertificate.Parsed memory c = FinalCertificate.parseCa(tbs);
// An issuer that cannot sign is an end entity wearing a profile —
// and an end entity belongs in `registerWallet`.
if (c.depth == 0 || c.maxDelegationDepth <= c.depth) {
revert IssuerCannotSign(c.depth, c.maxDelegationDepth);
}
if (c.notAfter == 0) revert IssuerMustExpire();
if (c.notAfter - c.notBefore > MAX_ISSUER_VALIDITY_MS) {
revert IssuerValidityTooLong(c.notBefore, c.notAfter);
}
if (c.issuerDnHash != CHAIN_ISSUER_DN_HASH) revert WrongIssuerDn(c.issuerDnHash);
_requireLineage(parent, c);
_requireJurisdiction(c);
_requireAdmissionProof(account, c, bytes32(0), proof, admissionNonce);
certHash = c.certHash;
_write(account, c, c, roles | ROLE_CERTIFICATE_AUTHORITY, version, true);
}
/// @notice The validity ceiling a registered issuer's certificate may not exceed, in this chain's
/// milliseconds: two 366-day years.
/// @dev Expiry is the passive half of an issuer's lifecycle — the touchpoint that proves an issuer is
/// still there without anyone having to act — so a registered issuer always carries a real
/// `NotAfter` and a bounded window. Renewal re-issues under the same registered keys with a version
/// bump rather than extending a certificate in place.
uint64 public constant MAX_ISSUER_VALIDITY_MS = 2 * 366 days * 1000;
/// @notice Pin one stage of a chain-attested end-entity certificate.
/// @dev Three checks, run once per stage: the certificate names the chain's authority key, it carries the
/// chain's issuer name, and its depth pair is exactly that of an end entity — depth 1, directly
/// under the chain, issuing nothing. The depth pair is immutable per version, which is why
/// {identityTreeLeafOf} discriminates record kinds by it rather than by a role bit.
/// @param c The parsed certificate stage.
function _requireChainAttestedEndEntity(FinalCertificate.Parsed memory c) private pure {
if (c.authorityKeyId != CHAIN_AUTHORITY_KEY_ID) revert NotChainAttested(c.authorityKeyId);
if (c.issuerDnHash != CHAIN_ISSUER_DN_HASH) revert WrongIssuerDn(c.issuerDnHash);
if (c.depth != 1 || c.maxDelegationDepth != c.depth) {
revert NotAnEndEntity(c.depth, c.maxDelegationDepth);
}
}
/// @notice Check a nested issuer's lineage to its registered parent.
/// @dev Delegation is governed by DEPTH, not by a boolean: a parent may sign only while
/// `depth < maxDelegationDepth`, a child sits exactly one level down so it cannot skip levels to
/// escape that bound, and its own bound may never widen past its parent's. The child's
/// `AuthorityKeyId` must equal the parent's `SubjectKeyId`, which is the link the chain follows.
///
/// A zero `parent` means the issuer hangs directly under the chain: it must then name the chain's
/// own authority key and sit at depth 1. No parent SIGNS anything here — admission by this chain is
/// the issuance, and lineage is what keeps the delegation bounds honest across it.
/// @param parent The registered parent issuer, or zero for one directly under the chain.
/// @param c The parsed issuer certificate.
function _requireLineage(address parent, FinalCertificate.Parsed memory c) private view {
if (parent == address(0)) {
if (c.authorityKeyId != CHAIN_AUTHORITY_KEY_ID) {
revert NotChainAttested(c.authorityKeyId);
}
if (c.depth != 1) revert WrongDepth(c.depth, 1);
return;
}
Identity storage ca = _identity[parent];
if (!hasRole(parent, ROLE_CERTIFICATE_AUTHORITY)) {
revert IssuerNotACertificateAuthority(parent);
}
// Delegation is governed by depth, not by a boolean. `Depth <
// MaxDelegationDepth` permits signing, and a child sits exactly one
// level down — an issuer cannot skip levels to escape its own bound.
if (ca.depth >= ca.maxDelegationDepth) {
revert IssuerMayNotSign(parent, ca.depth, ca.maxDelegationDepth);
}
if (c.depth != ca.depth + 1) revert WrongDepth(c.depth, ca.depth + 1);
if (c.maxDelegationDepth > ca.maxDelegationDepth) {
revert DelegationWidened(c.maxDelegationDepth, ca.maxDelegationDepth);
}
if (c.authorityKeyId != ca.subjectKeyId) {
revert AuthorityKeyIdMismatch(c.authorityKeyId, ca.subjectKeyId);
}
}
/// @notice Refuse an issuer whose subject name carries no jurisdiction, or one that disagrees with its
/// institution extension.
/// @dev An issuer that answers for itself somewhere is an issuer a verifier has recourse against, so a
/// registered institution must name its jurisdiction and must name it once. Only the trust root is
/// jurisdiction-silent, because the root is the worldwide network rather than a legal entity.
///
/// The rule is a real ISO 3166 alpha-2 `C=` component in the subject name, equal to the
/// `jurisdiction` field of the certificate's institution extension. The name is in canonical
/// comma-separated form, so `C=` matches at the start or immediately after a comma, and the
/// component value is exactly two bytes — a longer one is a different component that happens to
/// start with the same letter.
/// @param c The parsed issuer certificate.
function _requireJurisdiction(FinalCertificate.Parsed memory c) private pure {
bytes memory dn = c.subjectDn;
bytes2 country;
bool found = false;
for (uint256 i = 0; i + 4 <= dn.length; i++) {
if ((i == 0 || dn[i - 1] == ",") && dn[i] == "C" && dn[i + 1] == "=") {
// Exactly two bytes, then end-of-DN or the next component.
if (i + 4 < dn.length && dn[i + 4] != ",") revert JurisdictionMissing();
country = bytes2(bytes.concat(dn[i + 2], dn[i + 3]));
found = true;
break;
}
}
if (!found) revert JurisdictionMissing();
// Institution extension: legalNameLength ‖ legalName ‖
// registrationNoLength ‖ registrationNo ‖ jurisdictionLength ‖
// jurisdiction. The jurisdiction must EQUAL the DN's country.
bytes memory ext = c.institutionExt;
if (ext.length < 6) revert JurisdictionMissing();
uint256 q = 2 + (uint256(uint8(ext[0])) << 8 | uint256(uint8(ext[1])));
if (ext.length < q + 2) revert JurisdictionMissing();
q += 2 + (uint256(uint8(ext[q])) << 8 | uint256(uint8(ext[q + 1])));
if (ext.length < q + 2) revert JurisdictionMissing();
uint256 jLen = uint256(uint8(ext[q])) << 8 | uint256(uint8(ext[q + 1]));
q += 2;
if (jLen != 2 || ext.length < q + 2) revert JurisdictionMismatch();
if (bytes2(bytes.concat(ext[q], ext[q + 1])) != country) revert JurisdictionMismatch();
}
/// @notice Verify the holder's proof of possession over the admission digest.
/// @dev Both live-stage families, in the precompiles, inside this transaction: an ML-DSA-87 signature
/// under the certificate's transaction key and an SLH-DSA-SHAKE-256s signature under its access
/// key. Possession lives in the TRANSACTION rather than in the artifact, so holding a copy of
/// somebody's public certificate proves nothing.
///
/// The keys come out of the certificate being admitted, not out of calldata, which is what makes
/// this a proof rather than a self-signed assertion.
///
/// Burns the gate nonce on the bootstrap path — the quorum path burned it already — so an admission
/// is one-shot in both regimes and a captured proof cannot be replayed into a second registration.
/// @param account The account being admitted; named in the revert so a failure is attributable.
/// @param live The parsed live-stage certificate whose keys verify the proof.
/// @param recoveryCertHash The recovery certificate's handle, bound into the digest; zero for an issuer.
/// @param proof The holder's two signatures.
/// @param admissionNonce The gate-nonce value the digest was built over.
function _requireAdmissionProof(
address account,
FinalCertificate.Parsed memory live,
bytes32 recoveryCertHash,
AdmissionProof calldata proof,
uint64 admissionNonce
) private {
bytes memory message = abi.encodePacked(
keccak256(
abi.encode(
DOMAIN_IDENTITY_ADMISSION,
block.chainid,
address(this),
live.certHash,
recoveryCertHash,
admissionNonce
)
)
);
if (
!FinalChainPrecompiles.verifyMlDsa87(live.transactionKey, message, proof.mlDsaSignature)
|| !FinalChainPrecompiles.verifySlhDsa(live.accessKey, message, proof.slhDsaSignature)
) revert AdmissionProofInvalid(account);
if (_gateNonce[address(this)] == admissionNonce) {
_gateNonce[address(this)] = admissionNonce + 1;
}
}
/**
* @notice Commit one parsed certificate set to storage and project the result.
* @dev The single write path behind both registration entry points, so a wallet record and an issuer
* record cannot diverge in how they are stored. Every authorization, parse and pin has already run;
* what is left is the ordering that keeps the record consistent with its indexes.
*
* A rotation RELEASES the previous certificate's binding rather than revoking it: a superseded
* certificate and a compromised one are different facts, and revocation is the louder of the two.
* The sender binding moves with the transaction key for the same reason — a rotation is the account
* disowning that key, and a gate that still resolved the old sender would honour a retired key.
*
* A certificate already bound to another account is refused, and so is a version that does not
* advance, so neither a replayed registration nor a stolen certificate can take a record over.
* @param account The identity being written. Zero is refused.
* @param live The parsed live-stage certificate; for an issuer, its single certificate.
* @param recovery The parsed recovery-stage certificate; for an issuer, the same value, discarded.
* @param roles The complete capability bitmask to store.
* @param version Monotonic per account. Must exceed the stored value.
* @param isCa Whether this is a certificate authority, which stores no recovery, seal or
* encapsulation material.
*/
function _write(
address account,
FinalCertificate.Parsed memory live,
FinalCertificate.Parsed memory recovery,
uint256 roles,
uint64 version,
bool isCa
) private {
if (account == address(0)) revert UnknownAccount(account);
if (certificateRevoked[live.certHash]) revert CertificateIsRevoked(live.certHash);
address boundTo = accountOfCertificate[live.certHash];
if (boundTo != address(0) && boundTo != account) {
revert CertificateAlreadyBound(live.certHash, boundTo);
}
Identity storage id = _identity[account];
if (!id.registered) {
_accounts.push(account);
id.registered = true;
} else {
if (version <= id.version) revert VersionNotNewer(id.version, version);
if (id.revoked) revert CertificateIsRevoked(id.certHash);
// A rotation releases the previous certificate's binding. It is NOT
// revoked — a superseded certificate and a compromised one are
// different facts and revocation is the louder of the two.
if (id.certHash != live.certHash) delete accountOfCertificate[id.certHash];
}
id.certHash = live.certHash;
id.recoveryCertHash = recovery.certHash;
id.serial = live.serial;
id.subjectKeyId = live.subjectKeyId;
id.roles = roles;
id.depth = live.depth;
id.maxDelegationDepth = live.maxDelegationDepth;
id.notBefore = live.notBefore;
id.notAfter = live.notAfter;
id.version = version;
// The sender binding moves with the transaction key. The old sender is
// released rather than kept: a rotation is the account disowning that
// key, and a gate that still resolved it would honour a retired key.
address sender = senderFor(live.transactionKey);
address senderBoundTo = accountOfSender[sender];
if (senderBoundTo != address(0) && senderBoundTo != account) {
revert SenderAlreadyBound(sender, senderBoundTo);
}
if (_activeTransactionKey[account].length != 0) {
address previousSender = senderFor(_activeTransactionKey[account]);
if (previousSender != sender) delete accountOfSender[previousSender];
}
accountOfSender[sender] = account;
_activeTransactionKey[account] = live.transactionKey;
_activeAccessKey[account] = live.accessKey;
// A CA has no recovery pair; the two active slots are all it has.
_recoveryTransactionKey[account] = isCa ? bytes("") : recovery.transactionKey;
_recoveryAccessKey[account] = isCa ? bytes("") : recovery.accessKey;
// Cleared on a rotation to a certificate without one, for the same
// reason the encapsulation pair is: a stale seal surviving a rotation
// would let a retired key keep co-signing execution.
_activeSealKey[account] = isCa ? bytes("") : live.sealKey;
// The encapsulation pair, validated before it is stored.
//
// **The registry is where a sender looks up "encapsulate to this
// party", so a malformed key here is not a bad record — it is an
// account nobody can seal an intent to.** The discovery would happen at
// the first attempt, and on the hybrid path it would happen as a pair
// silently reduced to one family, which is identical on the wire. The
// precompiles make it a refusal at registration instead.
//
// Neither is a re-implementation of the KEM: `0x0203` runs FIPS 203
// §7.2's own encapsulation-key check and `0x0207` runs the structural
// check HQC-5's encoding admits. Encapsulation is a sender operation
// and decapsulation needs the secret key, so nothing more belongs here.
//
// A CA is sealed to by nobody and carries no encapsulation stage, so
// its slots are cleared rather than checked.
_storeKemPair(account, isCa, live.kemMlKem, live.kemHqc, true);
_storeKemPair(account, isCa, recovery.kemMlKem, recovery.kemHqc, false);
accountOfCertificate[live.certHash] = account;
emit IdentityRegistered(account, live.certHash, roles, version);
// Same-tx: a registration or rotation is visible to every execution
// chain's admission set the moment it is visible here.
_projectIdentity(account);
}
/**
* @notice Store one stage's encapsulation pair, or clear it.
* @dev Empty is legitimate and is not the same as absent-and-wrong: a certificate authority has no
* encapsulation stage, and a certificate may be issued without one. The parser has already refused
* the half-populated case, so by here the pair is both or neither.
*
* Cleared rather than left alone on a rotation to an empty pair. A stale key surviving a rotation is
* a sender encapsulating to a credential the account has disowned, and the message then never
* decrypts — the failure mode with no error attached, and the one this pairing exists to avoid.
* @param account The identity being written.
* @param isCa Whether the record is a certificate authority, which carries no encapsulation stage.
* @param mlKem The stage's ML-KEM-1024 key, or empty.
* @param hqc The stage's HQC-5 key, or empty.
* @param isLive Whether this is the live stage; false selects the recovery slots.
*/
function _storeKemPair(address account, bool isCa, bytes memory mlKem, bytes memory hqc, bool isLive)
private
{
if (isCa || mlKem.length == 0) {
delete (isLive ? _activeKemMlKem : _recoveryKemMlKem)[account];
delete (isLive ? _activeKemHqc : _recoveryKemHqc)[account];
return;
}
if (!FinalChainPrecompiles.isWellFormedMlKem1024(mlKem)) {
revert MalformedEncapsulationKey(account, FinalCertificate.ALG_ML_KEM_1024);
}
if (!FinalChainPrecompiles.isWellFormedHqc5(hqc)) {
revert MalformedEncapsulationKey(account, FinalCertificate.ALG_HQC_5);
}
if (isLive) {
_activeKemMlKem[account] = mlKem;
_activeKemHqc[account] = hqc;
} else {
_recoveryKemMlKem[account] = mlKem;
_recoveryKemHqc[account] = hqc;
}
}
/// @notice Grant or withdraw capabilities without rotating keys.
/// @dev Separate from registration because the two have different cadences: a role changes when a
/// service's job changes, a key changes when it is compromised or aged out. Folding them together
/// would force a key rotation to express a role change, which is the more dangerous of the two
/// operations doing the work of the safer one.
/// @param account Must already be registered and not revoked.
/// @param roles The complete new capability bitmask; it replaces the old one rather than merging.
/// @param anchorBlock The block the registrars read the roster at.
/// @param approvals The sealed registrar quorum. Empty while bootstrap is open.
function setRoles(
address account,
uint256 roles,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireMembershipAuthority(
DOMAIN_SET_ROLES, keccak256(abi.encode(account, roles)), anchorBlock, approvals
);
Identity storage id = _identity[account];
if (!id.registered) revert UnknownAccount(account);
if (id.revoked) revert CertificateIsRevoked(id.certHash);
uint256 previous = id.roles;
id.roles = roles;
_requireRegistrarQuorumReachable();
emit IdentityRolesChanged(account, previous, roles);
// Roles are not in the tree-8 leaf, so this rewrites the same value —
// kept anyway so "every identity mutation projects" has no exceptions
// to remember.
_projectIdentity(account);
}
/// @notice Refuse a mutation that would leave the registrar quorum unreachable.
/// @dev Once bootstrap is sealed, that is the one change nothing could ever undo: a registry whose
/// threshold exceeds its sealable membership can never be written to again, including to fix
/// itself. Checked AFTER the write so the count reflects the mutation being attempted.
function _requireRegistrarQuorumReachable() private view {
if (!bootstrapSealed) return;
uint256 sealable = sealableMemberCount(ROLE_REGISTRAR);
if (sealable < registrarThreshold) {
revert RegistrarThresholdUnreachable(sealable, registrarThreshold);
}
}
/// @notice Revoke an identity and its certificate. Irreversible.
/// @dev Clears the roles as well as setting the flag. Both are checked everywhere, but leaving a revoked
/// record carrying roles invites a future reader that checks only one of them. The fingerprints of
/// the named LMS slots are recorded into the revocation log after the flag lands, so the log's own
/// permanence gate sees the transition it requires.
/// @param account The identity to retire.
/// @param chainIds The chains whose LMS-key slots this account holds. The registrars supply the list and
/// the approval digest binds it, because a mapping cannot enumerate its own keys. A chain with no
/// slot is skipped, and a fingerprint an incomplete list missed stays permanently recordable
/// through the revocation log's permissionless door, since a revoked account never regains
/// standing.
/// @param anchorBlock The block the registrars read the roster at.
/// @param approvals The sealed registrar quorum. Empty while bootstrap is open.
function revoke(
address account,
uint64[] calldata chainIds,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireMembershipAuthority(
DOMAIN_REVOKE, keccak256(abi.encode(account, chainIds)), anchorBlock, approvals
);
Identity storage id = _identity[account];
if (!id.registered) revert UnknownAccount(account);
id.revoked = true;
id.roles = 0;
certificateRevoked[id.certHash] = true;
_requireRegistrarQuorumReachable();
emit IdentityRevoked(account, id.certHash);
// AFTER the flag lands, so the log's own gate sees the permanent
// transition it requires.
for (uint256 i = 0; i < chainIds.length; i++) {
LmsKey storage k = _lmsKey[account][chainIds[i]];
if (k.registered) _recordRevokedSigner(lmsSignerId(k.keyId, k.height, k.root));
}
_projectIdentity(account);
}
/**
* @notice Root-plane GLOBAL certificate revocation, by `certHash`.
* @dev The half of the revocation lane that gates registration and covers break-glass: any certificate —
* registered here, issued off chain, or never seen — can be killed by handle under the registrar
* quorum, because the handle is all a break-glass caller may have.
*
* When the handle is a registered identity's CURRENT certificate the identity falls with it: flag,
* roles cleared, same-transaction projection. So revoking by handle is never weaker than {revoke};
* it only skips the LMS-slot enumeration, and those fingerprints stay permanently recordable
* through the revocation log's own permissionless door.
* @param certHash The certificate to revoke. Need not correspond to any record.
* @param anchorBlock The block the registrars read the roster at.
* @param approvals The sealed registrar quorum. Empty while bootstrap is open.
*/
function revokeCertificate(
bytes32 certHash,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireMembershipAuthority(
DOMAIN_REVOKE_CERTIFICATE, keccak256(abi.encode(certHash)), anchorBlock, approvals
);
certificateRevoked[certHash] = true;
address bound = accountOfCertificate[certHash];
if (bound != address(0)) {
Identity storage id = _identity[bound];
if (!id.revoked) {
id.revoked = true;
id.roles = 0;
_requireRegistrarQuorumReachable();
emit IdentityRevoked(bound, certHash);
_projectIdentity(bound);
}
}
emit CertificateRevoked(certHash, address(0));
}
/**
* @notice The issuing identity's half of the revocation lane: a registered issuer revokes a certificate
* it signed off chain, by `certHash`.
* @dev This records WHO revoked, and a verifier honours the entry only when the recorded revoker is the
* certificate's own issuer — which the verifier knows, because it holds the certificate. It
* deliberately does NOT set the global `certificateRevoked` flag: that flag gates registration, and
* letting any registered issuer set it for an arbitrary handle would be a griefing lane over other
* people's certificates.
*
* Anyone may SUBMIT. Authority is the two signatures — the issuer's registered cert-signing keys
* over a digest binding this registry, this chain, the handle and the issuer's own gate nonce, both
* verified in the precompiles inside this transaction. The keys come from storage, so a submitter
* cannot supply the pair its own signatures verify under.
*
* One-way: the first revoker of a handle is recorded and a second write is refused, because
* "revoked twice by two parties" is two facts where this lane models one.
* @param issuer The registered certificate authority making the statement.
* @param certHash The certificate being revoked.
* @param proof The issuer's own ML-DSA-87 and SLH-DSA-SHAKE-256s signatures over the revocation digest.
*/
function revokeIssuedCertificate(
address issuer,
bytes32 certHash,
AdmissionProof calldata proof
) external {
if (!hasRole(issuer, ROLE_CERTIFICATE_AUTHORITY)) {
revert IssuerNotACertificateAuthority(issuer);
}
if (certificateRevokedBy[certHash] != address(0)) revert CertificateIsRevoked(certHash);
uint64 nonce = _gateNonce[issuer];
_gateNonce[issuer] = nonce + 1;
bytes memory message = abi.encodePacked(
keccak256(
abi.encode(
DOMAIN_ISSUER_CERT_REVOCATION,
block.chainid,
address(this),
issuer,
certHash,
nonce
)
)
);
if (
!FinalChainPrecompiles.verifyMlDsa87(
_activeTransactionKey[issuer], message, proof.mlDsaSignature
)
|| !FinalChainPrecompiles.verifySlhDsa(
_activeAccessKey[issuer], message, proof.slhDsaSignature
)
) revert AdmissionProofInvalid(issuer);
certificateRevokedBy[certHash] = issuer;
emit CertificateRevoked(certHash, issuer);
}
// ---------------------------------------------------------------- views
/// @notice The full identity record.
/// @dev Returns the zero struct for an address no record claims, so `registered` is the field to branch
/// on rather than any of the hashes.
/// @param account The identity to read.
/// @return The stored record, copied to memory.
function identityOf(address account) external view returns (Identity memory) {
return _identity[account];
}
/// @notice The live transaction key, ML-DSA-87: what a quorum vote is verified against.
/// @dev Read from STORAGE by every quorum on this chain, never from a caller's argument — a key supplied
/// as calldata proves nothing, because anyone holding a keypair can sign under it.
/// @param account The identity to read.
/// @return The raw public key, or empty when the account holds none.
function activeTransactionKeyOf(address account) external view returns (bytes memory) {
return _activeTransactionKey[account];
}
/// @notice The live access key, SLH-DSA-SHAKE-256s: identity, rotation, and guardianship.
/// @dev A different hardness assumption from the transaction key, so a lattice break leaves the key that
/// governs identity standing intact.
/// @param account The identity to read.
/// @return The raw public key, or empty when the account holds none.
function activeAccessKeyOf(address account) external view returns (bytes memory) {
return _activeAccessKey[account];
}
/// @notice The seal key, SLH-DSA-SHAKE-256s: what `FinalPqQuorum` verifies an approval's seal against.
/// @dev A service's second hash-based key, distinct from its access key, so a quorum decision carries
/// one signature from each hardness assumption. Empty when the identity carries no seal, in which
/// case it cannot take part in a sealed quorum at all — which is why {sealableMemberCount} counts
/// this rather than counting role bits.
/// @param account The identity to read.
/// @return The raw public key, or empty when the account holds no seal.
function activeSealKeyOf(address account) external view returns (bytes memory) {
return _activeSealKey[account];
}
/// @notice The recovery-stage transaction key, ML-DSA-87.
/// @dev Authorizes rotating this account's own credentials and nothing else — acting as a guardian is an
/// ordinary action for an account and uses the live keys. Empty for a certificate authority.
/// @param account The identity to read.
/// @return The raw public key, or empty when the account holds none.
function recoveryTransactionKeyOf(address account) external view returns (bytes memory) {
return _recoveryTransactionKey[account];
}
/// @notice The recovery-stage access key, SLH-DSA-SHAKE-256s.
/// @dev The other half of the pre-committed recovery stage. Empty for a certificate authority, which has
/// no recovery stage at all.
/// @param account The identity to read.
/// @return The raw public key, or empty when the account holds none.
function recoveryAccessKeyOf(address account) external view returns (bytes memory) {
return _recoveryAccessKey[account];
}
/// @notice The four signing-key commitments, in the order tree 1's leaf wants them.
/// @dev keccak, not SHA3: these feed `FinalWalletFactory.accountStateLeafHash`, which every execution
/// chain verifies with, and that one hashes with keccak. An account missing a slot commits to the
/// hash of the empty string rather than reverting, so the leaf stays buildable for a certificate
/// authority, which holds no recovery pair.
/// @param account The identity to commit to.
/// @return liveAccess Commitment to the live access key.
/// @return liveTransaction Commitment to the live transaction key.
/// @return recoveryAccess Commitment to the recovery access key.
/// @return recoveryTransaction Commitment to the recovery transaction key.
function keyCommitments(address account)
external
view
returns (
bytes32 liveAccess,
bytes32 liveTransaction,
bytes32 recoveryAccess,
bytes32 recoveryTransaction
)
{
liveAccess = keccak256(_activeAccessKey[account]);
liveTransaction = keccak256(_activeTransactionKey[account]);
recoveryAccess = keccak256(_recoveryAccessKey[account]);
recoveryTransaction = keccak256(_recoveryTransactionKey[account]);
}
/**
* @notice The tree-8 leaf `account` currently earns: the execution chains' identity leaf while the
* identity stands, zero once it does not.
* @dev The leaf VALUE is `keccak256(DOMAIN_IDENTITY_LEAF ‖ serial ‖ keysHash)` — byte-identical to
* `IdentityRootModule.identityLeafHash`, which is also the `certHash` inside a wallet's address
* derivation — with `keysHash` folded exactly as the certificate issuer folds it:
* `keccak256(activeAccess ‖ activeTransaction ‖ recoveryAccess ‖ recoveryTransaction ‖ activeKem ‖
* recoveryKem)`, six commitment words packed in slot order. The issuing tooling and this function
* are pinned against each other by test over the premined certificate fixtures, because a wallet
* whose address was derived from a different fold is a wallet no chain can admit.
*
* Zero — the empty slot's own value, unprovable as a leaf because no certificate hashes to it — for
* anything that must not admit a wallet creation: a revoked identity, one outside its validity
* window, and any certificate authority. The authority exclusion is STRUCTURAL rather than a role
* read: an end entity has `depth == maxDelegationDepth` because it issues nothing, an authority
* never does, and that pair is immutable per version where `roles` is not.
*
* Lives here rather than on the state-trees contract that consumes it because every input is this
* contract's storage, and the trees contract has no bytecode headroom to spare.
* @param account The identity to project. Reverts for an account with no record at all.
* @return The tree-8 leaf value, or zero while the identity does not stand.
*/
function identityTreeLeafOf(address account) external view returns (bytes32) {
Identity storage id = _identity[account];
if (!id.registered) revert UnknownAccount(account);
if (id.revoked || !_withinValidity(id)) return bytes32(0);
if (id.depth != id.maxDelegationDepth) {
// An ISSUER exists in tree 8 under its own domain, so its record is stapleable for offline
// licence verification while the distinct domain keeps it out of wallet admission. `certHash`
// suffices — it covers the whole TBS and the verifier holds the certificate — `version` makes
// supersession move the leaf, and the third word RESERVES the issuer's own certificate-tree
// anchor, zero until one is wired. Zero-on-revoke above is load-bearing for both record kinds:
// a fresh staple is an unrevoked statement.
return keccak256(
abi.encodePacked(DOMAIN_ISSUER_LEAF, id.certHash, uint64(id.version), bytes32(0))
);
}
bytes32 liveKem = keccak256(
abi.encodePacked(DOMAIN_KEM_BUNDLE, _activeKemMlKem[account], _activeKemHqc[account]));
bytes32 recoveryKem = keccak256(
abi.encodePacked(DOMAIN_KEM_BUNDLE, _recoveryKemMlKem[account], _recoveryKemHqc[account]));
bytes32 keysHash = keccak256(
abi.encodePacked(
keccak256(_activeAccessKey[account]),
keccak256(_activeTransactionKey[account]),
keccak256(_recoveryAccessKey[account]),
keccak256(_recoveryTransactionKey[account]),
liveKem,
recoveryKem
)
);
return keccak256(abi.encodePacked(DOMAIN_IDENTITY_LEAF, id.serial, keysHash));
}
/// @notice Per-stage encapsulation commitments, in the order the account-state leaf wants them.
/// @dev One word per STAGE, folded over both of that stage's encapsulation public keys under
/// `DOMAIN_KEM_BUNDLE`. The pair is the unit — an account holds both keys or neither — so
/// committing to them separately would model a state the protocol does not recognise, and every
/// downstream record would carry two words where one says the same thing.
///
/// An account whose certificate carries no encapsulation stage folds the empty string here rather
/// than reverting: the projection into the state trees must keep succeeding for it, and a leaf that
/// cannot be built is a party that cannot be revoked.
/// @param account The identity to commit to.
/// @return liveKem The live stage's encapsulation commitment.
/// @return recoveryKem The recovery stage's encapsulation commitment.
function kemCommitments(address account)
external
view
returns (bytes32 liveKem, bytes32 recoveryKem)
{
liveKem = keccak256(
abi.encodePacked(DOMAIN_KEM_BUNDLE, _activeKemMlKem[account], _activeKemHqc[account]));
recoveryKem = keccak256(
abi.encodePacked(DOMAIN_KEM_BUNDLE, _recoveryKemMlKem[account], _recoveryKemHqc[account]));
}
/// @notice The live-stage encapsulation keys themselves, for a party composing a sealed message.
/// @dev Returns both halves of the pair together because the pair is the unit: encapsulating to one
/// family alone is indistinguishable on the wire from a hybrid, and silently dropping the hedge is
/// the failure this pairing exists to prevent. Empty for an account with no encapsulation stage.
/// @param account The party to encapsulate to.
/// @return activeMlKem The lattice half, ML-KEM-1024.
/// @return activeHqc The code-based half, HQC-5.
function kemKeysOf(address account)
external
view
returns (bytes memory activeMlKem, bytes memory activeHqc)
{
return (_activeKemMlKem[account], _activeKemHqc[account]);
}
// ------------------------------------------------------------- senders
/**
* @notice The sender address a transaction key produces on this chain.
* @dev `keccak256(uint8(4) ‖ publicKey)[12:]` — byte-identical to what the node derives from a
* post-quantum transaction envelope and to the backend's own derivation. The leading algorithm byte
* is what domain-separates it, so a key of another family can never derive the same address.
*
* Pure, so a client can compute the address from a certificate before the identity is registered —
* which is what lets an admission transaction be funded and submitted from the very sender it is
* about to bind.
* @param transactionKey The raw ML-DSA-87 public key.
* @return The sender address that key signs from.
*/
function senderFor(bytes memory transactionKey) public pure returns (address) {
return address(uint160(uint256(keccak256(abi.encodePacked(ENVELOPE_ALG_ML_DSA_87, transactionKey)))));
}
/// @notice The sender `account`'s transactions arrive from.
/// @dev The forward direction of {accountOfSender}, derived rather than stored, so it cannot disagree
/// with the transaction key on record.
/// @param account The identity to resolve.
/// @return The derived sender, or zero for an account with no transaction key on record.
function senderOf(address account) external view returns (address) {
bytes storage key = _activeTransactionKey[account];
if (key.length == 0) return address(0);
return senderFor(key);
}
/// @notice {hasRole} for a `msg.sender`: resolves the sender to its identity first.
/// @dev The form every `msg.sender` gate on this chain uses. A sender is derived from a transaction key
/// and holds no authority itself, so asking it directly would be asking the wrong address. False for
/// a sender no identity claims.
/// @param sender The address a transaction arrived from.
/// @param roleMask The capability required.
/// @return Whether the identity behind that sender stands and carries the whole mask.
function senderHasRole(address sender, uint256 roleMask) external view returns (bool) {
address account = accountOfSender[sender];
return account != address(0) && hasRole(account, roleMask);
}
/// @notice How many accounts carrying `roleMask` also hold a seal key — the members that can take part
/// in a sealed quorum.
/// @dev The count every membership threshold is checked against, because membership approvals are the
/// hybrid class and a member with no seal can never contribute one. A certificate authority
/// carrying `ROLE_REGISTRAR` is registered from a certificate with no seal slot, so it is counted
/// out here rather than being discovered at the first quorum that fails to reach its threshold.
/// @param roleMask The capability the quorum is over.
/// @return sealable How many standing accounts carry the mask and hold a seal key.
function sealableMemberCount(uint256 roleMask) public view returns (uint256 sealable) {
uint256 n = _accounts.length;
for (uint256 i = 0; i < n; i++) {
address a = _accounts[i];
if (hasRole(a, roleMask) && _activeSealKey[a].length != 0) sealable++;
}
}
/// @notice Number of registered accounts.
/// @dev Never decreases: revocation clears a record's roles and sets its flag but leaves it in the list,
/// so an index handed out once keeps pointing at the same account for good.
/// @return How many accounts have ever been registered.
function accountCount() external view returns (uint256) {
return _accounts.length;
}
/// @notice Registered account by index, in registration order.
/// @dev Reverts on an out-of-range index rather than answering zero, so a caller paging the list cannot
/// mistake the end of it for a hole in the middle.
/// @param index Position in the registration-ordered list, below {accountCount}.
/// @return The account at that position.
function accountAt(uint256 index) external view returns (address) {
return _accounts[index];
}
/// @notice Every account carrying every bit in `roleMask`.
/// @dev A view, so the linear scan over the account list costs nothing to a caller reading off chain.
/// Callers that need a roster inside a transaction pass the member list explicitly instead — see
/// `FinalPqQuorum`, which takes signers rather than searching for them, so a quorum's cost does not
/// grow with the size of the registry.
/// @param roleMask The capability to filter on.
/// @return found The matching accounts, in registration order.
function accountsWithRole(uint256 roleMask) external view returns (address[] memory found) {
uint256 n = _accounts.length;
address[] memory buf = new address[](n);
uint256 count;
for (uint256 i = 0; i < n; i++) {
if (hasRole(_accounts[i], roleMask)) {
buf[count++] = _accounts[i];
}
}
found = new address[](count);
for (uint256 i = 0; i < count; i++) {
found[i] = buf[i];
}
}
/**
* @notice How many accounts could satisfy a quorum for `roleMask` right now.
* @dev The number a threshold has to be reachable against. A threshold above it is not a strict quorum,
* it is a quorum that cannot be met — and the way that presents is an operation reverting forever
* with nothing naming the roster as the cause. Counts standing alone; use {sealableMemberCount} for
* a quorum that also needs a seal.
* @param roleMask The capability the quorum is over.
* @return live How many standing accounts carry the whole mask.
*/
function liveMemberCount(uint256 roleMask) public view returns (uint256 live) {
uint256 n = _accounts.length;
for (uint256 i = 0; i < n; i++) {
if (hasRole(_accounts[i], roleMask)) live++;
}
}
/**
* @notice Whether `account` currently carries every bit in `roleMask`.
* @dev Every gate in this system asks this one question, so every gate gets the same answer: registered,
* not revoked, inside its validity window, and holding the capability. A caller that checked only
* the role bit would accept an expired certificate.
*
* `roleMask == 0` is false. A zero mask asks nothing and must not read as "yes" — that is the shape
* of an uninitialised configuration variable, and the one reading it must not be a universal pass.
*
* Every bit in the mask must be present, so a mask naming two capabilities asks for both rather than
* either.
* @param account The account to test.
* @param roleMask One or more `ROLE_*` bits, OR-ed together.
* @return Whether the account stands and carries the whole mask.
*/
function hasRole(address account, uint256 roleMask) public view returns (bool) {
if (roleMask == 0) return false;
Identity storage id = _identity[account];
if (!id.registered || id.revoked) return false;
if (id.roles & roleMask != roleMask) return false;
return _withinValidity(id);
}
/// @notice Whether `account` is registered, unrevoked and in date, regardless of capability.
/// @dev The standing half of {hasRole}, for callers that care that a party is honoured at all rather
/// than that it holds a particular capability. {lmsSignerIsLive} asks this rather than spelling the
/// three conditions out a second time, because a second spelling is how two answers drift apart.
/// @param account The account to test. An address no record claims answers false.
/// @return Whether the identity currently stands.
function isActive(address account) public view returns (bool) {
Identity storage id = _identity[account];
return id.registered && !id.revoked && _withinValidity(id);
}
/// @notice Whether a record's certificate is inside its validity window right now.
/// @dev Both bounds are milliseconds on this chain's clock and both are optional: a zero `notBefore`
/// means valid from issuance and a zero `notAfter` means never expires, which the certificate
/// schema allows and personal identity certificates use. The upper bound is exclusive, so a
/// certificate stops being honoured on the millisecond it names rather than after it.
/// @param id The record to test, taken as a storage pointer so no copy of a multi-word struct is made.
/// @return Whether the window admits the current block time.
function _withinValidity(Identity storage id) private view returns (bool) {
if (id.notBefore != 0 && FinalChainTime.nowMs() < id.notBefore) return false;
if (id.notAfter != 0 && FinalChainTime.nowMs() >= id.notAfter) return false;
return true;
}
// ------------------------------------------------------------------ sweep
/// @inheritdoc FinalSweep
/// @dev The registry's own configuration gate, in the `msg.sender` form a no-argument seam can express:
/// the bootstrap admin alone while the window is open, a live registrar afterwards.
///
/// The rest of the state plane inherits this rule from `FinalPlaneSweep`, which reads it off a
/// registry pointer. This contract answers it from its own storage because it IS that registry, and
/// importing the shared mixin here would make this file import a file that imports it back.
///
/// The sealed half of the gate is a K-of-N over `ROLE_REGISTRAR` whose approvals arrive in calldata,
/// which `sweepAsset`'s shared signature has no room for; what survives is membership in that same
/// roster. The narrowing is safe because the other two gates hold regardless: a sweep moves surplus
/// only, this contract owes nothing, so there is nothing behind the line to reach — and the
/// destination is not the caller's to invent.
function _requireSweepAuthority() internal view override {
if (!bootstrapSealed && msg.sender == bootstrapAdmin) return;
if (hasRole(msg.sender, ROLE_REGISTRAR)) return;
revert SweepUnauthorized(msg.sender);
}
/// @inheritdoc FinalSweep
/// @dev The bootstrap admin, and the proven authority that called. The first of those is zero once the
/// window is sealed, which `FinalSweep` refuses as a destination, so a sealed registry can only
/// sweep to the registrar that authorised the sweep.
function _sweepDestinations() internal view override returns (address, address) {
return (bootstrapAdmin, msg.sender);
}
/// @dev Nothing is reserved because nothing is owed: the registry holds
/// certificates and role bits, has no payable entrypoint and no custody
/// line. Anything it carries arrived by accident.
}
contracts/finalchain/FinalPlaneSweep.sol
// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may inherit this mixin from a contract deployed as
// part of a Final DeFi Protocol state plane, and may operate the asset-rescue
// surface it completes.
// 2. Integrators, indexers and operators may call the resulting rescue surface
// where the state plane's own configuration authority permits it, and may
// read the authority and destination answers it gives.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
// deployment of a Fork of this mixin or a competing state-plane rescue
// authority without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
import {FinalSweep} from "../utils/FinalSweep.sol";
import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";
/**
* @title Final Plane Sweep
* @notice The authority and destination halves of the shared asset-rescue surface, answered once for every
* contract of the protocol's own state plane.
* @dev `FinalSweep` gives every contract that can end up holding a stray asset one rescue surface and leaves two
* questions for the inheritor: who may call it, and where the value may go. Every contract on this state
* plane answers both the same way — the registry's bootstrap admin alone while that window is open, and the
* sealed registrar authority afterwards — and stating that once per contract would be one chance per
* contract to state it differently. An inheritor of this mixin answers a single question instead: which
* registry is mine.
*
* **The authority is the plane's own configuration gate, narrowed to what a fixed signature can carry.**
* The sealed half of that gate is a K-of-N over the registrar role, and its approvals arrive in CALLDATA.
* The rescue entrypoint's signature is shared across every contract on the plane and cannot grow a
* per-contract quorum argument, so what survives into a no-argument `internal view` is MEMBERSHIP: the
* bootstrap admin while the window is open, and afterwards any account the registry currently attests as a
* live registrar.
*
* That is a narrowing — one registrar rather than K of them — and it is deliberate rather than overlooked.
* Two other gates make it safe, and a registrar can widen neither:
*
* - a rescue moves SURPLUS only. Every contract that owes something declares the debt as a reservation,
* and no key reaches behind that line: an intent log's bonds, a billing plane's prepaid credit and a gas
* well's entire float are all unreachable by this surface however it is called.
* - the destination is not the caller's to invent.
*
* A registrar already configures tree writers, thresholds and consumers. An account that can decide who may
* write the account tree is not meaningfully restrained from moving a stray token, so demanding a quorum
* ceremony for the rescue lane would buy nothing and would instead guarantee the lane is never used when it
* is needed. No new role and no new authority pointer is introduced here: the registrar role is the
* registry's own, and membership in it moves in the registry rather than in any contract that reads it.
*
* **The destination is the authority that ordered the rescue.** This state plane has no treasury pointer,
* and adding one would be exactly the new authority this mixin is not allowed to invent — a per-contract
* treasury setter would need its own quorum action on every contract of the plane, to configure something
* the plane has never needed. So the two legitimate destinations are the two addresses already proven: the
* bootstrap admin, and the caller.
*
* The caller is not a free parameter. The rescue entrypoint proves the authority BEFORE it resolves
* destinations, so by the time this mixin is asked, the sender is already either the bootstrap admin or a
* live registrar. Every service on this chain is a Final Wallet with a registered identity and no EOA
* signing key, so the value lands on an account the chain itself attests to. What the gate rules out is the
* thing worth ruling out: a rescue paying an address the plane knows nothing about.
*
* Once the bootstrap window is sealed the admin address is zero, and the base contract refuses a zero
* destination, so the pair collapses to the caller alone — one legitimate destination, which is the case the
* base contract already handles.
*/
abstract contract FinalPlaneSweep is FinalSweep {
/// @notice The membership registry an inheriting contract's configuration gate reads.
/// @dev The one question this mixin leaves open, and the only line an inheritor has to supply. It exists
/// because some contracts of the plane hold the registry directly while others reach it through another
/// contract they already hold, and both must resolve to the SAME registry their configuration answers
/// to — a rescue authority read from a different source would be a second authority in disguise.
/// @return The registry whose bootstrap admin and registrar membership decide this contract's rescue
/// authority and destinations.
function _sweepRegistry() internal view virtual returns (FinalIdentityRegistry);
/// @notice The plane's configuration gate, in the caller-only form the shared rescue surface can express.
/// @dev Two accepting branches, checked in order: the bootstrap admin while the window is open, and any live
/// registrar once it is sealed. The bootstrap branch is guarded on the seal as well as on the address,
/// so it closes the moment the window does rather than depending on the admin field being cleared.
/// Membership is read live from the registry on every call, so revoking a registrar there revokes this
/// authority everywhere on the plane at once. Anything else reverts.
function _requireSweepAuthority() internal view virtual override {
FinalIdentityRegistry reg = _sweepRegistry();
if (!reg.bootstrapSealed() && msg.sender == reg.bootstrapAdmin()) return;
if (reg.hasRole(msg.sender, reg.ROLE_REGISTRAR())) return;
revert SweepUnauthorized(msg.sender);
}
/// @notice The two addresses a rescue on this plane may pay.
/// @dev The bootstrap admin, and the authority that called — which the base contract has already proven by
/// the time this is read, so the second is never an address of the caller's choosing. After the seal the
/// admin half is the zero address, which the base contract refuses as a destination, leaving the proven
/// caller as the single legitimate target.
/// @return The bootstrap admin, and the proven caller.
function _sweepDestinations() internal view virtual override returns (address, address) {
return (_sweepRegistry().bootstrapAdmin(), msg.sender);
}
}
contracts/finalchain/FinalPqQuorum.sol
// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy and operate this quorum as part of a
// Final DeFi Protocol chain, and may inherit it to gate an action behind a
// post-quantum K-of-N.
// 2. Integrators, auditors, and node operators may read its membership and
// thresholds and independently re-verify any approval it recorded, as part
// of their integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
// deployment of a Fork of this quorum or a competing identity or
// authorization plane derived from it without permission prior to the
// Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";
/**
* @title FinalPqQuorum
* @notice K-of-N approval where the signatures are post-quantum and the chain
* is what checks them.
*
* @dev This library is the reason Final Chain exists in this design.
*
* `FinalBackend/src/pq/credential.js` carries a rule it had to enforce in code
* because nothing else could: **a surface whose signature is verified on chain
* cannot be PQ.** A co-signer approval reaching `FinalRootAuthority` is checked
* by ECDSA/ERC-1271 in Solidity, so a PQ co-signer would produce approvals the
* contract cannot read, and the quorum would stop reaching threshold with
* nothing in any log naming the cause. `PQ_SURFACE` and `assertBackendVerified`
* exist to keep anyone from crossing that line by accident.
*
* Here the line is gone. The precompiles verify ML-DSA-87 and
* SLH-DSA-SHAKE-256s natively, so a quorum can be PQ *and* on chain, and
* "the backend says these four signatures verified" becomes "these four
* signatures verify, and any node re-derives that independently".
*
* ## Three rules, each closing a specific hole
*
* 1. **Keys come from the registry, never from calldata.** A key passed as an
* argument proves nothing — anyone with a keypair can sign under it. This is
* the difference between a 4-of-5 quorum and a 1-of-1 held by whoever built
* the transaction.
*
* 2. **Signers strictly ascending.** One comparison per entry rejects duplicates
* outright, so a single member cannot supply four approvals and satisfy a
* threshold of four. The alternative — an O(n²) seen-check — is the same
* guarantee with more ways to get it wrong.
*
* 3. **The digest binds chain id and verifying contract.** Without both, an
* approval collected for one contract is replayable against another with the
* same payload shape, and an approval from the test chain is replayable on
* the production one. These co-signers hold one key across environments.
*
* ## Which algorithm
*
* The stack splits its keys by hardness assumption, not by convenience:
* ML-DSA-87 (lattice) signs transactions, SLH-DSA-SHAKE-256s (hash-based) signs
* identity. Two families, so one cryptanalytic result cannot take both.
*
* So an action inherits the class of what it authorizes. Advancing a state root
* is operational and high-cadence: transaction class. Registering or revoking
* an identity is the thing the access class exists for. `ALG_ANY` is available
* and should be used sparingly — accepting either means a break in one family
* takes the quorum.
*
* An action that authorizes EXECUTION takes both: the ML-DSA-87 approval and a
* `seal`, an SLH-DSA-SHAKE-256s signature over the same digest by the member's
* `activeSeal` key. Neither family alone can then move funds, and the seal key
* is its own slot — never the access key — so the process that seals cannot
* also rotate the identity it seals for.
*
* Every digest binds an `anchorBlock`: the block at which the members read
* tree 1 to decide who is in the round. Binding it means every approval in a
* round was made against ONE roster view, and the window in `require_` means a
* view older than `ANCHOR_WINDOW` blocks is refused rather than honoured.
*
* The practical cost is worth stating: an SLH-DSA signature is 29,792 bytes, so
* a 4-of-5 access-class quorum is ~119 KB of calldata. That is affordable here
* only because this is our own chain. Do not carry this pattern to a chain
* where it is not.
*/
library FinalPqQuorum {
/// @notice ML-DSA-87 — FIPS 204. Algorithm ids are the FIPS numbers: the
/// same ids `FinalCertificate` and the backend registry use, and the numbers
/// the precompile addresses end in (`0x0204`).
uint8 internal constant ALG_ML_DSA_87 = 4;
/// @notice SLH-DSA-SHAKE-256s — FIPS 205 (`0x0205`).
uint8 internal constant ALG_SLH_DSA_SHAKE_256S = 5;
/// @notice Either scheme is acceptable for this action.
uint8 internal constant ALG_ANY = 0;
/// @notice How far behind the chain head an approval's anchor may sit.
/// @dev Members evaluate roster membership against tree 1 AT the anchor
/// block. 600 blocks is ten minutes at the chain's one-second cadence —
/// generous against a round that takes seconds, and short enough that a
/// roster rotated away is refused rather than counted.
uint64 internal constant ANCHOR_WINDOW = 600;
/// @dev Domain separator for every quorum digest. Distinct from any
/// EIP-712 domain in the stack: these are not typed-data signatures and
/// must not be confusable with one.
bytes32 internal constant DOMAIN_PQ_QUORUM = keccak256("FINAL_CHAIN_PQ_QUORUM_v01");
/// @notice One member's approval.
struct Approval {
/// The member's account, which is also the key it is looked up by.
address signer;
/// `ALG_ML_DSA_87` or `ALG_SLH_DSA_SHAKE_256S`.
uint8 algorithm;
/// Over the 32-byte digest from `digest()`, verbatim. Both schemes
/// hash internally, so the digest is not re-hashed before signing.
bytes signature;
/// SLH-DSA-SHAKE-256s over the same digest, by the member's `activeSeal`
/// key. Required where the action authorizes execution; empty otherwise.
bytes seal;
}
/// @notice Thrown when fewer valid approvals were supplied than the action requires.
/// @param valid Approvals that verified.
/// @param required Approvals the action demands.
error ThresholdNotMet(uint256 valid, uint256 required);
/// @notice Thrown when approvals are not in strictly ascending signer order.
/// @dev Ascending order is what makes duplicate detection a single comparison instead of a quadratic scan,
/// so it is the rule that stops one signer being counted twice toward a threshold.
/// @param previous The preceding signer.
/// @param next The signer that failed to exceed it.
error SignersNotAscending(address previous, address next);
/// @notice Thrown when an approving signer does not hold the role this action is gated on.
/// @param signer The approving signer.
/// @param roleMask The role the action requires.
error SignerLacksRole(address signer, uint256 roleMask);
/// @notice Thrown when an approval is signed under an algorithm this action does not accept.
/// @param signer The approving signer.
/// @param got The algorithm the approval declared.
/// @param required The algorithm the action demands.
error WrongAlgorithm(address signer, uint8 got, uint8 required);
/// @notice Thrown when an approval's signature fails verification in the precompile.
/// @param signer The approving signer.
/// @param algorithm The algorithm it was verified under.
error BadSignature(address signer, uint8 algorithm);
/// @notice Thrown when an approval's access seal fails verification.
/// @param signer The approving signer.
error BadSeal(address signer);
/// @notice Thrown when an approval anchors to a block this chain has not reached.
/// @param anchorBlock The block the approval anchored to.
/// @param blockNumber The current block.
error AnchorAhead(uint64 anchorBlock, uint256 blockNumber);
/// @notice Thrown when an approval's anchor is older than the accepted window.
/// @dev Bounding the window is what stops an approval collected once being replayed indefinitely later.
/// @param anchorBlock The block the approval anchored to.
/// @param blockNumber The current block.
error AnchorStale(uint64 anchorBlock, uint256 blockNumber);
/// @notice Thrown when an action is gated on a threshold of zero.
/// @dev Refused rather than treated as "no approvals needed": a zero threshold is always a
/// misconfiguration, and reading it as permissive would silently remove the quorum.
error ThresholdIsZero();
/**
* @notice The message every member of this quorum signs.
* @param verifyingContract The contract consuming the approvals. Binding it
* stops an approval collected for one contract being replayed
* against another with the same payload shape.
* @param actionDomain What is being authorized — a per-action constant, so
* an approval for "advance the accounts tree" cannot be replayed as
* one for "revoke an identity".
* @param anchorBlock The Final Chain block the members read tree 1 at to
* decide the roster. Bound here so every approval in a round names
* the same view; checked against `ANCHOR_WINDOW` by `require_`.
* @param payloadDigest The action's own committed content. Callers MUST
* include a nonce or a monotonic counter in it; nothing here can
* tell a replay of round 7 from a fresh round 7.
*/
function digest(
address verifyingContract,
bytes32 actionDomain,
uint64 anchorBlock,
bytes32 payloadDigest
) internal view returns (bytes32) {
return keccak256(
abi.encode(
DOMAIN_PQ_QUORUM,
block.chainid,
verifyingContract,
actionDomain,
anchorBlock,
payloadDigest
)
);
}
/**
* @notice Reverts unless at least `threshold` distinct members holding
* `roleMask` have signed `quorumDigest`.
* @param registry Where public keys and roles come from. Not a parameter
* for flexibility — a parameter so the caller's own immutable
* registry address is what is used, rather than one from calldata.
* @param requiredAlgorithm `ALG_ANY` to accept either scheme.
* @param anchorBlock The anchor the digest was built over. Refused if it is
* ahead of this block or more than `ANCHOR_WINDOW` behind it.
* @param requireSeal Whether every approval must also carry a valid `seal`
* by the member's `activeSeal` key — the execution class.
* @return valid The number of approvals that verified, which is at least
* `threshold` if this returns at all.
*
* @dev Every failure reverts with the offending signer named. A quorum that
* silently skipped bad approvals and counted the rest would let a
* misconfigured co-signer sit broken indefinitely: the threshold would keep
* being met by the others and nothing would say one member had stopped
* contributing. That is exactly the failure this program has already had,
* in `fanOut`, where a per-chain advance failure was recorded and execution
* continued.
*/
function require_(
FinalIdentityRegistry registry,
Approval[] calldata approvals,
bytes32 quorumDigest,
uint256 roleMask,
uint256 threshold,
uint8 requiredAlgorithm,
uint64 anchorBlock,
bool requireSeal
) internal view returns (uint256 valid) {
if (threshold == 0) revert ThresholdIsZero();
if (anchorBlock > block.number) revert AnchorAhead(anchorBlock, block.number);
if (block.number - anchorBlock > ANCHOR_WINDOW) revert AnchorStale(anchorBlock, block.number);
bytes memory message = abi.encodePacked(quorumDigest);
address previous = address(0);
uint256 n = approvals.length;
for (uint256 i = 0; i < n; i++) {
Approval calldata a = approvals[i];
// Strictly ascending. `address(0)` as the initial value works
// because it can never be a registered signer.
if (a.signer <= previous) revert SignersNotAscending(previous, a.signer);
previous = a.signer;
if (!registry.hasRole(a.signer, roleMask)) revert SignerLacksRole(a.signer, roleMask);
if (requiredAlgorithm != ALG_ANY && a.algorithm != requiredAlgorithm) {
revert WrongAlgorithm(a.signer, a.algorithm, requiredAlgorithm);
}
if (!_verify(registry, a, message)) revert BadSignature(a.signer, a.algorithm);
if (requireSeal && !_verifySeal(registry, a, message)) revert BadSeal(a.signer);
valid++;
}
if (valid < threshold) revert ThresholdNotMet(valid, threshold);
}
/// @notice Non-reverting form, for views and for callers that want to
/// report rather than refuse.
function count(
FinalIdentityRegistry registry,
Approval[] calldata approvals,
bytes32 quorumDigest,
uint256 roleMask,
uint8 requiredAlgorithm,
uint64 anchorBlock,
bool requireSeal
) internal view returns (uint256 valid) {
if (anchorBlock > block.number || block.number - anchorBlock > ANCHOR_WINDOW) return 0;
bytes memory message = abi.encodePacked(quorumDigest);
address previous = address(0);
uint256 n = approvals.length;
for (uint256 i = 0; i < n; i++) {
Approval calldata a = approvals[i];
if (a.signer <= previous) return valid;
previous = a.signer;
if (!registry.hasRole(a.signer, roleMask)) continue;
if (requiredAlgorithm != ALG_ANY && a.algorithm != requiredAlgorithm) continue;
if (!_verify(registry, a, message)) continue;
if (requireSeal && !_verifySeal(registry, a, message)) continue;
valid++;
}
}
/// @dev The seal: SLH-DSA-SHAKE-256s by the member's `activeSeal` key over
/// the same digest. A member with no seal key on record cannot seal, and an
/// approval with no seal bytes is not one.
function _verifySeal(
FinalIdentityRegistry registry,
Approval calldata a,
bytes memory message
) private view returns (bool) {
bytes memory key = registry.activeSealKeyOf(a.signer);
if (key.length == 0 || a.seal.length == 0) return false;
return FinalChainPrecompiles.verifySlhDsa(key, message, a.seal);
}
/// @dev Verifies one approval against the key the REGISTRY holds for that signer, never against a key
/// supplied in the approval. A key passed as an argument proves nothing, because anyone holding a
/// keypair can sign under it; reading from storage is what makes the verdict re-derivable from public
/// state rather than a claim by whoever assembled the call.
/// @param registry The identity registry that holds each signer's live keys.
/// @param a The approval being verified.
/// @param message The exact bytes the approval must cover.
/// @return valid True when the signature verifies under the signer's live key for the declared algorithm.
function _verify(
FinalIdentityRegistry registry,
Approval calldata a,
bytes memory message
) private view returns (bool) {
// The LIVE pair, always. The recovery pair authorizes rotating this
// account's own credentials and NOTHING else — a quorum that accepted
// it would hand the recovery keys everyday authority, which is exactly
// the separation the two stages exist to draw.
if (a.algorithm == ALG_ML_DSA_87) {
return FinalChainPrecompiles.verifyMlDsa87(
registry.activeTransactionKeyOf(a.signer), message, a.signature
);
}
if (a.algorithm == ALG_SLH_DSA_SHAKE_256S) {
return FinalChainPrecompiles.verifySlhDsa(
registry.activeAccessKeyOf(a.signer), message, a.signature
);
}
// Any other id is a refusal, never a default — including the KEM ids
// (3, 7) and the reserved FN-DSA id (6), none of which is a signature
// scheme this quorum verifies.
return false;
}
}
contracts/finalchain/FinalStateTrees.sol
// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may deploy this state-tree contract as the state
// plane of a Final DeFi Protocol chain, and may operate that chain.
// 2. Integrators, indexers, operators and end users may read every tree, take
// inclusion proofs, branch roots, tree roots and round roots from it, and
// write into a tree they hold the quorum, the writer seat or the
// configuration authority for, as part of their integration with the Final
// DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
// deployment of a Fork of this state-tree contract or a competing state
// plane derived from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
import {FinalIdentityRegistry} from "./FinalIdentityRegistry.sol";
import {FinalChainTime} from "./FinalChainTime.sol";
import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalPlaneSweep} from "./FinalPlaneSweep.sol";
/// @title Chain Source
/// @notice The one question `syncIdentities` asks the asset registry.
/// @dev An interface rather than an import of `FinalAssetRegistry`, which
/// imports this file: the registry is tree 6's writer and holds the trees
/// as an immutable, so the dependency runs that way and this is the one
/// read that runs the other.
interface IChainSource {
/// @notice Every chain reference the asset registry currently has enabled.
/// @dev Read once per `syncIdentities` batch, so a service account's
/// `deployedChains` table is DERIVED from registry state instead of
/// being supplied by the caller. A caller-chosen table would let
/// anyone place a service identity on a chain of their choosing,
/// which is why the projection reads and never accepts.
/// @return The enabled chain references, in the registry's own order.
function enabledChainRefs() external view returns (bytes32[] memory);
}
/// @title Slot Key Source
/// @notice The one question {FinalStateTrees.syncSlotKeyLeaves} asks the
/// slot-key registry: the leaf value for one member's slot — the
/// registry's own verdict, zero when the slot holds nothing usable.
interface ISlotKeySource {
/// @notice The leaf value one member's slot-key ring position carries.
/// @dev The registry decides; this contract only copies. Zero is the
/// answer for a slot that never held a key and for one whose window
/// has passed, so re-projecting a lapsed slot retires its leaf.
/// @param member The co-signer whose slot key is being read.
/// @param slotIndex The slot the key belongs to, before the ring modulus.
/// @return The registry's leaf value, or zero when the slot holds nothing usable.
function slotKeyLeafOf(address member, uint64 slotIndex) external view returns (bytes32);
}
/// @title Endpoint Source
/// @notice The one question {FinalStateTrees.syncEndpointLeaves} asks the
/// endpoint registry: the leaf value for one tunnel endpoint — the
/// registry's own verdict (certificate hash, status, expiry, region),
/// zero when nothing is registered under the id.
interface IEndpointSource {
/// @notice The leaf value one tunnel endpoint carries.
/// @dev The registry admitted the certificate under its own quorum with
/// the holder's proof of possession, so this read carries a verdict
/// rather than a claim. Zero means nothing stands under the id.
/// @param endpointId The endpoint's certificate subject key id.
/// @return The registry's leaf value, or zero when nothing is registered under the id.
function endpointLeafOf(bytes32 endpointId) external view returns (bytes32);
}
/**
* @title Final State Trees
* @notice Final Chain's state plane: eight fixed-depth Merkle trees, and the rounds that publish all
* eight of their roots as one contemporaneous snapshot.
*
* @dev This contract runs on the project's own reth-based chains and nowhere else. Every signer is
* resolved through an identity registry that verifies post-quantum signatures in precompiles those chains
* alone provide, so a deployment anywhere else cannot authorize a single write. Nothing under
* `contracts/` outside the Final Chain directory imports it, and it takes part in no CREATE2 derivation —
* its address is whatever its deploy transaction produced, never a mined constant that other code pins.
* Gas is deliberately NOT a design constraint here and must not be optimised for: full sibling paths are
* stored, every branch enumerates on chain, and a configuration row keeps its value beside its hash,
* precisely so that no reader ever has to rebuild anything off chain to be sure of it.
*
* **Immutable, and behind no proxy.** There is no upgrade path and no authority that can replace this
* code. Any change to the surface below is a REDEPLOY at a new address, and everything holding the old
* address — the account ledger, the registries, the records contract, every service configured against
* it, every consumer pinning a root — is orphaned the moment that happens and has to be repointed. The
* registry projections into trees 1 and 8 do not travel with a redeploy either: they are derived from the
* registry, so a fresh deployment re-derives them rather than migrating anything.
*
* ## What each tree carries
*
* One tree per domain, because they change at unrelated cadences and a combined tree invalidates every
* outstanding proof on every tick:
*
* | # | tree | holds | cadence |
* |---|---|---|---|
* | 1 | accounts | every Final Wallet's public state | per rotation / creation |
* | 2 | phi | the PHI record: per (wallet, chain) balances, the lock, exposures | per publisher round |
* | 3 | vasset | issued vAsset supply and backing, per (asset, chain) | per settlement |
* | 4 | oracle | published prices and their inputs | ~10 s; 1 s for morph and fee assets |
* | 5 | settlement | chain and asset registry roots | rarely |
* | 6 | allowlist | assets, chains, policy, price sources, DEX deployments | rarely |
* | 7 | intents | intent status, ring-keyed over the posting sequence | per posting |
* | 8 | identity | the wallet-creation admission set, projected from the registry | per identity mutation |
*
* ## Tree 1 is READ, never rebuilt
*
* Tree 1 is a Final Wallet's public state and the SOURCE OF TRUTH every execution chain projects from.
* The sanctioned way to ask it a question is {proofFor} for the sibling path and {liveRoot} for the root
* each chain republishes — {branchProofFor} with {branchRoot} to prove against a branch instead,
* {roundProofFor} with {roundRootAt} to prove against a published round. Those entrypoints are the whole
* interface, and their answers are the only ones that verify.
*
* Do NOT fold the same leaves off chain. This tree is FIXED DEPTH — `DEPTH` levels, with a branch subtree
* at `BRANCH_DEPTH` — zero-padded to that depth, and INSERTION-ORDERED: a key keeps the slot it was first
* handed, permanently, and empty slots hash as the empty subtree rather than being skipped. A rebuild
* that sorts its leaves, or sizes itself `log2(n)` to the number of leaves present, is a DIFFERENT tree.
* Its root is not this root, no proof against it verifies anywhere, and nothing in the failure names the
* cause: the execution chain simply refuses a proof that looks perfectly well formed.
*
* ## Who may write which tree
*
* Four kinds of door, and every tree sits on exactly one of the first three:
*
* - **A service quorum.** {setLeaves} for trees 5 and 6, {setAccountStates} for tree 1: at least
* `threshold[treeId]` approvals from members holding `writerRole[treeId]`, each an ML-DSA-87 vote over
* a digest binding the tree, its nonce and the whole batch. Tree 1's round additionally carries each
* member's SLH-DSA seal, because a leaf there states who an account IS on every chain.
* - **A typed writer.** Trees 2, 3 and 4 are reachable only through {writeTyped}, from the records
* contract, which holds the preimage behind each leaf and computes the hash from it. {setLeaves}
* refuses those three outright, so a stored value can never drift from the commitment beside it.
* - **A writer contract.** `treeWriter[treeId]` writes its tree with no quorum at all: the account ledger
* for tree 1, the intent log for tree 7, the ledger again for tree 8's user admissions. Trees 7 and 8
* have no quorum path whatsoever — {setLeaves} refuses both.
* - **The configuration authority.** Branch 0 of every tree through {setConfig}, plus the pointers,
* rosters and thresholds themselves. Never a tree's own writer or quorum: what a service states is not
* authority over how that service is configured.
*
* `treeWriter[1]` being the account ledger, with no service quorum layered on top, is the design and not
* a gap. A writer contract is not a key: its rules are its bytecode, it has no owner and no proxy, and it
* authorizes every transition by verifying the ACCOUNT HOLDER'S own SLH-DSA credential against the
* commitment this chain holds. That is stronger evidence than a K-of-N of our own services attesting to
* what they read. A quorum on top would be strictly worse than nothing — it would let operators withhold
* approval from a user rotating a stolen key, which is a censorship power over the exact operation the
* account plane exists to make possible.
*
* ## Seeding the chain and asset trees
*
* Trees 5 and 6 are the two a fresh plane cannot infer. Tree 5 carries the settlement chain and asset
* registry roots; tree 6 carries the allowlist those roots stand over — supported chains, supported
* assets, policy, price sources, DEX deployments. Both are quorum-written, and both are expected to be
* SEEDED before the plane is usable: an execution chain copies its chain set and its asset set from these
* roots, so an unseeded pair means every settlement toward a chain is refused at the source and no vAsset
* ever registers. A test plane seeds the test chains; a production plane seeds the production chains and
* their assets. `chainSource` belongs in the same window, because `syncIdentities` derives a service
* account's `deployedChains` table from the enabled chain set, and an unset source quietly produces
* service leaves that exist on Final Chain alone.
*
* The bootstrap ordering is load bearing in one more place: {configureTree} refuses a threshold no live
* roster can meet, so members are registered first and trees configured after. A plane whose trees were
* never configured accepts no quorum write at all while looking perfectly healthy from outside.
*
* ## The hash shape is not a choice
*
* Leaves hash as `keccak256(0x00 ‖ leaf)` and internal nodes as
* `keccak256(0x01 ‖ lo ‖ hi)` with the pair sorted. That is
* `FinalMerkle.verifyTaggedSortedProof`, verbatim, which is what
* `FinalWalletFactory.syncAccountState` and `FinalSettlement` already run on
* every supported chain. A proof produced here is consumed there with no
* translation and no contract change, and tree 1's leaf preimage is exactly
* `FinalWalletFactory.accountStateLeafHash` — same fields, same order, the
* `deployedChains` table `abi.encode`d like every other field.
*
* Getting this wrong is not a compile error anywhere. It is a root every chain
* silently rejects, with nothing pointing at the cause.
*
* ## Positional slots under a sorted-pair tree
*
* Sorted pairs make a proof position-agnostic, which is why it carries no
* direction bits. That does not stop the TREE from being positional, and here
* it is: every key gets a permanent slot, so a single leaf update is `DEPTH`
* hashes instead of a rebuild over every leaf. The verifier neither knows nor
* needs to know that a slot exists.
*
* ## Branches
*
* The slot space of every tree is cut into `BRANCH_COUNT` branches by the top
* `BRANCH_BITS` of the slot: a branch is a subtree with a permanent place, its
* root is one internal node, and a leaf's path to the tree root passes through
* it. Branches hold what belongs to the same domain but not to the same rows
* — branch 0 is the owning service's CONFIGURATION on every tree, tree 8 adds
* the owner → wallets index and the co-signers' slot keys beside the admission
* set — and they are chosen over more trees because a branch shares its
* tree's authority doors and writer, while a tree would need its own. A leaf
* proves against its branch root with `BRANCH_DEPTH` siblings, against the
* tree root with `DEPTH`, against the round root with `ROUND_DEPTH`: one path,
* cut at three heights, one verifier.
*
* ## Rounds, and why the live roots are not the product
*
* `setLeaves` moves a tree. It does not publish one. A consumer that fetched
* eight roots one at a time would get a price proof from one moment and a
* roster proof from another, and something delisted in between would still
* verify.
*
* `publishRound` snapshots all eight together, and folds them into ONE round
* root — the tree roots as the level-`DEPTH` nodes of a depth-`ROUND_DEPTH`
* tree, tree `t` at position `t` — so a single word commits to the whole
* plane and any leaf in it proves against that word with four more siblings.
* A round is the unit a consumer pins, and it is the only thing this contract
* promises is contemporaneous. The execution chains keep anchoring per-tree
* roots (identity, account state, registry roots): those must move at their
* own cadence, not at the oracle's.
*/
contract FinalStateTrees is FinalPlaneSweep {
// ---------------------------------------------------------------- trees
/// @notice Every Final Wallet's public state. The source of truth other
/// chains copy through `syncAccountState`.
uint8 public constant TREE_ACCOUNTS = 1;
/// @notice The PHI record, per `(wallet, chain)`: balances, the lock, its
/// terms, the exposures carved from it and the accrual between reconciliations.
uint8 public constant TREE_PHI = 2;
/// @notice vAsset supply and backing.
uint8 public constant TREE_VASSET = 3;
/// @notice Oracle prices and their inputs.
uint8 public constant TREE_ORACLE = 4;
/// @notice Settlement chain and asset registry roots.
uint8 public constant TREE_SETTLEMENT = 5;
/// @notice Which assets and chains are supported.
uint8 public constant TREE_ALLOWLIST = 6;
/// @notice Intent status, keyed by a RING over the posting sequence.
/// @dev The search structure beside `FinalBundleLog`'s permanent record.
/// Written only by `FinalIntentLog` through `treeWriter[7]` — the tree-1
/// argument verbatim: the log verified the bond, the commitment, the
/// approval and the consume itself, and a service quorum on top would be a
/// censorship point over posting. Slots are permanent and intents are
/// unbounded flow, so the log recycles keys modulo `CAPACITY`: the tree is
/// an index with a ~1M-posting retention window, never the record.
uint8 public constant TREE_INTENTS = 7;
/// @notice The wallet-creation admission set — the identity leaves
/// (`keccak256(DOMAIN_IDENTITY_LEAF ‖ serial ‖ keysHash)`) every execution
/// chain's gateway verifies certificates against.
/// @dev The root the gateways anchor as `currentIdentityRoot`, CONTINUOUS
/// over this tree: an admission or a revocation is live the moment it
/// lands here, with no off-chain folding step standing between the two.
/// Two feeders, one per identity plane, and NO quorum door for either:
///
/// - SERVICE identities: {syncIdentityLeaves}, the permissionless
/// projection of `FinalIdentityRegistry`'s own verdict — the registry
/// calls it same-tx on every identity mutation, and anyone may call it
/// to retire a leaf whose standing lapsed by TIME (expiry moves no
/// registry storage, so only a projection pass can zero it).
/// - USER identities: `treeWriter[8]` — `FinalAccountLedger`, which
/// computes the leaf from the genesis certificate fields it verified
/// under its opener quorum and writes it once at `openAccount`. A user
/// admission leaf is permanent by construction: the certificate IS the
/// address, rotation never changes it, and a post-rotation creation on
/// a new chain reads PUBLISHED account state out of tree 1, never the
/// certificate's genesis keys.
///
/// A quorum of service signatures must not be able to state an identity
/// neither ruler decided, so `setLeaves` refuses this tree outright.
uint8 public constant TREE_IDENTITY = 8;
/// @notice Count, for iteration. Trees are 1-indexed; 0 is not a tree.
/// @notice Tree 9 — compliance: the approved set (branch 1), revocations (2), per-jurisdiction
/// counters (3) and minutes-lived action attestations (4); branch 0 pins the jurisdiction
/// policy in force and the attestation life. Typed-only: `FinalStateRecords` writes it under
/// the REGISTRAR quorum (an attestation is an admission) through `writeTypedInBranch`, and
/// the presale ledger mirrors its counters through the same companion; no `setLeaves` door
/// — no set of service signatures may attest what the provider and the screening did not
/// decide. Leaves are `FinalComplianceLeaves`; nothing in them names a person.
uint8 public constant TREE_COMPLIANCE = 9;
/// @notice Number of trees. The round root has room for 2**FOREST_BITS; a new tree is a redeploy.
uint8 public constant TREE_COUNT = 9;
/// @notice Tree height: 2^`DEPTH` slots per tree, laid out as 16 BRANCHES
/// of 2^20. The top `BRANCH_BITS` of a slot name the branch, the rest its
/// position inside it.
/// @dev FIXED, and baked into every root this contract produces. A tree is
/// padded to this height with the empty-subtree hash whether it holds one
/// leaf or a million, which is why an off-chain rebuild must use this
/// depth verbatim: a `log2(n)` tree over the same leaves is a different
/// tree and proves nothing here. Raising it is a migration and not a
/// parameter change — every outstanding proof and every root anchored on
/// another chain would have to be replaced in the same instant.
uint256 public constant DEPTH = 24;
/// @notice How many of a slot's top bits name the branch it lives in.
/// @dev `BRANCH_COUNT` is `1 << BRANCH_BITS` and `BRANCH_DEPTH` is
/// `DEPTH - BRANCH_BITS`; the three move together, or the branch a slot
/// belongs to stops matching the subtree its proof passes through.
uint256 public constant BRANCH_BITS = 4;
/// @notice Branches per tree. Ids run `0 .. BRANCH_COUNT - 1`.
/// @dev Sixteen is deliberately generous: an unused branch costs only the
/// empty-subtree hash it contributes, so a domain can grow a new family of
/// rows without a new tree, a new writer or a new authority.
uint8 public constant BRANCH_COUNT = 16;
/// @notice Height of a branch: a leaf proves against its branch root with
/// this many siblings.
uint256 public constant BRANCH_DEPTH = DEPTH - BRANCH_BITS;
/// @notice Slots per branch.
/// @dev The hard ceiling `_set` enforces: a branch that runs out of slots
/// reverts `BranchFull` rather than spilling into its neighbour, because a
/// key in the wrong branch would prove against the wrong branch root.
uint256 public constant BRANCH_CAPACITY = 1 << BRANCH_DEPTH;
/// @notice Slots per tree, all branches together.
uint256 public constant CAPACITY = 1 << DEPTH;
/// @notice How many of the round root's levels sit above the tree roots.
/// @dev The round root is a tree over the tree roots — position `t` holds
/// tree `t`'s root, positions 0 and 9..15 the empty tree — folded with the
/// same node hash. It is literally the root of a depth-`ROUND_DEPTH` tree
/// whose level-`DEPTH` nodes are the eight tree roots, which is what lets
/// one path prove a leaf against it.
uint256 public constant FOREST_BITS = 4;
/// @notice Height of the round tree: a leaf proves against a round root
/// with this many siblings, the last `FOREST_BITS` of them from
/// {roundProofFor}.
uint256 public constant ROUND_DEPTH = DEPTH + FOREST_BITS;
/// @notice Branch 0 of EVERY tree: the configuration of the service that
/// owns the tree — key → one word, the VALUE stored so a contract on this
/// chain reads it directly (`configValue`), the hash in the tree so it is
/// provable wherever a round root is. Written only by {setConfig} under
/// the configuration authority; every other door refuses the branch.
uint8 public constant BRANCH_CONFIG = 0;
/// @notice Branch 1 of every tree: the domain's own rows — accounts, PHI
/// records, vAssets, prices, registry roots, the allowlist, the intent
/// ring, the identity admission set.
uint8 public constant BRANCH_MAIN = 1;
/// @notice Tree 8, branch 2: the owner → wallets index. Key = the owner
/// (`ownerIndexKeyFor`), leaf = {ownerIndexLeafHash} over the ledger's
/// `walletsByOwner(owner)`. Written by tree 8's writer, the ledger, beside
/// every open and every owner transfer — the tree is the search structure,
/// the ledger holds the readable array it proves.
uint8 public constant BRANCH_OWNER_INDEX = 2;
/// @notice Tree 8, branch 3: the co-signers' per-slot KEM publics — a RING
/// of `SLOT_KEY_RING` positions per member, projected from
/// `slotKeySource` by {syncSlotKeyLeaves} exactly as identities are.
uint8 public constant BRANCH_SLOT_KEYS = 3;
/// @notice Tree 8, branch 4: the tunnel endpoints — the Final Node
/// identities a wallet's FNP session terminates at. Key = the endpoint id
/// (`endpointKeyFor`, the certificate's subject key id), leaf = the
/// endpoint registry's verdict, projected from `endpointSource` by
/// {syncEndpointLeaves} exactly as slot keys are. An execution chain never
/// parses an endpoint certificate; it anchors this tree's root and a client
/// proves the leaf against it.
uint8 public constant BRANCH_ENDPOINTS = 4;
/// @notice Slot-key positions per member. A slot index wraps modulo this,
/// so the branch is an index over the recent slots and never fills; 1024
/// members × 1024 positions is the branch exactly.
uint64 public constant SLOT_KEY_RING = 1024;
/// @notice The domain every tree-1 leaf is hashed under.
/// @dev Must equal `FinalWalletFactory.DOMAIN_ACCOUNT_STATE_LEAF` byte for
/// byte, and the leaf's fields must be encoded in the same order on both
/// sides. A field reordered on one side only is not a compile error
/// anywhere: it is a root every execution chain rejects, with nothing
/// pointing at the cause.
///
/// The version suffix is part of the domain, so a leaf built under a
/// different account-state shape hashes into a different domain and cannot
/// verify against this one by accident.
bytes32 public constant DOMAIN_ACCOUNT_STATE_LEAF =
keccak256("FINAL_ACCOUNT_STATE_LEAF_v02");
/// @dev The quorum action every leaf write is approved under — {setLeaves},
/// {setAccountStates} and {writeTyped} share it, so a member recomputes one
/// digest whichever door a batch came through and there is no second
/// approval shape to get wrong.
bytes32 private constant ACTION_SET_LEAVES = keccak256("FinalStateTrees.setLeaves.v01");
/// @notice Configuration action: set a tree's writer role and threshold.
/// @dev Registrar-quorum actions, verified by the registry with this
/// contract as the verifying contract. See `FinalIdentityRegistry.requireRegistrarQuorum`.
bytes32 public constant ACTION_CONFIGURE_TREE = keccak256("FINAL_STATE_TREES_CONFIGURE_TREE_v01");
/// @notice Configuration action: point a tree at its writer contract.
bytes32 public constant ACTION_SET_TREE_WRITER = keccak256("FINAL_STATE_TREES_SET_TREE_WRITER_v01");
/// @notice Configuration action: point `syncIdentities` at the chain set.
bytes32 public constant ACTION_SET_CHAIN_SOURCE = keccak256("FINAL_STATE_TREES_SET_CHAIN_SOURCE_v01");
/// @notice Configuration action: point tree 8's branch 3 at the slot-key registry.
bytes32 public constant ACTION_SET_SLOT_KEY_SOURCE = keccak256("FINAL_STATE_TREES_SET_SLOT_KEY_SOURCE_v01");
/// @notice Configuration action: point tree 8's branch 4 at the endpoint registry.
bytes32 public constant ACTION_SET_ENDPOINT_SOURCE = keccak256("FINAL_STATE_TREES_SET_ENDPOINT_SOURCE_v01");
/// @notice Configuration action: adopt a preceding plane's version and round counters.
bytes32 public constant ACTION_SEED_COUNTERS = keccak256("FINAL_STATE_TREES_SEED_COUNTERS_v01");
/// @notice Configuration action: install the records contract that writes the typed trees.
bytes32 public constant ACTION_SET_TYPED_WRITER = keccak256("FINAL_STATE_TREES_SET_TYPED_WRITER_v01");
/// @notice Configuration action: write rows into a tree's branch 0.
bytes32 public constant ACTION_SET_CONFIG = keccak256("FINAL_STATE_TREES_SET_CONFIG_v01");
/// @dev Tree-1 key domain. A full-width hash rather than the packed address
/// it came from, which matters: an address key occupies only the low 160
/// bits, so a hashed key colliding with one needs ~2^96 work rather than a
/// full collision. That is expensive but not comfortable, and the
/// consequence would be a service identity landing in a wallet's slot.
bytes32 private constant DOMAIN_ACCOUNT_KEY = keccak256("FinalStateTrees.key.account.v01");
/// @dev Tree-8 admission key domain, separated from the tree-1 domain for
/// the same reason: one account's two keys must never be the same word.
bytes32 private constant DOMAIN_IDENTITY_TREE_KEY = keccak256("FinalStateTrees.key.identity.v01");
/// @dev Tree 8, branches 2 and 3, and branch 0 of every tree. Each is its
/// own domain so a key can never land in another branch's slot by
/// construction — `_set` refuses a key whose slot sits in a different
/// branch, and the domain is what makes that refusal unreachable.
bytes32 private constant DOMAIN_OWNER_INDEX_KEY = keccak256("FinalStateTrees.key.ownerIndex.v01");
/// @dev Tree 8, branch 3: one key per `(member, ring position)` pair.
bytes32 private constant DOMAIN_SLOT_KEY = keccak256("FinalStateTrees.key.slotKey.v01");
/// @dev Tree 8, branch 4: one key per tunnel endpoint id.
bytes32 private constant DOMAIN_ENDPOINT_KEY = keccak256("FinalStateTrees.key.endpoint.v01");
/// @dev Branch 0 of every tree: one key per `(name, sub)` configuration row.
bytes32 private constant DOMAIN_CONFIG_KEY = keccak256("FinalStateTrees.key.config.v01");
/// @notice Leaf domain for the owner index in tree 8, branch 2.
/// @dev Separate from the key domain above so the leaf and the slot it
/// occupies can never be confused for one another by a reader that has
/// only one of the two.
bytes32 public constant DOMAIN_OWNER_INDEX_LEAF = keccak256("FINAL_OWNER_INDEX_LEAF_v01");
/// @notice Leaf domain for configuration rows in branch 0 of every tree.
/// @dev The leaf binds the tree id as well as the key and value, so the
/// same row written into two trees produces two different leaves and a
/// proof cannot be carried from one tree's branch 0 to another's.
bytes32 public constant DOMAIN_CONFIG_LEAF = keccak256("FINAL_CONFIG_LEAF_v01");
// -------------------------------------------------------------- storage
/// @notice The registry every signer is resolved through. Immutable so the
/// quorum can never be pointed at a registry supplied in calldata.
FinalIdentityRegistry public immutable registry;
/// @notice Approvals required per tree.
///
/// @dev Per-tree and not a scalar, because each tree is gated by a
/// DIFFERENT role — account co-signers, PHI, vAsset and oracle
/// publishers, registry publishers — so K is a property of that
/// tree's roster, not of the contract. All six read 2 today; that is
/// a deploy-time default, not an invariant, and collapsing them would
/// put the oracle roster's quorum on the account co-signers'.
///
/// The VALUE is a full word: it is a quantity compared against a live
/// member count, and every other threshold in the system is `uint256`.
/// The KEY is `uint8` because that is what a tree id is here — six
/// `uint8` constants, every parameter, every event, every error,
/// `_assertTree`, and the ten sibling mappings below. Widening it
/// would buy nothing (a narrow key is padded to 32 bytes before
/// hashing, so the slot is identical) and cost the getter's selector
/// on a contract that is live on both Final Chains.
mapping(uint8 treeId => uint256) public threshold;
/// @notice Role a signer must hold to write to a tree.
mapping(uint8 treeId => uint256) public writerRole;
/// @notice Raw (untagged) leaf value by tree and slot.
/// @dev The tag is applied when the leaf is hashed, never when it is
/// stored, so what a caller wrote is what {leafOf} hands back.
mapping(uint8 => mapping(uint256 => bytes32)) private _leaf;
/// @notice Internal nodes, levels 1..`DEPTH`, by tree, level and index.
/// @dev Level 0 is DERIVED from `_leaf` rather than duplicated here, so a
/// leaf lives in exactly one place and the two can never disagree. An
/// unwritten position reads zero and falls through to `_zero[level]`.
mapping(uint8 => mapping(uint256 => mapping(uint256 => bytes32))) private _node;
/// @notice Empty-subtree hash per level, computed once at construction.
/// @dev Sized to the ROUND root's height, not the tree's, because the
/// round tree's unused positions are themselves empty trees. Built in
/// the constructor rather than declared as constants: it depends on
/// the tagging, and a constant table that drifted from the tagging
/// would produce roots nothing can verify, silently, since both sides
/// would still be internally consistent.
bytes32[ROUND_DEPTH + 1] private _zero;
/// @notice Permanent slot for a key, stored 1-based so 0 means unassigned.
/// @dev The slot's top `BRANCH_BITS` are the branch the key lives in, and
/// the assignment is permanent: a key handed a slot keeps it for the
/// life of the contract. This is what makes an update `DEPTH` hashes
/// rather than a rebuild, and what makes the tree insertion-ordered.
mapping(uint8 => mapping(bytes32 => uint256)) private _slotPlusOne;
/// @notice The key a slot was handed to — the reverse of `_slotPlusOne`.
/// @dev Lets any branch enumerate on chain ({keyAt} over
/// `0 .. branchSlotsUsed`) with no log window and no indexer. Costs
/// one extra word per NEW key, never one per update.
mapping(uint8 => mapping(uint256 => bytes32)) private _keyAt;
/// @notice Slots handed out per tree, all branches together.
mapping(uint8 => uint256) public slotsUsed;
/// @notice Slots handed out per branch — the next free position in it.
/// @dev Per branch and not per tree, because a branch is a fixed region of
/// the slot space: positions are allocated from the branch's own base
/// so a key can never be handed a slot outside the branch it belongs
/// to, and `BranchFull` is raised rather than spilling into the next.
mapping(uint8 => mapping(uint8 => uint256)) private _branchSlotsUsed;
/// @notice The VALUE behind a configuration row (branch 0), by tree and key.
/// @dev Kept beside the leaf hash so a contract on this chain reads the row
/// directly through {configValue} while the same row stays provable
/// off chain against a round root — one source for the fleet, the
/// contracts and any explorer, rather than one per reader.
mapping(uint8 => mapping(bytes32 => bytes32)) private _configValue;
/// @notice Live root per tree. Moves on every `setLeaves`.
mapping(uint8 treeId => bytes32) public liveRoot;
/// @notice Writes applied per tree, for change detection between rounds.
mapping(uint8 treeId => uint64) public treeVersion;
/// @notice A contemporaneous snapshot of all eight roots, and the one
/// round root that folds them.
struct Round {
/// @dev Live root per tree at the instant of the snapshot, indexed by
/// the `TREE_*` constants. Index 0 is unused, so a tree id needs
/// no translation.
bytes32[TREE_COUNT + 1] roots;
/// @dev The single word committing to all eight — the roots folded as
/// the level-`DEPTH` nodes of a depth-`ROUND_DEPTH` tree.
bytes32 roundRoot;
/// @dev Block the snapshot was taken in, for a consumer reconciling a
/// round against chain history.
uint64 blockNumber;
/// @dev Snapshot instant in MILLISECONDS, like every instant on this
/// chain, so a reader never has to guess the unit.
uint64 timestamp;
}
/// @notice Published rounds, 1-indexed. Round 0 is "nothing published".
/// @dev Kept forever: a consumer pinning an old round can still fetch the
/// roots it verified against. Only rounds this deployment published
/// are here — {seedCounters} moves the counter, never the history.
mapping(uint64 => Round) private _rounds;
/// @notice Highest published round.
uint64 public round;
/// @notice Tree versions as of the last published round.
/// @dev The change detector {publishRound} reads: a round that would carry
/// nothing new is refused, so the round number cannot be advanced by
/// anyone with gas to spend.
mapping(uint8 => uint64) private _publishedVersion;
/// @notice Per-tree nonce, bound into every quorum digest.
mapping(uint8 treeId => uint64) public nonce;
/**
* @notice A CONTRACT allowed to write one tree without a quorum.
*
* @dev Exactly one per tree, and today exactly one exists: tree 1's is
* `FinalAccountLedger`.
*
* This looks like a hole and is the opposite. The quorum on `setLeaves`
* exists because a tree's writer is otherwise one key deciding what the
* chain states. A writer contract is not a key — its rules are its
* bytecode, it has no owner and no proxy, and tree 1's writer authorizes
* every change by verifying the ACCOUNT HOLDER'S own post-quantum signature
* in this chain's precompiles. That is strictly stronger evidence than a
* K-of-N of our own services attesting to what they read.
*
* Keeping the quorum on top of it would be actively worse: our fleet could
* then withhold approval from a user rotating a stolen key, which is a
* censorship power over the exact operation the account plane exists to
* make possible.
*
* The writer is set on the same bootstrap window as `configureTree` and can
* be moved by a registrar afterwards — an immutable pointer would mean a
* ledger upgrade abandons the tree it writes.
*/
mapping(uint8 treeId => address) public treeWriter;
/**
* @notice Where `syncIdentities` reads the chain set from — the asset
* registry, which is also tree 6's writer.
*
* @dev A service identity is a Final Wallet whose address is the same on
* every EVM chain, so its tree-1 `deployedChains` table is derivable: one
* `(chainRef, itself)` row per chain the registry has enabled. The table
* is DERIVED from state rather than supplied by the caller precisely so
* that `syncIdentities` can stay permissionless — a caller-chosen table
* would let anyone place a service identity on a chain of their choosing.
*
* Unset (zero) means services carry an empty table and exist on Final
* Chain alone, which is what a plane looks like before its registry is
* seeded. Same configuration gate as `setTreeWriter`, because pointing this
* at a different contract changes what every service leaf says.
*/
address public chainSource;
/// @notice Where {syncSlotKeyLeaves} reads the co-signers' slot keys from
/// — the slot-key registry, whose verdict tree 8's branch 3
/// projects. Same configuration gate as `chainSource`; unset means
/// the branch cannot be written.
address public slotKeySource;
/// @notice The endpoint registry whose verdict tree 8's branch 4 projects.
address public endpointSource;
/// @notice The one contract admitted to {writeTyped}: `FinalStateRecords`,
/// which holds the preimages behind trees 2, 3 and 4 and computes
/// their keys and hashes. Same configuration gate as `treeWriter`.
address public typedWriter;
// --------------------------------------------------------------- events
/// @notice A batch of leaves landed in a tree and moved its live root.
/// @dev Emitted once per write door call, not once per leaf, and always
/// after the root has settled — so `newRoot` is the value {liveRoot}
/// answers from that block onward.
/// @param treeId The tree that moved.
/// @param count Leaves in the batch. Zero is possible for an empty call.
/// @param newRoot The tree's live root after the batch.
/// @param treeVersion The tree's write counter after the batch.
event LeavesSet(uint8 indexed treeId, uint256 count, bytes32 newRoot, uint64 treeVersion);
/// @notice Every tree's root was snapshotted into a new round.
/// @param round The round number, one above its predecessor.
/// @param blockNumber Block the snapshot was taken in.
/// @param timestamp Snapshot instant, in milliseconds.
event RoundPublished(uint64 indexed round, uint64 blockNumber, uint64 timestamp);
/// @notice A tree's writer role and approval threshold were installed.
/// @param treeId The tree configured.
/// @param writerRole Role a signer must hold to approve a write to it.
/// @param threshold Approvals a write needs; zero leaves the tree closed.
event TreeConfigured(uint8 indexed treeId, uint256 writerRole, uint256 threshold);
/// @notice A tree's quorum-free writer contract was installed or moved.
/// @param treeId The tree whose writer changed.
/// @param writer The contract now allowed to write it; zero removes the path.
event TreeWriterSet(uint8 indexed treeId, address writer);
/// @notice The contract `syncIdentities` reads the enabled chain set from was set.
/// @param source The asset registry now consulted; zero means no chain set.
event ChainSourceSet(address source);
/// @notice The registry tree 8's branch 3 projects slot keys from was set.
/// @param source The slot-key registry now consulted; zero closes the branch.
event SlotKeySourceSet(address source);
/// @notice The registry tree 8's branch 4 projects endpoints from was set.
/// @param source The endpoint registry now consulted; zero closes the branch.
event EndpointSourceSet(address source);
/// @notice A fresh plane adopted a preceding plane's counters.
/// @dev Carries the counters only. The roots behind those rounds stay with
/// the plane that published them, so {roundRootAt} below the seed
/// answers zero on this one.
/// @param round The round number this plane continues from.
/// @param versions Per-tree write counters, indexed by tree id; index 0 unused.
event CountersSeeded(uint64 round, uint64[] versions);
/// @notice The records contract admitted to the typed trees was installed.
/// @param writer The contract now allowed through {writeTyped}.
event TypedWriterSet(address writer);
/// @notice One configuration row was written into a tree's branch 0.
/// @param treeId The tree whose owning service the row configures.
/// @param key The row's branch-0 key, as {configKey} computes it.
/// @param value The row's single word of value.
event ConfigSet(uint8 indexed treeId, bytes32 indexed key, bytes32 value);
// --------------------------------------------------------------- errors
/// @notice A tree id outside `1 .. TREE_COUNT` was supplied. Zero is not a tree.
/// @param treeId The rejected id.
error UnknownTree(uint8 treeId);
/// @notice Two parallel arrays did not have the same length, or a batch was empty
/// where at least one row is required.
/// @param keys Length of the key array.
/// @param leaves Length of the value array.
error LengthMismatch(uint256 keys, uint256 leaves);
/// @notice A branch has handed out every slot it owns and cannot take a new key.
/// @dev Raised rather than spilling into the neighbouring branch: a key in
/// the wrong branch would prove against the wrong branch root.
/// @param treeId The tree the branch belongs to.
/// @param branch The exhausted branch.
error BranchFull(uint8 treeId, uint8 branch);
/// @notice A branch id at or above `BRANCH_COUNT` was supplied.
/// @param branch The rejected id.
error UnknownBranch(uint8 branch);
/// @notice A key already holds a slot in another branch of this tree.
/// @dev Slots are permanent, so a key cannot be moved between branches.
/// Reaching this means two callers disagree about where a row lives.
/// @param treeId The tree involved.
/// @param key The key whose slot is already assigned.
/// @param have The branch the key's slot actually sits in.
/// @param want The branch the caller tried to write it into.
error BranchMismatch(uint8 treeId, bytes32 key, uint8 have, uint8 want);
/// @notice Branch 0 is written by `setConfig` alone.
/// @dev Every other door refuses it, so a tree's writer or quorum can never
/// restate the configuration of the service that feeds it.
/// @param treeId The tree whose branch 0 was targeted.
error ConfigBranchReserved(uint8 treeId);
/// @notice Tree 8's branch 3 was written while no slot-key registry is installed.
error SlotKeySourceUnset();
/// @notice Tree 8's branch 4 was written while no endpoint registry is installed.
error EndpointSourceUnset();
/// @notice Counters can be seeded only into a plane that has published nothing.
/// @dev Seeding a plane that already moved would rewind counters consumers
/// have compared against, so it is refused rather than reconciled.
error NotFresh();
/// @notice The seeded version array was not one entry per tree plus the unused index 0.
/// @param given The length supplied.
error VersionCountMismatch(uint256 given);
/// @notice The tree has no threshold installed, so no quorum write can be authorized.
/// @param treeId The unconfigured tree.
error TreeNotConfigured(uint8 treeId);
/// @notice A round was requested while no tree has moved since the last one.
/// @dev The round number is therefore not advanceable by anyone with gas
/// to spend, and a round always means something changed.
error NothingToPublish();
/// @notice The key holds no slot in this tree, so there is nothing to prove or read.
/// @param treeId The tree searched.
/// @param key The key with no slot.
error UnknownKey(uint8 treeId, bytes32 key);
/// @notice The caller is not the writer seat or typed writer this door requires.
/// @param caller The rejected address.
error NotAuthorized(address caller);
/// @notice A round was asked for on a plane that has published none, or one above the latest.
error NoRounds();
/// @notice A threshold was configured above the number of members who could meet it.
/// @dev Refused at configuration time so a tree is never installed already
/// unwritable. Register the roster first; that ordering is the point.
/// Revocation can still walk a live tree into this state later, which
/// is what {quorumHealth} exists for — revocation must never be
/// blocked on quorum arithmetic.
/// @param treeId The tree being configured.
/// @param live Members currently holding the role.
/// @param required Approvals the rejected configuration would demand.
error ThresholdUnreachable(uint8 treeId, uint256 live, uint256 required);
/// @notice Trees 7 and 8 take no quorum writes — only their writer
/// contract (and, for tree 8, the registry projection).
/// @dev An intent's status is what the intent log verified and an identity
/// is what the registry or the ledger verified. No set of service
/// signatures can make a different answer true, so there is no quorum
/// door to refuse at — the door does not exist.
/// @param treeId The writer-only tree a quorum write was aimed at.
error WriterOnlyTree(uint8 treeId);
/// @notice `setLeaves` was called on a tree that has a typed writer.
/// @dev Trees 2, 3 and 4 keep the leaf's preimage beside its hash so a
/// consumer can read the VALUE. An untyped write sets the hash and
/// cannot set the preimage — the pair would disagree, and the stored
/// value would look authoritative while committing to nothing. The
/// typed entrypoint is not a convenience over this one; it is the
/// only door.
/// @param treeId The typed tree an untyped write was aimed at.
error TypedTreeOnly(uint8 treeId);
/// @notice A `deployedChains` row names the zero chain or the zero account,
/// or repeats a chain. A table with either proves nothing about
/// where the account exists.
/// @dev Checked wherever the leaf is hashed, so no door — quorum, writer
/// contract, identity projection — can publish a table a resolver on
/// another chain would read two ways.
/// @param chainRef The offending row's chain reference.
/// @param account The offending row's account on that chain.
error InvalidChainAccount(bytes32 chainRef, bytes32 account);
// ---------------------------------------------------------- constructor
/**
* @notice Pin the identity registry and bring all eight trees up empty.
* @param registry_ The identity registry. Every signer, key and role is
* resolved through it.
* @dev The registry is `immutable`, so no later call can point the quorum
* at a registry supplied in calldata — a roster chosen by the caller is a
* roster that approves whatever the caller wants.
*
* The empty-subtree table is built here rather than as constants because it
* depends on the tagging, and a constant table that drifted from the
* tagging would produce roots nothing can verify — silently, since both
* sides would still be self-consistent.
*
* Every tree starts at the empty root rather than zero, so a consumer can
* tell "this tree holds nothing" from "this contract has never run".
*/
constructor(FinalIdentityRegistry registry_) {
registry = registry_;
// Level 0: the tagged hash of an empty (zero) leaf.
_zero[0] = keccak256(abi.encodePacked(bytes1(0x00), bytes32(0)));
for (uint256 l = 0; l < ROUND_DEPTH; l++) {
// Both children equal, so the sort is a no-op and the order is
// irrelevant — which is the only reason this table is one value per
// level rather than one per position.
_zero[l + 1] = keccak256(abi.encodePacked(bytes1(0x01), _zero[l], _zero[l]));
}
for (uint8 t = 1; t <= TREE_COUNT; t++) {
liveRoot[t] = _zero[DEPTH];
}
}
// ------------------------------------------------------- configuration
/**
* @notice The gate every configuration entrypoint on this contract passes through.
* @dev The registry's bootstrap admin alone while its window is open, the
* sealed `ROLE_REGISTRAR` quorum afterwards. The same window the registry
* uses, for the same reason — every roster has to be installed by someone
* before it can install itself — and the same quorum, because a threshold
* is membership by another name: whoever can set K to one owns the tree.
*
* Not `view`: the registrar path burns the registry's own nonce, so an
* approved configuration payload cannot be replayed at a later block.
* @param actionDomain The `ACTION_*` constant naming what is being configured.
* @param payloadDigest Hash of the arguments this call would apply.
* @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
* @param approvals The sealed registrar quorum. Empty during bootstrap.
*/
function _requireConfigurationAuthority(
bytes32 actionDomain,
bytes32 payloadDigest,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) private {
if (!registry.bootstrapSealed() && msg.sender == registry.bootstrapAdmin()) return;
registry.requireRegistrarQuorum(actionDomain, payloadDigest, anchorBlock, approvals);
}
/**
* @notice Set which role may write a tree and how many approvals it needs.
* @dev The configuration authority, never the tree's own quorum: a roster
* that could raise or lower its own threshold is a roster with no
* threshold. A tree left at `k == 0` refuses every quorum write with
* `TreeNotConfigured`, which is the state a fresh plane starts in.
* @param treeId The tree being configured.
* @param role Role a signer must hold for an approval to count.
* @param k Approvals a write needs; `0` leaves the tree unconfigured.
* @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
* @param approvals The sealed registrar quorum. Empty during bootstrap.
*/
function configureTree(
uint8 treeId,
uint256 role,
uint256 k,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_assertTree(treeId);
_requireConfigurationAuthority(
ACTION_CONFIGURE_TREE, keccak256(abi.encode(treeId, role, k)), anchorBlock, approvals
);
// Refuse a threshold nobody can meet. Register the members first; that
// ordering is the point, not an inconvenience. A 4-of-5 configured
// against three registered co-signers is a tree that reverts on every
// write, and the revert names the threshold rather than the roster.
if (k != 0) {
uint256 live = registry.liveMemberCount(role);
if (live < k) revert ThresholdUnreachable(treeId, live, k);
}
writerRole[treeId] = role;
threshold[treeId] = k;
emit TreeConfigured(treeId, role, k);
}
/**
* @notice Point a tree at the contract allowed to write it directly.
* @dev Same gate as `configureTree`, for the same reason. Setting it to the
* zero address removes the path entirely and leaves the tree quorum-only.
*
* Point this at a CONTRACT, never at an externally owned account. The whole
* argument for a quorum-free writer is that its rules are its bytecode; an
* account holding a key is exactly the single-key authority the quorum on
* {setLeaves} exists to prevent.
*
* Movable rather than immutable on purpose: an immutable pointer would mean
* a ledger redeploy abandons the tree it writes, with no way back.
* @param treeId The tree whose writer seat is being set.
* @param writer The contract admitted to it; zero removes the seat.
* @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
* @param approvals The sealed registrar quorum. Empty during bootstrap.
*/
function setTreeWriter(
uint8 treeId,
address writer,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_assertTree(treeId);
_requireConfigurationAuthority(
ACTION_SET_TREE_WRITER, keccak256(abi.encode(treeId, writer)), anchorBlock, approvals
);
treeWriter[treeId] = writer;
emit TreeWriterSet(treeId, writer);
}
/**
* @notice Point `syncIdentities` at the contract that knows the chain set.
* @dev Same gate as `setTreeWriter`. Zero removes the source, after which
* service leaves carry an empty `deployedChains` table — which is what a
* plane looks like before its asset registry is seeded, and is why this
* pointer belongs in the same bootstrap window as the seed itself.
* @param source The asset registry to read the enabled chain set from.
* @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
* @param approvals The sealed registrar quorum. Empty during bootstrap.
*/
function setChainSource(
address source,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireConfigurationAuthority(
ACTION_SET_CHAIN_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
);
chainSource = source;
emit ChainSourceSet(source);
}
/// @notice Point tree 8's branch 3 at the slot-key registry it projects.
/// @dev Same gate as `setChainSource`. Zero closes the branch entirely:
/// {syncSlotKeyLeaves} reverts `SlotKeySourceUnset` rather than
/// writing leaves whose value nothing vouched for.
/// @param source The slot-key registry whose verdict the branch projects.
/// @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
/// @param approvals The sealed registrar quorum. Empty during bootstrap.
function setSlotKeySource(
address source,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireConfigurationAuthority(
ACTION_SET_SLOT_KEY_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
);
slotKeySource = source;
emit SlotKeySourceSet(source);
}
/// @notice Point tree 8's branch 4 at the endpoint registry it projects.
/// @dev Same gate as `setSlotKeySource`, and the same fail-closed shape:
/// zero makes {syncEndpointLeaves} revert `EndpointSourceUnset`.
/// @param source The endpoint registry whose verdict the branch projects.
/// @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
/// @param approvals The sealed registrar quorum. Empty during bootstrap.
function setEndpointSource(
address source,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireConfigurationAuthority(
ACTION_SET_ENDPOINT_SOURCE, keccak256(abi.encode(source)), anchorBlock, approvals
);
endpointSource = source;
emit EndpointSourceSet(source);
}
/**
* @notice Adopt a preceding plane's counters — one `treeVersion` per tree
* (index = treeId, 0 unused) and the published `round` — so a
* redeploy stays monotonic for every consumer that compares them:
* rings, explorers, the round feed.
* @dev This contract is immutable, so replacing it means a new address, and
* a fresh address would otherwise restart every counter at zero. A consumer
* that treats a counter as monotonic would then read the new plane as
* older than the state it already holds, and quietly ignore live data.
*
* It carries the counters and nothing else. The roots behind those rounds
* stay with the plane that published them, so {roundRootAt} below the seed
* answers zero here — pin a round on the plane that produced it.
*
* Configuration authority (bootstrap admin before the seal, registrar
* quorum after), and only while this plane has published nothing:
* `NotFresh` otherwise, because rewinding a counter a consumer has already
* compared against is worse than never seeding at all.
* @param versions Per-tree write counters to adopt, indexed by tree id;
* index 0 is unused and must still be present.
* @param round_ The round number this plane continues from.
* @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
* @param approvals The sealed registrar quorum. Empty during bootstrap.
*/
function seedCounters(
uint64[] calldata versions,
uint64 round_,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireConfigurationAuthority(
ACTION_SEED_COUNTERS, keccak256(abi.encode(versions, round_)), anchorBlock, approvals
);
if (versions.length != TREE_COUNT + 1) revert VersionCountMismatch(versions.length);
if (round != 0) revert NotFresh();
for (uint8 t = 1; t <= TREE_COUNT; t++) {
if (treeVersion[t] != 0) revert NotFresh();
}
for (uint8 t = 1; t <= TREE_COUNT; t++) {
treeVersion[t] = versions[t];
}
round = round_;
emit CountersSeeded(round_, versions);
}
/// @notice Install the records contract that writes the typed trees.
/// @dev Trees 2, 3 and 4 have no other door at all — {setLeaves} refuses
/// them outright — so leaving this unset closes those three
/// completely. Same gate as `setTreeWriter`, and the same rule: a
/// contract, never an account holding a key.
/// @param writer The records contract admitted to {writeTyped}.
/// @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
/// @param approvals The sealed registrar quorum. Empty during bootstrap.
function setTypedWriter(
address writer,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireConfigurationAuthority(
ACTION_SET_TYPED_WRITER, keccak256(abi.encode(writer)), anchorBlock, approvals
);
typedWriter = writer;
emit TypedWriterSet(writer);
}
/**
* @notice Write configuration rows into a tree's branch 0.
* @param treeId The tree whose owning service the rows configure.
* @param keys `configKey(name, sub)` per row.
* @param values One word per row — a duration, a count, an address, a
* flag; the reader knows the shape from the name.
* @param anchorBlock The registrars' roster anchor. Ignored during bootstrap.
* @param approvals The sealed registrar quorum. Empty during bootstrap.
*
* @dev The configuration authority, not the tree's writer or quorum: a
* tree's writer states what its domain verified, its quorum attests to
* what it read, and neither is the authority over how the service that
* feeds it is configured.
*
* The value is stored beside the hash so a contract on this chain reads it
* in one call ({configValue}) while the same row is provable off chain
* against a round root. That is one source of truth for the fleet, the
* contracts and any explorer at once — a service reading its own
* environment instead would be a second source, free to disagree with this
* one and with nothing on chain able to notice.
*/
function setConfig(
uint8 treeId,
bytes32[] calldata keys,
bytes32[] calldata values,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_assertTree(treeId);
if (keys.length != values.length || keys.length == 0) revert LengthMismatch(keys.length, values.length);
_requireConfigurationAuthority(
ACTION_SET_CONFIG, keccak256(abi.encode(treeId, keys, values)), anchorBlock, approvals
);
for (uint256 i = 0; i < keys.length; i++) {
_configValue[treeId][keys[i]] = values[i];
_set(treeId, BRANCH_CONFIG, keys[i], configLeafHash(treeId, keys[i], values[i]));
emit ConfigSet(treeId, keys[i], values[i]);
}
_bump(treeId, keys.length);
}
// ------------------------------------------------------------- writing
/**
* @notice Write leaves into one branch of one tree under a PQ quorum.
* @param treeId Which tree.
* @param branch Which branch — never 0, which `setConfig` alone writes.
* @param keys Domain keys — a wallet address for accounts, an asset id for
* the allowlist, whatever identifies a row in that domain. Each gets
* a permanent slot in the branch on first write.
* @param leaves The raw (untagged) leaf values.
* @param anchorBlock The block the approving roster is read as of.
* @param approvals At least `threshold[treeId]` of them, ascending by signer.
*
* @dev The digest binds the tree, its nonce, and the full batch. Binding the
* nonce is what stops the same approved batch being replayed: without it,
* an approval to set a price is an approval to set that price again at any
* later block, which for an oracle is the whole attack.
*
* ML-DSA-87 is required rather than accepted. These are operational,
* high-cadence writes — the transaction class — and leaving the choice open
* would mean a break in either scheme takes the tree.
*
* Three tree classes are refused here outright, each with its own error:
* the typed trees (2, 3 and 4) because their preimage has to be built by
* the records contract, and the writer-only trees (7 and 8) because no set
* of service signatures can make a different answer true about an intent's
* status or an identity's standing.
*/
function setLeaves(
uint8 treeId,
uint8 branch,
bytes32[] calldata keys,
bytes32[] calldata leaves,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_assertTree(treeId);
_assertDataBranch(treeId, branch);
if (treeId == TREE_PHI || treeId == TREE_VASSET || treeId == TREE_ORACLE || treeId == TREE_COMPLIANCE) {
revert TypedTreeOnly(treeId);
}
// Trees 7 and 8 have their own rulers and NO quorum path at all: an
// intent's status is what `FinalIntentLog` verified, an identity is
// what the registry or the ledger verified, and no set of service
// signatures can make a different answer true.
if (treeId == TREE_INTENTS || treeId == TREE_IDENTITY) revert WriterOnlyTree(treeId);
if (keys.length != leaves.length) revert LengthMismatch(keys.length, leaves.length);
uint256 k = threshold[treeId];
if (k == 0) revert TreeNotConfigured(treeId);
uint64 n = nonce[treeId];
FinalPqQuorum.require_(
registry,
approvals,
FinalPqQuorum.digest(
address(this),
ACTION_SET_LEAVES,
anchorBlock,
keccak256(abi.encode(treeId, branch, n, keys, leaves))
),
writerRole[treeId],
k,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
false
);
nonce[treeId] = n + 1;
for (uint256 i = 0; i < keys.length; i++) {
_set(treeId, branch, keys[i], leaves[i]);
}
_bump(treeId, keys.length);
}
/// @notice One chain an account exists on, and as what.
/// @dev `chainRef` is the registry's CAIP-derived chain reference — the one
/// identifier that names an EVM chain and a non-EVM one alike — and
/// `account` is the wallet's account there, in that chain's own account
/// space (an EVM address right-aligned, a 32-byte key filling the
/// width). Field-for-field with `IWalletTypes.ChainAccount`.
struct ChainAccount {
/// @dev The registry's CAIP-derived reference for the chain.
bytes32 chainRef;
/// @dev The account on that chain, in that chain's own account space.
bytes32 account;
}
/// @notice `FinalWalletFactory.AccountStateLeaf`, field for field.
/// @dev The preimage of every tree-1 leaf. The field set, the field ORDER
/// and the domain must match the factory's exactly on every supported
/// chain; a field added, removed or reordered on one side alone is a
/// root every execution chain rejects with nothing naming the cause.
struct AccountStateLeaf {
/// @dev The Final Wallet this leaf describes. Also what `accountKeyFor`
/// hashes into the tree-1 key, so one wallet holds one slot.
address wallet;
/// @dev Active-stage access-key commitment — the credential the account
/// ledger checks a state transition against.
bytes32 liveAccess;
/// @dev Active-stage transaction-key commitment.
bytes32 liveTransaction;
/// @dev Pre-committed successor to `liveAccess`, so a rotation reveals a
/// key that was already committed rather than one chosen after.
bytes32 recoveryAccess;
/// @dev Pre-committed successor to `liveTransaction`.
bytes32 recoveryTransaction;
/// @dev Active-stage encapsulation commitment and its pre-committed
/// successor. Field-for-field with `FinalWalletFactory.AccountStateLeaf`;
/// a field added on one side and not the other is a root every execution
/// chain rejects, with nothing pointing at the cause.
bytes32 liveKem;
/// @dev Pre-committed successor to `liveKem`.
bytes32 recoveryKem;
/// @dev Who may authorize for this account. This is the PROVEN owner an
/// execution chain resolves authority from; a copy stored there is
/// wrong for as long as nobody has pushed to that chain, and
/// nothing there can tell.
address owner;
/// @dev Whether the account authorizes post-quantum. One-way once set.
bool pqEnabled;
/// @dev Whether the account is frozen. Returned to a resolver rather
/// than enforced by it, so a reader can still learn who owns a
/// frozen account; the wallet refuses on this PROVEN value rather
/// than on a synced copy, so a chain behind on the fan-out cannot
/// let a frozen account transact.
bool frozen;
/// @dev The chains this account exists on, and its account on each —
/// including chains whose accounts are not EVM addresses. Decided HERE
/// (set by the holder through the ledger) and enforced there: an
/// execution chain refuses to create the account unless the table has a
/// row for it, and a settlement toward a chain with no row is refused at
/// the source. This is also what a zero beneficiary resolves through: a
/// table naming the account on each chain answers "as what", which a
/// bare membership flag never could. `_assertChainAccounts` rejects a
/// zero chain, a zero account and a repeated chain, so no door can
/// publish a table a resolver would read two ways.
ChainAccount[] deployedChains;
/// @dev Per-chain dormancy verdict, one bit per asset-registry chain
/// slot, so the bit positions are the registry's slot numbering rather
/// than this table's row order.
uint32 dormantChains;
/// @dev Monotonic per-account revision. Lets a reader holding two
/// proofs tell which one is newer without consulting a round.
uint64 version;
}
/**
* @notice Write account state into tree 1 from the typed leaf.
* @dev The typed form exists so the leaf preimage is built HERE rather than
* by whoever assembles the calldata. Tree 1 is the source of truth for every
* other chain, and `syncAccountState` will accept any 32 bytes that carry a
* valid proof — so if the publisher chose the preimage, the publisher could
* write an account state that no wallet record on this chain agrees with,
* and the proof would still verify everywhere.
*
* **Sealed.** Tree 1 is membership: a leaf here is who an account is, on
* every chain. So the round takes the hybrid class — each approval carries
* the ML-DSA-87 vote AND the member's SLH-DSA seal — where the other trees
* take the transaction class alone. A lattice break rewrites a price; it
* does not rewrite an account.
* @param leaves The account states to write, one per wallet.
* @param anchorBlock The block the approving roster is read as of.
* @param approvals At least `threshold[TREE_ACCOUNTS]` of them, ascending by signer.
*/
function setAccountStates(
AccountStateLeaf[] calldata leaves,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
uint256 k = threshold[TREE_ACCOUNTS];
if (k == 0) revert TreeNotConfigured(TREE_ACCOUNTS);
bytes32[] memory keys = new bytes32[](leaves.length);
bytes32[] memory hashes = new bytes32[](leaves.length);
for (uint256 i = 0; i < leaves.length; i++) {
keys[i] = accountKeyFor(leaves[i].wallet);
hashes[i] = accountStateLeafHash(leaves[i]);
}
uint64 n = nonce[TREE_ACCOUNTS];
FinalPqQuorum.require_(
registry,
approvals,
FinalPqQuorum.digest(
address(this),
ACTION_SET_LEAVES,
anchorBlock,
keccak256(abi.encode(TREE_ACCOUNTS, n, keys, hashes))
),
writerRole[TREE_ACCOUNTS],
k,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
true
);
nonce[TREE_ACCOUNTS] = n + 1;
for (uint256 i = 0; i < leaves.length; i++) {
_set(TREE_ACCOUNTS, BRANCH_MAIN, keys[i], hashes[i]);
}
_bump(TREE_ACCOUNTS, leaves.length);
}
/**
* @notice Write account state into tree 1 from the contract that owns it.
* @dev No quorum, and no nonce burned: `treeWriter[1]` is the ledger, and
* the ledger already verified the holder's own signature before it called
* here. See {treeWriter} for why adding a service quorum on top would be a
* censorship power rather than a safeguard.
*
* Typed, exactly as `setAccountStates` is: the preimage is built HERE, so
* even the writer contract cannot publish a leaf whose meaning no record on
* this chain agrees with.
* @param leaves The account states to write, one per wallet.
*/
function setAccountStatesAsWriter(AccountStateLeaf[] calldata leaves) external {
if (msg.sender != treeWriter[TREE_ACCOUNTS]) revert NotAuthorized(msg.sender);
for (uint256 i = 0; i < leaves.length; i++) {
_set(TREE_ACCOUNTS, BRANCH_MAIN, accountKeyFor(leaves[i].wallet), accountStateLeafHash(leaves[i]));
}
_bump(TREE_ACCOUNTS, leaves.length);
}
/**
* @notice Write raw leaves into any tree from the contract that owns it.
* @dev The generic sibling of {setAccountStatesAsWriter}, for a tree whose
* writer is a contract rather than a service quorum. Same authorization —
* `treeWriter[treeId]` and nothing else — and the same reasoning: the
* writer has already verified whatever its domain requires, and layering a
* quorum on top of a contract's own rules is a censorship power rather
* than a safeguard.
*
* UNTYPED, unlike the account path, and that is the trade. Tree 1's
* preimage is built here so even the ledger cannot publish a leaf whose
* meaning no record agrees with; a generic writer supplies its own hash,
* so the leaf means whatever that contract says it means. Acceptable only
* because the writer is a specific contract this chain's operators
* installed — its rules are its bytecode, it has no owner and no proxy —
* and NOT acceptable for a role-gated key. Point `treeWriter` at a
* contract, never at an externally owned account.
* @param treeId The tree to write.
* @param branch The branch within it. Never 0, which `setConfig` alone writes.
* @param keys Domain keys, one per leaf. Each takes a permanent slot in the
* branch on first write.
* @param leaves The raw (untagged) leaf values.
*/
function setLeavesAsWriter(uint8 treeId, uint8 branch, bytes32[] calldata keys, bytes32[] calldata leaves)
external
{
if (msg.sender != treeWriter[treeId]) revert NotAuthorized(msg.sender);
_assertDataBranch(treeId, branch);
if (keys.length != leaves.length) revert LengthMismatch(keys.length, leaves.length);
for (uint256 i = 0; i < keys.length; i++) {
_set(treeId, branch, keys[i], leaves[i]);
}
_bump(treeId, keys.length);
}
/// @notice The leaf hash `FinalWalletFactory.accountStateLeafHash` computes.
/// @dev Identical `abi.encode`, identical field order, identical domain, and
/// that identity is the whole contract between this chain and every
/// execution chain. `deployedChains` rides through `abi.encode` like every
/// other field — head offset, then length and rows — so the table is
/// committed whole and in order. The table is validated here rather than at
/// each door, so every path into tree 1 gets the same refusal.
/// @param leaf The account state to commit to.
/// @return The tagged leaf hash, ready to be placed in tree 1.
function accountStateLeafHash(AccountStateLeaf memory leaf) public pure returns (bytes32) {
_assertChainAccounts(leaf.deployedChains);
return keccak256(
abi.encode(
DOMAIN_ACCOUNT_STATE_LEAF,
leaf.wallet,
leaf.liveAccess,
leaf.liveTransaction,
leaf.recoveryAccess,
leaf.recoveryTransaction,
leaf.liveKem,
leaf.recoveryKem,
leaf.owner,
leaf.pqEnabled,
leaf.frozen,
leaf.deployedChains,
leaf.dormantChains,
leaf.version
)
);
}
/// @notice Reject a `deployedChains` table a resolver could not read.
/// @dev A well-formed table: no zero chain, no zero account, no chain twice.
/// Checked where the leaf is hashed so no door — quorum, writer
/// contract, identity projection — can publish a table a resolver
/// would read two ways. The duplicate scan is quadratic in the row
/// count, which is deliberate: gas is not a constraint on this chain,
/// and a sort or a seen-set would cost correctness or storage to save
/// something nobody is paying for.
/// @param rows The table to validate.
function _assertChainAccounts(ChainAccount[] memory rows) private pure {
for (uint256 i = 0; i < rows.length; i++) {
if (rows[i].chainRef == bytes32(0) || rows[i].account == bytes32(0)) {
revert InvalidChainAccount(rows[i].chainRef, rows[i].account);
}
for (uint256 j = 0; j < i; j++) {
if (rows[j].chainRef == rows[i].chainRef) {
revert InvalidChainAccount(rows[i].chainRef, rows[i].account);
}
}
}
}
/// @notice The account `wallet`'s published table names on `chainRef`, or
/// zero if it has no row there.
/// @dev A convenience over `accountStateLeafHash`'s input for readers on
/// this chain; execution chains answer the same question from their synced
/// record (`FinalWalletFactory.addressOn`). Pure, so it reads the leaf it is
/// handed and never this contract's storage — the caller is responsible for
/// having proved that leaf first.
/// @param leaf The account state to search.
/// @param chainRef The chain being asked about.
/// @return The account on that chain, or zero when the table has no row for it.
function accountOn(AccountStateLeaf memory leaf, bytes32 chainRef) public pure returns (bytes32) {
for (uint256 i = 0; i < leaf.deployedChains.length; i++) {
if (leaf.deployedChains[i].chainRef == chainRef) return leaf.deployedChains[i].account;
}
return bytes32(0);
}
/**
* @notice The typed trees' write door — `FinalStateRecords` alone.
* @dev The quorum, the nonce and the write, shared by every typed record.
* The records contract computed the keys and hashes from the structs it
* stores; this contract admits nobody else to trees 2, 3 and 4
* (`setLeaves` refuses them), so the value there can never drift from
* the commitment here.
*
* The digest is byte-identical to `setLeaves`' over the same keys and
* hashes, deliberately: the typed entrypoints choose the PREIMAGE, not the
* authorization. A member recomputes one digest whichever door the batch
* came through, and there is no second approval shape to get wrong.
*
* Always branch 1: a typed record is a domain row, and branch 0 belongs to
* the configuration authority on every tree without exception.
* @param treeId The typed tree being written.
* @param keys Domain keys the records contract computed, one per leaf.
* @param hashes Leaf hashes the records contract computed from its structs.
* @param anchorBlock The block the approving roster is read as of.
* @param approvals At least `threshold[treeId]` of them, ascending by signer.
*/
function writeTyped(
uint8 treeId,
bytes32[] memory keys,
bytes32[] memory hashes,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
if (msg.sender != typedWriter) revert NotAuthorized(msg.sender);
uint256 k = threshold[treeId];
if (k == 0) revert TreeNotConfigured(treeId);
uint64 n = nonce[treeId];
FinalPqQuorum.require_(
registry,
approvals,
FinalPqQuorum.digest(
address(this),
ACTION_SET_LEAVES,
anchorBlock,
keccak256(abi.encode(treeId, n, keys, hashes))
),
writerRole[treeId],
k,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
false
);
nonce[treeId] = n + 1;
for (uint256 i = 0; i < keys.length; i++) {
_set(treeId, BRANCH_MAIN, keys[i], hashes[i]);
}
_bump(treeId, keys.length);
}
/**
* @notice The typed door for a tree whose leaves live in SEVERAL data branches — tree 9, whose
* approvals, revocations, counters and attestations are four key families, each with a
* permanent branch. Same writer, same role, same threshold and the same per-tree nonce as
* `writeTyped`; the branch is folded into the signed payload so a quorum that approved a
* revocation cannot be replayed as an approval.
* @dev `writeTyped` stays byte-for-byte what it is (trees 2–4 write `BRANCH_MAIN` and their lanes
* sign `(treeId, n, keys, hashes)`); this door signs `(treeId, branch, n, keys, hashes)`.
* Branch 0 is `setConfig`'s alone.
* @param treeId The tree.
* @param branch The data branch every key of this write lives in (`1 .. BRANCH_COUNT - 1`).
* @param keys Domain keys, as the companion derived them.
* @param hashes The leaf hashes, one per key.
* @param anchorBlock The roster anchor the approvals were made against.
* @param approvals `threshold[treeId]` ML-DSA-87 votes from `writerRole[treeId]` members.
*/
function writeTypedInBranch(
uint8 treeId,
uint8 branch,
bytes32[] memory keys,
bytes32[] memory hashes,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
if (msg.sender != typedWriter) revert NotAuthorized(msg.sender);
_assertDataBranch(treeId, branch);
if (keys.length != hashes.length) revert LengthMismatch(keys.length, hashes.length);
uint256 k = threshold[treeId];
if (k == 0) revert TreeNotConfigured(treeId);
uint64 n = nonce[treeId];
FinalPqQuorum.require_(
registry,
approvals,
FinalPqQuorum.digest(
address(this),
ACTION_SET_LEAVES,
anchorBlock,
keccak256(abi.encode(treeId, branch, n, keys, hashes))
),
writerRole[treeId],
k,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
false
);
nonce[treeId] = n + 1;
for (uint256 i = 0; i < keys.length; i++) {
_set(treeId, branch, keys[i], hashes[i]);
}
_bump(treeId, keys.length);
}
/**
* @notice Snapshot every tree's root into a new round.
* @dev Permissionless, deliberately. Every root being snapshotted was
* already authorized by its tree's quorum, so this adds no authority — it
* only fixes a moment. Requiring a signature would put a liveness
* dependency in front of publication for no security gain.
*
* A round that would change nothing is refused, so the round number cannot
* be advanced by anyone with gas to spend.
* @return published The round number just written.
*/
function publishRound() external returns (uint64 published) {
bool changed;
for (uint8 t = 1; t <= TREE_COUNT; t++) {
if (treeVersion[t] != _publishedVersion[t]) {
changed = true;
break;
}
}
if (!changed) revert NothingToPublish();
published = round + 1;
Round storage r = _rounds[published];
for (uint8 t = 1; t <= TREE_COUNT; t++) {
r.roots[t] = liveRoot[t];
_publishedVersion[t] = treeVersion[t];
}
r.roundRoot = _foldForest(_forestLeaves(r.roots));
r.blockNumber = uint64(block.number);
// MILLISECONDS, like every instant on this chain.
r.timestamp = FinalChainTime.nowMs();
round = published;
emit RoundPublished(published, r.blockNumber, r.timestamp);
}
// ---------------------------------------------------------------- views
/// @notice Every root from one round. Index by the `TREE_*` constants;
/// index 0 is unused.
/// @dev An unpublished round answers all zeros rather than reverting, so a
/// caller scanning forward can tell where the history ends.
/// @param which The round number.
/// @return The eight tree roots at that round, indexed by tree id.
function rootsAt(uint64 which) external view returns (bytes32[TREE_COUNT + 1] memory) {
return _rounds[which].roots;
}
/// @notice One tree's root at one round.
/// @param which The round number.
/// @param treeId The tree to read.
/// @return That tree's root at that round; zero if the round is unpublished.
function rootAt(uint64 which, uint8 treeId) external view returns (bytes32) {
_assertTree(treeId);
return _rounds[which].roots[treeId];
}
/// @notice The one word that commits to every tree at one round.
/// @dev The value a consumer pins. Everything in the plane at that instant
/// proves against it, which is the only contemporaneity this contract
/// offers — the live roots move independently and do not.
/// @param which The round number.
/// @return The round root; zero if the round is unpublished on this plane.
function roundRootAt(uint64 which) external view returns (bytes32) {
return _rounds[which].roundRoot;
}
/**
* @notice The `FOREST_BITS` siblings that take a tree's root at one round
* up to that round's root — appended to `proofFor`, they make a
* leaf provable against `roundRootAt(which)` by the same verifier.
* @dev Folds the round's stored roots in memory rather than keeping the
* upper levels in storage: the fold is cheap, and one stored copy of a
* value is one fewer place for two copies to disagree.
* @param which The round number. Must be published on this plane.
* @param treeId The tree whose root is being lifted to the round root.
* @return path The `FOREST_BITS` siblings, lowest level first.
*/
function roundProofFor(uint64 which, uint8 treeId) external view returns (bytes32[] memory path) {
_assertTree(treeId);
if (which == 0 || which > round) revert NoRounds();
bytes32[] memory level = _forestLeaves(_rounds[which].roots);
path = new bytes32[](FOREST_BITS);
uint256 idx = treeId;
uint256 n = level.length;
for (uint256 l = 0; l < FOREST_BITS; l++) {
path[l] = level[idx ^ 1];
n >>= 1;
for (uint256 i = 0; i < n; i++) {
level[i] = _pair(level[2 * i], level[2 * i + 1]);
}
idx >>= 1;
}
}
/// @notice The latest round's roots, with the block it was taken at.
/// @dev Reverts `NoRounds` on a plane that has published nothing, rather
/// than answering an empty round that a caller could mistake for a
/// real snapshot of an empty plane.
/// @return which The round number.
/// @return roots The eight tree roots, indexed by tree id; index 0 unused.
/// @return blockNumber Block the snapshot was taken in.
/// @return timestamp Snapshot instant, in milliseconds.
function latestRound()
external
view
returns (uint64 which, bytes32[TREE_COUNT + 1] memory roots, uint64 blockNumber, uint64 timestamp)
{
which = round;
if (which == 0) revert NoRounds();
Round storage r = _rounds[which];
return (which, r.roots, r.blockNumber, r.timestamp);
}
/// @notice The raw leaf stored for a key, and whether it has a slot.
/// @dev The UNTAGGED value, as it was written. The tag is applied when the
/// leaf is hashed into the tree, so a caller reproducing a leaf hash
/// applies it themselves. A key with no slot answers `(0, false)`
/// rather than reverting, so presence is a question this view can be
/// asked directly.
/// @param treeId The tree to read.
/// @param key The domain key.
/// @return leaf The stored value, or zero when the key has no slot.
/// @return present Whether the key holds a slot in this tree.
function leafOf(uint8 treeId, bytes32 key) external view returns (bytes32 leaf, bool present) {
uint256 s = _slotPlusOne[treeId][key];
if (s == 0) return (bytes32(0), false);
return (_leaf[treeId][s - 1], true);
}
/// @notice The permanent slot for a key. Reverts if it has none. The
/// slot's top `BRANCH_BITS` are its branch.
/// @dev Stored one-based internally so an unassigned key is distinguishable
/// from slot 0, and returned zero-based here — slot 0 of branch 0 is a
/// real position.
/// @param treeId The tree to read.
/// @param key The domain key.
/// @return The key's zero-based slot index within the tree.
function slotOf(uint8 treeId, bytes32 key) public view returns (uint256) {
uint256 s = _slotPlusOne[treeId][key];
if (s == 0) revert UnknownKey(treeId, key);
return s - 1;
}
/// @notice The key a slot was handed to, or zero if it is still free —
/// the enumeration every branch offers: slots `branch << BRANCH_DEPTH`
/// through `+ branchSlotsUsed(treeId, branch) - 1`.
/// @dev Because slots are handed out in order and never reused, that range
/// is exactly the branch's contents: a reader enumerates a branch on
/// chain without an event window and without an indexer.
/// @param treeId The tree to read.
/// @param slot The slot index.
/// @return The key holding that slot, or zero when it was never handed out.
function keyAt(uint8 treeId, uint256 slot) external view returns (bytes32) {
return _keyAt[treeId][slot];
}
/// @notice Slots handed out in one branch.
/// @param treeId The tree to read.
/// @param branch The branch to read.
/// @return How many slots of that branch are in use — its enumeration bound.
function branchSlotsUsed(uint8 treeId, uint8 branch) external view returns (uint256) {
return _branchSlotsUsed[treeId][branch];
}
/// @notice One branch's root: the level-`BRANCH_DEPTH` node at its position.
/// @dev A branch that has never been written answers the empty-subtree hash
/// at that level, not zero, because that is genuinely its root.
/// @param treeId The tree the branch belongs to.
/// @param branch The branch to read.
/// @return The branch's root node.
function branchRoot(uint8 treeId, uint8 branch) external view returns (bytes32) {
_assertTree(treeId);
_assertBranch(branch);
return _nodeAt(treeId, BRANCH_DEPTH, branch);
}
/// @notice The first `BRANCH_DEPTH` siblings of `proofFor` — a proof
/// against the leaf's branch root rather than the tree root.
/// @dev The same path cut lower. A consumer that only ever needs one
/// branch can pin `branchRoot` and verify with fewer siblings; the
/// verifier is unchanged, since sorted pairs carry no direction bits.
/// @param treeId The tree to read.
/// @param key The domain key. Must already hold a slot.
/// @return The sibling path from the leaf up to its branch root.
function branchProofFor(uint8 treeId, bytes32 key) external view returns (bytes32[] memory) {
_assertTree(treeId);
return _path(treeId, slotOf(treeId, key), BRANCH_DEPTH);
}
/// @notice A configuration row's value, and whether the row exists.
/// @dev Presence is read from the slot table, not from the value: a row
/// deliberately set to zero exists and answers `present`.
/// @param treeId The tree whose branch 0 holds the row.
/// @param key The row key, as {configKey} computes it.
/// @return value The row's single word of value.
/// @return present Whether the row has ever been written.
function configValue(uint8 treeId, bytes32 key) external view returns (bytes32 value, bool present) {
present = _slotPlusOne[treeId][key] != 0;
value = _configValue[treeId][key];
}
/// @notice The branch-0 key of a configuration row: a name the owning
/// service defines, and a sub-key (a chain reference, an asset, zero).
/// @dev Its own key domain, so a configuration row can never be handed a
/// slot that a domain row of the same tree would want.
/// @param name The row's name, defined by the service that owns the tree.
/// @param sub The row's sub-key, or zero when the name stands alone.
/// @return The branch-0 key.
function configKey(bytes32 name, bytes32 sub) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_CONFIG_KEY, name, sub));
}
/// @notice The leaf a configuration row hashes to.
/// @dev Binds the tree id as well as the key and the value, so the same row
/// in two trees is two different leaves and a proof cannot be carried
/// from one tree's branch 0 to another's.
/// @param treeId The tree the row belongs to.
/// @param key The row key.
/// @param value The row value.
/// @return The untagged leaf value for that row.
function configLeafHash(uint8 treeId, bytes32 key, bytes32 value) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_CONFIG_LEAF, treeId, key, value));
}
/// @notice The tree-8 branch-2 key an owner occupies.
/// @param owner The owner whose wallet list the row indexes.
/// @return The branch-2 key.
function ownerIndexKeyFor(address owner) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_OWNER_INDEX_KEY, owner));
}
/// @notice The owner-index leaf: a commitment to the ledger's ordered
/// `walletsByOwner(owner)`.
/// @dev A commitment, not the list. The tree is the search structure; the
/// ledger holds the readable array this leaf proves, so ORDER matters
/// — the same wallets in a different order are a different leaf.
/// @param owner The owner the index row belongs to.
/// @param wallets The owner's wallets, in the ledger's own order.
/// @return The untagged leaf value for that row.
function ownerIndexLeafHash(address owner, address[] memory wallets) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_OWNER_INDEX_LEAF, owner, wallets));
}
/// @notice The tree-8 branch-3 key of one member's slot — a ring position.
/// @dev The index is reduced modulo `SLOT_KEY_RING` here, so the branch is
/// an index over the recent slots and never fills. A caller passes the
/// real slot number and does not do the reduction itself.
/// @param member The co-signer the slot key belongs to.
/// @param slotIndex The slot number, before the ring modulus.
/// @return The branch-3 key.
function slotKeyFor(address member, uint64 slotIndex) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_SLOT_KEY, member, slotIndex % SLOT_KEY_RING));
}
/**
* @notice Project slot keys into tree 8's branch 3 — the co-signers'
* per-slot KEM publics the private option seals to.
* @dev Permissionless, for {syncIdentityLeaves}' reason: the leaf VALUE
* is `slotKeySource`'s own verdict (the registry verified the member's
* signature when the key was published, and answers zero once the slot's
* window has passed), so this adds no authority and only projects. The
* registry calls it same-tx on publication; anyone may call it to retire a
* slot that lapsed by time.
* @param member The co-signer whose ring positions are being projected.
* @param slotIndexes The slots to project. Reduced modulo `SLOT_KEY_RING`.
*/
function syncSlotKeyLeaves(address member, uint64[] calldata slotIndexes) external {
address source = slotKeySource;
if (source == address(0)) revert SlotKeySourceUnset();
for (uint256 i = 0; i < slotIndexes.length; i++) {
_set(
TREE_IDENTITY,
BRANCH_SLOT_KEYS,
slotKeyFor(member, slotIndexes[i]),
ISlotKeySource(source).slotKeyLeafOf(member, slotIndexes[i])
);
}
_bump(TREE_IDENTITY, slotIndexes.length);
}
/// @notice The tree-8 branch-4 key of one tunnel endpoint.
/// @param endpointId The endpoint's certificate subject key id.
/// @return The branch-4 key.
function endpointKeyFor(bytes32 endpointId) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_ENDPOINT_KEY, endpointId));
}
/**
* @notice Project tunnel endpoints into tree 8's branch 4.
* @dev Permissionless, for {syncSlotKeyLeaves}' reason: the leaf VALUE is
* `endpointSource`'s own verdict — the registry admitted the certificate
* under the registrar quorum with the holder's proof of possession, and
* answers the revoked status once it is revoked — so this adds no authority
* and only projects. The registry calls it same-tx on registration and
* revocation; anyone may call it to re-project.
* @param endpointIds The endpoint ids to project.
*/
function syncEndpointLeaves(bytes32[] calldata endpointIds) external {
address source = endpointSource;
if (source == address(0)) revert EndpointSourceUnset();
for (uint256 i = 0; i < endpointIds.length; i++) {
_set(
TREE_IDENTITY,
BRANCH_ENDPOINTS,
endpointKeyFor(endpointIds[i]),
IEndpointSource(source).endpointLeafOf(endpointIds[i])
);
}
_bump(TREE_IDENTITY, endpointIds.length);
}
/**
* @notice The sibling path for a key, ready for
* `FinalMerkle.verifyTaggedSortedProof` on any chain.
* @dev The sanctioned way to ask any tree a question, tree 1 above all: a
* view, so a caller fetches a proof with one `eth_call` and never rebuilds
* the tree off chain. Rebuilding is where a divergence between what the
* chain holds and what a service believes it holds would come from, and
* this removes the second implementation entirely.
*
* A rebuild is not merely redundant, it is wrong. This tree is fixed depth,
* zero-padded and insertion-ordered; a fold that sorts its leaves or sizes
* itself to the leaf count produces a different root, and a proof against
* that root verifies nowhere while looking perfectly well formed.
*
* Pair the path with {liveRoot} for the current root, or append
* {roundProofFor} and verify against {roundRootAt} to pin a whole round.
* @param treeId The tree to read.
* @param key The domain key. Must already hold a slot.
* @return The `DEPTH` siblings from the leaf up to the tree root, lowest first.
*/
function proofFor(uint8 treeId, bytes32 key) external view returns (bytes32[] memory) {
_assertTree(treeId);
return _path(treeId, slotOf(treeId, key), DEPTH);
}
/// @notice The empty-subtree hash at a level. Level `DEPTH` is the root of
/// a tree with nothing in it.
/// @dev What an off-chain verifier needs to reproduce the padding this tree
/// uses. Levels run `0 .. ROUND_DEPTH`; anything above reverts on the
/// array bound.
/// @param level The level to read.
/// @return The hash of an empty subtree of that height.
function emptyRoot(uint256 level) external view returns (bytes32) {
return _zero[level];
}
/// @notice The tree-1 key a wallet occupies.
/// @dev A full-width hash rather than the packed address, so a hashed key
/// cannot be steered onto a slot an address key would take.
/// @param wallet The Final Wallet.
/// @return The tree-1 key.
function accountKeyFor(address wallet) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_ACCOUNT_KEY, wallet));
}
/**
* @notice Copy a registered identity into tree 1 as an account-state leaf.
* @dev Services are Final Wallets, so a service's leaf is the SAME leaf a
* user's wallet gets — `FinalWalletFactory.AccountStateLeaf`, four key
* commitments and all. There is no second shape and no second domain,
* which is what lets every chain that already consumes account state
* consume a co-signer's identity with no contract change.
*
* `owner` is the account itself: a service wallet is its own owner, having
* no separate holder to speak for it.
*
* Permissionless, and for the same reason `publishRound` is: every fact it
* writes was already authorized when it entered the registry, so this adds
* no authority and only projects. Gating it would put a liveness dependency
* in front of publishing a revocation, which is the one thing that must
* never wait.
* @param accounts The registered service identities to project. Each must
* already be registered; an unknown account reverts `UnknownKey`.
*/
function syncIdentities(address[] calldata accounts) external {
// One table for the batch: a service is its own canonical address on
// every enabled chain, so the rows differ only in `account`.
bytes32[] memory chainRefs = _enabledChainRefs();
for (uint256 i = 0; i < accounts.length; i++) {
address who = accounts[i];
FinalIdentityRegistry.Identity memory id = registry.identityOf(who);
if (!id.registered) revert UnknownKey(TREE_ACCOUNTS, accountKeyFor(who));
(bytes32 la, bytes32 lt, bytes32 ra, bytes32 rt) = registry.keyCommitments(who);
(bytes32 lk, bytes32 rk) = registry.kemCommitments(who);
ChainAccount[] memory table = new ChainAccount[](chainRefs.length);
for (uint256 c = 0; c < chainRefs.length; c++) {
table[c] = ChainAccount({chainRef: chainRefs[c], account: bytes32(uint256(uint160(who)))});
}
AccountStateLeaf memory leaf = AccountStateLeaf({
wallet: who,
liveAccess: la,
liveTransaction: lt,
recoveryAccess: ra,
recoveryTransaction: rt,
liveKem: lk,
recoveryKem: rk,
// A service reaches every chain the registry has enabled, at
// its own address, and is never dormant: dormancy measures an
// ABSENT holder, and these identities have no holder to be
// absent.
deployedChains: table,
dormantChains: 0,
owner: who,
// Every identity here is PQ by construction — there is no other
// kind of key in this registry.
pqEnabled: true,
// Revocation is a leaf that CHANGES, not one that disappears.
// A consumer holding an old proof gets a stale `false`, which is
// why the round is the thing to pin.
frozen: id.revoked,
version: id.version
});
_set(TREE_ACCOUNTS, BRANCH_MAIN, accountKeyFor(who), accountStateLeafHash(leaf));
}
_bump(TREE_ACCOUNTS, accounts.length);
}
/// @notice The tree-8 slot key an identity occupies.
/// @dev Its own domain, separate from the tree-1 account key, so one
/// account's admission row and its state row can never collide.
/// @param account The identity.
/// @return The tree-8 branch-1 key.
function identityKeyFor(address account) public pure returns (bytes32) {
return keccak256(abi.encode(DOMAIN_IDENTITY_TREE_KEY, account));
}
/**
* @notice Project identities into tree 8 — the wallet-creation admission
* set whose live root every execution chain anchors as its
* `currentIdentityRoot`.
*
* @dev The leaf VALUE is the registry's own verdict —
* `FinalIdentityRegistry.identityTreeLeafOf`: the execution chains'
* identity leaf while the identity stands, zero once it does not. Derived
* there rather than here because every input (serial, the six key
* commitments, standing, the CA depth pair) is registry storage, and this
* contract sits against EIP-170 while the registry does not.
*
* Permissionless, for exactly {syncIdentities}' reason: every fact
* written here was authorized when it entered the registry, so this adds
* no authority and only projects. The registry itself calls it same-tx on
* every identity mutation (register, rotate, roles, revoke, LMS-key ops),
* which is what makes the root CONTINUOUS; the open door additionally lets
* anyone retire a leaf whose standing lapsed by TIME — expiry moves no
* registry storage, so no mutation hook can ever fire for it.
*
* There is no quorum door and no writer seat (both raw doors refuse this
* tree), so the strongest thing any caller can do here is copy the
* registry's own verdict.
* @param accounts The identities to project. An unregistered account
* projects the registry's zero verdict, which retires its leaf.
*/
function syncIdentityLeaves(address[] calldata accounts) external {
for (uint256 i = 0; i < accounts.length; i++) {
_set(TREE_IDENTITY, BRANCH_MAIN, identityKeyFor(accounts[i]), registry.identityTreeLeafOf(accounts[i]));
}
_bump(TREE_IDENTITY, accounts.length);
}
/**
* @notice Per-tree quorum health: can each configured tree still be written?
* @dev A threshold above the live member count is not a strict quorum, it is
* a tree that reverts forever with nothing naming the roster as the cause.
* `configureTree` refuses to create that state, but revocation can arrive at
* it later — revocation must never be blocked on quorum arithmetic, so the
* check has to be something monitoring reads rather than something the
* contract enforces after the fact.
* @return live Members currently holding each tree's writer role; zero for
* an unconfigured tree, which is not the same as a starved one.
* @return required Each tree's threshold, indexed by tree id.
* @return ok Whether each tree can still be written. An unconfigured tree
* reports `true`: it is closed, not starved.
*/
function quorumHealth()
external
view
returns (uint256[] memory live, uint256[] memory required, bool[] memory ok)
{
live = new uint256[](TREE_COUNT + 1);
required = new uint256[](TREE_COUNT + 1);
ok = new bool[](TREE_COUNT + 1);
for (uint8 t = 1; t <= TREE_COUNT; t++) {
required[t] = threshold[t];
live[t] = required[t] == 0 ? 0 : registry.liveMemberCount(writerRole[t]);
ok[t] = required[t] == 0 || live[t] >= required[t];
}
}
// -------------------------------------------------------------- internal
/// @notice The chain set a service account's `deployedChains` table is built from.
/// @dev The enabled chain references `chainSource` knows, or none if it is
/// unset. Read through the narrow interface so this contract need not
/// import the registry that imports it. An unset source answers an
/// empty list rather than reverting, because a plane whose registry is
/// not yet seeded must still be able to project its identities.
/// @return The enabled chain references, or an empty list when unset.
function _enabledChainRefs() private view returns (bytes32[] memory) {
address source = chainSource;
if (source == address(0)) return new bytes32[](0);
return IChainSource(source).enabledChainRefs();
}
/// @notice Refuse a tree id outside `1 .. TREE_COUNT`.
/// @dev Trees are 1-indexed so a tree id doubles as its position in the
/// round tree; id 0 is the unused position there and not a tree here.
/// @param treeId The id to check.
function _assertTree(uint8 treeId) private pure {
if (treeId == 0 || treeId > TREE_COUNT) revert UnknownTree(treeId);
}
/// @notice Refuse a branch id no slot can encode.
/// @dev The bound is the branch COUNT, not the count of branches in use: an
/// unused branch is a legal, empty subtree.
/// @param branch The id to check.
function _assertBranch(uint8 branch) private pure {
if (branch >= BRANCH_COUNT) revert UnknownBranch(branch);
}
/// @notice Refuse a branch a quorum or a writer contract may not write.
/// @dev A branch a quorum or a writer may write: any but the config branch.
/// Branch 0 belongs to the configuration authority on every tree, so
/// the refusal is structural rather than per-tree.
/// @param treeId The tree, carried so the revert names it.
/// @param branch The branch being written.
function _assertDataBranch(uint8 treeId, uint8 branch) private pure {
_assertBranch(branch);
if (branch == BRANCH_CONFIG) revert ConfigBranchReserved(treeId);
}
/// @notice Advance a tree's write counter and announce the new root.
/// @dev Version + event, the tail of every write door. Called AFTER the
/// leaves have settled, so the event carries the root a reader will
/// see, and the counter is what {publishRound} compares to decide
/// whether a round would carry anything new.
/// @param treeId The tree that moved.
/// @param count Leaves in the batch, for the event.
function _bump(uint8 treeId, uint256 count) private {
uint64 v = treeVersion[treeId] + 1;
treeVersion[treeId] = v;
emit LeavesSet(treeId, count, liveRoot[treeId], v);
}
/// @notice The one internal-node hash every tree, branch and round shares.
/// @dev `keccak256(0x01 ‖ lo ‖ hi)`, the pair sorted — the one node hash.
/// Sorting is what makes a proof position-agnostic, so it carries no
/// direction bits; the 0x01 tag is what keeps an internal node from
/// ever colliding with a leaf, which is hashed under 0x00.
/// @param a One child.
/// @param b The other child.
/// @return The parent node.
function _pair(bytes32 a, bytes32 b) private pure returns (bytes32) {
(bytes32 lo, bytes32 hi) = a < b ? (a, b) : (b, a);
return keccak256(abi.encodePacked(bytes1(0x01), lo, hi));
}
/// @notice Collect the siblings from a slot up a given number of levels.
/// @dev The sibling path from a slot up `height` levels. One routine serves
/// the branch proof and the tree proof; only the height differs, which
/// is why the two can never disagree about a shared prefix.
/// @param treeId The tree to read.
/// @param idx The starting slot. Consumed as the walk climbs.
/// @param height How many levels to climb.
/// @return path The siblings, lowest level first.
function _path(uint8 treeId, uint256 idx, uint256 height) private view returns (bytes32[] memory path) {
path = new bytes32[](height);
for (uint256 l = 0; l < height; l++) {
path[l] = _nodeAt(treeId, l, idx ^ 1);
idx >>= 1;
}
}
/// @notice Lay the tree roots out as the leaves of the round tree.
/// @dev The forest's leaves: the tree roots at their positions, the
/// empty tree at the rest. Tree `t` sits at position `t`, so the
/// round proof's index is the tree id with no translation, and the
/// unused positions hold the empty TREE root rather than zero — they
/// are genuinely empty trees, and hashing them as zero would make the
/// round root unreproducible off chain.
/// @param roots The round's tree roots, indexed by tree id.
/// @return level The `1 << FOREST_BITS` leaves of the round tree.
function _forestLeaves(bytes32[TREE_COUNT + 1] memory roots) private view returns (bytes32[] memory level) {
level = new bytes32[](1 << FOREST_BITS);
for (uint256 p = 0; p < level.length; p++) {
level[p] = (p >= 1 && p <= TREE_COUNT) ? roots[p] : _zero[DEPTH];
}
}
/// @notice Fold the round tree's leaves down to the round root.
/// @dev Fold a power-of-two level to its root, in place. The input array is
/// overwritten, so the caller must not reuse it afterwards.
/// @param level The level to fold. Length must be a power of two.
/// @return The root of that level.
function _foldForest(bytes32[] memory level) private pure returns (bytes32) {
for (uint256 n = level.length; n > 1; n >>= 1) {
for (uint256 i = 0; i < n / 2; i++) {
level[i] = _pair(level[2 * i], level[2 * i + 1]);
}
}
return level[0];
}
/// @notice Place one leaf, assigning the key a permanent slot on first sight.
/// @dev The single point every write door funnels through, which is what
/// makes the slot discipline unconditional: a key is handed the next
/// free position in its branch, remembered in both directions, and
/// keeps it for the life of the contract. A key that already holds a
/// slot in a DIFFERENT branch is refused rather than moved — moving it
/// would silently invalidate every proof anyone holds for it.
///
/// The update then rehashes exactly `DEPTH` nodes up the leaf's own
/// path, so the cost of a write is the height of the tree and not the
/// number of leaves in it. This is also where the tree's shape comes
/// from: fixed height, zero-padded siblings, insertion-ordered slots.
/// @param treeId The tree to write.
/// @param branch The branch the key belongs to.
/// @param key The domain key.
/// @param leaf The raw (untagged) value to store.
function _set(uint8 treeId, uint8 branch, bytes32 key, bytes32 leaf) private {
uint256 s = _slotPlusOne[treeId][key];
uint256 idx;
if (s == 0) {
uint256 used = _branchSlotsUsed[treeId][branch];
if (used >= BRANCH_CAPACITY) revert BranchFull(treeId, branch);
idx = (uint256(branch) << BRANCH_DEPTH) | used;
_branchSlotsUsed[treeId][branch] = used + 1;
slotsUsed[treeId] += 1;
_slotPlusOne[treeId][key] = idx + 1;
_keyAt[treeId][idx] = key;
} else {
idx = s - 1;
uint8 have = uint8(idx >> BRANCH_DEPTH);
if (have != branch) revert BranchMismatch(treeId, key, have, branch);
}
_leaf[treeId][idx] = leaf;
bytes32 cursor = keccak256(abi.encodePacked(bytes1(0x00), leaf));
for (uint256 l = 0; l < DEPTH; l++) {
cursor = _pair(cursor, _nodeAt(treeId, l, idx ^ 1));
idx >>= 1;
_node[treeId][l + 1][idx] = cursor;
}
liveRoot[treeId] = cursor;
}
/// @notice One node of a tree, at any level, with empty positions filled in.
/// @dev Level 0 is derived from the leaf store rather than duplicated into
/// `_node`, so there is one place a leaf lives and no way for the two to
/// disagree. Unset positions fall through to the empty-subtree hash — the
/// zero padding that gives the tree its fixed height, and the reason an
/// off-chain rebuild must pad to the same height to reach the same root.
/// @param treeId The tree to read.
/// @param level The level, 0 being the leaves.
/// @param index The position at that level.
/// @return The node, or the empty-subtree hash when nothing was written there.
function _nodeAt(uint8 treeId, uint256 level, uint256 index) private view returns (bytes32) {
if (level == 0) {
return keccak256(abi.encodePacked(bytes1(0x00), _leaf[treeId][index]));
}
bytes32 v = _node[treeId][level][index];
return v == bytes32(0) ? _zero[level] : v;
}
// ------------------------------------------------------------------ sweep
/// @notice The registry the inherited sweep authority resolves members through.
/// @dev This contract's configuration gate reads the membership registry it
/// was constructed against, so the sweep authority reads the same one. One
/// registry for both means a member removed from the roster loses the sweep
/// at the same instant it loses everything else.
/// @return The immutable identity registry pinned at construction.
function _sweepRegistry() internal view override returns (FinalIdentityRegistry) {
return registry;
}
/// @dev Nothing is reserved because nothing is owed: this contract has no
/// payable entrypoint and no custody line — it records, it does not hold.
/// Anything it carries arrived by accident and is sweepable in full.
}
contracts/utils/FinalSweep.sol
// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2024-2026 Final DeFi
// Licensed under the Business Source License 1.1 (the "License")
//
// Change Date: 2029-01-01
// Change License: GPL-2.0-or-later
//
// Additional Use Grant:
// 1. Any person or entity may inherit this sweep surface into contracts that
// integrate with the Final DeFi Protocol, in order to recover assets sent to
// them by mistake.
// 2. Protocol operators and integrators may call the sweep entrypoints it
// declares, subject to each inheriting contract's own authority and reserved
// balance rules, as part of their integration with the Final DeFi Protocol.
// 3. For the avoidance of doubt, this Grant does NOT permit the commercial
// deployment of a Fork of this sweep surface or a competing asset-recovery
// plane derived from it without permission prior to the Change Date.
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
/// @notice The asset kinds a sweep can move. `Native` ignores `asset` and
/// `id`; `Erc20` ignores `id`; `Erc721` reads `id` as the token id and moves
/// exactly one; `Erc1155` reads both.
enum SweepKind { Native, Erc20, Erc721, Erc1155 }
/**
* @title Final Sweep
* @notice One sweep surface, on every contract of ours that can end up holding
* an asset it does not owe to anybody.
*
* @dev Assets arrive at protocol contracts that were never meant to hold them:
* a bridge delivers to the wrong leg, a user sends an ERC-20 to a registry, an
* airdrop lands on the gateway, an NFT is safe-transferred into the vault. Left
* alone that value is destroyed. The sweep is how it comes back — and the
* single rule it must never break is that a sweep moves SURPLUS and nothing
* else.
*
* Three seams make that rule per-contract:
*
* - `_requireSweepAuthority()` — the treasury role, expressed in whatever
* access plane the host contract already has (`FinalAccessController` roles,
* a cross-chain authority, a quorum). No new authority is introduced.
* - `_sweepDestinations()` — where a sweep may pay. Ours is a two-address
* answer because a contract normally has exactly two legitimate ones (the
* gateway and the treasury); a contract with one returns it twice.
* `FinalGateway` overrides `_requireSweepDestination` outright: the gateway
* is the drain of the whole system and sweeps ONWARD to anywhere.
* - `_sweepReserved(kind, asset, id)` — the part of the raw balance that is
* NOT surplus: fee deposits, the pending-settlement bucket, searcher
* collateral, settlement custody, vaulted entries, locked PHI. The default
* is zero, which is correct for a contract that custodies nothing; every
* contract that custodies something overrides it and is the one place the
* liability is stated.
*
* The surplus is measured LIVE against the raw balance at call time, so a
* re-entrant destination re-measures against a balance that already fell —
* there is no cached figure to double-spend. Nothing here writes storage, so
* there is no state for a callback to observe half-updated either.
*
* The three ERC-721/ERC-1155 receiver hooks are part of the same surface and
* for the same reason: `safeTransferFrom` reverts into a contract that does not
* answer them, so without these an NFT sent to one of ours does not land at
* all — which is not safety, it is a different way to lose it.
*/
abstract contract FinalSweep {
/// @notice `msg.sender` does not hold this contract's sweep authority.
error SweepUnauthorized(address caller);
/// @notice `to` is neither of this contract's sweep destinations.
error SweepDestinationNotAllowed(address to);
/// @notice The requested amount is above the surplus: the difference is
/// owed to somebody (a deposit, a custody total, a vaulted entry).
error SweepAboveSurplus(address asset, uint256 requested, uint256 surplus);
/// @notice A sweep of nothing.
error SweepZeroAmount();
/// @notice The transfer leg failed, or the token returned `false`.
error SweepTransferFailed(address asset);
/// @notice `amount` of `asset` (`id` for the non-fungible kinds) left this
/// contract for `to` under the sweep authority.
event AssetSwept(SweepKind indexed kind, address indexed asset, address indexed to, uint256 id, uint256 amount);
// ─────────────────────────────── seams ───────────────────────────────
/// @dev Reverts unless `msg.sender` may sweep. The host contract's own
/// treasury role — never a new one.
function _requireSweepAuthority() internal view virtual;
/// @dev The (at most two) addresses a sweep may pay. A contract with one
/// legitimate destination returns it twice.
function _sweepDestinations() internal view virtual returns (address a, address b);
/// @dev The part of the raw balance that is owed and therefore never
/// sweepable. Zero for a contract that custodies nothing.
function _sweepReserved(SweepKind, address, uint256) internal view virtual returns (uint256) {
return 0;
}
/// @dev Destination policy. Overridden by `FinalGateway`, which may sweep
/// onward to anywhere.
function _requireSweepDestination(address to) internal view virtual {
(address a, address b) = _sweepDestinations();
if (to == address(0) || (to != a && to != b)) revert SweepDestinationNotAllowed(to);
}
// ────────────────────────────── surface ──────────────────────────────
/// @notice The surplus of `asset` (`id` for the non-fungible kinds) — the
/// raw balance above everything this contract owes. What a sweep may move,
/// readable before calling one.
function sweepableSurplus(SweepKind kind, address asset, uint256 id) public view returns (uint256 surplus) {
uint256 raw = _rawBalance(kind, asset, id);
uint256 reserved = _sweepReserved(kind, asset, id);
return raw > reserved ? raw - reserved : 0;
}
/// @notice Move `amount` of an asset this contract does not owe to `to`.
/// @dev Role-gated, destination-gated and bounded by the live surplus. The
/// three gates are independent: a treasury key cannot pay a destination
/// the contract does not recognize, and neither key nor destination can
/// reach a wei that backs a liability.
/// @param kind Which asset kind is being moved.
/// @param asset Token contract; ignored for `Native`.
/// @param id Token id for `Erc721` / `Erc1155`; ignored otherwise.
/// @param amount Amount to move. `type(uint256).max` means the whole
/// surplus, which is what an operator draining a stray balance wants and
/// what avoids a race with an inflow landing between the read and the call.
/// @param to Destination.
/// @return moved Amount actually moved.
function sweepAsset(SweepKind kind, address asset, uint256 id, uint256 amount, address to)
external
returns (uint256 moved)
{
_requireSweepAuthority();
_requireSweepDestination(to);
uint256 surplus = sweepableSurplus(kind, asset, id);
moved = amount == type(uint256).max ? surplus : amount;
if (moved == 0) revert SweepZeroAmount();
if (moved > surplus) revert SweepAboveSurplus(asset, moved, surplus);
if (kind == SweepKind.Native) {
(bool ok,) = payable(to).call{value: moved}("");
if (!ok) revert SweepTransferFailed(address(0));
} else if (kind == SweepKind.Erc20) {
_callToken(asset, abi.encodeWithSelector(0xa9059cbb, to, moved)); // transfer(address,uint256)
} else if (kind == SweepKind.Erc721) {
// `transferFrom`, not `safeTransferFrom`: a rescue must not fail
// because the treasury destination declines a hook. Which
// destination is legitimate is already decided above.
moved = 1;
_callToken(asset, abi.encodeWithSelector(0x23b872dd, address(this), to, id)); // transferFrom
} else {
_callToken(
asset,
abi.encodeWithSelector(0xf242432a, address(this), to, id, moved, "") // safeTransferFrom(...)
);
}
emit AssetSwept(kind, asset, to, id, moved);
}
// ───────────────────────────── receivers ─────────────────────────────
/// @notice Accept safe ERC-721 transfers, so one sent here is recoverable
/// rather than rejected at the door.
function onERC721Received(address, address, uint256, bytes calldata) external pure virtual returns (bytes4) {
return 0x150b7a02;
}
/// @notice Accept safe ERC-1155 single transfers.
function onERC1155Received(address, address, uint256, uint256, bytes calldata)
external
pure
virtual
returns (bytes4)
{
return 0xf23a6e61;
}
/// @notice Accept safe ERC-1155 batch transfers.
function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata)
external
pure
virtual
returns (bytes4)
{
return 0xbc197c81;
}
// ───────────────────────────── internals ─────────────────────────────
/// @dev The raw held amount, before anything owed is subtracted.
function _rawBalance(SweepKind kind, address asset, uint256 id) internal view returns (uint256) {
if (kind == SweepKind.Native) return address(this).balance;
if (kind == SweepKind.Erc20) {
(bool ok, bytes memory ret) = asset.staticcall(abi.encodeWithSelector(0x70a08231, address(this)));
return (ok && ret.length >= 32) ? abi.decode(ret, (uint256)) : 0;
}
if (kind == SweepKind.Erc721) {
(bool ok, bytes memory ret) = asset.staticcall(abi.encodeWithSelector(0x6352211e, id)); // ownerOf
return (ok && ret.length >= 32 && abi.decode(ret, (address)) == address(this)) ? 1 : 0;
}
(bool ok1155, bytes memory ret1155) =
asset.staticcall(abi.encodeWithSelector(0x00fdd58e, address(this), id)); // balanceOf(address,uint256)
return (ok1155 && ret1155.length >= 32) ? abi.decode(ret1155, (uint256)) : 0;
}
/// @dev One transfer leg, tolerant of the legacy no-return ERC-20 shape the
/// way `FinalDeployer`'s rescue helpers are: success is "the call did not
/// revert AND it did not return `false`".
function _callToken(address token, bytes memory data) private {
if (token.code.length == 0) revert SweepTransferFailed(token);
(bool ok, bytes memory ret) = token.call(data);
if (!ok || (ret.length != 0 && !abi.decode(ret, (bool)))) revert SweepTransferFailed(token);
}
}
abi
[
{
"type": "constructor",
"inputs": [
{
"name": "registry_",
"type": "address",
"internalType": "contract FinalIdentityRegistry"
},
{
"name": "trees_",
"type": "address",
"internalType": "contract FinalStateTrees"
},
{
"name": "admin_",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "ACTION_CONFIGURE",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "CAIP_NAMESPACE_EIP155",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "CHAIN_ROLE_FULL",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "CHAIN_ROLE_OBSERVED",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "FINALITY_CONFIRMATIONS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "FINALITY_L2_SETTLED",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "FINALITY_TAG_FINALIZED",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "GAS_MODEL_EIP1559",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "GAS_MODEL_L2_WITH_L1_FEE",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "GAS_MODEL_LEGACY",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "LEVERAGE_CAP_MAX_PCT",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint16",
"internalType": "uint16"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "LEVERAGE_CAP_MIN_PCT",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint16",
"internalType": "uint16"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "PRICE_CADENCE_DEFAULT_MS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint32",
"internalType": "uint32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "PRICE_CADENCE_FAST_MS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint32",
"internalType": "uint32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "PRICE_MAX_AGE_DEFAULT_MS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint32",
"internalType": "uint32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "PRICE_MAX_AGE_FAST_MS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint32",
"internalType": "uint32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "PROTOCOL_CURVE",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "PROTOCOL_UNISWAP_V2",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "PROTOCOL_UNISWAP_V3",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "PROTOCOL_UNISWAP_V4",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "PROTOCOL_VELODROME",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "VM_EVM",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "VM_MOVE",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "VM_SVM",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "admin",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "allowlistKeyForAsset",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "allowlistKeyForAssetOnChain",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "allowlistKeyForBuilder",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "builderId",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "allowlistKeyForChain",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "allowlistKeyForChainTerms",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "allowlistKeyForPolicy",
"inputs": [
{
"name": "paramId",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "allowlistKeyForProtocol",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "protocolId",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "allowlistKeyForSource",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "venueId",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "assetAt",
"inputs": [
{
"name": "i",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.AssetEntry",
"components": [
{
"name": "originChainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "originToken",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "decimals",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "name",
"type": "string",
"internalType": "string"
},
{
"name": "symbol",
"type": "string",
"internalType": "string"
},
{
"name": "uses",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "priceCadenceMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "maxAgeMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "assetCount",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "assetIdFor",
"inputs": [
{
"name": "originChainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "originToken",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "assetLeafHash",
"inputs": [
{
"name": "a",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.AssetEntry",
"components": [
{
"name": "originChainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "originToken",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "decimals",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "name",
"type": "string",
"internalType": "string"
},
{
"name": "symbol",
"type": "string",
"internalType": "string"
},
{
"name": "uses",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "priceCadenceMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "maxAgeMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "assetOf",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.AssetEntry",
"components": [
{
"name": "originChainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "originToken",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "decimals",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "name",
"type": "string",
"internalType": "string"
},
{
"name": "symbol",
"type": "string",
"internalType": "string"
},
{
"name": "uses",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "priceCadenceMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "maxAgeMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "assetOnChain",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.AssetChainEntry",
"components": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "uses",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "maxLeveragePct",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "token",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "morph",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "vAsset",
"type": "bytes32",
"internalType": "bytes32"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "assetRegistryRoot",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "assetsFor",
"inputs": [
{
"name": "use",
"type": "uint8",
"internalType": "uint8"
}
],
"outputs": [
{
"name": "out",
"type": "tuple[]",
"internalType": "struct FinalAssetRegistry.AssetEntry[]",
"components": [
{
"name": "originChainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "originToken",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "decimals",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "name",
"type": "string",
"internalType": "string"
},
{
"name": "symbol",
"type": "string",
"internalType": "string"
},
{
"name": "uses",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "priceCadenceMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "maxAgeMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "assetsOnChainFor",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "use",
"type": "uint8",
"internalType": "uint8"
}
],
"outputs": [
{
"name": "out",
"type": "tuple[]",
"internalType": "struct FinalAssetRegistry.AssetChainEntry[]",
"components": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "uses",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "maxLeveragePct",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "token",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "morph",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "vAsset",
"type": "bytes32",
"internalType": "bytes32"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "builderIdsOn",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32[]",
"internalType": "bytes32[]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "builderOf",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "builderId",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.BuilderEntry",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "builderId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "flavour",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "endpointHash",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "capabilities",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "buildersOn",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "out",
"type": "tuple[]",
"internalType": "struct FinalAssetRegistry.BuilderEntry[]",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "builderId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "flavour",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "endpointHash",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "capabilities",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "chainAt",
"inputs": [
{
"name": "i",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.ChainEntry",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "caipNamespace",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "caipReference",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "settlement",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "accountSpace",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "nativeAsset",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "wrappedNative",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "finalityKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "finalityParam",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "blockTimeMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "gasReadBlocks",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "gasHistoryBlocks",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "multicall",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "gasModel",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "l1ChainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "gateway",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "vmKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "startHeight",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "role",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "paymaster",
"type": "bytes32",
"internalType": "bytes32"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "chainCount",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "chainLeafHash",
"inputs": [
{
"name": "c",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.ChainEntry",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "caipNamespace",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "caipReference",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "settlement",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "accountSpace",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "nativeAsset",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "wrappedNative",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "finalityKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "finalityParam",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "blockTimeMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "gasReadBlocks",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "gasHistoryBlocks",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "multicall",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "gasModel",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "l1ChainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "gateway",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "vmKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "startHeight",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "role",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "paymaster",
"type": "bytes32",
"internalType": "bytes32"
}
]
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "chainOf",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.ChainEntry",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "caipNamespace",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "caipReference",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "settlement",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "accountSpace",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "nativeAsset",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "wrappedNative",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "finalityKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "finalityParam",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "blockTimeMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "gasReadBlocks",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "gasHistoryBlocks",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "multicall",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "gasModel",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "l1ChainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "gateway",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "vmKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "startHeight",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "role",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "paymaster",
"type": "bytes32",
"internalType": "bytes32"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "chainRefFor",
"inputs": [
{
"name": "namespace",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "caipRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "chainRegistryRoot",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "chainTermsOf",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.ChainTermsEntry",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "defaultLane",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "lanes",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "quoteWindowSeconds",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "floatPremiumBps",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "admissionFloorUsdMicros",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "maxLegsPerIntent",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "configure",
"inputs": [
{
"name": "role",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "k",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "anchorBlock",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "approvals",
"type": "tuple[]",
"internalType": "struct FinalPqQuorum.Approval[]",
"components": [
{
"name": "signer",
"type": "address",
"internalType": "address"
},
{
"name": "algorithm",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "signature",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "seal",
"type": "bytes",
"internalType": "bytes"
}
]
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "dexProtocolIdsOn",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32[]",
"internalType": "bytes32[]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "dexProtocolOf",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "protocolId",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.DexProtocolEntry",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "protocolId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "protocolKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "factory",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "quoter",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "router",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "positionManager",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "enabledChainRefs",
"inputs": [],
"outputs": [
{
"name": "out",
"type": "bytes32[]",
"internalType": "bytes32[]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "evmChainRef",
"inputs": [
{
"name": "chainId",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "leverageCapPct",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainId",
"type": "uint64",
"internalType": "uint64"
}
],
"outputs": [
{
"name": "",
"type": "uint16",
"internalType": "uint16"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "mutate",
"inputs": [
{
"name": "chainUpdates",
"type": "tuple[]",
"internalType": "struct FinalAssetRegistry.ChainEntry[]",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "caipNamespace",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "caipReference",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "settlement",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "accountSpace",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "nativeAsset",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "wrappedNative",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "finalityKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "finalityParam",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "blockTimeMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "gasReadBlocks",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "gasHistoryBlocks",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "multicall",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "gasModel",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "l1ChainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "gateway",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "vmKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "startHeight",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "role",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "paymaster",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"name": "assetUpdates",
"type": "tuple[]",
"internalType": "struct FinalAssetRegistry.AssetEntry[]",
"components": [
{
"name": "originChainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "originToken",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "decimals",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "name",
"type": "string",
"internalType": "string"
},
{
"name": "symbol",
"type": "string",
"internalType": "string"
},
{
"name": "uses",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "priceCadenceMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "maxAgeMs",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
},
{
"name": "assetChainUpdates",
"type": "tuple[]",
"internalType": "struct FinalAssetRegistry.AssetChainEntry[]",
"components": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "uses",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "maxLeveragePct",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "token",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "morph",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "vAsset",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"name": "anchorBlock",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "approvals",
"type": "tuple[]",
"internalType": "struct FinalPqQuorum.Approval[]",
"components": [
{
"name": "signer",
"type": "address",
"internalType": "address"
},
{
"name": "algorithm",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "signature",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "seal",
"type": "bytes",
"internalType": "bytes"
}
]
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "nonce",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint64",
"internalType": "uint64"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "onERC1155BatchReceived",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
},
{
"name": "",
"type": "address",
"internalType": "address"
},
{
"name": "",
"type": "uint256[]",
"internalType": "uint256[]"
},
{
"name": "",
"type": "uint256[]",
"internalType": "uint256[]"
},
{
"name": "",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [
{
"name": "",
"type": "bytes4",
"internalType": "bytes4"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "onERC1155Received",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
},
{
"name": "",
"type": "address",
"internalType": "address"
},
{
"name": "",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [
{
"name": "",
"type": "bytes4",
"internalType": "bytes4"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "onERC721Received",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
},
{
"name": "",
"type": "address",
"internalType": "address"
},
{
"name": "",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [
{
"name": "",
"type": "bytes4",
"internalType": "bytes4"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "policyAt",
"inputs": [
{
"name": "i",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.PolicyEntry",
"components": [
{
"name": "paramId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "policyCount",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "policyOf",
"inputs": [
{
"name": "paramId",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.PolicyEntry",
"components": [
{
"name": "paramId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "priceSourceByKey",
"inputs": [
{
"name": "key",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.PriceSourceEntry",
"components": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "venueId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "venueKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "venue",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "protocolId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "symbol",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "quoteAsset",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "weight",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "baseToken",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "quoteToken",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "baseIsToken0",
"type": "bool",
"internalType": "bool"
},
{
"name": "twapWindowSeconds",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "twapMinWindowSeconds",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "minLiquidity",
"type": "uint128",
"internalType": "uint128"
},
{
"name": "maxSpotDeviationBps",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "priceSourceKeys",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32[]",
"internalType": "bytes32[]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "priceSourceKeysFor",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32[]",
"internalType": "bytes32[]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "priceSourceOf",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "venueId",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAssetRegistry.PriceSourceEntry",
"components": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "venueId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "venueKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "venue",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "protocolId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "symbol",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "quoteAsset",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "weight",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "baseToken",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "quoteToken",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "baseIsToken0",
"type": "bool",
"internalType": "bool"
},
{
"name": "twapWindowSeconds",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "twapMinWindowSeconds",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "minLiquidity",
"type": "uint128",
"internalType": "uint128"
},
{
"name": "maxSpotDeviationBps",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "publisherRole",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "registry",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract FinalIdentityRegistry"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "registryEpoch",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint64",
"internalType": "uint64"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "registryRootKey",
"inputs": [
{
"name": "which",
"type": "uint8",
"internalType": "uint8"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "pure"
},
{
"type": "function",
"name": "seal",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "seedEpoch",
"inputs": [
{
"name": "epoch_",
"type": "uint64",
"internalType": "uint64"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "setBuilders",
"inputs": [
{
"name": "updates",
"type": "tuple[]",
"internalType": "struct FinalAssetRegistry.BuilderEntry[]",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "builderId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "flavour",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "endpointHash",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "capabilities",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
},
{
"name": "anchorBlock",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "approvals",
"type": "tuple[]",
"internalType": "struct FinalPqQuorum.Approval[]",
"components": [
{
"name": "signer",
"type": "address",
"internalType": "address"
},
{
"name": "algorithm",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "signature",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "seal",
"type": "bytes",
"internalType": "bytes"
}
]
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "setChainTerms",
"inputs": [
{
"name": "updates",
"type": "tuple[]",
"internalType": "struct FinalAssetRegistry.ChainTermsEntry[]",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "defaultLane",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "lanes",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "quoteWindowSeconds",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "floatPremiumBps",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "admissionFloorUsdMicros",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "maxLegsPerIntent",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
},
{
"name": "anchorBlock",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "approvals",
"type": "tuple[]",
"internalType": "struct FinalPqQuorum.Approval[]",
"components": [
{
"name": "signer",
"type": "address",
"internalType": "address"
},
{
"name": "algorithm",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "signature",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "seal",
"type": "bytes",
"internalType": "bytes"
}
]
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "setPolicy",
"inputs": [
{
"name": "updates",
"type": "tuple[]",
"internalType": "struct FinalAssetRegistry.PolicyEntry[]",
"components": [
{
"name": "paramId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
},
{
"name": "anchorBlock",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "approvals",
"type": "tuple[]",
"internalType": "struct FinalPqQuorum.Approval[]",
"components": [
{
"name": "signer",
"type": "address",
"internalType": "address"
},
{
"name": "algorithm",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "signature",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "seal",
"type": "bytes",
"internalType": "bytes"
}
]
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "setSources",
"inputs": [
{
"name": "protocolUpdates",
"type": "tuple[]",
"internalType": "struct FinalAssetRegistry.DexProtocolEntry[]",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "protocolId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "protocolKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "factory",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "quoter",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "router",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "positionManager",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
},
{
"name": "sourceUpdates",
"type": "tuple[]",
"internalType": "struct FinalAssetRegistry.PriceSourceEntry[]",
"components": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "venueId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "venueKind",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "venue",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "protocolId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "symbol",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "quoteAsset",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "weight",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "baseToken",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "quoteToken",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "baseIsToken0",
"type": "bool",
"internalType": "bool"
},
{
"name": "twapWindowSeconds",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "twapMinWindowSeconds",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "minLiquidity",
"type": "uint128",
"internalType": "uint128"
},
{
"name": "maxSpotDeviationBps",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "enabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"internalType": "uint64"
}
]
},
{
"name": "anchorBlock",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "approvals",
"type": "tuple[]",
"internalType": "struct FinalPqQuorum.Approval[]",
"components": [
{
"name": "signer",
"type": "address",
"internalType": "address"
},
{
"name": "algorithm",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "signature",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "seal",
"type": "bytes",
"internalType": "bytes"
}
]
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "sweepAsset",
"inputs": [
{
"name": "kind",
"type": "uint8",
"internalType": "enum SweepKind"
},
{
"name": "asset",
"type": "address",
"internalType": "address"
},
{
"name": "id",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "amount",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "to",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "moved",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "sweepableSurplus",
"inputs": [
{
"name": "kind",
"type": "uint8",
"internalType": "enum SweepKind"
},
{
"name": "asset",
"type": "address",
"internalType": "address"
},
{
"name": "id",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "surplus",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "threshold",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "trees",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract FinalStateTrees"
}
],
"stateMutability": "view"
},
{
"type": "event",
"name": "AssetChainSet",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "uses",
"type": "uint8",
"indexed": false,
"internalType": "uint8"
},
{
"name": "enabled",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "AssetSet",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "uses",
"type": "uint8",
"indexed": false,
"internalType": "uint8"
},
{
"name": "enabled",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "AssetSwept",
"inputs": [
{
"name": "kind",
"type": "uint8",
"indexed": true,
"internalType": "enum SweepKind"
},
{
"name": "asset",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "to",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "id",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "amount",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"type": "event",
"name": "BuilderSet",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "builderId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "flavour",
"type": "uint8",
"indexed": false,
"internalType": "uint8"
},
{
"name": "endpointHash",
"type": "bytes32",
"indexed": false,
"internalType": "bytes32"
},
{
"name": "capabilities",
"type": "uint16",
"indexed": false,
"internalType": "uint16"
},
{
"name": "enabled",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "ChainSet",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "enabled",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "ChainTermsSet",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "defaultLane",
"type": "uint8",
"indexed": false,
"internalType": "uint8"
},
{
"name": "lanes",
"type": "uint16",
"indexed": false,
"internalType": "uint16"
},
{
"name": "quoteWindowSeconds",
"type": "uint32",
"indexed": false,
"internalType": "uint32"
},
{
"name": "floatPremiumBps",
"type": "uint16",
"indexed": false,
"internalType": "uint16"
},
{
"name": "epoch",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "DexProtocolSet",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "protocolId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "enabled",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "EpochSeeded",
"inputs": [
{
"name": "epoch",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "PolicySet",
"inputs": [
{
"name": "paramId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "value",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "enabled",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "PriceSourceSet",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "venueId",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "enabled",
"type": "bool",
"indexed": false,
"internalType": "bool"
},
{
"name": "epoch",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "RegistryConfigured",
"inputs": [
{
"name": "role",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "threshold",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"type": "event",
"name": "RootsPublished",
"inputs": [
{
"name": "chainRoot",
"type": "bytes32",
"indexed": false,
"internalType": "bytes32"
},
{
"name": "assetRoot",
"type": "bytes32",
"indexed": false,
"internalType": "bytes32"
},
{
"name": "epoch",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "Sealed",
"inputs": [],
"anonymous": false
},
{
"type": "error",
"name": "AlreadySealed",
"inputs": []
},
{
"type": "error",
"name": "AnchorAhead",
"inputs": [
{
"name": "anchorBlock",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "blockNumber",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"type": "error",
"name": "AnchorStale",
"inputs": [
{
"name": "anchorBlock",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "blockNumber",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"type": "error",
"name": "AssetNotRegistered",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "BadSeal",
"inputs": [
{
"name": "signer",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "BadSignature",
"inputs": [
{
"name": "signer",
"type": "address",
"internalType": "address"
},
{
"name": "algorithm",
"type": "uint8",
"internalType": "uint8"
}
]
},
{
"type": "error",
"name": "ChainNotRegistered",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "ChainRefMismatch",
"inputs": [
{
"name": "claimed",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "derived",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "EmptyBatch",
"inputs": []
},
{
"type": "error",
"name": "IncompleteChainDescription",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "InvalidBuilder",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "builderId",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "InvalidChainRole",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "role",
"type": "uint8",
"internalType": "uint8"
}
]
},
{
"type": "error",
"name": "InvalidDexProtocol",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "protocolId",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "InvalidPriceSource",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "venueId",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "LaneNotAvailable",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "defaultLane",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "lanes",
"type": "uint16",
"internalType": "uint16"
}
]
},
{
"type": "error",
"name": "LeverageCapOutOfRange",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "maxLeveragePct",
"type": "uint16",
"internalType": "uint16"
}
]
},
{
"type": "error",
"name": "NotAdmin",
"inputs": [
{
"name": "caller",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "NotAnEvmAddress",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "field",
"type": "string",
"internalType": "string"
},
{
"name": "word",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "NotConfigured",
"inputs": []
},
{
"type": "error",
"name": "NotFresh",
"inputs": []
},
{
"type": "error",
"name": "PrecompileUnavailable",
"inputs": [
{
"name": "precompile",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "SettlementRequired",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "SignerLacksRole",
"inputs": [
{
"name": "signer",
"type": "address",
"internalType": "address"
},
{
"name": "roleMask",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"type": "error",
"name": "SignersNotAscending",
"inputs": [
{
"name": "previous",
"type": "address",
"internalType": "address"
},
{
"name": "next",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "SweepAboveSurplus",
"inputs": [
{
"name": "asset",
"type": "address",
"internalType": "address"
},
{
"name": "requested",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "surplus",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"type": "error",
"name": "SweepDestinationNotAllowed",
"inputs": [
{
"name": "to",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "SweepTransferFailed",
"inputs": [
{
"name": "asset",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "SweepUnauthorized",
"inputs": [
{
"name": "caller",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "SweepZeroAmount",
"inputs": []
},
{
"type": "error",
"name": "ThresholdIsZero",
"inputs": []
},
{
"type": "error",
"name": "ThresholdNotMet",
"inputs": [
{
"name": "valid",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "required",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"type": "error",
"name": "ThresholdUnreachable",
"inputs": [
{
"name": "live",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "asked",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"type": "error",
"name": "UnknownAsset",
"inputs": [
{
"name": "assetId",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "UnknownChain",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "UseBitOutOfScope",
"inputs": [
{
"name": "uses",
"type": "uint8",
"internalType": "uint8"
}
]
},
{
"type": "error",
"name": "VmKindMismatch",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "vmKind",
"type": "uint8",
"internalType": "uint8"
}
]
},
{
"type": "error",
"name": "WrongAlgorithm",
"inputs": [
{
"name": "signer",
"type": "address",
"internalType": "address"
},
{
"name": "got",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "required",
"type": "uint8",
"internalType": "uint8"
}
]
}
]read contract
bytecode · 33,519 bytes
0x610120806040526004361015610013575f80fd5b5f610100525f3560e01c9081630292709e14615f3857508063036f9a4314615f1a5780630489d03814615e5e578063087a5cf414615deb57806308f0ce6214613af45780630900767d14615dd25780630eca186714615db9578063150b7a0214615d635780631591c4a014615741578063178bcc93146156fa5780631945098e146106805780631a633473146105605780631e000aa5146155f8578063200d43ad1461498e57806320d4f69614610560578063210de79d1461493f5780632b89089c146149205780633ec4cc34146148d55780633fb27b851461486657806342cde4e8146148465780634760fd43146147da5780634bcb06a9146147c15780634e2ed4bb146141e75780634ec824ff14613af45780635483a0b51461419f57806357d70648146141815780635b88f50e14613af45780635eb320b8146141615780635f61b15c14613af4578063608ed2ec146140ee57806360a18008146140ba57806360c29fac14610680578063622c3f211461409b57806365cea49c1461407d57806368cf465e146106805780636c2c44e2146140645780636db8175214614044578063711f3db414613f62578063767c39d2146105605780637b10399914613f1b5780637bb737bc14613f025780637dcdde8e14613d7f5780637f49d69f14613d55578063831f33a114613b355780638513e2d514613b17578063852a52e814613af957806388f06eaa14613af457806392a2e0b314613ad65780639377c22014613aad5780639493ba1d14613a3a57806394bc4e961461372d57806395102c421461370e5780639648eca21461344e57806396f51f3a1461319957806397b9857b1461312d578063a741113c146130f3578063aa9239f514613097578063ac65bc7014612f6e578063ad844b9614612ec5578063affed0e014612e9c578063b19f480514612e5f578063b8da302a14612e3f578063b9a7f07614612d56578063b9ed468614610cba578063bbbcda4414610c9c578063bc197c8114610c04578063bda1019e14610be4578063be380c8514610a63578063c5dbda6614610a44578063dc544a0914610a23578063de54d42914610a03578063df46c7131461089d578063e02e1bfd1461087d578063e597e65914610685578063e70475a714610680578063e94a52af14610662578063eafe7a7414610642578063eb77738214610565578063ee076a5014610560578063ee57e33c146104cb578063f23a6e6114610475578063f3178f4314610412578063f4e885db146103f25763f851a440146103be575f80fd5b346103eb57610100513660031901126103eb5761010051546040516001600160a01b039091168152602090f35b6101005180fd5b346103eb57610100513660031901126103eb576020600154604051908152f35b346103eb5760403660031901126103eb5761042b6163eb565b6004356101005152600a6020526104516001600160401b03604061010051209216617261565b6101005152602052602061ffff600260406101005120015460501c16604051908152f35b346103eb5760a03660031901126103eb5761048e616527565b5061049761653d565b506084356001600160401b0381116103eb576104b7903690600401616553565b505060405163f23a6e6160e01b8152602090f35b346103eb57610100513660031901126103eb57604051806020601354918281520190601361010051527f66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a09090610100515b81811061054a5761054685610532818703826167d9565b6040519182916020835260208301906164d9565b0390f35b825484526020909301926001928301920161051b565b6165cc565b346103eb57610100513660031901126103eb57600454610584816169be565b610100519091815b8181106105e057505061059e816169be565b91610100515b8281106105c15760405160208082528190610546908201876164d9565b806105ce60019284616a8c565b516105d98287616a8c565b52016105a4565b806105ec6001926169f0565b90549060031b1c6101005152600560205260ff600c60406101005120015460081c16610619575b0161058c565b610622816169f0565b90549060031b1c61063c61063586616bfb565b9587616a8c565b52610613565b346103eb57610100513660031901126103eb576020600754604051908152f35b346103eb57610100513660031901126103eb57602060405160058152f35b6165b1565b346103eb5760403660031901126103eb5760243560043560ff821682036103eb576007546106b281617348565b6101005190939092835b8381106107595785856106ce81617348565b91610100515b82811061072f5783604051809160208201602083528151809152602060408401920190610100515b81811061070a575050500390f35b919350916020610120826107216001948851616471565b0194019101918493926106fc565b8061073c60019284616a8c565b516107478287616a8c565b526107528186616a8c565b50016106d4565b61076281616a1c565b90549060031b1c806101005152600860205260ff600560406101005120015460481c1615610874576101005152600a6020526040610100512082610100515260205260406101005120604051906107b88261674e565b805482526001810154602083015284600282015460ff81166040850152600560ff8260081c161515938460608701526001600160401b038360101c16608087015261ffff8360501c1660a0870152600381015460c0870152600481015460e0870152015461010085015282610865575b505061083a575b506001905b016106bc565b949061085e8261084c60019491616bfb565b97610857828b616a8c565b5288616a8c565b509061082f565b60ff9250161615158489610828565b50600190610834565b346103eb57610100513660031901126103eb576020600454604051908152f35b346103eb5760203660031901126103eb576108b6616601565b600754906108c3826172f9565b6101005190929091825b828110610981575050506108e0816172f9565b91610100515b82811061095757836040518091602082016020835281518091526040830190602060408260051b86010193019161010051905b82821061092857505050500390f35b919360019193955060206109478192603f198a82030186528851616696565b9601920192018594939192610919565b8061096460019284616a8c565b5161096f8287616a8c565b5261097a8186616a8c565b50016108e6565b61098a81616a1c565b90549060031b1c610100515260086020526109aa6040610100512061707c565b6101008101511515806109f1575b6109c6575b506001016108cd565b93906109ea826109d860019491616bfb565b966109e3828a616a8c565b5287616a8c565b50906109bd565b5060ff8360a0830151161615156109b8565b346103eb57610100513660031901126103eb576020600d54604051908152f35b346103eb576020610a3c610a363661645b565b906172b3565b604051908152f35b346103eb57610100513660031901126103eb5760206040516101f48152f35b346103eb576102c03660031901126103eb57604051610a818161676a565b600435815260243560208201526044356040820152606435606082015260843560ff811681036103eb57608082015260a43560a082015260c43560c082015260e43560ff811681036103eb5760e0820152610104356001600160401b03811681036103eb576101008201526101243563ffffffff811681036103eb576101208201526101443561ffff811681036103eb576101408201526101643561ffff811681036103eb57610160820152610184356101808201526101a43560ff811681036103eb576101a08201526101c4356101c08201526101e4356101e08201526102043560ff811681036103eb576102008201526102243580151581036103eb57610220820152610244356001600160401b03811681036103eb57610240820152610264356001600160401b03811681036103eb576102608201526102843560ff811681036103eb5781610a3c9161028060209401526102a4356102a08201526171f1565b346103eb57610100513660031901126103eb576020601754604051908152f35b346103eb5760a03660031901126103eb57610c1d616527565b50610c2661653d565b506044356001600160401b0381116103eb57610c4690369060040161642b565b50506064356001600160401b0381116103eb57610c6790369060040161642b565b50506084356001600160401b0381116103eb57610c88903690600401616553565b505060405163bc197c8160e01b8152602090f35b346103eb5760203660031901126103eb576020610a3c6004356171be565b346103eb5760a03660031901126103eb576001600160401b03600435116103eb573660236004350112156103eb576001600160401b0360043560040135116103eb573660246102c060043560040135026004350101116103eb576024356001600160401b0381116103eb57610d3390369060040161642b565b60c0526044356001600160401b0381116103eb57610d55903690600401616580565b60643592906001600160401b03841684036103eb576084356001600160401b0381116103eb57610d8990369060040161642b565b946002548015612d4157600435600401351580612d37575b80612d2f575b612d1a57600354926040519760a089016001600160401b03861660208b0152608060408b015260043560040135905260c08901602460043501610100515b600435600401358110612b87575050601f198a82030160608b015260c051815260208101602060c05160051b830101908992610100515b60c0518110612a815750505060209150601f198b82030160808c0152878152019888610100515b8881106129ea575050610e6681610f0d9798999a9b03601f1981018352826167d9565b60208151910120610100515060405160208101915f51602061828f5f395f51905f5283524660408301523060608301527fe10634eb0bf7bd6ad00dc59a6c67fe9b09037b979405fa59137e7e530d60141460808301526001600160401b03871660a083015260c082015260c08152610edf60e0826167d9565b51902090600154927f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6173c3565b506001600160401b03610f21818316616975565b16906001600160401b03191617600355601954610f466001600160401b038216616975565b60a0526001600160401b0360a05116906001600160401b03191617601955610f7660c05160043560040135616bd3565b6001600160ff1b03821682036129d057610f97610fa8918360011b90616bd3565b610fa0816169be565b60e0526169be565b6101005192909190835b600435600401358510156118e057610fdb6004356102c0870201606481013590604401356172b3565b60246102c087026004350101350361189e576110026102446102c087026004350101616be0565b80611811575b6117ec57600160ff6110256102a46102c089026004350101616bed565b16116117ad576110406102446102c087026004350101616be0565b8061178e575b8061177a575b611755576004356102c0860201604401355f51602061826f5f395f51905f52148080611733575b6116f4576116ad575b6101e46102c08602600435010135151580611683575b61165d5760246102c086026004350101356101005152600660205260ff604061010051205416156115ca575b61010080516102c08702600435016024810135918290526005602052915160409020908155604482013560018201556064820135600282019081556084830135600383015590916111119060a401616bed565b6004838101805460ff191660ff939093169290921790915560c490356102c089020190810135600584015560e481013560068401556111539061010401616bed565b60ff1660ff196007840154161760078301556102c08702600435016101240161117b906171aa565b60078301906111a8919068ffffffffffffffff0082549160081b169068ffffffffffffffff001916179055565b6111bd6101446004356102c08a020101616c18565b600783015461ffff60681b6111dd6101646004356102c08d020101616c09565b60681b169061ffff60781b6111fd6101846004356102c08e020101616c09565b60781b169260481b6cffffffff000000000000000000169067ffffffffffffffff60481b191617171760078301556102c08702600435016101a4013560088301556102c08702600435016101c40161125490616bed565b60098301805460ff191660ff929092169190911790556101e46004356102c089020190810135600a840155610204810135600b8401556112979061022401616bed565b60ff1660ff19600c8401541617600c8301556102c0870260043501610244016112bf90616be0565b600c8301805460a05169ffffffffffffffff000060109190911b1692151560081b61ff001669ffffffffffffffffff00199091161791909117905561130f6102846004356102c08a0201016171aa565b600c8301805467ffffffffffffffff60501b191660509290921b67ffffffffffffffff60501b169190911790556113516102a46004356102c08a020101616bed565b600c8301805460ff60901b609084901b1660ff60901b198216179091556102c08902600435016102c4810135600d8601559092906113929060240135616ad3565b8560e051906113a091616a8c565b52835493600181015491546003820154600483015460ff166005840154600685015490600786015492600887015494600988015460ff1696600a89015498600b015499604051809e602082015f5160206182af5f395f51905f52905261010051604083015260608201526080015260a08d015260c08c015260e08b01526101008a015261012089015260ff81166101408901528060081c6001600160401b03166101608901528060481c63ffffffff166101808901528060681c61ffff166101a089015260781c61ffff166101c08801526101e087015261020086015261022085015261024084015260ff821661026084015260ff60901b8160901b1660ff60901b1983161760081c60ff16151561028084015260ff60901b8160901b1660ff60901b1983161760101c6001600160401b03166102a084015260ff60901b8160901b1660ff60901b1983161760501c6001600160401b03166102c084015260ff60901b9060901b169060ff60901b19161760901c60ff166102e08201526102c08602600435016102c401356103008201526103008152611542610320826167d9565b805190602001206115538286616a8c565b5261155d90616bfb565b936115736102446004356102c084020101616be0565b6040805160a05192151581526001600160401b0390921660208301526102c083026004350160240135917f523b72ac5fbe8982a57ea63c8ca4daf1602c22eda42d6cc14a884c0310698b809190a260010193610fb2565b60246102c086026004350101356101005152600660205260406101005120600160ff19825416179055600454600160401b8110156116435761161781600161163c93016004556004616a34565b60246102c089949394026004350101359083549060031b91821b915f19901b19161790565b90556110be565b634e487b7160e01b61010051526041600452602461010051fd5b6101e46102c086633f4b99b160e21b610100515202600435010135600452602461010051fd5b506101e46102c086026004350101356101005152600660205260ff60406101005120541615611092565b604080516116ef916116bf90826167d9565b60098152683830bcb6b0b9ba32b960b91b60208201526004356102c08802016102c4810135919060240135617a55565b61107c565b60ff8660246102c061170f6102248285026004350101616bed565b92631e9a7d7560e01b61010051520260043501013560045216602452604461010051fd5b50600160ff61174d6102246102c08a026004350101616bed565b161415611073565b60246102c086632eb72ead60e11b610100515202600435010135600452602461010051fd5b5060846102c086026004350101351561104c565b5060ff6117a66102a46102c088026004350101616bed565b1615611046565b60ff8560246102c06117c86102a48285026004350101616bed565b92631a7ce1dd60e01b61010051520260043501013560045216602452604461010051fd5b60246102c086633dc2fc1160e01b610100515202600435010135600452602461010051fd5b5063ffffffff61182c6101446102c088026004350101616c18565b1615801561187f575b8015611860575b80611008575060ff6118596102246102c088026004350101616bed565b1615611008565b5060ff6118786101c46102c088026004350101616bed565b161561183c565b5060ff6118976101046102c088026004350101616bed565b1615611835565b8460246102c06118bd60043582850201606481013590604401356172b3565b92631a35ac9960e01b610100515202600435010135600452602452604461010051fd5b929085610100515b60c051811015611ec557600581901b8301353684900361013e19018112156103eb576119179036908501616887565b608052608051516101005152600660205260ff60406101005120541615611ea95760a0608051015160ec8116611e8d575063ffffffff60c060805101511615611e5b575b63ffffffff60e060805101511615611e28575b6001600160401b0360a051166101206080510152611996608051516020608051015190616e2b565b806101005152600960205260ff60406101005120541615611dc9575b80610100515260086020526040610100512060805151815560206080510151600182015560ff604060805101511660ff60028301911660ff1982541617905560038101606060805101518051906001600160401b038211611643578190611a198454616fa4565b601f8111611d61575b506020906001601f841114611cf9576101005192611cee575b50508160011b915f199060031b1c19161790555b6080805101518051906001600160401b03821161164357611a736004840154616fa4565b601f8111611c8e575b506020906001601f841114611bf0576001959493837fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f94611ba8946005946101005192611be5575b50505f19600383901b1c191690881b1760048201555b60805160a081015192909101805460c083015160e0840151610100850180516101209096015167ffffffffffffffff60501b60509190911b1669ff00000000000000000096151560481b9690961671ffffffffffffffffffffffffffffffffffff1990941660ff9097169690961760089290921b64ffffffff00169190911768ffffffff000000000060289290921b91909116171791909117905599611b7f846171be565b611b8b8260e051616a8c565b52611b9860805185617a9d565b611ba2828b616a8c565b52616bfb565b60805160a0908101519a5160408051925160ff909d16835290151560208301526001600160401b03909b169a81019a909a5298606090a2016118e8565b015190508e80611ac4565b9060048401610100515280610100512091610100515b601f1985168110611c76575093600184611ba894600594839a99987fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f98601f19811610611c5e575b505050811b016004820155611ada565b01515f1960f88460031b161c191690558e8080611c4e565b91926020600181928685015181550194019201611c06565b82811115611a7c5760048401610100515260206101005120601f840160051c60208510611ce4575b610100515b81601f850160051c038110611cd257505050611a7c565b61010051838301820155600101611cbb565b5061010051611cb6565b015190508b80611a3b565b6101008051869052518281209350601f198516905b818110611d495750908460019594939210611d31575b505050811b019055611a4f565b01515f1960f88460031b161c191690558b8080611d24565b92936020600181928786015181550195019301611d0e565b82811115611a225790915083610100515260206101005120601f840160051c60208510611dbf575b9084939291610100515b81601f850160051c038110611daa57505050611a22565b61010051838301820155869550600101611d93565b5061010051611d89565b806101005152600960205260406101005120600160ff19825416179055600754600160401b81101561164357611e21611e0b8260018594016007556007616a34565b819391549060031b91821b915f19901b19161790565b90556119b2565b60805160a0015160021615611e4d5763ffffffff610bb85b1660e0608051015261196e565b63ffffffff6201d4c0611e40565b60805160a0015160021615611e805763ffffffff6103e85b1660c0608051015261195b565b63ffffffff612710611e73565b60ff906376cbaf6d60e01b610100515216600452602461010051fd5b60805151633f4b99b160e21b6101005152600452602461010051fd5b83868387610100515b81811061241257505060e0518290525081527f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b031690813b156103eb576040519063abf1570d60e01b82528180611f36610100519360e05160048401616aa0565b038161010051865af18015612219576123f7575b50600454611f57816169be565b6101005191825b8181106122885784611f708585618048565b80601755600754611f80816169be565b6101005191825b81811061222757505090611f9a91618048565b918260185560609260405190611fb085836167d9565b60028252601f198501938436602085013760405194611fcf87876167d9565b60028652366020870137604080517fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f60208201908152610100518284015291815261201a88826167d9565b51902061202684616a6f565b52604051602081019161010051507f205e4a5b42cbb22c56a771766b971165a627b616bd23460c8e002faad102d0918352610100516040830152878201526001600160401b0360a0511660808201526080815261208460a0826167d9565b51902061209085616a6f565b52604080517fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f602082019081526001828401529181526120d087826167d9565b5190206120dc83616a7c565b52604051602081019161010051507f205e4a5b42cbb22c56a771766b971165a627b616bd23460c8e002faad102d091835260016040830152868201526001600160401b0360a0511660808201526080815261213860a0826167d9565b51902061214484616a7c565b52813b156103eb5760405163abf1570d60e01b815260056004820152600160248201526080604482015292839161219691906121849060848501906164d9565b838103600319016064850152906164d9565b9181806101005194039161010051905af18015612219576121fe575b7ff95ac4707234b4ef5620ff790985a37679e4c7e0b0c603f110ae9bcc65b0cf5c8260175460185460405191825260208201526001600160401b0360a051166040820152a16101005180f35b6101005161220b916167d9565b610100516103eb57816121b2565b6040513d61010051823e3d90fd5b80612233600192616a1c565b90549060031b1c610100515260086020526122536040610100512061707c565b610100810151156122825761226790617116565b61227a61227387616bfb565b9686616a8c565b525b01611f87565b5061227c565b806122946001926169f0565b90549060031b1c6101005152600560205260406101005120604051906122b98261676a565b80548252838101546020830152600281015460408301526003810154606083015260ff6004820154166080830152600581015460a0830152600681015460c083015261ffff600782015460ff811660e08501526001600160401b038160081c1661010085015263ffffffff8160481c16610120850152818160681c1661014085015260781c16610160830152600881015461018083015260ff6009820154166101a0830152600a8101546101c0830152600b8101546101e0830152600d600c8201549160ff831661020085015260ff808460081c16159384156102208701526001600160401b038160101c166102408701526001600160401b038160501c1661026087015260901c1661028085015201546102a08301526123f1576123dd906171f1565b6123e961227387616bfb565b525b01611f5e565b506123eb565b61010051612404916167d9565b610100516103eb5781611f4a565b61241d818385616bc2565b93610120853603126103eb57604051946124368661674e565b8035865261010060208201359182602089015261245560408201616611565b60408901526124666060820161687a565b606089015261247760808201616417565b608089015261248860a08201616966565b60a089015260c081013560c089015260e081013560e089015201356101008701526101005152600660205260ff604061010051205416156129b35784516101005152600960205260ff6040610100512054161561299957604085015160f38116611e8d575061ffff60a08601511680151580612984575b61295d57506126e6906020860151610100515260056020525f51602061826f5f395f51905f526001604061010051200154146128bc575b6001600160401b0360a05116608087015285516101005152600a60205260406101005120602087015161010051526020526126296040610100512087518082556020890151918260018201556002810160ff8060408d0151161660ff198254161781556125bc60608c01511515829061ff00825491151560081b169061ff001916179055565b60808b0151815469ffffffffffffffff0000191660109190911b69ffffffffffffffff00001617815560a08b0151815461ffff60501b191660509190911b61ffff60501b1617905560c08a0151600382015560e08a015160048201556101008a0151600590910155616e4b565b6126358260e051616a8c565b528551602087015160ff60408901511688606081015115156001600160401b0360808301511661ffff60a0840151169060c08401519261010060e0860151950151956040519760208901995f5160206182af5f395f51905f528b52600260408b015260608a0152608089015260a088015260c087015260e086015261010085015261012084015261014083015261016082015261016081526126d9610180826167d9565b519020611ba28289616a8c565b9380516020820151907ffdf8afb51a6b8e68fa9d9946922368e4a2e54a69963cf7bcfcdff38235916e7960ff6040850151166060850151151561275360405192839260a05191849160409194936001600160401b039160ff60608601971685521515602085015216910152565b0390a360608101511515806128ac575b612771575b50600101611ece565b80516101005152600860205260406101005120600581019081546103e863ffffffff8260081c1611908115612896575b506127ae575b5050612768565b815460a05171ffffffffffffffff00ffffffffffffffff001990911660509190911b67ffffffffffffffff60501b1617660bb8000003e8001782559195600193927fed8946197b4bd5be9ac502b0c187bc46b61770261216a2d4f6b7d01562ef1b2f9161288b916128509190612846906128288c516171be565b6128348460e051616a8c565b526128408c519161707c565b90617a9d565b611ba2828d616a8c565b975192546040805160a05160ff808516835260489490941c909316151560208201526001600160401b03909216908201529081906060820190565b0390a29086806127a7565b610bb8915063ffffffff9060281c1611896127a1565b5060046040820151161515612763565b61295860208701516128f360c08901516040928351906128dc85836167d9565b60058252643a37b5b2b760d91b6020830152617a55565b602088015160e089015182516129249261290d85836167d9565b60058252640dadee4e0d60db1b6020830152617a55565b60208801516101008901518251909290919061294090836167d9565b60068252651d905cdcd95d60d21b6020830152617a55565b612536565b8560208151910151906368adc18560e01b6101005152600452602452604452606461010051fd5b5060648110806124ff57506101f481116124ff565b84516326ef68cf60e01b6101005152600452602461010051fd5b6020850151633f4b99b160e21b6101005152600452602461010051fd5b634e487b7160e01b61010051526011600452602461010051fd5b909a610120806001926101008f803583526020810135602084015260ff612a1360408301616611565b166040840152612a256060820161687a565b151560608401526001600160401b03612a4060808301616417565b16608084015261ffff612a5560a08301616966565b1660a084015260c081013560c084015260e081013560e08401520135610100820152019c019101610e43565b909192601f1983820301845261013e198c360301853512156103eb576020806001928e8835019081358152828201358382015260ff612ac260408401616611565b1660408201526101206001600160401b03612b7682612b18612afd612aea6060890189616ebb565b61014060608a0152610140890191616eec565b612b0a6080890189616ebb565b9088830360808a0152616eec565b9560ff612b2760a08301616611565b1660a087015263ffffffff612b3e60c08301616869565b1660c087015263ffffffff612b5560e08301616869565b1660e0870152612b68610100820161687a565b151561010087015201616417565b169101529601959401929101610e1c565b90916102c0806001928535815260208601356020820152604086013560408201526060860135606082015260ff612bc060808801616611565b16608082015260a086013560a082015260c086013560c082015260ff612be860e08801616611565b1660e08201526001600160401b03612c036101008801616417565b1661010082015263ffffffff612c1c6101208801616869565b1661012082015261ffff612c336101408801616966565b1661014082015261ffff612c4a6101608801616966565b1661016082015261018086013561018082015260ff612c6c6101a08801616611565b166101a08201526101c08601356101c08201526101e08601356101e082015260ff612c9a6102008801616611565b16610200820152612cae610220870161687a565b15156102208201526001600160401b03612ccb6102408801616417565b166102408201526001600160401b03612ce76102608801616417565b1661026082015260ff612cfd6102808801616611565b166102808201526102a08601356102a08201520193019101610de5565b63c2e5347d60e01b6101005152600461010051fd5b508315610da7565b5060c05115610da1565b63d311bc3960e01b6101005152600461010051fd5b346103eb5760203660031901126103eb57600435612d72616d88565b50600d54811015612e2557600d610100515260206101005120016101005150546101005160031b1c6101005152600b602052610546604061010051206001600160401b03600260405192612dc584616733565b8054845260018101546020850152015460ff81161515604084015260081c1660608201526040519182918291909160606001600160401b038160808401958051855260208101516020860152604081015115156040860152015116910152565b634e487b7160e01b61010051526032600452602461010051fd5b346103eb57610100513660031901126103eb5760206040516201d4c08152f35b346103eb57610100513660031901126103eb5760206040517f3d07e5a0f372bb3f92469df7a6dcb4a49539b8707a06a699d3727c07dbbb3a4b8152f35b346103eb57610100513660031901126103eb5760206001600160401b0360035416604051908152f35b346103eb57612ed33661645b565b90612edc616e85565b506101005152600f6020526040610100512090610100515260205260e0604061010051206001600160401b03600460405192612f17846167a2565b805484526001810154602085015260ff600282015416604085015260038101546060850152015461ffff8116608084015260ff8160101c16151560a084015260181c1660c0820152612f6c604051809261661f565bf35b346103eb5760203660031901126103eb57612f87616b0b565b506004356101005152600e6020526101005160409020604051612fa98161674e565b81549182825260010154906020810160ff83168152604082018360081c61ffff168152606083018460181c63ffffffff16815260808401908560381c61ffff16825260a08501928660481c6001600160401b0316845260c08601948760881c61ffff16865260e08701968860981c60ff1615158852610100019760a01c6001600160401b031688526040519889525160ff1660208901525161ffff1660408801525163ffffffff1660608701525161ffff166080860152516001600160401b031660a08501525161ffff1660c084015251151560e0830152516001600160401b031661010082015261012090f35b346103eb5760203660031901126103eb576130b0616f59565b506130bc600435616a1c565b90549060031b1c610100515260086020526105466130df6040610100512061707c565b604051918291602083526020830190616696565b346103eb5760203660031901126103eb576004356001600160401b0381116103eb57610a3c6131286020923690600401616887565b617116565b346103eb5760203660031901126103eb57600435613149616f59565b50806101005152600960205260ff6040610100512054161561318157610100515260086020526105466130df6040610100512061707c565b63082cdf1f60e11b6101005152600452602461010051fd5b346103eb5760a03660031901126103eb5760043560048110156103eb576131be61653d565b90606435906084356001600160a01b03811691604435918381036103eb576131e4617989565b61010051548415906001600160a01b03168115613433575b5061341a5761320c838784616ddf565b945f1981036134155750845b80958115613400578082116133d5575061323183616f0c565b826132a85750506101005180808087875af161324b616f2a565b501561328c575f5160206182cf5f395f51905f5260406020965b61326e84616f0c565b81519485528785018790526001600160a01b031693a4604051908152f35b6365f4a9ef60e11b610100515261010051600452602461010051fd5b60209691906132b684616f0c565b6001840361331d57506040805163a9059cbb60e01b898201526001600160a01b039092166024830152604482018790525f5160206182cf5f395f51905f529290916133189061331281606481015b03601f1981018352826167d9565b826179b5565b613265565b919591905061332b83616f0c565b6002830361337a5750505f5160206182cf5f395f51905f52604060019561331882516323b872dd60e01b8a820152306024820152876044820152866064820152606481526133126084826167d9565b5f5160206182cf5f395f51905f529195613318604092835190637921219560e11b8b830152306024830152886044830152876064830152608482015260a060a48201526101005160c482015260c4815261331260e4826167d9565b6101008051632190968160e01b90526001600160a01b03891660045260249290925260445251606490fd5b637c2e506f60e11b6101005152600461010051fd5b613218565b836315150d4d60e31b6101005152600452602461010051fd5b905084141580613444575b876131fc565b503384141561343e565b346103eb5760203660031901126103eb5760043561346a616c29565b50806101005152600660205260ff604061010051205416156136f6576101005152600560205261054660406101005120600d604051916134a98361676a565b8054835260018101546020840152600281015460408401526003810154606084015260ff6004820154166080840152600581015460a0840152600681015460c084015261ffff600782015460ff811660e08601526001600160401b038160081c1661010086015263ffffffff8160481c16610120860152818160681c1661014086015260781c16610160840152600881015461018084015260ff6009820154166101a0840152600a8101546101c0840152600b8101546101e084015260ff600c820154818116610200860152818160081c1615156102208601526001600160401b038160101c166102408601526001600160401b038160501c1661026086015260901c1661028084015201546102a0820152604051918291829190916102a0806102c08301948051845260208101516020850152604081015160408501526060810151606085015260ff608082015116608085015260a081015160a085015260c081015160c085015260ff60e08201511660e08501526001600160401b036101008201511661010085015263ffffffff6101208201511661012085015261ffff6101408201511661014085015261ffff6101608201511661016085015261018081015161018085015260ff6101a0820151166101a08501526101c08101516101c08501526101e08101516101e085015260ff6102008201511661020085015261022081015115156102208501526001600160401b03610240820151166102408501526001600160401b036102608201511661026085015260ff610280820151166102808501520151910152565b632a80afbd60e21b6101005152600452602461010051fd5b346103eb57610100513660031901126103eb576020604051610bb88152f35b346103eb5760803660031901126103eb5760243560043561374c616401565b6064356001600160401b0381116103eb5761376b90369060040161642b565b61010051549091906001600160a01b03163303613883575b505050816137cb575b807f40bd951b186ab08230e53414aeaafc99b557bb646ef875822965f0e5a8c32ca5926040926001558060025582519182526020820152a16101005180f35b60405163342f616360e01b8152600481018290526020816024817f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03165afa90811561221957610100519161384d575b50828110613830575061378c565b9050633770da3360e11b6101005152600452602452604461010051fd5b90506020813d60201161387b575b81613868602093836167d9565b81010312613877575183613822565b5f80fd5b3d915061385b565b60408051602081018681528183018890529181527f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03169291906138cf6060826167d9565b519020823b156103eb5790836001600160401b039593926040519687956322f3f44760e11b875260848701927f3d07e5a0f372bb3f92469df7a6dcb4a49539b8707a06a699d3727c07dbbb3a4b60048901526024880152166044860152608060648601525260a4830160a48560051b85010194826101005190607e19813603015b83831061399a5750505050505081806101005194039161010051905af180156122195761397f575b8080613783565b6101005161398c916167d9565b610100516103eb5782613978565b919395965091939660a3198982030186528735828112156103eb57830180356001600160a01b03811692908390036103eb57613a286020928260019585945260ff6139e6858401616611565b1684820152613a1a613a0f6139fe6040850185616ebb565b608060408601526080850191616eec565b926060810190616ebb565b916060818503910152616eec565b99019601930190918896959492613950565b346103eb5760203660031901126103eb57600435610100515260166020526040610100512060405190816020825491828152019161010051526020610100512090610100515b818110613a975761054685610532818703826167d9565b8254845260209093019260019283019201613a80565b346103eb57610100513660031901126103eb5760206001600160401b0360195416604051908152f35b346103eb57610100513660031901126103eb57602060405160048152f35b61650c565b346103eb57610100513660031901126103eb57602060405160648152f35b346103eb5760203660031901126103eb576020610a3c600435617261565b346103eb5760203660031901126103eb57613b4e616cc7565b5060043561010051526011602052610546604061010051206001600160401b03600c60405192613b7d84616786565b80548452600181015460208501526002810154604085015260ff600382015416606085015260048101546080850152600581015460a0850152600681015460c0850152600781015460e085015261ffff6008820154166101008501526009810154610120850152600a81015461014085015260ff600b820154818116151561016087015263ffffffff8160081c1661018087015263ffffffff8160281c166101a08701526001600160801b038160481c166101c087015261ffff8160c81c166101e087015260d81c161515610200850152015416610220820152604051918291829190916102206001600160401b038161024084019580518552602081015160208601526040810151604086015260ff60608201511660608601526080810151608086015260a081015160a086015260c081015160c086015260e081015160e086015261ffff61010082015116610100860152610120810151610120860152610140810151610140860152610160810151151561016086015263ffffffff6101808201511661018086015263ffffffff6101a0820151166101a08601526001600160801b036101c0820151166101c086015261ffff6101e0820151166101e08601526102008101511515610200860152015116910152565b346103eb57610100513660031901126103eb5760206040515f51602061826f5f395f51905f528152f35b346103eb5760203660031901126103eb57600435806101005152601060205260406101005120805490613db1826169a7565b92613dbf60405194856167d9565b828452601f19613dce846169a7565b01610100515b818110613eeb575050610100515b838110613e3c5784604051809160208201602083528151809152602060408401920190610100515b818110613e18575050500390f35b91935091602060e082613e2e600194885161661f565b019401910191849392613e0a565b600190826101005152600f60205260406101005120613e5b8286616a34565b90549060031b1c6101005152602052604061010051206001600160401b03600460405192613e88846167a2565b8054845285810154602085015260ff600282015416604085015260038101546060850152015461ffff8116608084015260ff8160101c16151560a084015260181c1660c0820152613ed98288616a8c565b52613ee48187616a8c565b5001613de2565b602090613ef6616e85565b82828901015201613dd4565b346103eb576020610a3c613f153661645b565b90616e4b565b346103eb57610100513660031901126103eb576040517f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b03168152602090f35b346103eb5760203660031901126103eb576004356001600160401b038116908190036103eb5761010051546001600160a01b0316330361402b576019546001600160401b03811615801590614020575b8015614015575b6140005767ffffffffffffffff191681176019556040519081527f1b0319a7c1b77359b02ef03a86a6e1677b3d55bc74afbe201986efaef1e8b4b590602090a16101005180f35b63dc63d81f60e01b6101005152600461010051fd5b506007541515613fb9565b506004541515613fb2565b630bd4212160e11b610100515233600452602461010051fd5b346103eb57610100513660031901126103eb576020604051610100518152f35b346103eb576020610a3c6140773661645b565b90616e2b565b346103eb5760203660031901126103eb576020610a3c600435616df8565b346103eb57610100513660031901126103eb5760206040516127108152f35b346103eb5760603660031901126103eb5760043560048110156103eb57610a3c6020916140e561653d565b60443591616ddf565b346103eb5760203660031901126103eb57600435610100515260106020526040610100512060405190816020825491828152019161010051526020610100512090610100515b81811061414b5761054685610532818703826167d9565b8254845260209093019260019283019201614134565b346103eb57610100513660031901126103eb576020601854604051908152f35b346103eb5760203660031901126103eb576020610a3c600435616dac565b346103eb5760203660031901126103eb576141b8616d88565b506004356101005152600b602052610546604061010051206001600160401b03600260405192612dc584616733565b346103eb5760603660031901126103eb576004356001600160401b0381116103eb57366023820112156103eb5780600401356001600160401b0381116103eb5736602460e08302840101116103eb5761423e6163eb565b6044356001600160401b0381116103eb5761425d90369060040161642b565b90916002548015612d41578415612d1a57600354936001600160401b0385169360405160208101908860608201888452604080840152526080810160248b01610100515b8b8110614741575050926001600160401b03979592826142d261434097946143469a9703601f1981018352826167d9565b519020610100515060405160208101915f51602061828f5f395f51905f5283524660408301523060608301527f8edbf41b64bab13aa09d5e590fb62757de47a32c3f5541e6a3cee757fde6d7e260808301528a871660a083015260c082015260c08152610edf60e0826167d9565b50616975565b16906001600160401b03191617600355601954916001600160401b0361436d818516616975565b1680936001600160401b03191617601955614387826169be565b92614391836169be565b91610100515b848110156146b45760e08181028301903682900360231901126103eb576040516143c0816167a2565b6024820135908181526020810191604484013583526143e160648501616611565b936040830194855260608301926084820135845261440160a48301616966565b926080820193845261442860e461441a60c4860161687a565b9460a0850195865201616417565b9060c083019182526101005152600660205260ff6040610100512054161561469a5785518015801561467a575b61465d5750928261ffff60ff8f948e6146078f9d8c60019f9e829f9c819f9e9b6145877f1924b91ab00797d4d2c4d71ee691a743d20afece46e1e02b5c41794959a23ae29f60a09f8f8f88908f93848f956004614581978f99978e988e5281516101005152600f60205260406101005120885161010051526020526001600160401b038360406101005120015460181c161561463a575b81516101005152600f602052604061010051208851610100515260205260ff60406101005120925198898455519960018b90850155511660ff60028401911660ff19825416179055516003820155019351161661ffff19835416178255511515815462ff00006affffffffffffffff0000008b5160181b169260101b16906affffffffffffffffff0000191617179055616b88565b92616a8c565b528787519351925116908c518a8d5116906001600160401b038d511515935116936040519560208701975f5160206182af5f395f51905f528952600860408901526060880152608087015260a086015260c085015260e084015261010083015261012082015261012081526145fe610140826167d9565b51902092616a8c565b5251975198511693519251169051151591604051938452602084015260408301526060820152886080820152a301614397565b81516101005152601060205261465860406101005120895190616a49565b6144ec565b825163c9c05b0b60e01b6101005152600452602452604461010051fd5b50835115158015614455575060ff88511615806144555750855115614455565b5051633f4b99b160e21b6101005152600452602461010051fd5b7f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b03168685823b156103eb5761470b92604051808095819463abf1570d60e01b8352610100519660048401616aa0565b039161010051905af1801561221957614726575b6101005180f35b61010051614733916167d9565b610100516103eb578061471f565b909160e080600192853581526020860135602082015260ff61476560408801616611565b1660408201526060860135606082015261ffff61478460808801616966565b16608082015261479660a0870161687a565b151560a08201526001600160401b036147b160c08801616417565b1660c082015201930191016142a1565b346103eb576020610a3c6147d4366165e7565b91616d49565b346103eb5760203660031901126103eb5760206147f5616601565b610100515060405160ff838201927fbf8f90cf3ca0101d2844ee0558886a5f257e05327d13dd56af76c4d3c8ead50f84521660408201526040815261483b6060826167d9565b519020604051908152f35b346103eb57610100513660031901126103eb576020600254604051908152f35b346103eb57610100513660031901126103eb5761010051546001600160a01b038116330361402b576bffffffffffffffffffffffff60a01b1661010051557f1b2d71eb44f882534bf4e86f940c56ccc869ffb927e2bab86561de93950c22166101005161010051a16101005180f35b346103eb576148f56148e6366165e7565b916148ef616cc7565b50616d49565b61010051526011602052610546604061010051206001600160401b03600c60405192613b7d84616786565b346103eb57610100513660031901126103eb5760206040516103e88152f35b346103eb5760203660031901126103eb57614958616c29565b506149646004356169f0565b90549060031b1c6101005152600560205261054660406101005120600d604051916134a98361676a565b346103eb5760803660031901126103eb576004356001600160401b0381116103eb576149be903690600401616580565b6001600160401b03602435116103eb573660236024350112156103eb5760243560040135906001600160401b0382116103eb57366024610240840281350101116103eb57614a0a616401565b6064356001600160401b0381116103eb57614a2990369060040161642b565b90916002548015612d41578415806155f0575b612d1a57600354936001600160401b038516936040518760808201876020840152606060408401525260a081018a90610100515b8a8110615562575060209150601f198382030160608401528a8152016024803501610100515b8b811061541e57505092614b38959282614ac66001600160401b039997946143409703601f1981018352826167d9565b60208151910120610100515060405160208101915f51602061828f5f395f51905f5283524660408301523060608301527f5596fa6720176cbfa2a11ee0d5be50e2016480c70c477d3273492663856b2d0b60808301528a871660a083015260c082015260c08152610edf60e0826167d9565b16906001600160401b0319161760035560195490614b5e6001600160401b038316616975565b67ffffffffffffffff199092166001600160401b03831617601955614b838382616bd3565b614b95614b8f826169be565b916169be565b6101005190959092835b8181106151425750505061010051915b848310156150eb57610100515060246102408402813501019283356101005152600960205260ff604061010051205416156150d157614bed84617644565b614c0a602435610240830201606481013590604401358635616d49565b91826101005152601260205260ff6040610100512054161561506c575b61010080518490526011602052516040902085358155602435610240840201604481013560018301556064810135600283015590919060ff90614c6c90608401616bed565b60038401805460ff19169290911691909117905560243561024084020160a4810135600484015560c4810135600584015560e48101356006840155610104810135600784015561ffff90614cc39061012401616c09565b60088401805461ffff1916929091169190911790556024356102408402016101448101356009840155610164810135600a840155614d1d90614d089061018401616be0565b600b84019060ff801983541691151516179055565b614d55614d356101a461024086026024350101616c18565b600b84019064ffffffff0082549160081b169064ffffffff001916179055565b614d95614d6d6101c461024086026024350101616c18565b600b84019068ffffffff000000000082549160281b169068ffffffff00000000001916179055565b6024356102408402016101e401356001600160801b03811690036103eb5760019382600b614ffb94015461ffff60c81b614dda61020461024089026024350101616c09565b60c81b1660ff60d81b614df86024356102408a020161022401616be0565b7fffffffff00000000000000000000000000000000000000ffffffffffffffffff9093166024356102408a02016101e4013560481b78ffffffffffffffffffffffffffffffff00000000000000000016179190911791151560d81b1617600b820155600c8101805467ffffffffffffffff19166001600160401b038b1617815591614e838489616a8c565b52614fee5f5160206182af5f395f51905f52602083549489850154600286015460ff600388015416600488015460058901549060068a01549260078b01549461ffff60088d0154169660806040519e8f908c82019d8e52600460408301526060820152015260a08d015260c08c015260e08b01526101008a01526101208901526101408801526101608701526101608652614f20610180876167d9565b60098501549060ff6001600160401b03600b600a890154980154925416916040519785890194855260408901528181161515606089015263ffffffff8160081c16608089015263ffffffff8160281c1660a08901526001600160801b038160481c1660c089015261ffff8160c81c1660e089015260d81c1615156101008701526101208601526101208552614fb7610140866167d9565b60405194859383850197518091895e84019083820190610100518252519283915e010161010051815203601f1981018352826167d9565b519020611ba2828b616a8c565b9361501161022461024084026024350101616be0565b6040805191151582526001600160401b0388166020830152602435610240850201606481013593604490910135929035917f9738e55ce3503cada06dd3d3338d42623309a94afb5495119306f4a19a92350b91a40191614baf565b826101005152601260205260406101005120600160ff19825416179055601354600160401b811015611643576150ae611e0b8260018794016013556013616a34565b90558435610100515260146020526150cc8360406101005120616a49565b614c27565b836326ef68cf60e01b610100515235600452602461010051fd5b7f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b03168287823b156103eb5761470b92604051808095819463abf1570d60e01b8352610100519660048401616aa0565b61514d818385616bc2565b9485356101005152600660205260ff604061010051205416156154045760208601351580156153cf575b6153ad57906153236001928735610100515260156020526040610100512060208901356101005152602052604061010051206001600160401b03600782015460081c1615615388575b8835815560208901358582015560ff6151db60408b01616bed565b1660ff19600283015416176002820155606089013560038201556080890135600482015560a0890135600582015560c0890135600682015561523761522260e08b01616be0565b600783019060ff801983541691151516179055565b60078101805468ffffffffffffffff00191660088c901b68ffffffffffffffff001617905561526b60208a01358a35616b4e565b615275838a616a8c565b528054906001600160401b03868201549160ff600282015416906003810154600482015460058301549160076006850154940154946040519760208901995f5160206182af5f395f51905f528b52600560408b015260608a0152608089015260a088015260c087015260e086015261010085015261012084015260ff8116151561014084015260081c166101608201526101608152615316610180826167d9565b519020611ba2828d616a8c565b9561533060e08201616be0565b7f4b9c1fac36a2aeaf69e62fe75b58c9e69ec27b26ca89e6cde77296d160d452da6040518061537f8c6020870135963595839092916001600160401b0360209160408401951515845216910152565b0390a301614b9f565b8835610100515260166020526153a860208a013560406101005120616a49565b6151c0565b602086638940a78560e01b610100515280356004520135602452604461010051fd5b506153dc60e08701616be0565b8015615177575060ff6153f160408801616bed565b1615806151775750606086013515615177565b85633f4b99b160e21b610100515235600452602461010051fd5b909182358152602083013560208201526040830135604082015260ff61544660608501616611565b1660608201526080830135608082015260a083013560a082015260c083013560c082015260e083013560e082015261ffff6154846101008501616966565b166101008201526101208301356101208201526101408301356101408201526154b0610160840161687a565b151561016082015263ffffffff6154ca6101808501616869565b1661018082015263ffffffff6154e36101a08501616869565b166101a08201526101c0830135906001600160801b0382168092036103eb57610240816001936101c08394015261ffff6155206101e08801616966565b166101e0820152615534610200870161687a565b15156102008201526001600160401b036155516102208801616417565b166102208201520193019101614a96565b9061012080600192853581526020860135602082015260ff61558660408801616611565b166040820152606086013560608201526080860135608082015260a086013560a082015260c086013560c08201526155c060e0870161687a565b151560e08201526001600160401b036155dc6101008801616417565b166101008201520193019101919091614a70565b508515614a3c565b346103eb576156063661645b565b9061560f616b0b565b506101005152601560205260406101005120906101005152602052610120604061010051206001600160401b03604051916156498361674e565b8054928381526001820154916020820192835260ff600282015416604083019081526003820154606084019081526004830154906080850191825260ff60058501549360a08701948552600760068701549660c089019788520154978961010060e08a0199858c1615158b52019960081c1689526040519a8b525160208b0152511660408901525160608801525160808701525160a08601525160c085015251151560e08401525116610100820152f35b346103eb57610100513660031901126103eb576040517f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b03168152602090f35b346103eb5760603660031901126103eb576004356001600160401b0381116103eb57615771903690600401616580565b9061577a6163eb565b6044356001600160401b0381116103eb5761579990369060040161642b565b90916002548015612d41578515612d1a57600354936001600160401b0385169360405160208101908960608201888452604080840152526080810189610100515b8c8110615c99575050926001600160401b039795928261580b61434097946158799a9703601f1981018352826167d9565b519020610100515060405160208101915f51602061828f5f395f51905f5283524660408301523060608301527f7d6f52ebac641da43fc11d5911cdb8aabf4931733ac493d1f5540628174e88d360808301528a871660a083015260c082015260c08152610edf60e0826167d9565b16906001600160401b03191617600355601954906001600160401b036158a0818416616975565b1680926001600160401b031916176019556158ba836169be565b916158c4846169be565b93610100515b818110615928577f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b03168587823b156103eb5761470b92604051808095819463abf1570d60e01b8352610100519660048401616aa0565b615933818386616bc2565b610120813603126103eb576040519061594b8261674e565b6159d461010082359283855261596360208201616611565b602086015261597460408201616966565b604086015261598560608201616869565b606086015261599660808201616966565b60808601526159a760a08201616417565b60a08601526159b860c08201616966565b60c08601526159c960e0820161687a565b60e086015201616417565b6101008301526101005152600660205260ff60406101005120541615615c8057604081015160ff60208301511661ffff6001821b83161615615c5c575050908184610100600194015280516101005152600e602052615b1a60406101005120848351918281550160ff806020860151161660ff19825416178155604084015181549066ffffffff000000606087015160181b169068ffff00000000000000608088015160381b169167ffffffffffffffff60481b60a089015160481b1661ffff60881b60c08a015160881b169260e08a0151151560981b946001600160401b0360a01b6101008c015160a01b16966001600160401b0360a01b199460ff60981b199361ffff60881b199262ffff0067ffffffffffffffff60481b199260081b169068ffffffffffffffff0019161716171617161716179060ff60981b1617179055616df8565b615b248389616a8c565b52805160ff60208301511661ffff60408401511663ffffffff60608501511661ffff6080860151166001600160401b0360a08701511661ffff60c0880151169160e08801511515936001600160401b036101008a015116956040519760208901995f5160206182af5f395f51905f528b52600760408b015260608a0152608089015260a088015260c087015260e08601526101008501526101208401526101408301526101608201526101608152615bde610180826167d9565b519020615beb838a616a8c565b527ffb325cc55af6e4500b502905b39f0679ffc3ea5f77a1534709444fdb64a99f0a60a082519260ff6020820151169061ffff6040820151169061ffff608063ffffffff6060840151169201511691604051938452602084015260408301526060820152876080820152a2016158ca565b61ffff9251633ecb481360e21b610100515260045260245216604452606461010051fd5b51633f4b99b160e21b6101005152600452602461010051fd5b9091610120806001928535815260ff615cb460208801616611565b16602082015261ffff615cc960408801616966565b16604082015263ffffffff615ce060608801616869565b16606082015261ffff615cf560808801616966565b1660808201526001600160401b03615d0f60a08801616417565b1660a082015261ffff615d2460c08801616966565b1660c0820152615d3660e0870161687a565b151560e08201526001600160401b03615d526101008801616417565b1661010082015201930191016157da565b346103eb5760803660031901126103eb57615d7c616527565b50615d8561653d565b506064356001600160401b0381116103eb57615da5903690600401616553565b5050604051630a85bd0160e11b8152602090f35b346103eb576020610a3c615dcc3661645b565b90616b88565b346103eb576020610a3c615de53661645b565b90616b4e565b346103eb5760203660031901126103eb57600435610100515260146020526040610100512060405190816020825491828152019161010051526020610100512090610100515b818110615e485761054685610532818703826167d9565b8254845260209093019260019283019201615e31565b346103eb57615e6c3661645b565b90615e75616b0b565b506101005152600a6020526040610100512090610100515260205261012060406101005120600560405191615ea98361674e565b805483526001810154602084015261ffff600282015460ff8116604086015260ff8160081c16151560608601526001600160401b038160101c16608086015260501c1660a0840152600381015460c0840152600481015460e08401520154610100820152612f6c6040518092616471565b346103eb5760203660031901126103eb576020610a3c600435616ad3565b3461387757606036600319011261387757600435906001600160401b0382116138775736602383011215613877578160040135906001600160401b038211613877573660248360071b8501011161387757615f916163eb565b906044356001600160401b03811161387757615fb190369060040161642b565b92906002549081156163dc5785156163cd57600354946001600160401b0386169460208101908860608201888452604080840152526080810160248b015f5b8b8110616380575050926001600160401b0397959282616021614340979461608a9a9703601f1981018352826167d9565b51902060405160208101915f51602061828f5f395f51905f5283524660408301523060608301527fe3b31cc24d29add9661dc3a1f58e781f69070e12dc384bcd05cb5622a2f1c57b60808301528a871660a083015260c082015260c08152610edf60e0826167d9565b16906001600160401b031916176003556019546001600160401b036160b0818316616975565b1680916001600160401b031916176019556160ca826169be565b906160d4836169be565b935f5b848110156162f5578060071b820190608060231983360301126138775760405161610081616733565b60248301359283825260208201916044820135835261613460846161266064850161687a565b936040840194855201616417565b5060608101948786525f52600c6020528960ff60405f20541615616265575b91817fed4d5e005c9676e1dcabfeb18ab560394b1c30a4409c368d6b8e5b76095135359361624587600199606096515f52600b6020528d6161ec836145818c6161e7600260405f208c5193848255516001820155016161c18d511515829060ff801983541691151516179055565b8751815468ffffffffffffffff00191660089190911b68ffffffffffffffff0016179055565b616dac565b528451908951906001600160401b0388511515915116906040519260208401945f5160206182af5f395f51905f528652600360408601528b850152608084015260a083015260c082015260c081526145fe60e0826167d9565b52519351905115156040519182526020820152876040820152a2016160d7565b5080515f52600c60205260405f20600160ff19825416179055805191600d5491600160401b8310156162e1578b616245876001997fed4d5e005c9676e1dcabfeb18ab560394b1c30a4409c368d6b8e5b7609513535976162d1611e0b898e60609b01600d55600d616a34565b9055995050509193509150616153565b634e487b7160e01b5f52604160045260245ffd5b7f0000000000000000000000004c2a95b0c6ac4f1e00ff1f6742c667008adf2c6d6001600160a01b03168487823b156138775761634b925f928360405180968195829463abf1570d60e01b845260048401616aa0565b03925af1801561637557616360576101005180f35b5f61636a916167d9565b5f610100528061471f565b6040513d5f823e3d90fd5b909160808060019285358152602086013560208201526163a26040870161687a565b151560408201526001600160401b036163bd60608801616417565b1660608201520193019101615ff0565b63c2e5347d60e01b5f5260045ffd5b63d311bc3960e01b5f5260045ffd5b602435906001600160401b038216820361387757565b604435906001600160401b038216820361387757565b35906001600160401b038216820361387757565b9181601f84011215613877578235916001600160401b038311613877576020808501948460051b01011161387757565b6040906003190112613877576004359060243590565b6101008091805184526020810151602085015260ff60408201511660408501526060810151151560608501526001600160401b03608082015116608085015261ffff60a08201511660a085015260c081015160c085015260e081015160e08501520151910152565b90602080835192838152019201905f5b8181106164f65750505090565b82518452602093840193909201916001016164e9565b34613877575f36600319011261387757602060405160018152f35b600435906001600160a01b038216820361387757565b602435906001600160a01b038216820361387757565b9181601f84011215613877578235916001600160401b038311613877576020838186019501011161387757565b9181601f84011215613877578235916001600160401b03831161387757602080850194610120850201011161387757565b34613877575f36600319011261387757602060405160028152f35b34613877575f36600319011261387757602060405160038152f35b606090600319011261387757600435906024359060443590565b6004359060ff8216820361387757565b359060ff8216820361387757565b6001600160401b0360c08092805185526020810151602086015260ff60408201511660408601526060810151606086015261ffff608082015116608086015260a0810151151560a0860152015116910152565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90815181526020820151602082015260ff60408301511660408201526101206001600160401b03816166ee6166dc60608701516101406060880152610140870190616672565b60808701518682036080880152616672565b9460ff60a08201511660a086015263ffffffff60c08201511660c086015263ffffffff60e08201511660e0860152610100810151151561010086015201511691015290565b608081019081106001600160401b038211176162e157604052565b61012081019081106001600160401b038211176162e157604052565b6102c081019081106001600160401b038211176162e157604052565b61024081019081106001600160401b038211176162e157604052565b60e081019081106001600160401b038211176162e157604052565b61014081019081106001600160401b038211176162e157604052565b90601f801991011681019081106001600160401b038211176162e157604052565b6001600160401b0381116162e157601f01601f191660200190565b929192616821826167fa565b9161682f60405193846167d9565b829481845281830111613877578281602093845f960137010152565b9080601f830112156138775781602061686693359101616815565b90565b359063ffffffff8216820361387757565b3590811515820361387757565b9190916101408184031261387757604051906168a2826167bd565b819381358352602082013560208401526168be60408301616611565b604084015260608201356001600160401b03811161387757816168e291840161684b565b60608401526080820135906001600160401b038211613877578261691061012094926169619486940161684b565b608086015261692160a08201616611565b60a086015261693260c08201616869565b60c086015261694360e08201616869565b60e0860152616955610100820161687a565b61010086015201616417565b910152565b359061ffff8216820361387757565b6001600160401b036001911601906001600160401b03821161699357565b634e487b7160e01b5f52601160045260245ffd5b6001600160401b0381116162e15760051b60200190565b906169c8826169a7565b6169d560405191826167d9565b82815280926169e6601f19916169a7565b0190602036910137565b600454811015616a085760045f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b600754811015616a085760075f5260205f2001905f90565b8054821015616a08575f5260205f2001905f90565b805490600160401b8210156162e15781611e0b916001616a6b94018155616a34565b9055565b805115616a085760200190565b805160011015616a085760400190565b8051821015616a085760209160051b010190565b9091616ac56168669360068452600160208501526080604085015260808401906164d9565b9160608184039101526164d9565b60405160208101915f51602061824f5f395f51905f5283525f6040830152606082015260608152616b056080826167d9565b51902090565b60405190616b188261674e565b5f610100838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201520152565b906040519060208201925f51602061824f5f395f51905f528452600560408401526060830152608082015260808152616b0560a0826167d9565b906040519060208201925f51602061824f5f395f51905f528452600860408401526060830152608082015260808152616b0560a0826167d9565b9190811015616a0857610120020190565b9190820180921161699357565b3580151581036138775790565b3560ff811681036138775790565b5f1981146169935760010190565b3561ffff811681036138775790565b3563ffffffff811681036138775790565b60405190616c368261676a565b5f6102a0838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e082015282610200820152826102208201528261024082015282610260820152826102808201520152565b60405190616cd482616786565b5f610220838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e0820152826102008201520152565b916040519160208301935f51602061824f5f395f51905f528552600460408501526060840152608083015260a082015260a08152616b0560c0826167d9565b60405190616d9582616733565b5f6060838281528260208201528260408201520152565b60405160208101915f51602061824f5f395f51905f52835260036040830152606082015260608152616b056080826167d9565b90616dea9291617833565b8015616df35790565b505f90565b60405160208101915f51602061824f5f395f51905f52835260076040830152606082015260608152616b056080826167d9565b906040519060208201928352604082015260408152616b056060826167d9565b906040519060208201925f51602061824f5f395f51905f528452600260408401526060830152608082015260808152616b0560a0826167d9565b60405190616e92826167a2565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b9035601e19823603018112156138775701602081359101916001600160401b03821161387757813603831361387757565b908060209392818452848401375f828201840152601f01601f1916010190565b60041115616f1657565b634e487b7160e01b5f52602160045260245ffd5b3d15616f54573d90616f3b826167fa565b91616f4960405193846167d9565b82523d5f602084013e565b606090565b60405190616f66826167bd565b5f61012083828152826020820152826040820152606080820152606060808201528260a08201528260c08201528260e0820152826101008201520152565b90600182811c92168015616fd2575b6020831014616fbe57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691616fb3565b9060405191825f825492616fef84616fa4565b808452936001811690811561705a5750600114617016575b50617014925003836167d9565b565b90505f9291925260205f20905f915b81831061703e575050906020617014928201015f617007565b6020919350806001915483858901015201910190918492617025565b90506020925061701494915060ff191682840152151560051b8201015f617007565b90604051617089816167bd565b6101206001600160401b0360058395805485526001810154602086015260ff60028201541660408601526170bf60038201616fdc565b60608601526170d060048201616fdc565b6080860152015460ff811660a085015263ffffffff8160081c1660c085015263ffffffff8160281c1660e085015260ff8160481c16151561010085015260501c16910152565b80519060208101519060ff604082015116906060810151602081519101206001600160401b0361012060808401516020815191012093015116926040519460208601967f8db2525bc769e56adb6fd2402771f48c08c4503c9dc0bc507a776eaca65d700f885260408701526060860152608085015260a084015260c083015260e082015260e08152616b05610100826167d9565b356001600160401b03811681036138775790565b60405160208101915f51602061824f5f395f51905f52835260016040830152606082015260608152616b056080826167d9565b8051906060810151906001600160401b0361024060ff60808401511692015116906040519260208401947f2d9686edff91aeaad85c9917be9d5ee284127481a229d9857b748073fba5b1c0865260408501526060840152608083015260a082015260a08152616b0560c0826167d9565b60405160208101917faf7a3b9335082a3b4f22ddf585b52ecc40819755789412bb4253e79b45db1ab383525f51602061826f5f395f51905f526040830152606082015260608152616b056080826167d9565b906040519060208201927faf7a3b9335082a3b4f22ddf585b52ecc40819755789412bb4253e79b45db1ab384526040830152606082015260608152616b056080826167d9565b90617303826169a7565b61731060405191826167d9565b8281528092617321601f19916169a7565b01905f5b82811061733157505050565b60209061733c616f59565b82828501015201617325565b90617352826169a7565b61735f60405191826167d9565b8281528092617370601f19916169a7565b01905f5b82811061738057505050565b60209061738b616b0b565b82828501015201617374565b356001600160a01b03811681036138775790565b90816020910312613877575180151581036138775790565b93919594965f958815617635576001600160401b031643811161761f57804303438111616993576102581061760957506040519360208501526020845261740b6040856167d9565b5f975f965b888810156175df578760051b840135607e1985360301811215613877578401996174398b617397565b6001600160a01b0391821691168110156175b357506174578a617397565b9961749860208761746784617397565b604051632e4bfa5160e11b81526001600160a01b039091166004820152602481019190915291829081906044820190565b03816001600160a01b038d165afa908115616375575f91617585575b501561755e576020810190600460ff6174cc84616bed565b160361752b576174dd88828b617c02565b156174f75750506174ef600191616bfb565b970196617410565b9061750c61750660ff93617397565b91616bed565b9063bbf82ba360e01b5f5260018060a01b03166004521660245260445ffd5b9061753a61750660ff93617397565b9063587548c360e11b5f5260018060a01b031660045216602452600460445260645ffd5b6175688691617397565b63ae8bb03960e01b5f5260018060a01b031660045260245260445ffd5b6175a6915060203d81116175ac575b61759e81836167d9565b8101906173ab565b5f6174b4565b503d617594565b6175bc8b617397565b6311641feb60e21b5f9081526004929092526001600160a01b0316602452604490fd5b985095509550505050508083106175f35750565b826305bc216760e51b5f5260045260245260445ffd5b630ed38fd160e41b5f526004524360245260445ffd5b637b51505560e01b5f526004524360245260445ffd5b631fc460bf60e11b5f5260045ffd5b60408101359081156177c55761765d6102008201616be0565b1561782f5761ffff6176726101008301616c09565b16158015617823575b6177c55760608101600260ff61769083616bed565b16146177fc57600160ff6176a383616bed565b16141590816177e4575b506177c557602081013590815f52600660205260ff60405f205416156177b257815f52601560205260405f2060a082013590815f5260205260405f2090159081156177a2575b5061773857610120810135158015617795575b61773857610180810163ffffffff61771d82616c18565b1615908115617779575b8115617751575b5061773857505050565b638307aca560e01b5f523560045260245260445260645ffd5b905063ffffffff8061776f6177696101a08601616c18565b93616c18565b169116115f61772e565b905063ffffffff61778d6101a08401616c18565b161590617727565b5061014081013515617706565b60ff91506007015416155f6176f3565b50633f4b99b160e21b5f5260045260245ffd5b602090638307aca560e01b5f528035600452013560245260445260645ffd5b600391506177f360ff91616bed565b1614155f6176ad565b506020810135908115801590617816575b61773857505050565b5060a0810135151561780d565b5060808101351561767b565b5050565b9061783d82616f0c565b8115617982575f92839261785081616f0c565b600181146179595760029061786481616f0c565b146178d157604051627eeac760e11b6020820190815230602483015260448201929092526178958160648101613304565b51915afa6178a1616f2a565b90806178c5575b15616df35760208151918180820193849201010312613877575190565b506020815110156178a8565b60405160208101916331a9108f60e11b83526024820152602481526178f76044826167d9565b51915afa617903616f2a565b8161794b575b8161791e575b501561791a57600190565b5f90565b905060208180518101031261387757602001516001600160a01b038116908190036138775730145f61790f565b905060208151101590617909565b505060405160208101906370a0823160e01b8252306024820152602481526178956044826167d9565b5050504790565b5f546001600160a01b031680151590816179ab575b5061701457617014617d44565b905033145f61799e565b90813b15617a34575f816020829351910182855af16179d2616f2a565b9015908115617a04575b506179e45750565b6365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd5b8051801515925082617a19575b50505f6179dc565b617a2c92506020809183010191016173ab565b155f80617a11565b506365f4a9ef60e11b5f9081526001600160a01b0391909116600452602490fd5b90916001600160a01b038111617a6a57505050565b617a93604051938493639a49f29360e01b85526004850152606060248501526064840190616672565b9060448301520390fd5b9080519060208101519060ff60408201511660608201516020815191012060808301516020815191012060ff60a0850151169063ffffffff60c0860151169263ffffffff60e087015116946001600160401b03610120610100890151151598015116976040519960208b019b5f5160206182af5f395f51905f528d52600160408d015260608c015260808b015260a08a015260c089015260e08801526101008701526101208601526101408501526101608401526101808301526101a08201526101a08152616b056101c0826167d9565b602081830312613877578051906001600160401b038211613877570181601f8201121561387757805190617ba1826167fa565b92617baf60405194856167d9565b8284526020838301011161387757815f9260208093018386015e8301015290565b903590601e198136030182121561387757018035906001600160401b0382116138775760200191813603831361387757565b9160208201600460ff617c1483616bed565b1614617cc35760ff617c27600592616bed565b1614617c34575050505f90565b5f617c3e83617397565b604051639e5adaeb60e01b81526001600160a01b0391821660048201529485916024918391165afa91821561637557616866935f93617c97575b50617c8a816040617c91930190617bd0565b3691616815565b91617fcc565b617c91919350617cbb617c8a913d805f833e617cb381836167d9565b810190617b6e565b939150617c78565b505f617cce83617397565b60405163b7af85d760e01b81526001600160a01b0391821660048201529485916024918391165afa91821561637557616866935f93617d20575b50617c8a816040617d1a930190617bd0565b91617f03565b617d1a919350617d3c617c8a913d805f833e617cb381836167d9565b939150617d08565b6040516328305db160e21b81527f00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf6001600160a01b031690602081600481855afa908115616375575f91617ee4575b501580617e6c575b617e695760405163e14c465b60e01b8152602081600481855afa908115616375575f91617e35575b50604051632e4bfa5160e11b815233600482015260248101919091529060209082908180604481015b03915afa908115616375575f91617e16575b506170145763321cbc0960e21b5f523360045260245ffd5b617e2f915060203d6020116175ac5761759e81836167d9565b5f617dfe565b90506020813d602011617e61575b81617e50602093836167d9565b810103126138775751617dec617dc3565b3d9150617e43565b50565b5060405163f5778b0360e01b8152602081600481855afa908115616375575f91617ea2575b506001600160a01b03163314617d9b565b90506020813d602011617edc575b81617ebd602093836167d9565b8101031261387757516001600160a01b0381168103613877575f617e91565b3d9150617eb0565b617efd915060203d6020116175ac5761759e81836167d9565b5f617d93565b610a20815114801590617fbf575b617fb8576020617f645f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f1981018352826167d9565b51906102045afa617f73616f2a565b81617fac575b81617f82575090565b9050602081519101519060208110617f9b575b50151590565b5f199060200360031b1b165f617f95565b80516020149150617f79565b5050505f90565b5061121383511415617f11565b604081511480159061803b575b617fb857602061802c5f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f1981018352826167d9565b51906102055afa617f73616f2a565b5061746083511415617fd9565b9080156182485760015b8181106181b657505f5b81811061817557505b6001811161807b575061807790616a6f565b5190565b5f905f5b81811061808d575050618065565b6001810180821161699357828114618157576180a98286616a8c565b51905f916180b78288616a8c565b51111561813b576180c88387616a8c565b5191616993576180d89086616a8c565b515b604051906020820192600160f81b845260218301526041820152604181526181036061826167d9565b51902061811961811285616bfb565b9486616a8c565b525b600281018091111561807f57634e487b7160e01b5f52601160045260245ffd5b618146915085616a8c565b516181518286616a8c565b516180da565b506181628185616a8c565b5161816f61811285616bfb565b5261811b565b8061818260019285616a8c565b5160405160208101915f83526021820152602181526181a26041826167d9565b5190206181af8286616a8c565b520161805c565b926181c384849394616a8c565b5192845b801580158061822a575b15618211575f19820191808311616993576181f76181ef8488616a8c565b519187616a8c565b52156181c757634e487b7160e01b5f52601160045260245ffd5b50936182236001939692949586616a8c565b5201618052565b505f19820182811161699357618241879187616a8c565b51116181d1565b50505f9056febe98de78ab1a89101b9b77b1b3013a49647e63c349ee09cc55e0a33f97e351a984e75c8576483b53128ed23c26158522388ddb0a5df8516c83887d522196f91fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267553bfca378ab6d3ea8c58df6b962c50de5515a3d2878713fa6dd8d84f220809ff7643c83e539cea2f6bf506545392e52cfd5f917e327efbcd0ba28f29c28d042e
No CBOR metadata tail — this bytecode was built with cbor_metadata off, the setting our own contracts pin for CREATE2 address invariance.
disassembly (first 4,000 ops)
| pc | op | operand |
|---|---|---|
| 0000 | PUSH2 | 0x0120 |
| 0003 | DUP1 | |
| 0004 | PUSH1 | 0x40 |
| 0006 | MSTORE | |
| 0007 | PUSH1 | 0x04 |
| 0009 | CALLDATASIZE | |
| 000a | LT | |
| 000b | ISZERO | |
| 000c | PUSH2 | 0x0013 |
| 000f | JUMPI | |
| 0010 | PUSH0 | |
| 0011 | DUP1 | |
| 0012 | REVERT | |
| 0013 | JUMPDEST | |
| 0014 | PUSH0 | |
| 0015 | PUSH2 | 0x0100 |
| 0018 | MSTORE | |
| 0019 | PUSH0 | |
| 001a | CALLDATALOAD | |
| 001b | PUSH1 | 0xe0 |
| 001d | SHR | |
| 001e | SWAP1 | |
| 001f | DUP2 | |
| 0020 | PUSH4 | 0x0292709e |
| 0025 | EQ | |
| 0026 | PUSH2 | 0x5f38 |
| 0029 | JUMPI | |
| 002a | POP | |
| 002b | DUP1 | |
| 002c | PUSH4 | 0x036f9a43 |
| 0031 | EQ | |
| 0032 | PUSH2 | 0x5f1a |
| 0035 | JUMPI | |
| 0036 | DUP1 | |
| 0037 | PUSH4 | 0x0489d038 |
| 003c | EQ | |
| 003d | PUSH2 | 0x5e5e |
| 0040 | JUMPI | |
| 0041 | DUP1 | |
| 0042 | PUSH4 | 0x087a5cf4 |
| 0047 | EQ | |
| 0048 | PUSH2 | 0x5deb |
| 004b | JUMPI | |
| 004c | DUP1 | |
| 004d | PUSH4 | 0x08f0ce62 |
| 0052 | EQ | |
| 0053 | PUSH2 | 0x3af4 |
| 0056 | JUMPI | |
| 0057 | DUP1 | |
| 0058 | PUSH4 | 0x0900767d |
| 005d | EQ | |
| 005e | PUSH2 | 0x5dd2 |
| 0061 | JUMPI | |
| 0062 | DUP1 | |
| 0063 | PUSH4 | 0x0eca1867 |
| 0068 | EQ | |
| 0069 | PUSH2 | 0x5db9 |
| 006c | JUMPI | |
| 006d | DUP1 | |
| 006e | PUSH4 | 0x150b7a02 |
| 0073 | EQ | |
| 0074 | PUSH2 | 0x5d63 |
| 0077 | JUMPI | |
| 0078 | DUP1 | |
| 0079 | PUSH4 | 0x1591c4a0 |
| 007e | EQ | |
| 007f | PUSH2 | 0x5741 |
| 0082 | JUMPI | |
| 0083 | DUP1 | |
| 0084 | PUSH4 | 0x178bcc93 |
| 0089 | EQ | |
| 008a | PUSH2 | 0x56fa |
| 008d | JUMPI | |
| 008e | DUP1 | |
| 008f | PUSH4 | 0x1945098e |
| 0094 | EQ | |
| 0095 | PUSH2 | 0x0680 |
| 0098 | JUMPI | |
| 0099 | DUP1 | |
| 009a | PUSH4 | 0x1a633473 |
| 009f | EQ | |
| 00a0 | PUSH2 | 0x0560 |
| 00a3 | JUMPI | |
| 00a4 | DUP1 | |
| 00a5 | PUSH4 | 0x1e000aa5 |
| 00aa | EQ | |
| 00ab | PUSH2 | 0x55f8 |
| 00ae | JUMPI | |
| 00af | DUP1 | |
| 00b0 | PUSH4 | 0x200d43ad |
| 00b5 | EQ | |
| 00b6 | PUSH2 | 0x498e |
| 00b9 | JUMPI | |
| 00ba | DUP1 | |
| 00bb | PUSH4 | 0x20d4f696 |
| 00c0 | EQ | |
| 00c1 | PUSH2 | 0x0560 |
| 00c4 | JUMPI | |
| 00c5 | DUP1 | |
| 00c6 | PUSH4 | 0x210de79d |
| 00cb | EQ | |
| 00cc | PUSH2 | 0x493f |
| 00cf | JUMPI | |
| 00d0 | DUP1 | |
| 00d1 | PUSH4 | 0x2b89089c |
| 00d6 | EQ | |
| 00d7 | PUSH2 | 0x4920 |
| 00da | JUMPI | |
| 00db | DUP1 | |
| 00dc | PUSH4 | 0x3ec4cc34 |
| 00e1 | EQ | |
| 00e2 | PUSH2 | 0x48d5 |
| 00e5 | JUMPI | |
| 00e6 | DUP1 | |
| 00e7 | PUSH4 | 0x3fb27b85 |
| 00ec | EQ | |
| 00ed | PUSH2 | 0x4866 |
| 00f0 | JUMPI | |
| 00f1 | DUP1 | |
| 00f2 | PUSH4 | 0x42cde4e8 |
| 00f7 | EQ | |
| 00f8 | PUSH2 | 0x4846 |
| 00fb | JUMPI | |
| 00fc | DUP1 | |
| 00fd | PUSH4 | 0x4760fd43 |
| 0102 | EQ | |
| 0103 | PUSH2 | 0x47da |
| 0106 | JUMPI | |
| 0107 | DUP1 | |
| 0108 | PUSH4 | 0x4bcb06a9 |
| 010d | EQ | |
| 010e | PUSH2 | 0x47c1 |
| 0111 | JUMPI | |
| 0112 | DUP1 | |
| 0113 | PUSH4 | 0x4e2ed4bb |
| 0118 | EQ | |
| 0119 | PUSH2 | 0x41e7 |
| 011c | JUMPI | |
| 011d | DUP1 | |
| 011e | PUSH4 | 0x4ec824ff |
| 0123 | EQ | |
| 0124 | PUSH2 | 0x3af4 |
| 0127 | JUMPI | |
| 0128 | DUP1 | |
| 0129 | PUSH4 | 0x5483a0b5 |
| 012e | EQ | |
| 012f | PUSH2 | 0x419f |
| 0132 | JUMPI | |
| 0133 | DUP1 | |
| 0134 | PUSH4 | 0x57d70648 |
| 0139 | EQ | |
| 013a | PUSH2 | 0x4181 |
| 013d | JUMPI | |
| 013e | DUP1 | |
| 013f | PUSH4 | 0x5b88f50e |
| 0144 | EQ | |
| 0145 | PUSH2 | 0x3af4 |
| 0148 | JUMPI | |
| 0149 | DUP1 | |
| 014a | PUSH4 | 0x5eb320b8 |
| 014f | EQ | |
| 0150 | PUSH2 | 0x4161 |
| 0153 | JUMPI | |
| 0154 | DUP1 | |
| 0155 | PUSH4 | 0x5f61b15c |
| 015a | EQ | |
| 015b | PUSH2 | 0x3af4 |
| 015e | JUMPI | |
| 015f | DUP1 | |
| 0160 | PUSH4 | 0x608ed2ec |
| 0165 | EQ | |
| 0166 | PUSH2 | 0x40ee |
| 0169 | JUMPI | |
| 016a | DUP1 | |
| 016b | PUSH4 | 0x60a18008 |
| 0170 | EQ | |
| 0171 | PUSH2 | 0x40ba |
| 0174 | JUMPI | |
| 0175 | DUP1 | |
| 0176 | PUSH4 | 0x60c29fac |
| 017b | EQ | |
| 017c | PUSH2 | 0x0680 |
| 017f | JUMPI | |
| 0180 | DUP1 | |
| 0181 | PUSH4 | 0x622c3f21 |
| 0186 | EQ | |
| 0187 | PUSH2 | 0x409b |
| 018a | JUMPI | |
| 018b | DUP1 | |
| 018c | PUSH4 | 0x65cea49c |
| 0191 | EQ | |
| 0192 | PUSH2 | 0x407d |
| 0195 | JUMPI | |
| 0196 | DUP1 | |
| 0197 | PUSH4 | 0x68cf465e |
| 019c | EQ | |
| 019d | PUSH2 | 0x0680 |
| 01a0 | JUMPI | |
| 01a1 | DUP1 | |
| 01a2 | PUSH4 | 0x6c2c44e2 |
| 01a7 | EQ | |
| 01a8 | PUSH2 | 0x4064 |
| 01ab | JUMPI | |
| 01ac | DUP1 | |
| 01ad | PUSH4 | 0x6db81752 |
| 01b2 | EQ | |
| 01b3 | PUSH2 | 0x4044 |
| 01b6 | JUMPI | |
| 01b7 | DUP1 | |
| 01b8 | PUSH4 | 0x711f3db4 |
| 01bd | EQ | |
| 01be | PUSH2 | 0x3f62 |
| 01c1 | JUMPI | |
| 01c2 | DUP1 | |
| 01c3 | PUSH4 | 0x767c39d2 |
| 01c8 | EQ | |
| 01c9 | PUSH2 | 0x0560 |
| 01cc | JUMPI | |
| 01cd | DUP1 | |
| 01ce | PUSH4 | 0x7b103999 |
| 01d3 | EQ | |
| 01d4 | PUSH2 | 0x3f1b |
| 01d7 | JUMPI | |
| 01d8 | DUP1 | |
| 01d9 | PUSH4 | 0x7bb737bc |
| 01de | EQ | |
| 01df | PUSH2 | 0x3f02 |
| 01e2 | JUMPI | |
| 01e3 | DUP1 | |
| 01e4 | PUSH4 | 0x7dcdde8e |
| 01e9 | EQ | |
| 01ea | PUSH2 | 0x3d7f |
| 01ed | JUMPI | |
| 01ee | DUP1 | |
| 01ef | PUSH4 | 0x7f49d69f |
| 01f4 | EQ | |
| 01f5 | PUSH2 | 0x3d55 |
| 01f8 | JUMPI | |
| 01f9 | DUP1 | |
| 01fa | PUSH4 | 0x831f33a1 |
| 01ff | EQ | |
| 0200 | PUSH2 | 0x3b35 |
| 0203 | JUMPI | |
| 0204 | DUP1 | |
| 0205 | PUSH4 | 0x8513e2d5 |
| 020a | EQ | |
| 020b | PUSH2 | 0x3b17 |
| 020e | JUMPI | |
| 020f | DUP1 | |
| 0210 | PUSH4 | 0x852a52e8 |
| 0215 | EQ | |
| 0216 | PUSH2 | 0x3af9 |
| 0219 | JUMPI | |
| 021a | DUP1 | |
| 021b | PUSH4 | 0x88f06eaa |
| 0220 | EQ | |
| 0221 | PUSH2 | 0x3af4 |
| 0224 | JUMPI | |
| 0225 | DUP1 | |
| 0226 | PUSH4 | 0x92a2e0b3 |
| 022b | EQ | |
| 022c | PUSH2 | 0x3ad6 |
| 022f | JUMPI | |
| 0230 | DUP1 | |
| 0231 | PUSH4 | 0x9377c220 |
| 0236 | EQ | |
| 0237 | PUSH2 | 0x3aad |
| 023a | JUMPI | |
| 023b | DUP1 | |
| 023c | PUSH4 | 0x9493ba1d |
| 0241 | EQ | |
| 0242 | PUSH2 | 0x3a3a |
| 0245 | JUMPI | |
| 0246 | DUP1 | |
| 0247 | PUSH4 | 0x94bc4e96 |
| 024c | EQ | |
| 024d | PUSH2 | 0x372d |
| 0250 | JUMPI | |
| 0251 | DUP1 | |
| 0252 | PUSH4 | 0x95102c42 |
| 0257 | EQ | |
| 0258 | PUSH2 | 0x370e |
| 025b | JUMPI | |
| 025c | DUP1 | |
| 025d | PUSH4 | 0x9648eca2 |
| 0262 | EQ | |
| 0263 | PUSH2 | 0x344e |
| 0266 | JUMPI | |
| 0267 | DUP1 | |
| 0268 | PUSH4 | 0x96f51f3a |
| 026d | EQ | |
| 026e | PUSH2 | 0x3199 |
| 0271 | JUMPI | |
| 0272 | DUP1 | |
| 0273 | PUSH4 | 0x97b9857b |
| 0278 | EQ | |
| 0279 | PUSH2 | 0x312d |
| 027c | JUMPI | |
| 027d | DUP1 | |
| 027e | PUSH4 | 0xa741113c |
| 0283 | EQ | |
| 0284 | PUSH2 | 0x30f3 |
| 0287 | JUMPI | |
| 0288 | DUP1 | |
| 0289 | PUSH4 | 0xaa9239f5 |
| 028e | EQ | |
| 028f | PUSH2 | 0x3097 |
| 0292 | JUMPI | |
| 0293 | DUP1 | |
| 0294 | PUSH4 | 0xac65bc70 |
| 0299 | EQ | |
| 029a | PUSH2 | 0x2f6e |
| 029d | JUMPI | |
| 029e | DUP1 | |
| 029f | PUSH4 | 0xad844b96 |
| 02a4 | EQ | |
| 02a5 | PUSH2 | 0x2ec5 |
| 02a8 | JUMPI | |
| 02a9 | DUP1 | |
| 02aa | PUSH4 | 0xaffed0e0 |
| 02af | EQ | |
| 02b0 | PUSH2 | 0x2e9c |
| 02b3 | JUMPI | |
| 02b4 | DUP1 | |
| 02b5 | PUSH4 | 0xb19f4805 |
| 02ba | EQ | |
| 02bb | PUSH2 | 0x2e5f |
| 02be | JUMPI | |
| 02bf | DUP1 | |
| 02c0 | PUSH4 | 0xb8da302a |
| 02c5 | EQ | |
| 02c6 | PUSH2 | 0x2e3f |
| 02c9 | JUMPI | |
| 02ca | DUP1 | |
| 02cb | PUSH4 | 0xb9a7f076 |
| 02d0 | EQ | |
| 02d1 | PUSH2 | 0x2d56 |
| 02d4 | JUMPI | |
| 02d5 | DUP1 | |
| 02d6 | PUSH4 | 0xb9ed4686 |
| 02db | EQ | |
| 02dc | PUSH2 | 0x0cba |
| 02df | JUMPI | |
| 02e0 | DUP1 | |
| 02e1 | PUSH4 | 0xbbbcda44 |
| 02e6 | EQ | |
| 02e7 | PUSH2 | 0x0c9c |
| 02ea | JUMPI | |
| 02eb | DUP1 | |
| 02ec | PUSH4 | 0xbc197c81 |
| 02f1 | EQ | |
| 02f2 | PUSH2 | 0x0c04 |
| 02f5 | JUMPI | |
| 02f6 | DUP1 | |
| 02f7 | PUSH4 | 0xbda1019e |
| 02fc | EQ | |
| 02fd | PUSH2 | 0x0be4 |
| 0300 | JUMPI | |
| 0301 | DUP1 | |
| 0302 | PUSH4 | 0xbe380c85 |
| 0307 | EQ | |
| 0308 | PUSH2 | 0x0a63 |
| 030b | JUMPI | |
| 030c | DUP1 | |
| 030d | PUSH4 | 0xc5dbda66 |
| 0312 | EQ | |
| 0313 | PUSH2 | 0x0a44 |
| 0316 | JUMPI | |
| 0317 | DUP1 | |
| 0318 | PUSH4 | 0xdc544a09 |
| 031d | EQ | |
| 031e | PUSH2 | 0x0a23 |
| 0321 | JUMPI | |
| 0322 | DUP1 | |
| 0323 | PUSH4 | 0xde54d429 |
| 0328 | EQ | |
| 0329 | PUSH2 | 0x0a03 |
| 032c | JUMPI | |
| 032d | DUP1 | |
| 032e | PUSH4 | 0xdf46c713 |
| 0333 | EQ | |
| 0334 | PUSH2 | 0x089d |
| 0337 | JUMPI | |
| 0338 | DUP1 | |
| 0339 | PUSH4 | 0xe02e1bfd |
| 033e | EQ | |
| 033f | PUSH2 | 0x087d |
| 0342 | JUMPI | |
| 0343 | DUP1 | |
| 0344 | PUSH4 | 0xe597e659 |
| 0349 | EQ | |
| 034a | PUSH2 | 0x0685 |
| 034d | JUMPI | |
| 034e | DUP1 | |
| 034f | PUSH4 | 0xe70475a7 |
| 0354 | EQ | |
| 0355 | PUSH2 | 0x0680 |
| 0358 | JUMPI | |
| 0359 | DUP1 | |
| 035a | PUSH4 | 0xe94a52af |
| 035f | EQ | |
| 0360 | PUSH2 | 0x0662 |
| 0363 | JUMPI | |
| 0364 | DUP1 | |
| 0365 | PUSH4 | 0xeafe7a74 |
| 036a | EQ | |
| 036b | PUSH2 | 0x0642 |
| 036e | JUMPI | |
| 036f | DUP1 | |
| 0370 | PUSH4 | 0xeb777382 |
| 0375 | EQ | |
| 0376 | PUSH2 | 0x0565 |
| 0379 | JUMPI | |
| 037a | DUP1 | |
| 037b | PUSH4 | 0xee076a50 |
| 0380 | EQ | |
| 0381 | PUSH2 | 0x0560 |
| 0384 | JUMPI | |
| 0385 | DUP1 | |
| 0386 | PUSH4 | 0xee57e33c |
| 038b | EQ | |
| 038c | PUSH2 | 0x04cb |
| 038f | JUMPI | |
| 0390 | DUP1 | |
| 0391 | PUSH4 | 0xf23a6e61 |
| 0396 | EQ | |
| 0397 | PUSH2 | 0x0475 |
| 039a | JUMPI | |
| 039b | DUP1 | |
| 039c | PUSH4 | 0xf3178f43 |
| 03a1 | EQ | |
| 03a2 | PUSH2 | 0x0412 |
| 03a5 | JUMPI | |
| 03a6 | DUP1 | |
| 03a7 | PUSH4 | 0xf4e885db |
| 03ac | EQ | |
| 03ad | PUSH2 | 0x03f2 |
| 03b0 | JUMPI | |
| 03b1 | PUSH4 | 0xf851a440 |
| 03b6 | EQ | |
| 03b7 | PUSH2 | 0x03be |
| 03ba | JUMPI | |
| 03bb | PUSH0 | |
| 03bc | DUP1 | |
| 03bd | REVERT | |
| 03be | JUMPDEST | |
| 03bf | CALLVALUE | |
| 03c0 | PUSH2 | 0x03eb |
| 03c3 | JUMPI | |
| 03c4 | PUSH2 | 0x0100 |
| 03c7 | MLOAD | |
| 03c8 | CALLDATASIZE | |
| 03c9 | PUSH1 | 0x03 |
| 03cb | NOT | |
| 03cc | ADD | |
| 03cd | SLT | |
| 03ce | PUSH2 | 0x03eb |
| 03d1 | JUMPI | |
| 03d2 | PUSH2 | 0x0100 |
| 03d5 | MLOAD | |
| 03d6 | SLOAD | |
| 03d7 | PUSH1 | 0x40 |
| 03d9 | MLOAD | |
| 03da | PUSH1 | 0x01 |
| 03dc | PUSH1 | 0x01 |
| 03de | PUSH1 | 0xa0 |
| 03e0 | SHL | |
| 03e1 | SUB | |
| 03e2 | SWAP1 | |
| 03e3 | SWAP2 | |
| 03e4 | AND | |
| 03e5 | DUP2 | |
| 03e6 | MSTORE | |
| 03e7 | PUSH1 | 0x20 |
| 03e9 | SWAP1 | |
| 03ea | RETURN | |
| 03eb | JUMPDEST | |
| 03ec | PUSH2 | 0x0100 |
| 03ef | MLOAD | |
| 03f0 | DUP1 | |
| 03f1 | REVERT | |
| 03f2 | JUMPDEST | |
| 03f3 | CALLVALUE | |
| 03f4 | PUSH2 | 0x03eb |
| 03f7 | JUMPI | |
| 03f8 | PUSH2 | 0x0100 |
| 03fb | MLOAD | |
| 03fc | CALLDATASIZE | |
| 03fd | PUSH1 | 0x03 |
| 03ff | NOT | |
| 0400 | ADD | |
| 0401 | SLT | |
| 0402 | PUSH2 | 0x03eb |
| 0405 | JUMPI | |
| 0406 | PUSH1 | 0x20 |
| 0408 | PUSH1 | 0x01 |
| 040a | SLOAD | |
| 040b | PUSH1 | 0x40 |
| 040d | MLOAD | |
| 040e | SWAP1 | |
| 040f | DUP2 | |
| 0410 | MSTORE | |
| 0411 | RETURN | |
| 0412 | JUMPDEST | |
| 0413 | CALLVALUE | |
| 0414 | PUSH2 | 0x03eb |
| 0417 | JUMPI | |
| 0418 | PUSH1 | 0x40 |
| 041a | CALLDATASIZE | |
| 041b | PUSH1 | 0x03 |
| 041d | NOT | |
| 041e | ADD | |
| 041f | SLT | |
| 0420 | PUSH2 | 0x03eb |
| 0423 | JUMPI | |
| 0424 | PUSH2 | 0x042b |
| 0427 | PUSH2 | 0x63eb |
| 042a | JUMP | |
| 042b | JUMPDEST | |
| 042c | PUSH1 | 0x04 |
| 042e | CALLDATALOAD | |
| 042f | PUSH2 | 0x0100 |
| 0432 | MLOAD | |
| 0433 | MSTORE | |
| 0434 | PUSH1 | 0x0a |
| 0436 | PUSH1 | 0x20 |
| 0438 | MSTORE | |
| 0439 | PUSH2 | 0x0451 |
| 043c | PUSH1 | 0x01 |
| 043e | PUSH1 | 0x01 |
| 0440 | PUSH1 | 0x40 |
| 0442 | SHL | |
| 0443 | SUB | |
| 0444 | PUSH1 | 0x40 |
| 0446 | PUSH2 | 0x0100 |
| 0449 | MLOAD | |
| 044a | KECCAK256 | |
| 044b | SWAP3 | |
| 044c | AND | |
| 044d | PUSH2 | 0x7261 |
| 0450 | JUMP | |
| 0451 | JUMPDEST | |
| 0452 | PUSH2 | 0x0100 |
| 0455 | MLOAD | |
| 0456 | MSTORE | |
| 0457 | PUSH1 | 0x20 |
| 0459 | MSTORE | |
| 045a | PUSH1 | 0x20 |
| 045c | PUSH2 | 0xffff |
| 045f | PUSH1 | 0x02 |
| 0461 | PUSH1 | 0x40 |
| 0463 | PUSH2 | 0x0100 |
| 0466 | MLOAD | |
| 0467 | KECCAK256 | |
| 0468 | ADD | |
| 0469 | SLOAD | |
| 046a | PUSH1 | 0x50 |
| 046c | SHR | |
| 046d | AND | |
| 046e | PUSH1 | 0x40 |
| 0470 | MLOAD | |
| 0471 | SWAP1 | |
| 0472 | DUP2 | |
| 0473 | MSTORE | |
| 0474 | RETURN | |
| 0475 | JUMPDEST | |
| 0476 | CALLVALUE | |
| 0477 | PUSH2 | 0x03eb |
| 047a | JUMPI | |
| 047b | PUSH1 | 0xa0 |
| 047d | CALLDATASIZE | |
| 047e | PUSH1 | 0x03 |
| 0480 | NOT | |
| 0481 | ADD | |
| 0482 | SLT | |
| 0483 | PUSH2 | 0x03eb |
| 0486 | JUMPI | |
| 0487 | PUSH2 | 0x048e |
| 048a | PUSH2 | 0x6527 |
| 048d | JUMP | |
| 048e | JUMPDEST | |
| 048f | POP | |
| 0490 | PUSH2 | 0x0497 |
| 0493 | PUSH2 | 0x653d |
| 0496 | JUMP | |
| 0497 | JUMPDEST | |
| 0498 | POP | |
| 0499 | PUSH1 | 0x84 |
| 049b | CALLDATALOAD | |
| 049c | PUSH1 | 0x01 |
| 049e | PUSH1 | 0x01 |
| 04a0 | PUSH1 | 0x40 |
| 04a2 | SHL | |
| 04a3 | SUB | |
| 04a4 | DUP2 | |
| 04a5 | GT | |
| 04a6 | PUSH2 | 0x03eb |
| 04a9 | JUMPI | |
| 04aa | PUSH2 | 0x04b7 |
| 04ad | SWAP1 | |
| 04ae | CALLDATASIZE | |
| 04af | SWAP1 | |
| 04b0 | PUSH1 | 0x04 |
| 04b2 | ADD | |
| 04b3 | PUSH2 | 0x6553 |
| 04b6 | JUMP | |
| 04b7 | JUMPDEST | |
| 04b8 | POP | |
| 04b9 | POP | |
| 04ba | PUSH1 | 0x40 |
| 04bc | MLOAD | |
| 04bd | PUSH4 | 0xf23a6e61 |
| 04c2 | PUSH1 | 0xe0 |
| 04c4 | SHL | |
| 04c5 | DUP2 | |
| 04c6 | MSTORE | |
| 04c7 | PUSH1 | 0x20 |
| 04c9 | SWAP1 | |
| 04ca | RETURN | |
| 04cb | JUMPDEST | |
| 04cc | CALLVALUE | |
| 04cd | PUSH2 | 0x03eb |
| 04d0 | JUMPI | |
| 04d1 | PUSH2 | 0x0100 |
| 04d4 | MLOAD | |
| 04d5 | CALLDATASIZE | |
| 04d6 | PUSH1 | 0x03 |
| 04d8 | NOT | |
| 04d9 | ADD | |
| 04da | SLT | |
| 04db | PUSH2 | 0x03eb |
| 04de | JUMPI | |
| 04df | PUSH1 | 0x40 |
| 04e1 | MLOAD | |
| 04e2 | DUP1 | |
| 04e3 | PUSH1 | 0x20 |
| 04e5 | PUSH1 | 0x13 |
| 04e7 | SLOAD | |
| 04e8 | SWAP2 | |
| 04e9 | DUP3 | |
| 04ea | DUP2 | |
| 04eb | MSTORE | |
| 04ec | ADD | |
| 04ed | SWAP1 | |
| 04ee | PUSH1 | 0x13 |
| 04f0 | PUSH2 | 0x0100 |
| 04f3 | MLOAD | |
| 04f4 | MSTORE | |
| 04f5 | PUSH32 | 0x66de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090 |
| 0516 | SWAP1 | |
| 0517 | PUSH2 | 0x0100 |
| 051a | MLOAD | |
| 051b | JUMPDEST | |
| 051c | DUP2 | |
| 051d | DUP2 | |
| 051e | LT | |
| 051f | PUSH2 | 0x054a |
| 0522 | JUMPI | |
| 0523 | PUSH2 | 0x0546 |
| 0526 | DUP6 | |
| 0527 | PUSH2 | 0x0532 |
| 052a | DUP2 | |
| 052b | DUP8 | |
| 052c | SUB | |
| 052d | DUP3 | |
| 052e | PUSH2 | 0x67d9 |
| 0531 | JUMP | |
| 0532 | JUMPDEST | |
| 0533 | PUSH1 | 0x40 |
| 0535 | MLOAD | |
| 0536 | SWAP2 | |
| 0537 | DUP3 | |
| 0538 | SWAP2 | |
| 0539 | PUSH1 | 0x20 |
| 053b | DUP4 | |
| 053c | MSTORE | |
| 053d | PUSH1 | 0x20 |
| 053f | DUP4 | |
| 0540 | ADD | |
| 0541 | SWAP1 | |
| 0542 | PUSH2 | 0x64d9 |
| 0545 | JUMP | |
| 0546 | JUMPDEST | |
| 0547 | SUB | |
| 0548 | SWAP1 | |
| 0549 | RETURN | |
| 054a | JUMPDEST | |
| 054b | DUP3 | |
| 054c | SLOAD | |
| 054d | DUP5 | |
| 054e | MSTORE | |
| 054f | PUSH1 | 0x20 |
| 0551 | SWAP1 | |
| 0552 | SWAP4 | |
| 0553 | ADD | |
| 0554 | SWAP3 | |
| 0555 | PUSH1 | 0x01 |
| 0557 | SWAP3 | |
| 0558 | DUP4 | |
| 0559 | ADD | |
| 055a | SWAP3 | |
| 055b | ADD | |
| 055c | PUSH2 | 0x051b |
| 055f | JUMP | |
| 0560 | JUMPDEST | |
| 0561 | PUSH2 | 0x65cc |
| 0564 | JUMP | |
| 0565 | JUMPDEST | |
| 0566 | CALLVALUE | |
| 0567 | PUSH2 | 0x03eb |
| 056a | JUMPI | |
| 056b | PUSH2 | 0x0100 |
| 056e | MLOAD | |
| 056f | CALLDATASIZE | |
| 0570 | PUSH1 | 0x03 |
| 0572 | NOT | |
| 0573 | ADD | |
| 0574 | SLT | |
| 0575 | PUSH2 | 0x03eb |
| 0578 | JUMPI | |
| 0579 | PUSH1 | 0x04 |
| 057b | SLOAD | |
| 057c | PUSH2 | 0x0584 |
| 057f | DUP2 | |
| 0580 | PUSH2 | 0x69be |
| 0583 | JUMP | |
| 0584 | JUMPDEST | |
| 0585 | PUSH2 | 0x0100 |
| 0588 | MLOAD | |
| 0589 | SWAP1 | |
| 058a | SWAP2 | |
| 058b | DUP2 | |
| 058c | JUMPDEST | |
| 058d | DUP2 | |
| 058e | DUP2 | |
| 058f | LT | |
| 0590 | PUSH2 | 0x05e0 |
| 0593 | JUMPI | |
| 0594 | POP | |
| 0595 | POP | |
| 0596 | PUSH2 | 0x059e |
| 0599 | DUP2 | |
| 059a | PUSH2 | 0x69be |
| 059d | JUMP | |
| 059e | JUMPDEST | |
| 059f | SWAP2 | |
| 05a0 | PUSH2 | 0x0100 |
| 05a3 | MLOAD | |
| 05a4 | JUMPDEST | |
| 05a5 | DUP3 | |
| 05a6 | DUP2 | |
| 05a7 | LT | |
| 05a8 | PUSH2 | 0x05c1 |
| 05ab | JUMPI | |
| 05ac | PUSH1 | 0x40 |
| 05ae | MLOAD | |
| 05af | PUSH1 | 0x20 |
| 05b1 | DUP1 | |
| 05b2 | DUP3 | |
| 05b3 | MSTORE | |
| 05b4 | DUP2 | |
| 05b5 | SWAP1 | |
| 05b6 | PUSH2 | 0x0546 |
| 05b9 | SWAP1 | |
| 05ba | DUP3 | |
| 05bb | ADD | |
| 05bc | DUP8 | |
| 05bd | PUSH2 | 0x64d9 |
| 05c0 | JUMP | |
| 05c1 | JUMPDEST | |
| 05c2 | DUP1 | |
| 05c3 | PUSH2 | 0x05ce |
| 05c6 | PUSH1 | 0x01 |
| 05c8 | SWAP3 | |
| 05c9 | DUP5 | |
| 05ca | PUSH2 | 0x6a8c |
| 05cd | JUMP | |
| 05ce | JUMPDEST | |
| 05cf | MLOAD | |
| 05d0 | PUSH2 | 0x05d9 |
| 05d3 | DUP3 | |
| 05d4 | DUP8 | |
| 05d5 | PUSH2 | 0x6a8c |
| 05d8 | JUMP | |
| 05d9 | JUMPDEST | |
| 05da | MSTORE | |
| 05db | ADD | |
| 05dc | PUSH2 | 0x05a4 |
| 05df | JUMP | |
| 05e0 | JUMPDEST | |
| 05e1 | DUP1 | |
| 05e2 | PUSH2 | 0x05ec |
| 05e5 | PUSH1 | 0x01 |
| 05e7 | SWAP3 | |
| 05e8 | PUSH2 | 0x69f0 |
| 05eb | JUMP | |
| 05ec | JUMPDEST | |
| 05ed | SWAP1 | |
| 05ee | SLOAD | |
| 05ef | SWAP1 | |
| 05f0 | PUSH1 | 0x03 |
| 05f2 | SHL | |
| 05f3 | SHR | |
| 05f4 | PUSH2 | 0x0100 |
| 05f7 | MLOAD | |
| 05f8 | MSTORE | |
| 05f9 | PUSH1 | 0x05 |
| 05fb | PUSH1 | 0x20 |
| 05fd | MSTORE | |
| 05fe | PUSH1 | 0xff |
| 0600 | PUSH1 | 0x0c |
| 0602 | PUSH1 | 0x40 |
| 0604 | PUSH2 | 0x0100 |
| 0607 | MLOAD | |
| 0608 | KECCAK256 | |
| 0609 | ADD | |
| 060a | SLOAD | |
| 060b | PUSH1 | 0x08 |
| 060d | SHR | |
| 060e | AND | |
| 060f | PUSH2 | 0x0619 |
| 0612 | JUMPI | |
| 0613 | JUMPDEST | |
| 0614 | ADD | |
| 0615 | PUSH2 | 0x058c |
| 0618 | JUMP | |
| 0619 | JUMPDEST | |
| 061a | PUSH2 | 0x0622 |
| 061d | DUP2 | |
| 061e | PUSH2 | 0x69f0 |
| 0621 | JUMP | |
| 0622 | JUMPDEST | |
| 0623 | SWAP1 | |
| 0624 | SLOAD | |
| 0625 | SWAP1 | |
| 0626 | PUSH1 | 0x03 |
| 0628 | SHL | |
| 0629 | SHR | |
| 062a | PUSH2 | 0x063c |
| 062d | PUSH2 | 0x0635 |
| 0630 | DUP7 | |
| 0631 | PUSH2 | 0x6bfb |
| 0634 | JUMP | |
| 0635 | JUMPDEST | |
| 0636 | SWAP6 | |
| 0637 | DUP8 | |
| 0638 | PUSH2 | 0x6a8c |
| 063b | JUMP | |
| 063c | JUMPDEST | |
| 063d | MSTORE | |
| 063e | PUSH2 | 0x0613 |
| 0641 | JUMP | |
| 0642 | JUMPDEST | |
| 0643 | CALLVALUE | |
| 0644 | PUSH2 | 0x03eb |
| 0647 | JUMPI | |
| 0648 | PUSH2 | 0x0100 |
| 064b | MLOAD | |
| 064c | CALLDATASIZE | |
| 064d | PUSH1 | 0x03 |
| 064f | NOT | |
| 0650 | ADD | |
| 0651 | SLT | |
| 0652 | PUSH2 | 0x03eb |
| 0655 | JUMPI | |
| 0656 | PUSH1 | 0x20 |
| 0658 | PUSH1 | 0x07 |
| 065a | SLOAD | |
| 065b | PUSH1 | 0x40 |
| 065d | MLOAD | |
| 065e | SWAP1 | |
| 065f | DUP2 | |
| 0660 | MSTORE | |
| 0661 | RETURN | |
| 0662 | JUMPDEST | |
| 0663 | CALLVALUE | |
| 0664 | PUSH2 | 0x03eb |
| 0667 | JUMPI | |
| 0668 | PUSH2 | 0x0100 |
| 066b | MLOAD | |
| 066c | CALLDATASIZE | |
| 066d | PUSH1 | 0x03 |
| 066f | NOT | |
| 0670 | ADD | |
| 0671 | SLT | |
| 0672 | PUSH2 | 0x03eb |
| 0675 | JUMPI | |
| 0676 | PUSH1 | 0x20 |
| 0678 | PUSH1 | 0x40 |
| 067a | MLOAD | |
| 067b | PUSH1 | 0x05 |
| 067d | DUP2 | |
| 067e | MSTORE | |
| 067f | RETURN | |
| 0680 | JUMPDEST | |
| 0681 | PUSH2 | 0x65b1 |
| 0684 | JUMP | |
| 0685 | JUMPDEST | |
| 0686 | CALLVALUE | |
| 0687 | PUSH2 | 0x03eb |
| 068a | JUMPI | |
| 068b | PUSH1 | 0x40 |
| 068d | CALLDATASIZE | |
| 068e | PUSH1 | 0x03 |
| 0690 | NOT | |
| 0691 | ADD | |
| 0692 | SLT | |
| 0693 | PUSH2 | 0x03eb |
| 0696 | JUMPI | |
| 0697 | PUSH1 | 0x24 |
| 0699 | CALLDATALOAD | |
| 069a | PUSH1 | 0x04 |
| 069c | CALLDATALOAD | |
| 069d | PUSH1 | 0xff |
| 069f | DUP3 | |
| 06a0 | AND | |
| 06a1 | DUP3 | |
| 06a2 | SUB | |
| 06a3 | PUSH2 | 0x03eb |
| 06a6 | JUMPI | |
| 06a7 | PUSH1 | 0x07 |
| 06a9 | SLOAD | |
| 06aa | PUSH2 | 0x06b2 |
| 06ad | DUP2 | |
| 06ae | PUSH2 | 0x7348 |
| 06b1 | JUMP | |
| 06b2 | JUMPDEST | |
| 06b3 | PUSH2 | 0x0100 |
| 06b6 | MLOAD | |
| 06b7 | SWAP1 | |
| 06b8 | SWAP4 | |
| 06b9 | SWAP1 | |
| 06ba | SWAP3 | |
| 06bb | DUP4 | |
| 06bc | JUMPDEST | |
| 06bd | DUP4 | |
| 06be | DUP2 | |
| 06bf | LT | |
| 06c0 | PUSH2 | 0x0759 |
| 06c3 | JUMPI | |
| 06c4 | DUP6 | |
| 06c5 | DUP6 | |
| 06c6 | PUSH2 | 0x06ce |
| 06c9 | DUP2 | |
| 06ca | PUSH2 | 0x7348 |
| 06cd | JUMP | |
| 06ce | JUMPDEST | |
| 06cf | SWAP2 | |
| 06d0 | PUSH2 | 0x0100 |
| 06d3 | MLOAD | |
| 06d4 | JUMPDEST | |
| 06d5 | DUP3 | |
| 06d6 | DUP2 | |
| 06d7 | LT | |
| 06d8 | PUSH2 | 0x072f |
| 06db | JUMPI | |
| 06dc | DUP4 | |
| 06dd | PUSH1 | 0x40 |
| 06df | MLOAD | |
| 06e0 | DUP1 | |
| 06e1 | SWAP2 | |
| 06e2 | PUSH1 | 0x20 |
| 06e4 | DUP3 | |
| 06e5 | ADD | |
| 06e6 | PUSH1 | 0x20 |
| 06e8 | DUP4 | |
| 06e9 | MSTORE | |
| 06ea | DUP2 | |
| 06eb | MLOAD | |
| 06ec | DUP1 | |
| 06ed | SWAP2 | |
| 06ee | MSTORE | |
| 06ef | PUSH1 | 0x20 |
| 06f1 | PUSH1 | 0x40 |
| 06f3 | DUP5 | |
| 06f4 | ADD | |
| 06f5 | SWAP3 | |
| 06f6 | ADD | |
| 06f7 | SWAP1 | |
| 06f8 | PUSH2 | 0x0100 |
| 06fb | MLOAD | |
| 06fc | JUMPDEST | |
| 06fd | DUP2 | |
| 06fe | DUP2 | |
| 06ff | LT | |
| 0700 | PUSH2 | 0x070a |
| 0703 | JUMPI | |
| 0704 | POP | |
| 0705 | POP | |
| 0706 | POP | |
| 0707 | SUB | |
| 0708 | SWAP1 | |
| 0709 | RETURN | |
| 070a | JUMPDEST | |
| 070b | SWAP2 | |
| 070c | SWAP4 | |
| 070d | POP | |
| 070e | SWAP2 | |
| 070f | PUSH1 | 0x20 |
| 0711 | PUSH2 | 0x0120 |
| 0714 | DUP3 | |
| 0715 | PUSH2 | 0x0721 |
| 0718 | PUSH1 | 0x01 |
| 071a | SWAP5 | |
| 071b | DUP9 | |
| 071c | MLOAD | |
| 071d | PUSH2 | 0x6471 |
| 0720 | JUMP | |
| 0721 | JUMPDEST | |
| 0722 | ADD | |
| 0723 | SWAP5 | |
| 0724 | ADD | |
| 0725 | SWAP2 | |
| 0726 | ADD | |
| 0727 | SWAP2 | |
| 0728 | DUP5 | |
| 0729 | SWAP4 | |
| 072a | SWAP3 | |
| 072b | PUSH2 | 0x06fc |
| 072e | JUMP | |
| 072f | JUMPDEST | |
| 0730 | DUP1 | |
| 0731 | PUSH2 | 0x073c |
| 0734 | PUSH1 | 0x01 |
| 0736 | SWAP3 | |
| 0737 | DUP5 | |
| 0738 | PUSH2 | 0x6a8c |
| 073b | JUMP | |
| 073c | JUMPDEST | |
| 073d | MLOAD | |
| 073e | PUSH2 | 0x0747 |
| 0741 | DUP3 | |
| 0742 | DUP8 | |
| 0743 | PUSH2 | 0x6a8c |
| 0746 | JUMP | |
| 0747 | JUMPDEST | |
| 0748 | MSTORE | |
| 0749 | PUSH2 | 0x0752 |
| 074c | DUP2 | |
| 074d | DUP7 | |
| 074e | PUSH2 | 0x6a8c |
| 0751 | JUMP | |
| 0752 | JUMPDEST | |
| 0753 | POP | |
| 0754 | ADD | |
| 0755 | PUSH2 | 0x06d4 |
| 0758 | JUMP | |
| 0759 | JUMPDEST | |
| 075a | PUSH2 | 0x0762 |
| 075d | DUP2 | |
| 075e | PUSH2 | 0x6a1c |
| 0761 | JUMP | |
| 0762 | JUMPDEST | |
| 0763 | SWAP1 | |
| 0764 | SLOAD | |
| 0765 | SWAP1 | |
| 0766 | PUSH1 | 0x03 |
| 0768 | SHL | |
| 0769 | SHR | |
| 076a | DUP1 | |
| 076b | PUSH2 | 0x0100 |
| 076e | MLOAD | |
| 076f | MSTORE | |
| 0770 | PUSH1 | 0x08 |
| 0772 | PUSH1 | 0x20 |
| 0774 | MSTORE | |
| 0775 | PUSH1 | 0xff |
| 0777 | PUSH1 | 0x05 |
| 0779 | PUSH1 | 0x40 |
| 077b | PUSH2 | 0x0100 |
| 077e | MLOAD | |
| 077f | KECCAK256 | |
| 0780 | ADD | |
| 0781 | SLOAD | |
| 0782 | PUSH1 | 0x48 |
| 0784 | SHR | |
| 0785 | AND | |
| 0786 | ISZERO | |
| 0787 | PUSH2 | 0x0874 |
| 078a | JUMPI | |
| 078b | PUSH2 | 0x0100 |
| 078e | MLOAD | |
| 078f | MSTORE | |
| 0790 | PUSH1 | 0x0a |
| 0792 | PUSH1 | 0x20 |
| 0794 | MSTORE | |
| 0795 | PUSH1 | 0x40 |
| 0797 | PUSH2 | 0x0100 |
| 079a | MLOAD | |
| 079b | KECCAK256 | |
| 079c | DUP3 | |
| 079d | PUSH2 | 0x0100 |
| 07a0 | MLOAD | |
| 07a1 | MSTORE | |
| 07a2 | PUSH1 | 0x20 |
| 07a4 | MSTORE | |
| 07a5 | PUSH1 | 0x40 |
| 07a7 | PUSH2 | 0x0100 |
| 07aa | MLOAD | |
| 07ab | KECCAK256 | |
| 07ac | PUSH1 | 0x40 |
| 07ae | MLOAD | |
| 07af | SWAP1 | |
| 07b0 | PUSH2 | 0x07b8 |
| 07b3 | DUP3 | |
| 07b4 | PUSH2 | 0x674e |
| 07b7 | JUMP | |
| 07b8 | JUMPDEST | |
| 07b9 | DUP1 | |
| 07ba | SLOAD | |
| 07bb | DUP3 | |
| 07bc | MSTORE | |
| 07bd | PUSH1 | 0x01 |
| 07bf | DUP2 | |
| 07c0 | ADD | |
| 07c1 | SLOAD | |
| 07c2 | PUSH1 | 0x20 |
| 07c4 | DUP4 | |
| 07c5 | ADD | |
| 07c6 | MSTORE | |
| 07c7 | DUP5 | |
| 07c8 | PUSH1 | 0x02 |
| 07ca | DUP3 | |
| 07cb | ADD | |
| 07cc | SLOAD | |
| 07cd | PUSH1 | 0xff |
| 07cf | DUP2 | |
| 07d0 | AND | |
| 07d1 | PUSH1 | 0x40 |
| 07d3 | DUP6 | |
| 07d4 | ADD | |
| 07d5 | MSTORE | |
| 07d6 | PUSH1 | 0x05 |
| 07d8 | PUSH1 | 0xff |
| 07da | DUP3 | |
| 07db | PUSH1 | 0x08 |
| 07dd | SHR | |
| 07de | AND | |
| 07df | ISZERO | |
| 07e0 | ISZERO | |
| 07e1 | SWAP4 | |
| 07e2 | DUP5 | |
| 07e3 | PUSH1 | 0x60 |
| 07e5 | DUP8 | |
| 07e6 | ADD | |
| 07e7 | MSTORE | |
| 07e8 | PUSH1 | 0x01 |
| 07ea | PUSH1 | 0x01 |
| 07ec | PUSH1 | 0x40 |
| 07ee | SHL | |
| 07ef | SUB | |
| 07f0 | DUP4 | |
| 07f1 | PUSH1 | 0x10 |
| 07f3 | SHR | |
| 07f4 | AND | |
| 07f5 | PUSH1 | 0x80 |
| 07f7 | DUP8 | |
| 07f8 | ADD | |
| 07f9 | MSTORE | |
| 07fa | PUSH2 | 0xffff |
| 07fd | DUP4 | |
| 07fe | PUSH1 | 0x50 |
| 0800 | SHR | |
| 0801 | AND | |
| 0802 | PUSH1 | 0xa0 |
| 0804 | DUP8 | |
| 0805 | ADD | |
| 0806 | MSTORE | |
| 0807 | PUSH1 | 0x03 |
| 0809 | DUP2 | |
| 080a | ADD | |
| 080b | SLOAD | |
| 080c | PUSH1 | 0xc0 |
| 080e | DUP8 | |
| 080f | ADD | |
| 0810 | MSTORE | |
| 0811 | PUSH1 | 0x04 |
| 0813 | DUP2 | |
| 0814 | ADD | |
| 0815 | SLOAD | |
| 0816 | PUSH1 | 0xe0 |
| 0818 | DUP8 | |
| 0819 | ADD | |
| 081a | MSTORE | |
| 081b | ADD | |
| 081c | SLOAD | |
| 081d | PUSH2 | 0x0100 |
| 0820 | DUP6 | |
| 0821 | ADD | |
| 0822 | MSTORE | |
| 0823 | DUP3 | |
| 0824 | PUSH2 | 0x0865 |
| 0827 | JUMPI | |
| 0828 | JUMPDEST | |
| 0829 | POP | |
| 082a | POP | |
| 082b | PUSH2 | 0x083a |
| 082e | JUMPI | |
| 082f | JUMPDEST | |
| 0830 | POP | |
| 0831 | PUSH1 | 0x01 |
| 0833 | SWAP1 | |
| 0834 | JUMPDEST | |
| 0835 | ADD | |
| 0836 | PUSH2 | 0x06bc |
| 0839 | JUMP | |
| 083a | JUMPDEST | |
| 083b | SWAP5 | |
| 083c | SWAP1 | |
| 083d | PUSH2 | 0x085e |
| 0840 | DUP3 | |
| 0841 | PUSH2 | 0x084c |
| 0844 | PUSH1 | 0x01 |
| 0846 | SWAP5 | |
| 0847 | SWAP2 | |
| 0848 | PUSH2 | 0x6bfb |
| 084b | JUMP | |
| 084c | JUMPDEST | |
| 084d | SWAP8 | |
| 084e | PUSH2 | 0x0857 |
| 0851 | DUP3 | |
| 0852 | DUP12 | |
| 0853 | PUSH2 | 0x6a8c |
| 0856 | JUMP | |
| 0857 | JUMPDEST | |
| 0858 | MSTORE | |
| 0859 | DUP9 | |
| 085a | PUSH2 | 0x6a8c |
| 085d | JUMP | |
| 085e | JUMPDEST | |
| 085f | POP | |
| 0860 | SWAP1 | |
| 0861 | PUSH2 | 0x082f |
| 0864 | JUMP | |
| 0865 | JUMPDEST | |
| 0866 | PUSH1 | 0xff |
| 0868 | SWAP3 | |
| 0869 | POP | |
| 086a | AND | |
| 086b | AND | |
| 086c | ISZERO | |
| 086d | ISZERO | |
| 086e | DUP5 | |
| 086f | DUP10 | |
| 0870 | PUSH2 | 0x0828 |
| 0873 | JUMP | |
| 0874 | JUMPDEST | |
| 0875 | POP | |
| 0876 | PUSH1 | 0x01 |
| 0878 | SWAP1 | |
| 0879 | PUSH2 | 0x0834 |
| 087c | JUMP | |
| 087d | JUMPDEST | |
| 087e | CALLVALUE | |
| 087f | PUSH2 | 0x03eb |
| 0882 | JUMPI | |
| 0883 | PUSH2 | 0x0100 |
| 0886 | MLOAD | |
| 0887 | CALLDATASIZE | |
| 0888 | PUSH1 | 0x03 |
| 088a | NOT | |
| 088b | ADD | |
| 088c | SLT | |
| 088d | PUSH2 | 0x03eb |
| 0890 | JUMPI | |
| 0891 | PUSH1 | 0x20 |
| 0893 | PUSH1 | 0x04 |
| 0895 | SLOAD | |
| 0896 | PUSH1 | 0x40 |
| 0898 | MLOAD | |
| 0899 | SWAP1 | |
| 089a | DUP2 | |
| 089b | MSTORE | |
| 089c | RETURN | |
| 089d | JUMPDEST | |
| 089e | CALLVALUE | |
| 089f | PUSH2 | 0x03eb |
| 08a2 | JUMPI | |
| 08a3 | PUSH1 | 0x20 |
| 08a5 | CALLDATASIZE | |
| 08a6 | PUSH1 | 0x03 |
| 08a8 | NOT | |
| 08a9 | ADD | |
| 08aa | SLT | |
| 08ab | PUSH2 | 0x03eb |
| 08ae | JUMPI | |
| 08af | PUSH2 | 0x08b6 |
| 08b2 | PUSH2 | 0x6601 |
| 08b5 | JUMP | |
| 08b6 | JUMPDEST | |
| 08b7 | PUSH1 | 0x07 |
| 08b9 | SLOAD | |
| 08ba | SWAP1 | |
| 08bb | PUSH2 | 0x08c3 |
| 08be | DUP3 | |
| 08bf | PUSH2 | 0x72f9 |
| 08c2 | JUMP | |
| 08c3 | JUMPDEST | |
| 08c4 | PUSH2 | 0x0100 |
| 08c7 | MLOAD | |
| 08c8 | SWAP1 | |
| 08c9 | SWAP3 | |
| 08ca | SWAP1 | |
| 08cb | SWAP2 | |
| 08cc | DUP3 | |
| 08cd | JUMPDEST | |
| 08ce | DUP3 | |
| 08cf | DUP2 | |
| 08d0 | LT | |
| 08d1 | PUSH2 | 0x0981 |
| 08d4 | JUMPI | |
| 08d5 | POP | |
| 08d6 | POP | |
| 08d7 | POP | |
| 08d8 | PUSH2 | 0x08e0 |
| 08db | DUP2 | |
| 08dc | PUSH2 | 0x72f9 |
| 08df | JUMP | |
| 08e0 | JUMPDEST | |
| 08e1 | SWAP2 | |
| 08e2 | PUSH2 | 0x0100 |
| 08e5 | MLOAD | |
| 08e6 | JUMPDEST | |
| 08e7 | DUP3 | |
| 08e8 | DUP2 | |
| 08e9 | LT | |
| 08ea | PUSH2 | 0x0957 |
| 08ed | JUMPI | |
| 08ee | DUP4 | |
| 08ef | PUSH1 | 0x40 |
| 08f1 | MLOAD | |
| 08f2 | DUP1 | |
| 08f3 | SWAP2 | |
| 08f4 | PUSH1 | 0x20 |
| 08f6 | DUP3 | |
| 08f7 | ADD | |
| 08f8 | PUSH1 | 0x20 |
| 08fa | DUP4 | |
| 08fb | MSTORE | |
| 08fc | DUP2 | |
| 08fd | MLOAD | |
| 08fe | DUP1 | |
| 08ff | SWAP2 | |
| 0900 | MSTORE | |
| 0901 | PUSH1 | 0x40 |
| 0903 | DUP4 | |
| 0904 | ADD | |
| 0905 | SWAP1 | |
| 0906 | PUSH1 | 0x20 |
| 0908 | PUSH1 | 0x40 |
| 090a | DUP3 | |
| 090b | PUSH1 | 0x05 |
| 090d | SHL | |
| 090e | DUP7 | |
| 090f | ADD | |
| 0910 | ADD | |
| 0911 | SWAP4 | |
| 0912 | ADD | |
| 0913 | SWAP2 | |
| 0914 | PUSH2 | 0x0100 |
| 0917 | MLOAD | |
| 0918 | SWAP1 | |
| 0919 | JUMPDEST | |
| 091a | DUP3 | |
| 091b | DUP3 | |
| 091c | LT | |
| 091d | PUSH2 | 0x0928 |
| 0920 | JUMPI | |
| 0921 | POP | |
| 0922 | POP | |
| 0923 | POP | |
| 0924 | POP | |
| 0925 | SUB | |
| 0926 | SWAP1 | |
| 0927 | RETURN | |
| 0928 | JUMPDEST | |
| 0929 | SWAP2 | |
| 092a | SWAP4 | |
| 092b | PUSH1 | 0x01 |
| 092d | SWAP2 | |
| 092e | SWAP4 | |
| 092f | SWAP6 | |
| 0930 | POP | |
| 0931 | PUSH1 | 0x20 |
| 0933 | PUSH2 | 0x0947 |
| 0936 | DUP2 | |
| 0937 | SWAP3 | |
| 0938 | PUSH1 | 0x3f |
| 093a | NOT | |
| 093b | DUP11 | |
| 093c | DUP3 | |
| 093d | SUB | |
| 093e | ADD | |
| 093f | DUP7 | |
| 0940 | MSTORE | |
| 0941 | DUP9 | |
| 0942 | MLOAD | |
| 0943 | PUSH2 | 0x6696 |
| 0946 | JUMP | |
| 0947 | JUMPDEST | |
| 0948 | SWAP7 | |
| 0949 | ADD | |
| 094a | SWAP3 | |
| 094b | ADD | |
| 094c | SWAP3 | |
| 094d | ADD | |
| 094e | DUP6 | |
| 094f | SWAP5 | |
| 0950 | SWAP4 | |
| 0951 | SWAP2 | |
| 0952 | SWAP3 | |
| 0953 | PUSH2 | 0x0919 |
| 0956 | JUMP | |
| 0957 | JUMPDEST | |
| 0958 | DUP1 | |
| 0959 | PUSH2 | 0x0964 |
| 095c | PUSH1 | 0x01 |
| 095e | SWAP3 | |
| 095f | DUP5 | |
| 0960 | PUSH2 | 0x6a8c |
| 0963 | JUMP | |
| 0964 | JUMPDEST | |
| 0965 | MLOAD | |
| 0966 | PUSH2 | 0x096f |
| 0969 | DUP3 | |
| 096a | DUP8 | |
| 096b | PUSH2 | 0x6a8c |
| 096e | JUMP | |
| 096f | JUMPDEST | |
| 0970 | MSTORE | |
| 0971 | PUSH2 | 0x097a |
| 0974 | DUP2 | |
| 0975 | DUP7 | |
| 0976 | PUSH2 | 0x6a8c |
| 0979 | JUMP | |
| 097a | JUMPDEST | |
| 097b | POP | |
| 097c | ADD | |
| 097d | PUSH2 | 0x08e6 |
| 0980 | JUMP | |
| 0981 | JUMPDEST | |
| 0982 | PUSH2 | 0x098a |
| 0985 | DUP2 | |
| 0986 | PUSH2 | 0x6a1c |
| 0989 | JUMP | |
| 098a | JUMPDEST | |
| 098b | SWAP1 | |
| 098c | SLOAD | |
| 098d | SWAP1 | |
| 098e | PUSH1 | 0x03 |
| 0990 | SHL | |
| 0991 | SHR | |
| 0992 | PUSH2 | 0x0100 |
| 0995 | MLOAD | |
| 0996 | MSTORE | |
| 0997 | PUSH1 | 0x08 |
| 0999 | PUSH1 | 0x20 |
| 099b | MSTORE | |
| 099c | PUSH2 | 0x09aa |
| 099f | PUSH1 | 0x40 |
| 09a1 | PUSH2 | 0x0100 |
| 09a4 | MLOAD | |
| 09a5 | KECCAK256 | |
| 09a6 | PUSH2 | 0x707c |
| 09a9 | JUMP | |
| 09aa | JUMPDEST | |
| 09ab | PUSH2 | 0x0100 |
| 09ae | DUP2 | |
| 09af | ADD | |
| 09b0 | MLOAD | |
| 09b1 | ISZERO | |
| 09b2 | ISZERO | |
| 09b3 | DUP1 | |
| 09b4 | PUSH2 | 0x09f1 |
| 09b7 | JUMPI | |
| 09b8 | JUMPDEST | |
| 09b9 | PUSH2 | 0x09c6 |
| 09bc | JUMPI | |
| 09bd | JUMPDEST | |
| 09be | POP | |
| 09bf | PUSH1 | 0x01 |
| 09c1 | ADD | |
| 09c2 | PUSH2 | 0x08cd |
| 09c5 | JUMP | |
| 09c6 | JUMPDEST | |
| 09c7 | SWAP4 | |
| 09c8 | SWAP1 | |
| 09c9 | PUSH2 | 0x09ea |
| 09cc | DUP3 | |
| 09cd | PUSH2 | 0x09d8 |
| 09d0 | PUSH1 | 0x01 |
| 09d2 | SWAP5 | |
| 09d3 | SWAP2 | |
| 09d4 | PUSH2 | 0x6bfb |
| 09d7 | JUMP | |
| 09d8 | JUMPDEST | |
| 09d9 | SWAP7 | |
| 09da | PUSH2 | 0x09e3 |
| 09dd | DUP3 | |
| 09de | DUP11 | |
| 09df | PUSH2 | 0x6a8c |
| 09e2 | JUMP | |
| 09e3 | JUMPDEST | |
| 09e4 | MSTORE | |
| 09e5 | DUP8 | |
| 09e6 | PUSH2 | 0x6a8c |
| 09e9 | JUMP | |
| 09ea | JUMPDEST | |
| 09eb | POP | |
| 09ec | SWAP1 | |
| 09ed | PUSH2 | 0x09bd |
| 09f0 | JUMP | |
| 09f1 | JUMPDEST | |
| 09f2 | POP | |
| 09f3 | PUSH1 | 0xff |
| 09f5 | DUP4 | |
| 09f6 | PUSH1 | 0xa0 |
| 09f8 | DUP4 | |
| 09f9 | ADD | |
| 09fa | MLOAD | |
| 09fb | AND | |
| 09fc | AND | |
| 09fd | ISZERO | |
| 09fe | ISZERO | |
| 09ff | PUSH2 | 0x09b8 |
| 0a02 | JUMP | |
| 0a03 | JUMPDEST | |
| 0a04 | CALLVALUE | |
| 0a05 | PUSH2 | 0x03eb |
| 0a08 | JUMPI | |
| 0a09 | PUSH2 | 0x0100 |
| 0a0c | MLOAD | |
| 0a0d | CALLDATASIZE | |
| 0a0e | PUSH1 | 0x03 |
| 0a10 | NOT | |
| 0a11 | ADD | |
| 0a12 | SLT | |
| 0a13 | PUSH2 | 0x03eb |
| 0a16 | JUMPI | |
| 0a17 | PUSH1 | 0x20 |
| 0a19 | PUSH1 | 0x0d |
| 0a1b | SLOAD | |
| 0a1c | PUSH1 | 0x40 |
| 0a1e | MLOAD | |
| 0a1f | SWAP1 | |
| 0a20 | DUP2 | |
| 0a21 | MSTORE | |
| 0a22 | RETURN | |
| 0a23 | JUMPDEST | |
| 0a24 | CALLVALUE | |
| 0a25 | PUSH2 | 0x03eb |
| 0a28 | JUMPI | |
| 0a29 | PUSH1 | 0x20 |
| 0a2b | PUSH2 | 0x0a3c |
| 0a2e | PUSH2 | 0x0a36 |
| 0a31 | CALLDATASIZE | |
| 0a32 | PUSH2 | 0x645b |
| 0a35 | JUMP | |
| 0a36 | JUMPDEST | |
| 0a37 | SWAP1 | |
| 0a38 | PUSH2 | 0x72b3 |
| 0a3b | JUMP | |
| 0a3c | JUMPDEST | |
| 0a3d | PUSH1 | 0x40 |
| 0a3f | MLOAD | |
| 0a40 | SWAP1 | |
| 0a41 | DUP2 | |
| 0a42 | MSTORE | |
| 0a43 | RETURN | |
| 0a44 | JUMPDEST | |
| 0a45 | CALLVALUE | |
| 0a46 | PUSH2 | 0x03eb |
| 0a49 | JUMPI | |
| 0a4a | PUSH2 | 0x0100 |
| 0a4d | MLOAD | |
| 0a4e | CALLDATASIZE | |
| 0a4f | PUSH1 | 0x03 |
| 0a51 | NOT | |
| 0a52 | ADD | |
| 0a53 | SLT | |
| 0a54 | PUSH2 | 0x03eb |
| 0a57 | JUMPI | |
| 0a58 | PUSH1 | 0x20 |
| 0a5a | PUSH1 | 0x40 |
| 0a5c | MLOAD | |
| 0a5d | PUSH2 | 0x01f4 |
| 0a60 | DUP2 | |
| 0a61 | MSTORE | |
| 0a62 | RETURN | |
| 0a63 | JUMPDEST | |
| 0a64 | CALLVALUE | |
| 0a65 | PUSH2 | 0x03eb |
| 0a68 | JUMPI | |
| 0a69 | PUSH2 | 0x02c0 |
| 0a6c | CALLDATASIZE | |
| 0a6d | PUSH1 | 0x03 |
| 0a6f | NOT | |
| 0a70 | ADD | |
| 0a71 | SLT | |
| 0a72 | PUSH2 | 0x03eb |
| 0a75 | JUMPI | |
| 0a76 | PUSH1 | 0x40 |
| 0a78 | MLOAD | |
| 0a79 | PUSH2 | 0x0a81 |
| 0a7c | DUP2 | |
| 0a7d | PUSH2 | 0x676a |
| 0a80 | JUMP | |
| 0a81 | JUMPDEST | |
| 0a82 | PUSH1 | 0x04 |
| 0a84 | CALLDATALOAD | |
| 0a85 | DUP2 | |
| 0a86 | MSTORE | |
| 0a87 | PUSH1 | 0x24 |
| 0a89 | CALLDATALOAD | |
| 0a8a | PUSH1 | 0x20 |
| 0a8c | DUP3 | |
| 0a8d | ADD | |
| 0a8e | MSTORE | |
| 0a8f | PUSH1 | 0x44 |
| 0a91 | CALLDATALOAD | |
| 0a92 | PUSH1 | 0x40 |
| 0a94 | DUP3 | |
| 0a95 | ADD | |
| 0a96 | MSTORE | |
| 0a97 | PUSH1 | 0x64 |
| 0a99 | CALLDATALOAD | |
| 0a9a | PUSH1 | 0x60 |
| 0a9c | DUP3 | |
| 0a9d | ADD | |
| 0a9e | MSTORE | |
| 0a9f | PUSH1 | 0x84 |
| 0aa1 | CALLDATALOAD | |
| 0aa2 | PUSH1 | 0xff |
| 0aa4 | DUP2 | |
| 0aa5 | AND | |
| 0aa6 | DUP2 | |
| 0aa7 | SUB | |
| 0aa8 | PUSH2 | 0x03eb |
| 0aab | JUMPI | |
| 0aac | PUSH1 | 0x80 |
| 0aae | DUP3 | |
| 0aaf | ADD | |
| 0ab0 | MSTORE | |
| 0ab1 | PUSH1 | 0xa4 |
| 0ab3 | CALLDATALOAD | |
| 0ab4 | PUSH1 | 0xa0 |
| 0ab6 | DUP3 | |
| 0ab7 | ADD | |
| 0ab8 | MSTORE | |
| 0ab9 | PUSH1 | 0xc4 |
| 0abb | CALLDATALOAD | |
| 0abc | PUSH1 | 0xc0 |
| 0abe | DUP3 | |
| 0abf | ADD | |
| 0ac0 | MSTORE | |
| 0ac1 | PUSH1 | 0xe4 |
| 0ac3 | CALLDATALOAD | |
| 0ac4 | PUSH1 | 0xff |
| 0ac6 | DUP2 | |
| 0ac7 | AND | |
| 0ac8 | DUP2 | |
| 0ac9 | SUB | |
| 0aca | PUSH2 | 0x03eb |
| 0acd | JUMPI | |
| 0ace | PUSH1 | 0xe0 |
| 0ad0 | DUP3 | |
| 0ad1 | ADD | |
| 0ad2 | MSTORE | |
| 0ad3 | PUSH2 | 0x0104 |
| 0ad6 | CALLDATALOAD | |
| 0ad7 | PUSH1 | 0x01 |
| 0ad9 | PUSH1 | 0x01 |
| 0adb | PUSH1 | 0x40 |
| 0add | SHL | |
| 0ade | SUB | |
| 0adf | DUP2 | |
| 0ae0 | AND | |
| 0ae1 | DUP2 | |
| 0ae2 | SUB | |
| 0ae3 | PUSH2 | 0x03eb |
| 0ae6 | JUMPI | |
| 0ae7 | PUSH2 | 0x0100 |
| 0aea | DUP3 | |
| 0aeb | ADD | |
| 0aec | MSTORE | |
| 0aed | PUSH2 | 0x0124 |
| 0af0 | CALLDATALOAD | |
| 0af1 | PUSH4 | 0xffffffff |
| 0af6 | DUP2 | |
| 0af7 | AND | |
| 0af8 | DUP2 | |
| 0af9 | SUB | |
| 0afa | PUSH2 | 0x03eb |
| 0afd | JUMPI | |
| 0afe | PUSH2 | 0x0120 |
| 0b01 | DUP3 | |
| 0b02 | ADD | |
| 0b03 | MSTORE | |
| 0b04 | PUSH2 | 0x0144 |
| 0b07 | CALLDATALOAD | |
| 0b08 | PUSH2 | 0xffff |
| 0b0b | DUP2 | |
| 0b0c | AND | |
| 0b0d | DUP2 | |
| 0b0e | SUB | |
| 0b0f | PUSH2 | 0x03eb |
| 0b12 | JUMPI | |
| 0b13 | PUSH2 | 0x0140 |
| 0b16 | DUP3 | |
| 0b17 | ADD | |
| 0b18 | MSTORE | |
| 0b19 | PUSH2 | 0x0164 |
| 0b1c | CALLDATALOAD | |
| 0b1d | PUSH2 | 0xffff |
| 0b20 | DUP2 | |
| 0b21 | AND | |
| 0b22 | DUP2 | |
| 0b23 | SUB | |
| 0b24 | PUSH2 | 0x03eb |
| 0b27 | JUMPI | |
| 0b28 | PUSH2 | 0x0160 |
| 0b2b | DUP3 | |
| 0b2c | ADD | |
| 0b2d | MSTORE | |
| 0b2e | PUSH2 | 0x0184 |
| 0b31 | CALLDATALOAD | |
| 0b32 | PUSH2 | 0x0180 |
| 0b35 | DUP3 | |
| 0b36 | ADD | |
| 0b37 | MSTORE | |
| 0b38 | PUSH2 | 0x01a4 |
| 0b3b | CALLDATALOAD | |
| 0b3c | PUSH1 | 0xff |
| 0b3e | DUP2 | |
| 0b3f | AND | |
| 0b40 | DUP2 | |
| 0b41 | SUB | |
| 0b42 | PUSH2 | 0x03eb |
| 0b45 | JUMPI | |
| 0b46 | PUSH2 | 0x01a0 |
| 0b49 | DUP3 | |
| 0b4a | ADD | |
| 0b4b | MSTORE | |
| 0b4c | PUSH2 | 0x01c4 |
| 0b4f | CALLDATALOAD | |
| 0b50 | PUSH2 | 0x01c0 |
| 0b53 | DUP3 | |
| 0b54 | ADD | |
| 0b55 | MSTORE | |
| 0b56 | PUSH2 | 0x01e4 |
| 0b59 | CALLDATALOAD | |
| 0b5a | PUSH2 | 0x01e0 |
| 0b5d | DUP3 | |
| 0b5e | ADD | |
| 0b5f | MSTORE | |
| 0b60 | PUSH2 | 0x0204 |
| 0b63 | CALLDATALOAD | |
| 0b64 | PUSH1 | 0xff |
| 0b66 | DUP2 | |
| 0b67 | AND | |
| 0b68 | DUP2 | |
| 0b69 | SUB | |
| 0b6a | PUSH2 | 0x03eb |
| 0b6d | JUMPI | |
| 0b6e | PUSH2 | 0x0200 |
| 0b71 | DUP3 | |
| 0b72 | ADD | |
| 0b73 | MSTORE | |
| 0b74 | PUSH2 | 0x0224 |
| 0b77 | CALLDATALOAD | |
| 0b78 | DUP1 | |
| 0b79 | ISZERO | |
| 0b7a | ISZERO | |
| 0b7b | DUP2 | |
| 0b7c | SUB | |
| 0b7d | PUSH2 | 0x03eb |
| 0b80 | JUMPI | |
| 0b81 | PUSH2 | 0x0220 |
| 0b84 | DUP3 | |
| 0b85 | ADD | |
| 0b86 | MSTORE | |
| 0b87 | PUSH2 | 0x0244 |
| 0b8a | CALLDATALOAD | |
| 0b8b | PUSH1 | 0x01 |
| 0b8d | PUSH1 | 0x01 |
| 0b8f | PUSH1 | 0x40 |
| 0b91 | SHL | |
| 0b92 | SUB | |
| 0b93 | DUP2 | |
| 0b94 | AND | |
| 0b95 | DUP2 | |
| 0b96 | SUB | |
| 0b97 | PUSH2 | 0x03eb |
| 0b9a | JUMPI | |
| 0b9b | PUSH2 | 0x0240 |
| 0b9e | DUP3 | |
| 0b9f | ADD | |
| 0ba0 | MSTORE | |
| 0ba1 | PUSH2 | 0x0264 |
| 0ba4 | CALLDATALOAD | |
| 0ba5 | PUSH1 | 0x01 |
| 0ba7 | PUSH1 | 0x01 |
| 0ba9 | PUSH1 | 0x40 |
| 0bab | SHL | |
| 0bac | SUB | |
| 0bad | DUP2 | |
| 0bae | AND | |
| 0baf | DUP2 | |
| 0bb0 | SUB | |
| 0bb1 | PUSH2 | 0x03eb |
| 0bb4 | JUMPI | |
| 0bb5 | PUSH2 | 0x0260 |
| 0bb8 | DUP3 | |
| 0bb9 | ADD | |
| 0bba | MSTORE | |
| 0bbb | PUSH2 | 0x0284 |
| 0bbe | CALLDATALOAD | |
| 0bbf | PUSH1 | 0xff |
| 0bc1 | DUP2 | |
| 0bc2 | AND | |
| 0bc3 | DUP2 | |
| 0bc4 | SUB | |
| 0bc5 | PUSH2 | 0x03eb |
| 0bc8 | JUMPI | |
| 0bc9 | DUP2 | |
| 0bca | PUSH2 | 0x0a3c |
| 0bcd | SWAP2 | |
| 0bce | PUSH2 | 0x0280 |
| 0bd1 | PUSH1 | 0x20 |
| 0bd3 | SWAP5 | |
| 0bd4 | ADD | |
| 0bd5 | MSTORE | |
| 0bd6 | PUSH2 | 0x02a4 |
| 0bd9 | CALLDATALOAD | |
| 0bda | PUSH2 | 0x02a0 |
| 0bdd | DUP3 | |
| 0bde | ADD | |
| 0bdf | MSTORE | |
| 0be0 | PUSH2 | 0x71f1 |
| 0be3 | JUMP | |
| 0be4 | JUMPDEST | |
| 0be5 | CALLVALUE | |
| 0be6 | PUSH2 | 0x03eb |
| 0be9 | JUMPI | |
| 0bea | PUSH2 | 0x0100 |
| 0bed | MLOAD | |
| 0bee | CALLDATASIZE | |
| 0bef | PUSH1 | 0x03 |
| 0bf1 | NOT | |
| 0bf2 | ADD | |
| 0bf3 | SLT | |
| 0bf4 | PUSH2 | 0x03eb |
| 0bf7 | JUMPI | |
| 0bf8 | PUSH1 | 0x20 |
| 0bfa | PUSH1 | 0x17 |
| 0bfc | SLOAD | |
| 0bfd | PUSH1 | 0x40 |
| 0bff | MLOAD | |
| 0c00 | SWAP1 | |
| 0c01 | DUP2 | |
| 0c02 | MSTORE | |
| 0c03 | RETURN | |
| 0c04 | JUMPDEST | |
| 0c05 | CALLVALUE | |
| 0c06 | PUSH2 | 0x03eb |
| 0c09 | JUMPI | |
| 0c0a | PUSH1 | 0xa0 |
| 0c0c | CALLDATASIZE | |
| 0c0d | PUSH1 | 0x03 |
| 0c0f | NOT | |
| 0c10 | ADD | |
| 0c11 | SLT | |
| 0c12 | PUSH2 | 0x03eb |
| 0c15 | JUMPI | |
| 0c16 | PUSH2 | 0x0c1d |
| 0c19 | PUSH2 | 0x6527 |
| 0c1c | JUMP | |
| 0c1d | JUMPDEST | |
| 0c1e | POP | |
| 0c1f | PUSH2 | 0x0c26 |
| 0c22 | PUSH2 | 0x653d |
| 0c25 | JUMP | |
| 0c26 | JUMPDEST | |
| 0c27 | POP | |
| 0c28 | PUSH1 | 0x44 |
| 0c2a | CALLDATALOAD | |
| 0c2b | PUSH1 | 0x01 |
| 0c2d | PUSH1 | 0x01 |
| 0c2f | PUSH1 | 0x40 |
| 0c31 | SHL | |
| 0c32 | SUB | |
| 0c33 | DUP2 | |
| 0c34 | GT | |
| 0c35 | PUSH2 | 0x03eb |
| 0c38 | JUMPI | |
| 0c39 | PUSH2 | 0x0c46 |
| 0c3c | SWAP1 | |
| 0c3d | CALLDATASIZE | |
| 0c3e | SWAP1 | |
| 0c3f | PUSH1 | 0x04 |
| 0c41 | ADD | |
| 0c42 | PUSH2 | 0x642b |
| 0c45 | JUMP | |
| 0c46 | JUMPDEST | |
| 0c47 | POP | |
| 0c48 | POP | |
| 0c49 | PUSH1 | 0x64 |
| 0c4b | CALLDATALOAD | |
| 0c4c | PUSH1 | 0x01 |
| 0c4e | PUSH1 | 0x01 |
| 0c50 | PUSH1 | 0x40 |
| 0c52 | SHL | |
| 0c53 | SUB | |
| 0c54 | DUP2 | |
| 0c55 | GT | |
| 0c56 | PUSH2 | 0x03eb |
| 0c59 | JUMPI | |
| 0c5a | PUSH2 | 0x0c67 |
| 0c5d | SWAP1 | |
| 0c5e | CALLDATASIZE | |
| 0c5f | SWAP1 | |
| 0c60 | PUSH1 | 0x04 |
| 0c62 | ADD | |
| 0c63 | PUSH2 | 0x642b |
| 0c66 | JUMP | |
| 0c67 | JUMPDEST | |
| 0c68 | POP | |
| 0c69 | POP | |
| 0c6a | PUSH1 | 0x84 |
| 0c6c | CALLDATALOAD | |
| 0c6d | PUSH1 | 0x01 |
| 0c6f | PUSH1 | 0x01 |
| 0c71 | PUSH1 | 0x40 |
| 0c73 | SHL | |
| 0c74 | SUB | |
| 0c75 | DUP2 | |
| 0c76 | GT | |
| 0c77 | PUSH2 | 0x03eb |
| 0c7a | JUMPI | |
| 0c7b | PUSH2 | 0x0c88 |
| 0c7e | SWAP1 | |
| 0c7f | CALLDATASIZE | |
| 0c80 | SWAP1 | |
| 0c81 | PUSH1 | 0x04 |
| 0c83 | ADD | |
| 0c84 | PUSH2 | 0x6553 |
| 0c87 | JUMP | |
| 0c88 | JUMPDEST | |
| 0c89 | POP | |
| 0c8a | POP | |
| 0c8b | PUSH1 | 0x40 |
| 0c8d | MLOAD | |
| 0c8e | PUSH4 | 0xbc197c81 |
| 0c93 | PUSH1 | 0xe0 |
| 0c95 | SHL | |
| 0c96 | DUP2 | |
| 0c97 | MSTORE | |
| 0c98 | PUSH1 | 0x20 |
| 0c9a | SWAP1 | |
| 0c9b | RETURN | |
| 0c9c | JUMPDEST | |
| 0c9d | CALLVALUE | |
| 0c9e | PUSH2 | 0x03eb |
| 0ca1 | JUMPI | |
| 0ca2 | PUSH1 | 0x20 |
| 0ca4 | CALLDATASIZE | |
| 0ca5 | PUSH1 | 0x03 |
| 0ca7 | NOT | |
| 0ca8 | ADD | |
| 0ca9 | SLT | |
| 0caa | PUSH2 | 0x03eb |
| 0cad | JUMPI | |
| 0cae | PUSH1 | 0x20 |
| 0cb0 | PUSH2 | 0x0a3c |
| 0cb3 | PUSH1 | 0x04 |
| 0cb5 | CALLDATALOAD | |
| 0cb6 | PUSH2 | 0x71be |
| 0cb9 | JUMP | |
| 0cba | JUMPDEST | |
| 0cbb | CALLVALUE | |
| 0cbc | PUSH2 | 0x03eb |
| 0cbf | JUMPI | |
| 0cc0 | PUSH1 | 0xa0 |
| 0cc2 | CALLDATASIZE | |
| 0cc3 | PUSH1 | 0x03 |
| 0cc5 | NOT | |
| 0cc6 | ADD | |
| 0cc7 | SLT | |
| 0cc8 | PUSH2 | 0x03eb |
| 0ccb | JUMPI | |
| 0ccc | PUSH1 | 0x01 |
| 0cce | PUSH1 | 0x01 |
| 0cd0 | PUSH1 | 0x40 |
| 0cd2 | SHL | |
| 0cd3 | SUB | |
| 0cd4 | PUSH1 | 0x04 |
| 0cd6 | CALLDATALOAD | |
| 0cd7 | GT | |
| 0cd8 | PUSH2 | 0x03eb |
| 0cdb | JUMPI | |
| 0cdc | CALLDATASIZE | |
| 0cdd | PUSH1 | 0x23 |
| 0cdf | PUSH1 | 0x04 |
| 0ce1 | CALLDATALOAD | |
| 0ce2 | ADD | |
| 0ce3 | SLT | |
| 0ce4 | ISZERO | |
| 0ce5 | PUSH2 | 0x03eb |
| 0ce8 | JUMPI | |
| 0ce9 | PUSH1 | 0x01 |
| 0ceb | PUSH1 | 0x01 |
| 0ced | PUSH1 | 0x40 |
| 0cef | SHL | |
| 0cf0 | SUB | |
| 0cf1 | PUSH1 | 0x04 |
| 0cf3 | CALLDATALOAD | |
| 0cf4 | PUSH1 | 0x04 |
| 0cf6 | ADD | |
| 0cf7 | CALLDATALOAD | |
| 0cf8 | GT | |
| 0cf9 | PUSH2 | 0x03eb |
| 0cfc | JUMPI | |
| 0cfd | CALLDATASIZE | |
| 0cfe | PUSH1 | 0x24 |
| 0d00 | PUSH2 | 0x02c0 |
| 0d03 | PUSH1 | 0x04 |
| 0d05 | CALLDATALOAD | |
| 0d06 | PUSH1 | 0x04 |
| 0d08 | ADD | |
| 0d09 | CALLDATALOAD | |
| 0d0a | MUL | |
| 0d0b | PUSH1 | 0x04 |
| 0d0d | CALLDATALOAD | |
| 0d0e | ADD | |
| 0d0f | ADD | |
| 0d10 | GT | |
| 0d11 | PUSH2 | 0x03eb |
| 0d14 | JUMPI | |
| 0d15 | PUSH1 | 0x24 |
| 0d17 | CALLDATALOAD | |
| 0d18 | PUSH1 | 0x01 |
| 0d1a | PUSH1 | 0x01 |
| 0d1c | PUSH1 | 0x40 |
| 0d1e | SHL | |
| 0d1f | SUB | |
| 0d20 | DUP2 | |
| 0d21 | GT | |
| 0d22 | PUSH2 | 0x03eb |
| 0d25 | JUMPI | |
| 0d26 | PUSH2 | 0x0d33 |
| 0d29 | SWAP1 | |
| 0d2a | CALLDATASIZE | |
| 0d2b | SWAP1 | |
| 0d2c | PUSH1 | 0x04 |
| 0d2e | ADD | |
| 0d2f | PUSH2 | 0x642b |
| 0d32 | JUMP | |
| 0d33 | JUMPDEST | |
| 0d34 | PUSH1 | 0xc0 |
| 0d36 | MSTORE | |
| 0d37 | PUSH1 | 0x44 |
| 0d39 | CALLDATALOAD | |
| 0d3a | PUSH1 | 0x01 |
| 0d3c | PUSH1 | 0x01 |
| 0d3e | PUSH1 | 0x40 |
| 0d40 | SHL | |
| 0d41 | SUB | |
| 0d42 | DUP2 | |
| 0d43 | GT | |
| 0d44 | PUSH2 | 0x03eb |
| 0d47 | JUMPI | |
| 0d48 | PUSH2 | 0x0d55 |
| 0d4b | SWAP1 | |
| 0d4c | CALLDATASIZE | |
| 0d4d | SWAP1 | |
| 0d4e | PUSH1 | 0x04 |
| 0d50 | ADD | |
| 0d51 | PUSH2 | 0x6580 |
| 0d54 | JUMP | |
| 0d55 | JUMPDEST | |
| 0d56 | PUSH1 | 0x64 |
| 0d58 | CALLDATALOAD | |
| 0d59 | SWAP3 | |
| 0d5a | SWAP1 | |
| 0d5b | PUSH1 | 0x01 |
| 0d5d | PUSH1 | 0x01 |
| 0d5f | PUSH1 | 0x40 |
| 0d61 | SHL | |
| 0d62 | SUB | |
| 0d63 | DUP5 | |
| 0d64 | AND | |
| 0d65 | DUP5 | |
| 0d66 | SUB | |
| 0d67 | PUSH2 | 0x03eb |
| 0d6a | JUMPI | |
| 0d6b | PUSH1 | 0x84 |
| 0d6d | CALLDATALOAD | |
| 0d6e | PUSH1 | 0x01 |
| 0d70 | PUSH1 | 0x01 |
| 0d72 | PUSH1 | 0x40 |
| 0d74 | SHL | |
| 0d75 | SUB | |
| 0d76 | DUP2 | |
| 0d77 | GT | |
| 0d78 | PUSH2 | 0x03eb |
| 0d7b | JUMPI | |
| 0d7c | PUSH2 | 0x0d89 |
| 0d7f | SWAP1 | |
| 0d80 | CALLDATASIZE | |
| 0d81 | SWAP1 | |
| 0d82 | PUSH1 | 0x04 |
| 0d84 | ADD | |
| 0d85 | PUSH2 | 0x642b |
| 0d88 | JUMP | |
| 0d89 | JUMPDEST | |
| 0d8a | SWAP5 | |
| 0d8b | PUSH1 | 0x02 |
| 0d8d | SLOAD | |
| 0d8e | DUP1 | |
| 0d8f | ISZERO | |
| 0d90 | PUSH2 | 0x2d41 |
| 0d93 | JUMPI | |
| 0d94 | PUSH1 | 0x04 |
| 0d96 | CALLDATALOAD | |
| 0d97 | PUSH1 | 0x04 |
| 0d99 | ADD | |
| 0d9a | CALLDATALOAD | |
| 0d9b | ISZERO | |
| 0d9c | DUP1 | |
| 0d9d | PUSH2 | 0x2d37 |
| 0da0 | JUMPI | |
| 0da1 | JUMPDEST | |
| 0da2 | DUP1 | |
| 0da3 | PUSH2 | 0x2d2f |
| 0da6 | JUMPI | |
| 0da7 | JUMPDEST | |
| 0da8 | PUSH2 | 0x2d1a |
| 0dab | JUMPI | |
| 0dac | PUSH1 | 0x03 |
| 0dae | SLOAD | |
| 0daf | SWAP3 | |
| 0db0 | PUSH1 | 0x40 |
| 0db2 | MLOAD | |
| 0db3 | SWAP8 | |
| 0db4 | PUSH1 | 0xa0 |
| 0db6 | DUP10 | |
| 0db7 | ADD | |
| 0db8 | PUSH1 | 0x01 |
| 0dba | PUSH1 | 0x01 |
| 0dbc | PUSH1 | 0x40 |
| 0dbe | SHL | |
| 0dbf | SUB | |
| 0dc0 | DUP7 | |
| 0dc1 | AND | |
| 0dc2 | PUSH1 | 0x20 |
| 0dc4 | DUP12 | |
| 0dc5 | ADD | |
| 0dc6 | MSTORE | |
| 0dc7 | PUSH1 | 0x80 |
| 0dc9 | PUSH1 | 0x40 |
| 0dcb | DUP12 | |
| 0dcc | ADD | |
| 0dcd | MSTORE | |
| 0dce | PUSH1 | 0x04 |
| 0dd0 | CALLDATALOAD | |
| 0dd1 | PUSH1 | 0x04 |
| 0dd3 | ADD | |
| 0dd4 | CALLDATALOAD | |
| 0dd5 | SWAP1 | |
| 0dd6 | MSTORE | |
| 0dd7 | PUSH1 | 0xc0 |
| 0dd9 | DUP10 | |
| 0dda | ADD | |
| 0ddb | PUSH1 | 0x24 |
| 0ddd | PUSH1 | 0x04 |
| 0ddf | CALLDATALOAD | |
| 0de0 | ADD | |
| 0de1 | PUSH2 | 0x0100 |
| 0de4 | MLOAD | |
| 0de5 | JUMPDEST | |
| 0de6 | PUSH1 | 0x04 |
| 0de8 | CALLDATALOAD | |
| 0de9 | PUSH1 | 0x04 |
| 0deb | ADD | |
| 0dec | CALLDATALOAD | |
| 0ded | DUP2 | |
| 0dee | LT | |
| 0def | PUSH2 | 0x2b87 |
| 0df2 | JUMPI | |
| 0df3 | POP | |
| 0df4 | POP | |
| 0df5 | PUSH1 | 0x1f |
| 0df7 | NOT | |
| 0df8 | DUP11 | |
| 0df9 | DUP3 | |
| 0dfa | SUB | |
| 0dfb | ADD | |
| 0dfc | PUSH1 | 0x60 |
| 0dfe | DUP12 | |
| 0dff | ADD | |
| 0e00 | MSTORE | |
| 0e01 | PUSH1 | 0xc0 |
| 0e03 | MLOAD | |
| 0e04 | DUP2 | |
| 0e05 | MSTORE | |
| 0e06 | PUSH1 | 0x20 |
| 0e08 | DUP2 | |
| 0e09 | ADD | |
| 0e0a | PUSH1 | 0x20 |
| 0e0c | PUSH1 | 0xc0 |
| 0e0e | MLOAD | |
| 0e0f | PUSH1 | 0x05 |
| 0e11 | SHL | |
| 0e12 | DUP4 | |
| 0e13 | ADD | |
| 0e14 | ADD | |
| 0e15 | SWAP1 | |
| 0e16 | DUP10 | |
| 0e17 | SWAP3 | |
| 0e18 | PUSH2 | 0x0100 |
| 0e1b | MLOAD | |
| 0e1c | JUMPDEST | |
| 0e1d | PUSH1 | 0xc0 |
| 0e1f | MLOAD | |
| 0e20 | DUP2 | |
| 0e21 | LT | |
| 0e22 | PUSH2 | 0x2a81 |
| 0e25 | JUMPI | |
| 0e26 | POP | |
| 0e27 | POP | |
| 0e28 | POP | |
| 0e29 | PUSH1 | 0x20 |
| 0e2b | SWAP2 | |
| 0e2c | POP | |
| 0e2d | PUSH1 | 0x1f |
| 0e2f | NOT | |
| 0e30 | DUP12 | |
| 0e31 | DUP3 | |
| 0e32 | SUB | |
| 0e33 | ADD | |
| 0e34 | PUSH1 | 0x80 |
| 0e36 | DUP13 | |
| 0e37 | ADD | |
| 0e38 | MSTORE | |
| 0e39 | DUP8 | |
| 0e3a | DUP2 | |
| 0e3b | MSTORE | |
| 0e3c | ADD | |
| 0e3d | SWAP9 | |
| 0e3e | DUP9 | |
| 0e3f | PUSH2 | 0x0100 |
| 0e42 | MLOAD | |
| 0e43 | JUMPDEST | |
| 0e44 | DUP9 | |
| 0e45 | DUP2 | |
| 0e46 | LT | |
| 0e47 | PUSH2 | 0x29ea |
| 0e4a | JUMPI | |
| 0e4b | POP | |
| 0e4c | POP | |
| 0e4d | PUSH2 | 0x0e66 |
| 0e50 | DUP2 | |
| 0e51 | PUSH2 | 0x0f0d |
| 0e54 | SWAP8 | |
| 0e55 | SWAP9 | |
| 0e56 | SWAP10 | |
| 0e57 | SWAP11 | |
| 0e58 | SWAP12 | |
| 0e59 | SUB | |
| 0e5a | PUSH1 | 0x1f |
| 0e5c | NOT | |
| 0e5d | DUP2 | |
| 0e5e | ADD | |
| 0e5f | DUP4 | |
| 0e60 | MSTORE | |
| 0e61 | DUP3 | |
| 0e62 | PUSH2 | 0x67d9 |
| 0e65 | JUMP | |
| 0e66 | JUMPDEST | |
| 0e67 | PUSH1 | 0x20 |
| 0e69 | DUP2 | |
| 0e6a | MLOAD | |
| 0e6b | SWAP2 | |
| 0e6c | ADD | |
| 0e6d | KECCAK256 | |
| 0e6e | PUSH2 | 0x0100 |
| 0e71 | MLOAD | |
| 0e72 | POP | |
| 0e73 | PUSH1 | 0x40 |
| 0e75 | MLOAD | |
| 0e76 | PUSH1 | 0x20 |
| 0e78 | DUP2 | |
| 0e79 | ADD | |
| 0e7a | SWAP2 | |
| 0e7b | PUSH0 | |
| 0e7c | MLOAD | |
| 0e7d | PUSH1 | 0x20 |
| 0e7f | PUSH2 | 0x828f |
| 0e82 | PUSH0 | |
| 0e83 | CODECOPY | |
| 0e84 | PUSH0 | |
| 0e85 | MLOAD | |
| 0e86 | SWAP1 | |
| 0e87 | PUSH0 | |
| 0e88 | MSTORE | |
| 0e89 | DUP4 | |
| 0e8a | MSTORE | |
| 0e8b | CHAINID | |
| 0e8c | PUSH1 | 0x40 |
| 0e8e | DUP4 | |
| 0e8f | ADD | |
| 0e90 | MSTORE | |
| 0e91 | ADDRESS | |
| 0e92 | PUSH1 | 0x60 |
| 0e94 | DUP4 | |
| 0e95 | ADD | |
| 0e96 | MSTORE | |
| 0e97 | PUSH32 | 0xe10634eb0bf7bd6ad00dc59a6c67fe9b09037b979405fa59137e7e530d601414 |
| 0eb8 | PUSH1 | 0x80 |
| 0eba | DUP4 | |
| 0ebb | ADD | |
| 0ebc | MSTORE | |
| 0ebd | PUSH1 | 0x01 |
| 0ebf | PUSH1 | 0x01 |
| 0ec1 | PUSH1 | 0x40 |
| 0ec3 | SHL | |
| 0ec4 | SUB | |
| 0ec5 | DUP8 | |
| 0ec6 | AND | |
| 0ec7 | PUSH1 | 0xa0 |
| 0ec9 | DUP4 | |
| 0eca | ADD | |
| 0ecb | MSTORE | |
| 0ecc | PUSH1 | 0xc0 |
| 0ece | DUP3 | |
| 0ecf | ADD | |
| 0ed0 | MSTORE | |
| 0ed1 | PUSH1 | 0xc0 |
| 0ed3 | DUP2 | |
| 0ed4 | MSTORE | |
| 0ed5 | PUSH2 | 0x0edf |
| 0ed8 | PUSH1 | 0xe0 |
| 0eda | DUP3 | |
| 0edb | PUSH2 | 0x67d9 |
| 0ede | JUMP | |
| 0edf | JUMPDEST | |
| 0ee0 | MLOAD | |
| 0ee1 | SWAP1 | |
| 0ee2 | KECCAK256 | |
| 0ee3 | SWAP1 | |
| 0ee4 | PUSH1 | 0x01 |
| 0ee6 | SLOAD | |
| 0ee7 | SWAP3 | |
| 0ee8 | PUSH32 | 0x00000000000000000000000070b4f3c06e5d93d695129f1255c55c01e7be13bf |
| 0f09 | PUSH2 | 0x73c3 |
| 0f0c | JUMP | |
| 0f0d | JUMPDEST | |
| 0f0e | POP | |
| 0f0f | PUSH1 | 0x01 |
| 0f11 | PUSH1 | 0x01 |
| 0f13 | PUSH1 | 0x40 |
| 0f15 | SHL | |
| 0f16 | SUB | |
| 0f17 | PUSH2 | 0x0f21 |
| 0f1a | DUP2 | |
| 0f1b | DUP4 | |
| 0f1c | AND | |
| 0f1d | PUSH2 | 0x6975 |
| 0f20 | JUMP | |
| 0f21 | JUMPDEST | |
| 0f22 | AND | |
| 0f23 | SWAP1 | |
| 0f24 | PUSH1 | 0x01 |
| 0f26 | PUSH1 | 0x01 |
| 0f28 | PUSH1 | 0x40 |
| 0f2a | SHL | |
| 0f2b | SUB | |
| 0f2c | NOT | |
| 0f2d | AND | |
| 0f2e | OR | |
| 0f2f | PUSH1 | 0x03 |
| 0f31 | SSTORE | |
| 0f32 | PUSH1 | 0x19 |
| 0f34 | SLOAD | |
| 0f35 | PUSH2 | 0x0f46 |
| 0f38 | PUSH1 | 0x01 |
| 0f3a | PUSH1 | 0x01 |
| 0f3c | PUSH1 | 0x40 |
| 0f3e | SHL | |
| 0f3f | SUB | |
| 0f40 | DUP3 | |
| 0f41 | AND | |
| 0f42 | PUSH2 | 0x6975 |
| 0f45 | JUMP | |
| 0f46 | JUMPDEST | |
| 0f47 | PUSH1 | 0xa0 |
| 0f49 | MSTORE | |
| 0f4a | PUSH1 | 0x01 |
| 0f4c | PUSH1 | 0x01 |
| 0f4e | PUSH1 | 0x40 |
| 0f50 | SHL | |
| 0f51 | SUB | |
| 0f52 | PUSH1 | 0xa0 |
| 0f54 | MLOAD | |
| 0f55 | AND | |
| 0f56 | SWAP1 | |
| 0f57 | PUSH1 | 0x01 |
| 0f59 | PUSH1 | 0x01 |
| 0f5b | PUSH1 | 0x40 |
| 0f5d | SHL | |
| 0f5e | SUB | |
| 0f5f | NOT | |
| 0f60 | AND | |
| 0f61 | OR | |
| 0f62 | PUSH1 | 0x19 |
| 0f64 | SSTORE | |
| 0f65 | PUSH2 | 0x0f76 |
| 0f68 | PUSH1 | 0xc0 |
| 0f6a | MLOAD | |
| 0f6b | PUSH1 | 0x04 |
| 0f6d | CALLDATALOAD | |
| 0f6e | PUSH1 | 0x04 |
| 0f70 | ADD | |
| 0f71 | CALLDATALOAD | |
| 0f72 | PUSH2 | 0x6bd3 |
| 0f75 | JUMP | |
| 0f76 | JUMPDEST | |
| 0f77 | PUSH1 | 0x01 |
| 0f79 | PUSH1 | 0x01 |
| 0f7b | PUSH1 | 0xff |
| 0f7d | SHL | |
| 0f7e | SUB | |
| 0f7f | DUP3 | |
| 0f80 | AND | |
| 0f81 | DUP3 | |
| 0f82 | SUB | |
| 0f83 | PUSH2 | 0x29d0 |
| 0f86 | JUMPI | |
| 0f87 | PUSH2 | 0x0f97 |
| 0f8a | PUSH2 | 0x0fa8 |
| 0f8d | SWAP2 | |
| 0f8e | DUP4 | |
| 0f8f | PUSH1 | 0x01 |
| 0f91 | SHL | |
| 0f92 | SWAP1 | |
| 0f93 | PUSH2 | 0x6bd3 |
| 0f96 | JUMP | |
| 0f97 | JUMPDEST | |
| 0f98 | PUSH2 | 0x0fa0 |
| 0f9b | DUP2 | |
| 0f9c | PUSH2 | 0x69be |
| 0f9f | JUMP | |
| 0fa0 | JUMPDEST | |
| 0fa1 | PUSH1 | 0xe0 |
| 0fa3 | MSTORE | |
| 0fa4 | PUSH2 | 0x69be |
| 0fa7 | JUMP | |
| 0fa8 | JUMPDEST | |
| 0fa9 | PUSH2 | 0x0100 |
| 0fac | MLOAD | |
| 0fad | SWAP3 | |
| 0fae | SWAP1 | |
| 0faf | SWAP2 | |
| 0fb0 | SWAP1 | |
| 0fb1 | DUP4 | |
| 0fb2 | JUMPDEST | |
| 0fb3 | PUSH1 | 0x04 |
| 0fb5 | CALLDATALOAD | |
| 0fb6 | PUSH1 | 0x04 |
| 0fb8 | ADD | |
| 0fb9 | CALLDATALOAD | |
| 0fba | DUP6 | |
| 0fbb | LT | |
| 0fbc | ISZERO | |
| 0fbd | PUSH2 | 0x18e0 |
| 0fc0 | JUMPI | |
| 0fc1 | PUSH2 | 0x0fdb |
| 0fc4 | PUSH1 | 0x04 |
| 0fc6 | CALLDATALOAD | |
| 0fc7 | PUSH2 | 0x02c0 |
| 0fca | DUP8 | |
| 0fcb | MUL | |
| 0fcc | ADD | |
| 0fcd | PUSH1 | 0x64 |
| 0fcf | DUP2 | |
| 0fd0 | ADD | |
| 0fd1 | CALLDATALOAD | |
| 0fd2 | SWAP1 | |
| 0fd3 | PUSH1 | 0x44 |
| 0fd5 | ADD | |
| 0fd6 | CALLDATALOAD | |
| 0fd7 | PUSH2 | 0x72b3 |
| 0fda | JUMP | |
| 0fdb | JUMPDEST | |
| 0fdc | PUSH1 | 0x24 |
| 0fde | PUSH2 | 0x02c0 |
| 0fe1 | DUP8 | |
| 0fe2 | MUL | |
| 0fe3 | PUSH1 | 0x04 |
| 0fe5 | CALLDATALOAD | |
| 0fe6 | ADD | |
| 0fe7 | ADD | |
| 0fe8 | CALLDATALOAD | |
| 0fe9 | SUB | |
| 0fea | PUSH2 | 0x189e |
| 0fed | JUMPI | |
| 0fee | PUSH2 | 0x1002 |
| 0ff1 | PUSH2 | 0x0244 |
| 0ff4 | PUSH2 | 0x02c0 |
| 0ff7 | DUP8 | |
| 0ff8 | MUL | |
| 0ff9 | PUSH1 | 0x04 |
| 0ffb | CALLDATALOAD | |
| 0ffc | ADD | |
| 0ffd | ADD | |
| 0ffe | PUSH2 | 0x6be0 |
| 1001 | JUMP | |
| 1002 | JUMPDEST | |
| 1003 | DUP1 | |
| 1004 | PUSH2 | 0x1811 |
| 1007 | JUMPI | |
| 1008 | JUMPDEST | |
| 1009 | PUSH2 | 0x17ec |
| 100c | JUMPI | |
| 100d | PUSH1 | 0x01 |
| 100f | PUSH1 | 0xff |
| 1011 | PUSH2 | 0x1025 |
| 1014 | PUSH2 | 0x02a4 |
| 1017 | PUSH2 | 0x02c0 |
| 101a | DUP10 | |
| 101b | MUL | |
| 101c | PUSH1 | 0x04 |
| 101e | CALLDATALOAD | |
| 101f | ADD | |
| 1020 | ADD | |
| 1021 | PUSH2 | 0x6bed |
| 1024 | JUMP | |
| 1025 | JUMPDEST | |
| 1026 | AND | |
| 1027 | GT | |
| 1028 | PUSH2 | 0x17ad |
| 102b | JUMPI | |
| 102c | PUSH2 | 0x1040 |
| 102f | PUSH2 | 0x0244 |
| 1032 | PUSH2 | 0x02c0 |
| 1035 | DUP8 | |
| 1036 | MUL | |
| 1037 | PUSH1 | 0x04 |
| 1039 | CALLDATALOAD | |
| 103a | ADD | |
| 103b | ADD | |
| 103c | PUSH2 | 0x6be0 |
| 103f | JUMP | |
| 1040 | JUMPDEST | |
| 1041 | DUP1 | |
| 1042 | PUSH2 | 0x178e |
| 1045 | JUMPI | |
| 1046 | JUMPDEST | |
| 1047 | DUP1 | |
| 1048 | PUSH2 | 0x177a |
| 104b | JUMPI | |
| 104c | JUMPDEST | |
| 104d | PUSH2 | 0x1755 |
| 1050 | JUMPI | |
| 1051 | PUSH1 | 0x04 |
| 1053 | CALLDATALOAD | |
| 1054 | PUSH2 | 0x02c0 |
| 1057 | DUP7 | |
| 1058 | MUL | |
| 1059 | ADD | |
| 105a | PUSH1 | 0x44 |
| 105c | ADD | |
| 105d | CALLDATALOAD | |
| 105e | PUSH0 | |
| 105f | MLOAD | |
| 1060 | PUSH1 | 0x20 |
| 1062 | PUSH2 | 0x826f |
| 1065 | PUSH0 | |
| 1066 | CODECOPY | |
| 1067 | PUSH0 | |
| 1068 | MLOAD | |
| 1069 | SWAP1 | |
| 106a | PUSH0 | |
| 106b | MSTORE | |
| 106c | EQ | |
| 106d | DUP1 | |
| 106e | DUP1 | |
| 106f | PUSH2 | 0x1733 |
| 1072 | JUMPI | |
| 1073 | JUMPDEST | |
| 1074 | PUSH2 | 0x16f4 |
| 1077 | JUMPI | |
| 1078 | PUSH2 | 0x16ad |
| 107b | JUMPI | |
| 107c | JUMPDEST | |
| 107d | PUSH2 | 0x01e4 |
| 1080 | PUSH2 | 0x02c0 |
| 1083 | DUP7 | |
| 1084 | MUL | |
| 1085 | PUSH1 | 0x04 |
| 1087 | CALLDATALOAD | |
| 1088 | ADD | |
| 1089 | ADD | |
| 108a | CALLDATALOAD | |
| 108b | ISZERO | |
| 108c | ISZERO | |
| 108d | DUP1 | |
| 108e | PUSH2 | 0x1683 |
| 1091 | JUMPI | |
| 1092 | JUMPDEST | |
| 1093 | PUSH2 | 0x165d |
| 1096 | JUMPI | |
| 1097 | PUSH1 | 0x24 |
| 1099 | PUSH2 | 0x02c0 |
| 109c | DUP7 | |
| 109d | MUL | |
| 109e | PUSH1 | 0x04 |
| 10a0 | CALLDATALOAD | |
| 10a1 | ADD | |
| 10a2 | ADD | |
| 10a3 | CALLDATALOAD | |
| 10a4 | PUSH2 | 0x0100 |
| 10a7 | MLOAD | |
| 10a8 | MSTORE | |
| 10a9 | PUSH1 | 0x06 |
| 10ab | PUSH1 | 0x20 |
| 10ad | MSTORE | |
| 10ae | PUSH1 | 0xff |
| 10b0 | PUSH1 | 0x40 |
| 10b2 | PUSH2 | 0x0100 |
| 10b5 | MLOAD | |
| 10b6 | KECCAK256 | |
| 10b7 | SLOAD | |
| 10b8 | AND | |
| 10b9 | ISZERO | |
| 10ba | PUSH2 | 0x15ca |
| 10bd | JUMPI | |
| 10be | JUMPDEST | |
| 10bf | PUSH2 | 0x0100 |
| 10c2 | DUP1 | |
| 10c3 | MLOAD | |
| 10c4 | PUSH2 | 0x02c0 |
| 10c7 | DUP8 | |
| 10c8 | MUL | |
| 10c9 | PUSH1 | 0x04 |
| 10cb | CALLDATALOAD | |
| 10cc | ADD | |
| 10cd | PUSH1 | 0x24 |
| 10cf | DUP2 | |
| 10d0 | ADD | |
| 10d1 | CALLDATALOAD | |
| 10d2 | SWAP2 | |
| 10d3 | DUP3 | |
| 10d4 | SWAP1 | |
| 10d5 | MSTORE | |
| 10d6 | PUSH1 | 0x05 |
| 10d8 | PUSH1 | 0x20 |
| 10da | MSTORE | |
| 10db | SWAP2 | |
| 10dc | MLOAD | |
| 10dd | PUSH1 | 0x40 |
| 10df | SWAP1 | |
| 10e0 | KECCAK256 | |
| 10e1 | SWAP1 | |
| 10e2 | DUP2 | |
| 10e3 | SSTORE | |
| 10e4 | PUSH1 | 0x44 |
| 10e6 | DUP3 | |
| 10e7 | ADD | |
| 10e8 | CALLDATALOAD | |
| 10e9 | PUSH1 | 0x01 |
| 10eb | DUP3 | |
| 10ec | ADD | |
| 10ed | SSTORE | |
| 10ee | PUSH1 | 0x64 |
| 10f0 | DUP3 | |
| 10f1 | ADD | |
| 10f2 | CALLDATALOAD | |
| 10f3 | PUSH1 | 0x02 |
| 10f5 | DUP3 | |
| 10f6 | ADD | |
| 10f7 | SWAP1 | |
| 10f8 | DUP2 | |
| 10f9 | SSTORE | |
| 10fa | PUSH1 | 0x84 |
| 10fc | DUP4 | |
| 10fd | ADD | |
| 10fe | CALLDATALOAD | |
| 10ff | PUSH1 | 0x03 |
| 1101 | DUP4 | |
| 1102 | ADD | |
| 1103 | SSTORE | |
| 1104 | SWAP1 | |
| 1105 | SWAP2 | |
| 1106 | PUSH2 | 0x1111 |
| 1109 | SWAP1 | |
| 110a | PUSH1 | 0xa4 |
| 110c | ADD | |
| 110d | PUSH2 | 0x6bed |
| 1110 | JUMP | |
| 1111 | JUMPDEST | |
| 1112 | PUSH1 | 0x04 |
| 1114 | DUP4 | |
| 1115 | DUP2 | |
| 1116 | ADD | |
| 1117 | DUP1 | |
| 1118 | SLOAD | |
| 1119 | PUSH1 | 0xff |
| 111b | NOT | |
| 111c | AND | |
| 111d | PUSH1 | 0xff |
| 111f | SWAP4 | |
| 1120 | SWAP1 | |
| 1121 | SWAP4 | |
| 1122 | AND | |
| 1123 | SWAP3 | |
| 1124 | SWAP1 | |
| 1125 | SWAP3 | |
| 1126 | OR | |
| 1127 | SWAP1 | |
| 1128 | SWAP2 | |
| 1129 | SSTORE | |
| 112a | PUSH1 | 0xc4 |
| 112c | SWAP1 | |
| 112d | CALLDATALOAD | |
| 112e | PUSH2 | 0x02c0 |
| 1131 | DUP10 | |
| 1132 | MUL | |
| 1133 | ADD | |
| 1134 | SWAP1 | |
| 1135 | DUP2 | |
| 1136 | ADD | |
| 1137 | CALLDATALOAD | |
| 1138 | PUSH1 | 0x05 |
| 113a | DUP5 | |
| 113b | ADD | |
| 113c | SSTORE | |
| 113d | PUSH1 | 0xe4 |
| 113f | DUP2 | |
| 1140 | ADD | |
| 1141 | CALLDATALOAD | |
| 1142 | PUSH1 | 0x06 |
| 1144 | DUP5 | |
| 1145 | ADD | |
| 1146 | SSTORE | |
| 1147 | PUSH2 | 0x1153 |
| 114a | SWAP1 | |
| 114b | PUSH2 | 0x0104 |
| 114e | ADD | |
| 114f | PUSH2 | 0x6bed |
| 1152 | JUMP | |
| 1153 | JUMPDEST | |
| 1154 | PUSH1 | 0xff |
| 1156 | AND | |
| 1157 | PUSH1 | 0xff |
| 1159 | NOT | |
| 115a | PUSH1 | 0x07 |
| 115c | DUP5 | |
| 115d | ADD | |
| 115e | SLOAD | |
| 115f | AND | |
| 1160 | OR | |
| 1161 | PUSH1 | 0x07 |
| 1163 | DUP4 | |
| 1164 | ADD | |
| 1165 | SSTORE | |
| 1166 | PUSH2 | 0x02c0 |
| 1169 | DUP8 | |
| 116a | MUL | |
| 116b | PUSH1 | 0x04 |
| 116d | CALLDATALOAD | |
| 116e | ADD | |
| 116f | PUSH2 | 0x0124 |
| 1172 | ADD | |
| 1173 | PUSH2 | 0x117b |
| 1176 | SWAP1 | |
| 1177 | PUSH2 | 0x71aa |
| 117a | JUMP | |
| 117b | JUMPDEST | |
| 117c | PUSH1 | 0x07 |
| 117e | DUP4 | |
| 117f | ADD | |
| 1180 | SWAP1 | |
| 1181 | PUSH2 | 0x11a8 |
| 1184 | SWAP2 | |
| 1185 | SWAP1 | |
| 1186 | PUSH9 | 0xffffffffffffffff00 |
| 1190 | DUP3 | |
| 1191 | SLOAD | |
| 1192 | SWAP2 | |
| 1193 | PUSH1 | 0x08 |
| 1195 | SHL | |
| 1196 | AND | |
| 1197 | SWAP1 | |
| 1198 | PUSH9 | 0xffffffffffffffff00 |
| 11a2 | NOT | |
| 11a3 | AND | |
| 11a4 | OR | |
| 11a5 | SWAP1 | |
| 11a6 | SSTORE | |
| 11a7 | JUMP | |
| 11a8 | JUMPDEST | |
| 11a9 | PUSH2 | 0x11bd |
| 11ac | PUSH2 | 0x0144 |
| 11af | PUSH1 | 0x04 |
| 11b1 | CALLDATALOAD | |
| 11b2 | PUSH2 | 0x02c0 |
| 11b5 | DUP11 | |
| 11b6 | MUL | |
| 11b7 | ADD | |
| 11b8 | ADD | |
| 11b9 | PUSH2 | 0x6c18 |
| 11bc | JUMP | |
| 11bd | JUMPDEST | |
| 11be | PUSH1 | 0x07 |
| 11c0 | DUP4 | |
| 11c1 | ADD | |
| 11c2 | SLOAD | |
| 11c3 | PUSH2 | 0xffff |
| 11c6 | PUSH1 | 0x68 |
| 11c8 | SHL | |
| 11c9 | PUSH2 | 0x11dd |
| 11cc | PUSH2 | 0x0164 |
| 11cf | PUSH1 | 0x04 |
| 11d1 | CALLDATALOAD | |
| 11d2 | PUSH2 | 0x02c0 |
| 11d5 | DUP14 | |
| 11d6 | MUL | |
| 11d7 | ADD | |
| 11d8 | ADD | |
| 11d9 | PUSH2 | 0x6c09 |
| 11dc | JUMP | |
| 11dd | JUMPDEST | |
| 11de | PUSH1 | 0x68 |
| 11e0 | SHL | |
| 11e1 | AND | |
| 11e2 | SWAP1 | |
| 11e3 | PUSH2 | 0xffff |
| 11e6 | PUSH1 | 0x78 |
| 11e8 | SHL | |
| 11e9 | PUSH2 | 0x11fd |
| 11ec | PUSH2 | 0x0184 |
| 11ef | PUSH1 | 0x04 |
| 11f1 | CALLDATALOAD | |
| 11f2 | PUSH2 | 0x02c0 |
| 11f5 | DUP15 | |
| 11f6 | MUL | |
| 11f7 | ADD | |
| 11f8 | ADD | |
| 11f9 | PUSH2 | 0x6c09 |
| 11fc | JUMP | |
| 11fd | JUMPDEST | |
| 11fe | PUSH1 | 0x78 |
| 1200 | SHL | |
| 1201 | AND | |
| 1202 | SWAP3 | |
| 1203 | PUSH1 | 0x48 |
| 1205 | SHL | |
| 1206 | PUSH13 | 0xffffffff000000000000000000 |
| 1214 | AND | |
| 1215 | SWAP1 | |
| 1216 | PUSH8 | 0xffffffffffffffff |
| 121f | PUSH1 | 0x48 |
| 1221 | SHL | |
| 1222 | NOT | |
| 1223 | AND | |
| 1224 | OR | |
| 1225 | OR | |
| 1226 | OR | |
| 1227 | PUSH1 | 0x07 |
| 1229 | DUP4 | |
| 122a | ADD | |
| 122b | SSTORE | |
| 122c | PUSH2 | 0x02c0 |
| 122f | DUP8 | |
| 1230 | MUL | |
| 1231 | PUSH1 | 0x04 |
| 1233 | CALLDATALOAD | |
| 1234 | ADD | |
| 1235 | PUSH2 | 0x01a4 |
| 1238 | ADD | |
| 1239 | CALLDATALOAD | |
| 123a | PUSH1 | 0x08 |
| 123c | DUP4 | |
| 123d | ADD | |
| 123e | SSTORE | |
| 123f | PUSH2 | 0x02c0 |
| 1242 | DUP8 | |
| 1243 | MUL | |
| 1244 | PUSH1 | 0x04 |
| 1246 | CALLDATALOAD | |
| 1247 | ADD | |
| 1248 | PUSH2 | 0x01c4 |
| 124b | ADD | |
| 124c | PUSH2 | 0x1254 |
| 124f | SWAP1 | |
| 1250 | PUSH2 | 0x6bed |
| 1253 | JUMP | |
| 1254 | JUMPDEST | |
| 1255 | PUSH1 | 0x09 |
| 1257 | DUP4 | |
| 1258 | ADD | |
| 1259 | DUP1 | |
| 125a | SLOAD | |
| 125b | PUSH1 | 0xff |
| 125d | NOT | |
| 125e | AND | |
| 125f | PUSH1 | 0xff |
| 1261 | SWAP3 | |
| 1262 | SWAP1 | |
| 1263 | SWAP3 | |
| 1264 | AND | |
| 1265 | SWAP2 | |
| 1266 | SWAP1 | |
| 1267 | SWAP2 | |
| 1268 | OR | |
| 1269 | SWAP1 | |
| 126a | SSTORE | |
| 126b | PUSH2 | 0x01e4 |
| 126e | PUSH1 | 0x04 |
| 1270 | CALLDATALOAD | |
| 1271 | PUSH2 | 0x02c0 |
| 1274 | DUP10 | |
| 1275 | MUL | |
| 1276 | ADD | |
| 1277 | SWAP1 | |
| 1278 | DUP2 | |
| 1279 | ADD | |
| 127a | CALLDATALOAD | |
| 127b | PUSH1 | 0x0a |
| 127d | DUP5 | |
| 127e | ADD | |
| 127f | SSTORE | |
| 1280 | PUSH2 | 0x0204 |
| 1283 | DUP2 | |
| 1284 | ADD | |
| 1285 | CALLDATALOAD | |
| 1286 | PUSH1 | 0x0b |
| 1288 | DUP5 | |
| 1289 | ADD | |
| 128a | SSTORE | |
| 128b | PUSH2 | 0x1297 |
| 128e | SWAP1 | |
| 128f | PUSH2 | 0x0224 |
| 1292 | ADD | |
| 1293 | PUSH2 | 0x6bed |
| 1296 | JUMP | |
| 1297 | JUMPDEST | |
| 1298 | PUSH1 | 0xff |
| 129a | AND | |
| 129b | PUSH1 | 0xff |
| 129d | NOT | |
| 129e | PUSH1 | 0x0c |
| 12a0 | DUP5 | |
| 12a1 | ADD | |
| 12a2 | SLOAD | |
| 12a3 | AND | |
| 12a4 | OR | |
| 12a5 | PUSH1 | 0x0c |
| 12a7 | DUP4 | |
| 12a8 | ADD | |
| 12a9 | SSTORE | |
| 12aa | PUSH2 | 0x02c0 |
| 12ad | DUP8 | |
| 12ae | MUL | |
| 12af | PUSH1 | 0x04 |
| 12b1 | CALLDATALOAD | |
| 12b2 | ADD | |
| 12b3 | PUSH2 | 0x0244 |
| 12b6 | ADD | |
| 12b7 | PUSH2 | 0x12bf |
| 12ba | SWAP1 | |
| 12bb | PUSH2 | 0x6be0 |
| 12be | JUMP | |
| 12bf | JUMPDEST | |
| 12c0 | PUSH1 | 0x0c |
| 12c2 | DUP4 | |
| 12c3 | ADD | |
| 12c4 | DUP1 | |
| 12c5 | SLOAD | |
| 12c6 | PUSH1 | 0xa0 |
| 12c8 | MLOAD | |
| 12c9 | PUSH10 | 0xffffffffffffffff0000 |
| 12d4 | PUSH1 | 0x10 |
| 12d6 | SWAP2 | |
| 12d7 | SWAP1 | |
| 12d8 | SWAP2 | |
| 12d9 | SHL | |
| 12da | AND | |
| 12db | SWAP3 | |
| 12dc | ISZERO | |
| 12dd | ISZERO | |
| 12de | PUSH1 | 0x08 |
| 12e0 | SHL | |
| 12e1 | PUSH2 | 0xff00 |
| 12e4 | AND | |
| 12e5 | PUSH10 | 0xffffffffffffffffff00 |
| 12f0 | NOT | |
| 12f1 | SWAP1 | |
| 12f2 | SWAP2 | |
| 12f3 | AND | |
| 12f4 | OR | |
| 12f5 | SWAP2 | |
| 12f6 | SWAP1 | |
| 12f7 | SWAP2 | |
| 12f8 | OR | |
| 12f9 | SWAP1 | |
| 12fa | SSTORE | |
| 12fb | PUSH2 | 0x130f |
| 12fe | PUSH2 | 0x0284 |
| 1301 | PUSH1 | 0x04 |
| 1303 | CALLDATALOAD | |
| 1304 | PUSH2 | 0x02c0 |
| 1307 | DUP11 | |
| 1308 | MUL | |
| 1309 | ADD | |
| 130a | ADD | |
| 130b | PUSH2 | 0x71aa |
| 130e | JUMP | |
| 130f | JUMPDEST | |
| 1310 | PUSH1 | 0x0c |
| 1312 | DUP4 | |
| 1313 | ADD | |
| 1314 | DUP1 | |
| 1315 | SLOAD | |
| 1316 | PUSH8 | 0xffffffffffffffff |
| 131f | PUSH1 | 0x50 |
| 1321 | SHL | |
| 1322 | NOT | |
| 1323 | AND | |
| 1324 | PUSH1 | 0x50 |
| 1326 | SWAP3 | |
| 1327 | SWAP1 | |
| 1328 | SWAP3 | |
| 1329 | SHL | |
| 132a | PUSH8 | 0xffffffffffffffff |
| 1333 | PUSH1 | 0x50 |
| 1335 | SHL | |
| 1336 | AND | |
| 1337 | SWAP2 | |
| 1338 | SWAP1 | |
| 1339 | SWAP2 | |
| 133a | OR | |
| 133b | SWAP1 | |
| 133c | SSTORE | |
| 133d | PUSH2 | 0x1351 |
| 1340 | PUSH2 | 0x02a4 |
| 1343 | PUSH1 | 0x04 |
| 1345 | CALLDATALOAD | |
| 1346 | PUSH2 | 0x02c0 |
| 1349 | DUP11 | |
| 134a | MUL | |
| 134b | ADD | |
| 134c | ADD | |
| 134d | PUSH2 | 0x6bed |
| 1350 | JUMP | |
| 1351 | JUMPDEST | |
| 1352 | PUSH1 | 0x0c |
| 1354 | DUP4 | |
| 1355 | ADD | |
| 1356 | DUP1 | |
| 1357 | SLOAD | |
| 1358 | PUSH1 | 0xff |
| 135a | PUSH1 | 0x90 |
| 135c | SHL | |
| 135d | PUSH1 | 0x90 |
| 135f | DUP5 | |
| 1360 | SWAP1 | |
| 1361 | SHL | |
| 1362 | AND | |
| 1363 | PUSH1 | 0xff |
| 1365 | PUSH1 | 0x90 |
| 1367 | SHL | |
| 1368 | NOT | |
| 1369 | DUP3 | |
| 136a | AND | |
| 136b | OR | |
| 136c | SWAP1 | |
| 136d | SWAP2 | |
| 136e | SSTORE | |
| 136f | PUSH2 | 0x02c0 |
| 1372 | DUP10 | |
| 1373 | MUL | |
| 1374 | PUSH1 | 0x04 |
| 1376 | CALLDATALOAD | |
| 1377 | ADD | |
| 1378 | PUSH2 | 0x02c4 |
| 137b | DUP2 | |
| 137c | ADD | |
| 137d | CALLDATALOAD | |
| 137e | PUSH1 | 0x0d |
| 1380 | DUP7 | |
| 1381 | ADD | |
| 1382 | SSTORE | |
| 1383 | SWAP1 | |
| 1384 | SWAP3 | |
| 1385 | SWAP1 | |
| 1386 | PUSH2 | 0x1392 |
| 1389 | SWAP1 | |
| 138a | PUSH1 | 0x24 |
| 138c | ADD | |
| 138d | CALLDATALOAD | |
| 138e | PUSH2 | 0x6ad3 |
| 1391 | JUMP | |
| 1392 | JUMPDEST | |
| 1393 | DUP6 | |
| 1394 | PUSH1 | 0xe0 |
| 1396 | MLOAD | |
| 1397 | SWAP1 | |
| 1398 | PUSH2 | 0x13a0 |
| 139b | SWAP2 | |
| 139c | PUSH2 | 0x6a8c |
| 139f | JUMP | |
| 13a0 | JUMPDEST | |
| 13a1 | MSTORE | |
| 13a2 | DUP4 | |
| 13a3 | SLOAD | |
| 13a4 | SWAP4 | |
| 13a5 | PUSH1 | 0x01 |
| 13a7 | DUP2 | |
| 13a8 | ADD | |
| 13a9 | SLOAD | |
| 13aa | SWAP2 | |
| 13ab | SLOAD | |
| 13ac | PUSH1 | 0x03 |
| 13ae | DUP3 | |
| 13af | ADD | |
| 13b0 | SLOAD | |
| 13b1 | PUSH1 | 0x04 |
| 13b3 | DUP4 | |
| 13b4 | ADD | |
| 13b5 | SLOAD | |
| 13b6 | PUSH1 | 0xff |
| 13b8 | AND | |
| 13b9 | PUSH1 | 0x05 |
| 13bb | DUP5 | |
| 13bc | ADD | |
| 13bd | SLOAD | |
| 13be | PUSH1 | 0x06 |
| 13c0 | DUP6 | |
| 13c1 | ADD | |
| 13c2 | SLOAD | |
| 13c3 | SWAP1 | |
| 13c4 | PUSH1 | 0x07 |
| 13c6 | DUP7 | |
| 13c7 | ADD | |
| 13c8 | SLOAD | |
| 13c9 | SWAP3 | |
| 13ca | PUSH1 | 0x08 |
| 13cc | DUP8 | |
| 13cd | ADD | |
| 13ce | SLOAD | |
| 13cf | SWAP5 | |
| 13d0 | PUSH1 | 0x09 |
| 13d2 | DUP9 | |
| 13d3 | ADD | |
| 13d4 | SLOAD | |
| 13d5 | PUSH1 | 0xff |
| 13d7 | AND | |
| 13d8 | SWAP7 | |
| 13d9 | PUSH1 | 0x0a |
| 13db | DUP10 | |
| 13dc | ADD | |
| 13dd | SLOAD | |
| 13de | SWAP9 | |
| 13df | PUSH1 | 0x0b |
| 13e1 | ADD | |
| 13e2 | SLOAD | |
| 13e3 | SWAP10 | |
| 13e4 | PUSH1 | 0x40 |
| 13e6 | MLOAD | |
| 13e7 | DUP1 | |
| 13e8 | SWAP15 | |
| 13e9 | PUSH1 | 0x20 |
| 13eb | DUP3 | |
| 13ec | ADD | |
| 13ed | PUSH0 | |
| 13ee | MLOAD | |
| 13ef | PUSH1 | 0x20 |
| 13f1 | PUSH2 | 0x82af |
| 13f4 | PUSH0 | |
| 13f5 | CODECOPY | |
| 13f6 | PUSH0 | |
| 13f7 | MLOAD | |
| 13f8 | SWAP1 | |
| 13f9 | PUSH0 | |
| 13fa | MSTORE | |
| 13fb | SWAP1 | |
| 13fc | MSTORE | |
| 13fd | PUSH2 | 0x0100 |
| 1400 | MLOAD | |
| 1401 | PUSH1 | 0x40 |
| 1403 | DUP4 | |
| 1404 | ADD | |
| 1405 | MSTORE | |
| 1406 | PUSH1 | 0x60 |
| 1408 | DUP3 | |
| 1409 | ADD | |
| 140a | MSTORE | |
| 140b | PUSH1 | 0x80 |
| 140d | ADD | |
| 140e | MSTORE | |
| 140f | PUSH1 | 0xa0 |
| 1411 | DUP14 | |
| 1412 | ADD | |
| 1413 | MSTORE | |
| 1414 | PUSH1 | 0xc0 |
| 1416 | DUP13 | |
| 1417 | ADD | |
| 1418 | MSTORE | |
| 1419 | PUSH1 | 0xe0 |
| 141b | DUP12 | |
| 141c | ADD | |
| 141d | MSTORE | |
| 141e | PUSH2 | 0x0100 |
| 1421 | DUP11 | |
| 1422 | ADD | |
| 1423 | MSTORE | |
| 1424 | PUSH2 | 0x0120 |
| 1427 | DUP10 | |
| 1428 | ADD | |
| 1429 | MSTORE | |
| 142a | PUSH1 | 0xff |
| 142c | DUP2 | |
| 142d | AND | |
| 142e | PUSH2 | 0x0140 |
| 1431 | DUP10 | |
| 1432 | ADD | |
| 1433 | MSTORE | |
| 1434 | DUP1 | |
| 1435 | PUSH1 | 0x08 |
| 1437 | SHR | |
| 1438 | PUSH1 | 0x01 |
| 143a | PUSH1 | 0x01 |
| 143c | PUSH1 | 0x40 |
| 143e | SHL | |
| 143f | SUB | |
| 1440 | AND | |
| 1441 | PUSH2 | 0x0160 |
| 1444 | DUP10 | |
| 1445 | ADD | |
| 1446 | MSTORE | |
| 1447 | DUP1 | |
| 1448 | PUSH1 | 0x48 |
| 144a | SHR | |
| 144b | PUSH4 | 0xffffffff |
| 1450 | AND | |
| 1451 | PUSH2 | 0x0180 |
| 1454 | DUP10 | |
| 1455 | ADD | |
| 1456 | MSTORE | |
| 1457 | DUP1 | |
| 1458 | PUSH1 | 0x68 |
| 145a | SHR | |
| 145b | PUSH2 | 0xffff |
| 145e | AND | |
| 145f | PUSH2 | 0x01a0 |
| 1462 | DUP10 | |
| 1463 | ADD | |
| 1464 | MSTORE | |
| 1465 | PUSH1 | 0x78 |
| 1467 | SHR | |
| 1468 | PUSH2 | 0xffff |
| 146b | AND | |
| 146c | PUSH2 | 0x01c0 |
| 146f | DUP9 | |
| 1470 | ADD | |
| 1471 | MSTORE | |
| 1472 | PUSH2 | 0x01e0 |
| 1475 | DUP8 | |
| 1476 | ADD | |
| 1477 | MSTORE | |
| 1478 | PUSH2 | 0x0200 |
| 147b | DUP7 | |
| 147c | ADD | |
| 147d | MSTORE | |
| 147e | PUSH2 | 0x0220 |
| 1481 | DUP6 | |
| 1482 | ADD | |
| 1483 | MSTORE | |
| 1484 | PUSH2 | 0x0240 |
| 1487 | DUP5 | |
| 1488 | ADD | |
| 1489 | MSTORE | |
| 148a | PUSH1 | 0xff |
| 148c | DUP3 | |
| 148d | AND | |
| 148e | PUSH2 | 0x0260 |
| 1491 | DUP5 | |
| 1492 | ADD | |
| 1493 | MSTORE | |
| 1494 | PUSH1 | 0xff |
| 1496 | PUSH1 | 0x90 |
| 1498 | SHL | |
| 1499 | DUP2 | |
| 149a | PUSH1 | 0x90 |
| 149c | SHL | |
| 149d | AND | |
| 149e | PUSH1 | 0xff |
| 14a0 | PUSH1 | 0x90 |
| 14a2 | SHL | |
| 14a3 | NOT | |
| 14a4 | DUP4 | |
| 14a5 | AND | |
| 14a6 | OR | |
| 14a7 | PUSH1 | 0x08 |
| 14a9 | SHR | |
| 14aa | PUSH1 | 0xff |
| 14ac | AND | |
| 14ad | ISZERO | |
| 14ae | ISZERO | |
| 14af | PUSH2 | 0x0280 |
| 14b2 | DUP5 | |
| 14b3 | ADD | |
| 14b4 | MSTORE | |
| 14b5 | PUSH1 | 0xff |
| 14b7 | PUSH1 | 0x90 |
| 14b9 | SHL | |
| 14ba | DUP2 | |
| 14bb | PUSH1 | 0x90 |
| 14bd | SHL | |
| 14be | AND | |
| 14bf | PUSH1 | 0xff |
| 14c1 | PUSH1 | 0x90 |
| 14c3 | SHL | |
| 14c4 | NOT | |
| 14c5 | DUP4 | |
| 14c6 | AND | |
| 14c7 | OR | |
| 14c8 | PUSH1 | 0x10 |
| 14ca | SHR | |
| 14cb | PUSH1 | 0x01 |
| 14cd | PUSH1 | 0x01 |
| 14cf | PUSH1 | 0x40 |
| 14d1 | SHL | |
| 14d2 | SUB | |
| 14d3 | AND | |
| 14d4 | PUSH2 | 0x02a0 |
| 14d7 | DUP5 | |
| 14d8 | ADD | |
| 14d9 | MSTORE | |
| 14da | PUSH1 | 0xff |
| 14dc | PUSH1 | 0x90 |
| 14de | SHL | |
| 14df | DUP2 | |
| 14e0 | PUSH1 | 0x90 |
| 14e2 | SHL | |
| 14e3 | AND | |
| 14e4 | PUSH1 | 0xff |
| 14e6 | PUSH1 | 0x90 |
| 14e8 | SHL | |
| 14e9 | NOT | |
| 14ea | DUP4 | |
| 14eb | AND | |
| 14ec | OR | |
| 14ed | PUSH1 | 0x50 |
| 14ef | SHR | |
| 14f0 | PUSH1 | 0x01 |
| 14f2 | PUSH1 | 0x01 |
| 14f4 | PUSH1 | 0x40 |
| 14f6 | SHL | |
| 14f7 | SUB | |
| 14f8 | AND | |
| 14f9 | PUSH2 | 0x02c0 |
| 14fc | DUP5 | |
| 14fd | ADD | |
| 14fe | MSTORE | |
| 14ff | PUSH1 | 0xff |
| 1501 | PUSH1 | 0x90 |
| 1503 | SHL | |
| 1504 | SWAP1 | |
| 1505 | PUSH1 | 0x90 |
| 1507 | SHL | |
| 1508 | AND | |
| 1509 | SWAP1 | |
| 150a | PUSH1 | 0xff |
| 150c | PUSH1 | 0x90 |
| 150e | SHL | |
| 150f | NOT | |
| 1510 | AND | |
| 1511 | OR | |
| 1512 | PUSH1 | 0x90 |
| 1514 | SHR | |
| 1515 | PUSH1 | 0xff |
| 1517 | AND | |
| 1518 | PUSH2 | 0x02e0 |
| 151b | DUP3 | |
| 151c | ADD | |
| 151d | MSTORE | |
| 151e | PUSH2 | 0x02c0 |
| 1521 | DUP7 | |
| 1522 | MUL | |
| 1523 | PUSH1 | 0x04 |
| 1525 | CALLDATALOAD | |
| 1526 | ADD | |
| 1527 | PUSH2 | 0x02c4 |
| 152a | ADD | |
| 152b | CALLDATALOAD | |
| 152c | PUSH2 | 0x0300 |
| 152f | DUP3 | |
| 1530 | ADD | |
| 1531 | MSTORE | |
| 1532 | PUSH2 | 0x0300 |
| 1535 | DUP2 | |
| 1536 | MSTORE | |
| 1537 | PUSH2 | 0x1542 |
| 153a | PUSH2 | 0x0320 |
| 153d | DUP3 | |
| 153e | PUSH2 | 0x67d9 |
| 1541 | JUMP | |
| 1542 | JUMPDEST | |
| 1543 | DUP1 | |
| 1544 | MLOAD | |
| 1545 | SWAP1 | |
| 1546 | PUSH1 | 0x20 |
| 1548 | ADD | |
| 1549 | KECCAK256 | |
| 154a | PUSH2 | 0x1553 |
| 154d | DUP3 | |
| 154e | DUP7 | |
| 154f | PUSH2 | 0x6a8c |
| 1552 | JUMP | |
| 1553 | JUMPDEST | |
| 1554 | MSTORE | |
| 1555 | PUSH2 | 0x155d |
| 1558 | SWAP1 | |
| 1559 | PUSH2 | 0x6bfb |
| 155c | JUMP | |
| 155d | JUMPDEST | |
| 155e | SWAP4 | |
| 155f | PUSH2 | 0x1573 |
| 1562 | PUSH2 | 0x0244 |
| 1565 | PUSH1 | 0x04 |
| 1567 | CALLDATALOAD | |
| 1568 | PUSH2 | 0x02c0 |
| 156b | DUP5 | |
| 156c | MUL | |
| 156d | ADD | |
| 156e | ADD | |
| 156f | PUSH2 | 0x6be0 |
| 1572 | JUMP | |
| 1573 | JUMPDEST | |
| 1574 | PUSH1 | 0x40 |
| 1576 | DUP1 | |
| 1577 | MLOAD | |
| 1578 | PUSH1 | 0xa0 |
| 157a | MLOAD | |
| 157b | SWAP3 | |
| 157c | ISZERO | |
| 157d | ISZERO | |
| 157e | DUP2 | |
| 157f | MSTORE | |
| 1580 | PUSH1 | 0x01 |
| 1582 | PUSH1 | 0x01 |
| 1584 | PUSH1 | 0x40 |
| 1586 | SHL | |
| 1587 | SUB | |
| 1588 | SWAP1 | |
| 1589 | SWAP3 | |
| 158a | AND | |
| 158b | PUSH1 | 0x20 |
| 158d | DUP4 | |
| 158e | ADD | |
| 158f | MSTORE | |
| 1590 | PUSH2 | 0x02c0 |
| 1593 | DUP4 | |
| 1594 | MUL | |
| 1595 | PUSH1 | 0x04 |
| 1597 | CALLDATALOAD | |
| 1598 | ADD | |
| 1599 | PUSH1 | 0x24 |
| 159b | ADD | |
| 159c | CALLDATALOAD | |
| 159d | SWAP2 | |
| 159e | PUSH32 | 0x523b72ac5fbe8982a57ea63c8ca4daf1602c22eda42d6cc14a884c0310698b80 |
| 15bf | SWAP2 | |
| 15c0 | SWAP1 | |
| 15c1 | LOG2 | |
| 15c2 | PUSH1 | 0x01 |
| 15c4 | ADD | |
| 15c5 | SWAP4 | |
| 15c6 | PUSH2 | 0x0fb2 |
| 15c9 | JUMP | |
| 15ca | JUMPDEST | |
| 15cb | PUSH1 | 0x24 |
| 15cd | PUSH2 | 0x02c0 |
| 15d0 | DUP7 | |
| 15d1 | MUL | |
| 15d2 | PUSH1 | 0x04 |
| 15d4 | CALLDATALOAD | |
| 15d5 | ADD | |
| 15d6 | ADD | |
| 15d7 | CALLDATALOAD | |
| 15d8 | PUSH2 | 0x0100 |
| 15db | MLOAD | |
| 15dc | MSTORE | |
| 15dd | PUSH1 | 0x06 |
| 15df | PUSH1 | 0x20 |
| 15e1 | MSTORE | |
| 15e2 | PUSH1 | 0x40 |
| 15e4 | PUSH2 | 0x0100 |
| 15e7 | MLOAD | |
| 15e8 | KECCAK256 | |
| 15e9 | PUSH1 | 0x01 |
| 15eb | PUSH1 | 0xff |
| 15ed | NOT | |
| 15ee | DUP3 | |
| 15ef | SLOAD | |
| 15f0 | AND | |
| 15f1 | OR | |
| 15f2 | SWAP1 | |
| 15f3 | SSTORE | |
| 15f4 | PUSH1 | 0x04 |
| 15f6 | SLOAD | |
| 15f7 | PUSH1 | 0x01 |
| 15f9 | PUSH1 | 0x40 |
| 15fb | SHL | |
| 15fc | DUP2 | |
| 15fd | LT | |
| 15fe | ISZERO | |
| 15ff | PUSH2 | 0x1643 |
| 1602 | JUMPI | |
| 1603 | PUSH2 | 0x1617 |
| 1606 | DUP2 | |
| 1607 | PUSH1 | 0x01 |
| 1609 | PUSH2 | 0x163c |
| 160c | SWAP4 | |
| 160d | ADD | |
| 160e | PUSH1 | 0x04 |
| 1610 | SSTORE | |
| 1611 | PUSH1 | 0x04 |
| 1613 | PUSH2 | 0x6a34 |
| 1616 | JUMP | |
| 1617 | JUMPDEST | |
| 1618 | PUSH1 | 0x24 |
| 161a | PUSH2 | 0x02c0 |
| 161d | DUP10 | |
| 161e | SWAP5 | |
| 161f | SWAP4 | |
| 1620 | SWAP5 | |
| 1621 | MUL | |
| 1622 | PUSH1 | 0x04 |
| 1624 | CALLDATALOAD | |
| 1625 | ADD | |
| 1626 | ADD | |
| 1627 | CALLDATALOAD | |
| 1628 | SWAP1 | |
| 1629 | DUP4 | |
| 162a | SLOAD | |
| 162b | SWAP1 | |
| 162c | PUSH1 | 0x03 |
| 162e | SHL | |
| 162f | SWAP2 | |
| 1630 | DUP3 | |
| 1631 | SHL | |
| 1632 | SWAP2 | |
| 1633 | PUSH0 | |
| 1634 | NOT | |
| 1635 | SWAP1 | |
| 1636 | SHL | |
| 1637 | NOT | |
| 1638 | AND | |
| 1639 | OR | |
| 163a | SWAP1 | |
| 163b | JUMP | |
| 163c | JUMPDEST | |
| 163d | SWAP1 | |
| 163e | SSTORE | |
| 163f | PUSH2 | 0x10be |
| 1642 | JUMP | |
| 1643 | JUMPDEST | |
| 1644 | PUSH4 | 0x4e487b71 |
| 1649 | PUSH1 | 0xe0 |
| 164b | SHL | |
| 164c | PUSH2 | 0x0100 |
| 164f | MLOAD | |
| 1650 | MSTORE | |
| 1651 | PUSH1 | 0x41 |
| 1653 | PUSH1 | 0x04 |
| 1655 | MSTORE | |
| 1656 | PUSH1 | 0x24 |
| 1658 | PUSH2 | 0x0100 |
| 165b | MLOAD | |
| 165c | REVERT | |
| 165d | JUMPDEST | |
| 165e | PUSH2 | 0x01e4 |
| 1661 | PUSH2 | 0x02c0 |
| 1664 | DUP7 | |
| 1665 | PUSH4 | 0x3f4b99b1 |
| 166a | PUSH1 | 0xe2 |
| 166c | SHL | |
| 166d | PUSH2 | 0x0100 |
| 1670 | MLOAD | |
| 1671 | MSTORE | |
| 1672 | MUL | |
| 1673 | PUSH1 | 0x04 |
| 1675 | CALLDATALOAD | |
| 1676 | ADD | |
| 1677 | ADD | |
| 1678 | CALLDATALOAD | |
| 1679 | PUSH1 | 0x04 |
| 167b | MSTORE | |
| 167c | PUSH1 | 0x24 |
| 167e | PUSH2 | 0x0100 |
| 1681 | MLOAD | |
| 1682 | REVERT | |
| 1683 | JUMPDEST | |
| 1684 | POP | |
| 1685 | PUSH2 | 0x01e4 |
| 1688 | PUSH2 | 0x02c0 |
| 168b | DUP7 | |
| 168c | MUL | |
| 168d | PUSH1 | 0x04 |
| 168f | CALLDATALOAD | |
| 1690 | ADD | |
| 1691 | ADD | |
| 1692 | CALLDATALOAD | |
| 1693 | PUSH2 | 0x0100 |
| 1696 | MLOAD | |
| 1697 | MSTORE | |
| 1698 | PUSH1 | 0x06 |
| 169a | PUSH1 | 0x20 |
| 169c | MSTORE | |
| 169d | PUSH1 | 0xff |
| 169f | PUSH1 | 0x40 |
| 16a1 | PUSH2 | 0x0100 |
| 16a4 | MLOAD | |
| 16a5 | KECCAK256 | |
| 16a6 | SLOAD | |
| 16a7 | AND | |
| 16a8 | ISZERO | |
| 16a9 | PUSH2 | 0x1092 |
| 16ac | JUMP | |
| 16ad | JUMPDEST | |
| 16ae | PUSH1 | 0x40 |
| 16b0 | DUP1 | |
| 16b1 | MLOAD | |
| 16b2 | PUSH2 | 0x16ef |
| 16b5 | SWAP2 | |
| 16b6 | PUSH2 | 0x16bf |
| 16b9 | SWAP1 | |
| 16ba | DUP3 | |
| 16bb | PUSH2 | 0x67d9 |
| 16be | JUMP | |
| 16bf | JUMPDEST | |
| 16c0 | PUSH1 | 0x09 |
| 16c2 | DUP2 | |
| 16c3 | MSTORE | |
| 16c4 | PUSH9 | 0x3830bcb6b0b9ba32b9 |
| 16ce | PUSH1 | 0xb9 |
| 16d0 | SHL | |
| 16d1 | PUSH1 | 0x20 |
| 16d3 | DUP3 | |
| 16d4 | ADD | |
| 16d5 | MSTORE | |
| 16d6 | PUSH1 | 0x04 |
| 16d8 | CALLDATALOAD | |
| 16d9 | PUSH2 | 0x02c0 |
| 16dc | DUP9 | |
| 16dd | MUL | |
| 16de | ADD | |
| 16df | PUSH2 | 0x02c4 |
| 16e2 | DUP2 | |
| 16e3 | ADD | |
| 16e4 | CALLDATALOAD | |
| 16e5 | SWAP2 | |
| 16e6 | SWAP1 | |
| 16e7 | PUSH1 | 0x24 |
| 16e9 | ADD | |
| 16ea | CALLDATALOAD | |
| 16eb | PUSH2 | 0x7a55 |
| 16ee | JUMP | |
| 16ef | JUMPDEST | |
| 16f0 | PUSH2 | 0x107c |
| 16f3 | JUMP | |
| 16f4 | JUMPDEST | |
| 16f5 | PUSH1 | 0xff |
| 16f7 | DUP7 | |
| 16f8 | PUSH1 | 0x24 |
| 16fa | PUSH2 | 0x02c0 |
| 16fd | PUSH2 | 0x170f |
| 1700 | PUSH2 | 0x0224 |
| 1703 | DUP3 | |
| 1704 | DUP6 | |
| 1705 | MUL | |
| 1706 | PUSH1 | 0x04 |
| 1708 | CALLDATALOAD | |
| 1709 | ADD | |
| 170a | ADD | |
| 170b | PUSH2 | 0x6bed |
| 170e | JUMP | |
| 170f | JUMPDEST | |
| 1710 | SWAP3 | |
| 1711 | PUSH4 | 0x1e9a7d75 |
| 1716 | PUSH1 | 0xe0 |
| 1718 | SHL | |
| 1719 | PUSH2 | 0x0100 |
| 171c | MLOAD | |
| 171d | MSTORE | |
| 171e | MUL | |
| 171f | PUSH1 | 0x04 |
| 1721 | CALLDATALOAD | |
| 1722 | ADD | |
| 1723 | ADD | |
| 1724 | CALLDATALOAD | |
| 1725 | PUSH1 | 0x04 |
| 1727 | MSTORE | |
| 1728 | AND | |
| 1729 | PUSH1 | 0x24 |
| 172b | MSTORE | |
| 172c | PUSH1 | 0x44 |
| 172e | PUSH2 | 0x0100 |
| 1731 | MLOAD | |
| 1732 | REVERT | |
| 1733 | JUMPDEST | |
| 1734 | POP | |
| 1735 | PUSH1 | 0x01 |
| 1737 | PUSH1 | 0xff |
| 1739 | PUSH2 | 0x174d |
| 173c | PUSH2 | 0x0224 |
| 173f | PUSH2 | 0x02c0 |
| 1742 | DUP11 | |
| 1743 | MUL | |
| 1744 | PUSH1 | 0x04 |
| 1746 | CALLDATALOAD | |
| 1747 | ADD | |
| 1748 | ADD | |
| 1749 | PUSH2 | 0x6bed |
| 174c | JUMP | |
| 174d | JUMPDEST | |
| 174e | AND | |
| 174f | EQ | |
| 1750 | ISZERO | |
| 1751 | PUSH2 | 0x1073 |
| 1754 | JUMP | |
| 1755 | JUMPDEST | |
| 1756 | PUSH1 | 0x24 |
| 1758 | PUSH2 | 0x02c0 |
| 175b | DUP7 | |
| 175c | PUSH4 | 0x2eb72ead |
| 1761 | PUSH1 | 0xe1 |
| 1763 | SHL | |
| 1764 | PUSH2 | 0x0100 |
| 1767 | MLOAD | |
| 1768 | MSTORE | |
| 1769 | MUL | |
| 176a | PUSH1 | 0x04 |
| 176c | CALLDATALOAD | |
| 176d | ADD | |
| 176e | ADD | |
| 176f | CALLDATALOAD | |
| 1770 | PUSH1 | 0x04 |
| 1772 | MSTORE | |
| 1773 | PUSH1 | 0x24 |
| 1775 | PUSH2 | 0x0100 |
| 1778 | MLOAD | |
| 1779 | REVERT | |
| 177a | JUMPDEST | |
| 177b | POP | |
| 177c | PUSH1 | 0x84 |
| 177e | PUSH2 | 0x02c0 |
| 1781 | DUP7 | |
| 1782 | MUL | |
| 1783 | PUSH1 | 0x04 |
| 1785 | CALLDATALOAD | |
| 1786 | ADD | |
| 1787 | ADD | |
| 1788 | CALLDATALOAD | |
| 1789 | ISZERO | |
| 178a | PUSH2 | 0x104c |
| 178d | JUMP | |
| 178e | JUMPDEST | |
| 178f | POP | |
| 1790 | PUSH1 | 0xff |
| 1792 | PUSH2 | 0x17a6 |
| 1795 | PUSH2 | 0x02a4 |
| 1798 | PUSH2 | 0x02c0 |
| 179b | DUP9 | |
| 179c | MUL | |
| 179d | PUSH1 | 0x04 |
| 179f | CALLDATALOAD | |
| 17a0 | ADD | |
| 17a1 | ADD | |
| 17a2 | PUSH2 | 0x6bed |
| 17a5 | JUMP | |
| 17a6 | JUMPDEST | |
| 17a7 | AND | |
| 17a8 | ISZERO | |
| 17a9 | PUSH2 | 0x1046 |
| 17ac | JUMP | |
| 17ad | JUMPDEST | |
| 17ae | PUSH1 | 0xff |
| 17b0 | DUP6 | |
| 17b1 | PUSH1 | 0x24 |
| 17b3 | PUSH2 | 0x02c0 |
| 17b6 | PUSH2 | 0x17c8 |
| 17b9 | PUSH2 | 0x02a4 |
| 17bc | DUP3 | |
| 17bd | DUP6 | |
| 17be | MUL | |
| 17bf | PUSH1 | 0x04 |
| 17c1 | CALLDATALOAD | |
| 17c2 | ADD | |
| 17c3 | ADD | |
| 17c4 | PUSH2 | 0x6bed |
| 17c7 | JUMP | |
| 17c8 | JUMPDEST | |
| 17c9 | SWAP3 | |
| 17ca | PUSH4 | 0x1a7ce1dd |
| 17cf | PUSH1 | 0xe0 |
| 17d1 | SHL | |
| 17d2 | PUSH2 | 0x0100 |
| 17d5 | MLOAD | |
| 17d6 | MSTORE | |
| 17d7 | MUL | |
| 17d8 | PUSH1 | 0x04 |
| 17da | CALLDATALOAD | |
| 17db | ADD | |
| 17dc | ADD | |
| 17dd | CALLDATALOAD | |
| 17de | PUSH1 | 0x04 |
| 17e0 | MSTORE | |
| 17e1 | AND | |
| 17e2 | PUSH1 | 0x24 |
| 17e4 | MSTORE | |
| 17e5 | PUSH1 | 0x44 |
| 17e7 | PUSH2 | 0x0100 |
| 17ea | MLOAD | |
| 17eb | REVERT | |
| 17ec | JUMPDEST | |
| 17ed | PUSH1 | 0x24 |
| 17ef | PUSH2 | 0x02c0 |
| 17f2 | DUP7 | |
| 17f3 | PUSH4 | 0x3dc2fc11 |
| 17f8 | PUSH1 | 0xe0 |
| 17fa | SHL | |
| 17fb | PUSH2 | 0x0100 |
| 17fe | MLOAD | |
| 17ff | MSTORE | |
| 1800 | MUL | |
| 1801 | PUSH1 | 0x04 |
| 1803 | CALLDATALOAD | |
| 1804 | ADD | |
| 1805 | ADD | |
| 1806 | CALLDATALOAD | |
| 1807 | PUSH1 | 0x04 |
| 1809 | MSTORE | |
| 180a | PUSH1 | 0x24 |
| 180c | PUSH2 | 0x0100 |
| 180f | MLOAD | |
| 1810 | REVERT | |
| 1811 | JUMPDEST | |
| 1812 | POP | |
| 1813 | PUSH4 | 0xffffffff |
| 1818 | PUSH2 | 0x182c |
| 181b | PUSH2 | 0x0144 |
| 181e | PUSH2 | 0x02c0 |
| 1821 | DUP9 | |
| 1822 | MUL | |
| 1823 | PUSH1 | 0x04 |
| 1825 | CALLDATALOAD | |
| 1826 | ADD | |
| 1827 | ADD | |
| 1828 | PUSH2 | 0x6c18 |
| 182b | JUMP | |
| 182c | JUMPDEST | |
| 182d | AND | |
| 182e | ISZERO | |
| 182f | DUP1 | |
| 1830 | ISZERO | |
| 1831 | PUSH2 | 0x187f |
| 1834 | JUMPI | |
| 1835 | JUMPDEST | |
| 1836 | DUP1 | |
| 1837 | ISZERO | |
| 1838 | PUSH2 | 0x1860 |
| 183b | JUMPI | |
| 183c | JUMPDEST | |
| 183d | DUP1 | |
| 183e | PUSH2 | 0x1008 |
| 1841 | JUMPI | |
| 1842 | POP | |
| 1843 | PUSH1 | 0xff |
| 1845 | PUSH2 | 0x1859 |
| 1848 | PUSH2 | 0x0224 |
| 184b | PUSH2 | 0x02c0 |
| 184e | DUP9 | |
| 184f | MUL | |
| 1850 | PUSH1 | 0x04 |
| 1852 | CALLDATALOAD | |
| 1853 | ADD | |
| 1854 | ADD | |
| 1855 | PUSH2 | 0x6bed |
| 1858 | JUMP | |
| 1859 | JUMPDEST | |
| 185a | AND | |
| 185b | ISZERO | |
| 185c | PUSH2 | 0x1008 |
| 185f | JUMP | |
| 1860 | JUMPDEST | |
| 1861 | POP | |
| 1862 | PUSH1 | 0xff |
| 1864 | PUSH2 | 0x1878 |
| 1867 | PUSH2 | 0x01c4 |
| 186a | PUSH2 | 0x02c0 |
| 186d | DUP9 | |
| 186e | MUL | |
| 186f | PUSH1 | 0x04 |
| 1871 | CALLDATALOAD | |
| 1872 | ADD | |
| 1873 | ADD | |
| 1874 | PUSH2 | 0x6bed |
| 1877 | JUMP | |
| 1878 | JUMPDEST | |
| 1879 | AND | |
| 187a | ISZERO | |
| 187b | PUSH2 | 0x183c |
| 187e | JUMP | |
| 187f | JUMPDEST | |
| 1880 | POP | |
| 1881 | PUSH1 | 0xff |
| 1883 | PUSH2 | 0x1897 |
| 1886 | PUSH2 | 0x0104 |
| 1889 | PUSH2 | 0x02c0 |
| 188c | DUP9 | |
| 188d | MUL | |
| 188e | PUSH1 | 0x04 |
| 1890 | CALLDATALOAD | |
| 1891 | ADD | |
| 1892 | ADD | |
| 1893 | PUSH2 | 0x6bed |
| 1896 | JUMP | |
| 1897 | JUMPDEST | |
| 1898 | AND | |
| 1899 | ISZERO | |
| 189a | PUSH2 | 0x1835 |
| 189d | JUMP | |
| 189e | JUMPDEST | |
| 189f | DUP5 | |
| 18a0 | PUSH1 | 0x24 |
| 18a2 | PUSH2 | 0x02c0 |
| 18a5 | PUSH2 | 0x18bd |
| 18a8 | PUSH1 | 0x04 |
| 18aa | CALLDATALOAD | |
| 18ab | DUP3 | |
| 18ac | DUP6 | |
| 18ad | MUL | |
| 18ae | ADD | |
| 18af | PUSH1 | 0x64 |
| 18b1 | DUP2 | |
| 18b2 | ADD | |
| 18b3 | CALLDATALOAD | |
| 18b4 | SWAP1 | |
| 18b5 | PUSH1 | 0x44 |
| 18b7 | ADD | |
| 18b8 | CALLDATALOAD | |
| 18b9 | PUSH2 | 0x72b3 |
| 18bc | JUMP | |
| 18bd | JUMPDEST | |
| 18be | SWAP3 | |
| 18bf | PUSH4 | 0x1a35ac99 |
| 18c4 | PUSH1 | 0xe0 |
| 18c6 | SHL | |
| 18c7 | PUSH2 | 0x0100 |
| 18ca | MLOAD | |
| 18cb | MSTORE | |
| 18cc | MUL | |
| 18cd | PUSH1 | 0x04 |
| 18cf | CALLDATALOAD | |
| 18d0 | ADD | |
| 18d1 | ADD | |
| 18d2 | CALLDATALOAD | |
| 18d3 | PUSH1 | 0x04 |
| 18d5 | MSTORE | |
| 18d6 | PUSH1 | 0x24 |
| 18d8 | MSTORE | |
| 18d9 | PUSH1 | 0x44 |
| 18db | PUSH2 | 0x0100 |
| 18de | MLOAD | |
| 18df | REVERT | |
| 18e0 | JUMPDEST | |
| 18e1 | SWAP3 | |
| 18e2 | SWAP1 | |
| 18e3 | DUP6 | |
| 18e4 | PUSH2 | 0x0100 |
| 18e7 | MLOAD | |
| 18e8 | JUMPDEST | |
| 18e9 | PUSH1 | 0xc0 |
| 18eb | MLOAD | |
| 18ec | DUP2 | |
| 18ed | LT | |
| 18ee | ISZERO | |
| 18ef | PUSH2 | 0x1ec5 |
| 18f2 | JUMPI | |
| 18f3 | PUSH1 | 0x05 |
| 18f5 | DUP2 | |
| 18f6 | SWAP1 | |
| 18f7 | SHL | |
| 18f8 | DUP4 | |
| 18f9 | ADD |