Contract
0x6213244deb6b27c2106e3e15253c2ac3041ab9d1
- Address
- 0x6213244deb6b27c2106e3e15253c2ac3041ab9d1
- Kind
- verified contract FinalAccountLedger
- Balance
- 0 vETH
- Nonce
- 1
- Code
- 25,951 bytes codehash 0x40073f0d94ce80fe211eeb64609c7f60307183585f2ed7c3d0090d2631d97ca3
account tree
- Tree
- 1 · accounts
- Present
- no leaf
- Key
- 0x69ce8bb93294da80d52365ba02dccbe6b2e3d019021250e0f97d9116a7698326
- Live root
- 0x18f30182962d8af79e7ab628ce200be69d28f54119890d737c7e736de62e703c
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
- FinalAccountLedger exact match · immutables masked
- Compiler
- v0.8.33+commit.64118f21
- Optimizer
- enabled · 200 runs
- EVM version
- prague
- Verified
- 2026-09-06T09:34:56.993Z
- Provenance
- preverify-final-chain (forge artifact, bytecode compared against live code)
contracts/finalchain/FinalAccountLedger.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
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalChainTime} from "./FinalChainTime.sol";
import {FinalIdentityRegistry, DOMAIN_IDENTITY_LEAF} from "./FinalIdentityRegistry.sol";
import {FinalPqQuorum} from "./FinalPqQuorum.sol";
import {FinalStateTrees} from "./FinalStateTrees.sol";
/**
* @title FinalAccountLedger
* @notice Who owns a Final Wallet, and what its keys are. On Final Chain.
*
* ## What this replaces
*
* A Redis list. `FinalBackend/src/accounts/journal.js` held every account's
* state as a journal one process wrote under a fencing lease, and everything
* else read a replica of. That journal was self-authenticating — it stored the
* REQUEST and its signatures rather than the actor it resolved to — which was
* the best answer available while the verification had to happen in
* JavaScript. It bounded a compromised PUBLISHER and it did not bound a
* compromised WRITER, and the design said so in as many words.
*
* Here the chain verifies. A request carries the holder's own credential, this
* contract checks it in the SLH-DSA-SHAKE-256s precompile against the
* commitment it already holds, and applies the transition itself. So:
*
* - **`submitRequest` is permissionless.** Whoever pays gas is irrelevant;
* the signature decides. There is no writer to compromise, which is the
* entire point of the move.
* - **The record is public state**, not a replica of a cache. A guardian, a
* reconciler and a publisher read the same storage rather than three copies
* that agree by convention.
* - **Tree 1 is written from here**, so the root every other chain projects
* from is a consequence of the transition rather than a separate
* publication that could describe a different history.
*
* ## The actor is derived, never claimed
*
* A request does not say "I am the recovery key". It presents a credential, and
* which stored commitment that credential matches is what names the actor. So
* there is no path where a caller selects its own authority, and the strongest
* thing a live key can assert is `LIVE_KEY`.
*
* Only the ACCESS slots authorize here. The wallet holds four PQ keys in two
* pairs; each pair has an access key (SLH-DSA-SHAKE-256s) and a transaction key
* (ML-DSA-87). Account-plane changes take the ACCESS class and the transaction
* class is refused outright — splitting the classes buys nothing if a key that
* signs spends can also rotate the credential set.
*
* ## Genesis is attested, everything after it is proven
*
* `openAccount` takes a K-of-N ML-DSA quorum of `ROLE_ACCOUNT_COSIGNER`.
* Issuance happens at a different trust boundary — a certificate this chain
* cannot see — so the fleet attests that an account was issued with these four
* commitments. That is the one place a quorum of ours stands in for evidence,
* and it is bounded: after genesis, no set of our keys can move an account.
*
* ## What is deliberately NOT here
*
* **No admin path.** No owner, no pause, no setter that rewrites an account.
* A ledger whose operator can rewrite an owner is not a record of ownership.
*
* **No delegation to the identity registry.** Service wallets are projected
* into tree 1 by `FinalStateTrees.syncIdentities` and are not accounts here:
* they have no holder to sign for them, and giving them one would make the
* fleet's own credentials movable by the fleet.
*/
contract FinalAccountLedger {
// ------------------------------------------------------------ constants
/// @notice EIP-712 domain salt. Distinct from `FinalRecoveryModule`'s
/// `GuardianFreeze_v01` — that signature freezes ONE chain's copy as a
/// liveness backstop, this one freezes the account globally, and a guardian
/// asked for the local one must not thereby have authorized the global one.
bytes32 public constant DOMAIN_ACCOUNT_STATE_REQUEST =
keccak256("FINAL_ACCOUNT_STATE_REQUEST_v01");
/// @dev `EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)`.
/// No `verifyingContract`: the backend built this domain before any contract
/// existed to name, and changing it now would invalidate every credential a
/// holder has already produced.
bytes32 private constant EIP712_DOMAIN_TYPEHASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)");
bytes32 private constant DOMAIN_NAME = keccak256("FinalChainAccountState");
bytes32 private constant DOMAIN_VERSION = keccak256("01");
bytes32 private constant REQUEST_TYPEHASH = keccak256(
"AccountStateRequest(address wallet,string action,bytes32 payloadHash,uint64 nonce,uint64 expiresAt)"
);
/// @dev What a co-signer's approval authorizes at genesis. Per-action, so an
/// approval to open cannot be replayed as one for any other quorum here.
bytes32 private constant ACTION_OPEN = keccak256("FinalAccountLedger.open.v01");
/// @dev Registrar-quorum action, verified by the registry with this ledger
/// as the verifying contract.
bytes32 public constant ACTION_CONFIGURE = keccak256("FINAL_ACCOUNT_LEDGER_CONFIGURE_v01");
/// @dev Tree 8 and its admission branch — `FinalStateTrees.TREE_IDENTITY` /
/// `BRANCH_MAIN`, pinned by test. Constants rather than two external
/// reads per batch.
uint8 internal constant TREE_IDENTITY_ID = 8;
uint8 internal constant BRANCH_MAIN_ID = 1;
/// @dev Tree 8, branch 2 — the owner → wallets index (`FinalStateTrees.BRANCH_OWNER_INDEX`).
uint8 internal constant BRANCH_OWNER_INDEX_ID = 2;
/// @dev `FinalStateTrees.ownerIndexKeyFor` / `ownerIndexLeafHash`, restated
/// so the write costs two hashes and not two external calls — pinned
/// byte-for-byte against the trees by test.
bytes32 private constant DOMAIN_OWNER_INDEX_KEY = keccak256("FinalStateTrees.key.ownerIndex.v01");
bytes32 private constant DOMAIN_OWNER_INDEX_LEAF = keccak256("FINAL_OWNER_INDEX_LEAF_v01");
/// @dev The restore lane's quorum action (the opener quorum, a separate nonce).
bytes32 private constant ACTION_RESTORE = keccak256("FinalAccountLedger.restore.v01");
/// @dev Closes the restore lane for good — the configuration authority's action.
bytes32 public constant ACTION_SEAL_RESTORE = keccak256("FINAL_ACCOUNT_LEDGER_SEAL_RESTORE_v01");
/// @notice Delay bounds. Zero removes the cancel window entirely, which
/// deletes the only defence against a compromised recovery key; unbounded
/// lets a hostile guardian config strand a legitimate rotation forever.
/// @dev MILLISECONDS. `block.timestamp` on this chain is milliseconds, so a
/// second-denominated delay compared against it is 1000x short — the
/// 24-hour default elapsed in 86 seconds. Written as `hours *
/// MS_PER_SECOND` so the intent stays readable and the unit explicit.
uint64 public constant MIN_DELAY_MS = 1 hours * FinalChainTime.MS_PER_SECOND;
uint64 public constant MAX_DELAY_MS = 30 days * FinalChainTime.MS_PER_SECOND;
uint64 public constant DEFAULT_DELAY_MS = 24 hours * FinalChainTime.MS_PER_SECOND;
/// @notice How many times guardians may cancel one rotation before it
/// proceeds anyway. **Guardians delay a rotation; they do not veto it.** An
/// unbounded cancel makes a captured guardian set a permanent lockout —
/// strictly worse than having no guardians, because the user configured it
/// believing it helped.
uint8 public constant MAX_ROTATION_CANCELS = 2;
/// @notice The longest a request may stay valid.
/// @dev A nonce alone does not bound an unused authorization: a guardian's
/// freeze signature at nonce 5 stays spendable for as long as nothing else
/// freezes, which turns a one-off approval into a standing power held by
/// whoever has the bytes.
uint64 public constant MAX_REQUEST_TTL_MS = 7 days * FinalChainTime.MS_PER_SECOND;
/// @notice The PQ algorithm id the ACCESS class uses: 5, FIPS 205.
/// @dev Matches `FinalPqQuorum.ALG_SLH_DSA_SHAKE_256S` and the backend
/// registry. The transaction class (4, FIPS 204) is refused here on purpose.
uint8 public constant ALG_SLH_DSA_SHAKE_256S = 5;
/// @notice The owner every PQ account carries, on this chain and on every
/// execution chain.
/// @dev Must equal `FinalWalletShared.FINAL_PQ_NATIVE_OWNER`. Declared here
/// rather than imported for the same reason `FinalStateTrees` declares its
/// own `DOMAIN_ACCOUNT_STATE_LEAF`: these contracts deploy only to 20678 /
/// 48359 and pulling in a wallet-side compilation unit would couple two
/// deploy targets that share nothing else. Pinned by a parity test — a
/// mismatch is an owner field no execution chain agrees with, and nothing
/// would point at the cause.
///
/// Unspendable by construction: recovering a signature to a chosen 20-byte
/// value is a ~2^160 search, and the address holds no code, so the ERC-1271
/// branch is unreachable too.
address public constant FINAL_PQ_NATIVE_OWNER = 0x00000000000000000000000000000046494e414c;
// ------------------------------------------------------------- vocabulary
/// @notice The ten transitions, in the order the backend enumerates them.
/// @dev The ORDER is load-bearing twice over: it indexes the per-action
/// nonce, and `_actionName` maps it to the exact string the EIP-712 digest
/// hashes. Inserting one in the middle renumbers every stored nonce.
enum Action {
FREEZE,
UNFREEZE,
INITIATE_ROTATION,
CANCEL_ROTATION,
FINALIZE_ROTATION,
INITIATE_GUARDIAN_CHANGE,
CANCEL_GUARDIAN_CHANGE,
FINALIZE_GUARDIAN_CHANGE,
TRANSFER_OWNER,
/// @dev Appended, and appending is the only safe direction: the ordinal
/// indexes `nonceOf` and is committed to by every request digest, so
/// inserting one renumbers actions users have already signed for.
ENABLE_PQ,
/// @dev Set or replace the account's row for one chain in the tree-1
/// `deployedChains` table — `(chainRef, account)`, the account in that
/// chain's own account space. Holder-set, because only the holder knows
/// what it is on a chain whose accounts are not EVM addresses; this is
/// what a zero settlement beneficiary resolves through.
SET_CHAIN_ACCOUNT
}
/// @notice How many actions there are. Sizes `noncesOf`.
uint8 public constant ACTION_COUNT = 11;
/// @notice Who a verified credential establishes.
enum Actor { NONE, RECOVERY_KEY, LIVE_KEY, GUARDIANS }
// ---------------------------------------------------------------- types
/// @notice One authorization request.
/// @param payload The action's arguments, ABI-encoded exactly as the digest
/// commits to them. Empty for the actions that take none.
struct Request {
address wallet;
Action action;
uint64 nonce;
uint64 expiresAt;
bytes payload;
}
/// @notice The holder's credential. Exactly one field is populated.
/// @param pqBlob `abi.encode(uint8 algorithmId, bytes publicKey, bytes signature)`
/// — the same encoding `FinalBackend/src/pq/credential.js` produces.
/// @param ownerSignature The pre-PQ path: an ECDSA signature from `owner`.
struct Credential {
bytes pqBlob;
bytes ownerSignature;
}
/// @notice One guardian's authorization.
/// @dev Three forms, and which one applies is decided from ledger state
/// alone. A guardian of a PQ wallet is required to be a PQ Final Wallet, so
/// most guardians are CONTRACTS and cannot ECDSA-recover to their own
/// address — on an execution chain that is `SignatureChecker` falling
/// through to ERC-1271, and here it is a lookup, because a guardian that is
/// a Final Wallet is itself an account in this ledger.
///
/// 1. `guardian == 0` — an EOA guardian. Recover, and the recovered
/// address IS the guardian.
/// 2. `guardian`, `pqBlob` — a PQ Final Wallet, signing with its own LIVE
/// access key.
/// 3. `guardian`, `signature` — a pre-PQ Final Wallet, whose owner signs.
struct GuardianAuth {
address guardian;
bytes pqBlob;
bytes signature;
}
/// @notice Everything about one account except its guardian lists.
struct Account {
bool opened;
bytes32 liveAccess;
bytes32 liveTransaction;
bytes32 recoveryAccess;
bytes32 recoveryTransaction;
/// @dev The genesis certificate serial — the raw half of the tree-8
/// admission leaf's preimage. Kept so this record alone can re-derive
/// admission on a future plane (a redeployed ledger re-opens accounts
/// from snapshots of this record, and the derived hash in tree 8 is
/// not invertible). Never mutated: the certificate is the address.
bytes32 serial;
/// @dev Encapsulation commitments, one word per stage. `recoveryKem` is
/// the pre-committed successor `liveKem` rotates into — present for the
/// same reason `recoveryTransaction` is, so a rotation needs no key
/// ARGUMENT and cannot be handed a key nobody vouched for.
bytes32 liveKem;
bytes32 recoveryKem;
/// @dev Which generation of `liveKem` this is. 1 at open, +1 on every
/// promotion.
///
/// **This is what an intent header's `kemKeyVersion` names**, and it is
/// a KEM-rotation counter rather than the account `version` below. The
/// header field is a `uint16` and `version` moves on every transition —
/// a freeze, a guardian change, a dormancy refresh — so pinning the
/// header to it would expire an envelope for reasons that have nothing
/// to do with the key it was sealed to.
///
/// It exists because a forced-path intent sits encrypted for up to 48 h
/// and a rotation inside that window would strand it: the sender sealed
/// to a key the account has since disowned, and the intent then simply
/// never decrypts — no revert, no error, nothing to look at. The version
/// lets the recipient say *which* key this was sealed to, and a reader
/// say whether that key is still current.
///
/// `uint16` is honest here in a way it would not be for `version`: this
/// increments only when the encapsulation key actually rotates.
uint16 kemVersion;
/// @dev Per-chain dormancy verdict, one bit per asset-registry slot.
/// Derived here from `lastActivityAt` and the per-chain threshold; the
/// execution chains hold neither and read only the bit. The chains the
/// account exists on — and as what — are the `(chainRef, account)` table
/// in `_chainAccounts`, which used to be a bitmask beside this one.
uint32 dormantChains;
/// @dev Newest evidence of the holder acting, on ANY chain. Monotone
/// FORWARD and permissionless: anyone may push it later, nobody may push
/// it back. Understating liveness is the only dangerous direction — it
/// manufactures dormancy — and this rule puts that out of reach, since
/// the holder can always stamp it here directly. Overstating merely
/// delays a legitimate recovery, which fails safe.
uint64 lastActivityAt;
address owner;
bool pqEnabled;
bool frozen;
uint64 version;
uint64 delayMs;
uint16 threshold;
uint16 cancelThreshold;
bool rotationPending;
uint8 rotationCancels;
uint64 rotationInitiatedAt;
bytes32 pendingRecoveryAccess;
bytes32 pendingRecoveryTransaction;
/// @dev Staged alongside the other two. A rotation that promoted the
/// signing pair without the encapsulation key would leave the account
/// with new keys and an old KEM — and nothing would fail loudly:
/// intents addressed to it would simply never decrypt.
bytes32 pendingRecoveryKem;
bool guardianChangePending;
uint16 pendingThreshold;
uint16 pendingCancelThreshold;
uint64 guardianChangeInitiatedAt;
}
/// @notice What `openAccount` installs. Genesis, and the only shape a
/// quorum of ours may ever write.
struct Genesis {
address wallet;
bytes32 liveAccess;
bytes32 liveTransaction;
bytes32 recoveryAccess;
bytes32 recoveryTransaction;
/// @dev Both encapsulation commitments, per stage. An account opened
/// without them could receive nothing and would have no successor to
/// rotate into, so they are required here rather than settable later.
bytes32 liveKem;
bytes32 recoveryKem;
/// @dev The certificate serial (`16 B entropy ‖ 16 B counter`). With
/// the six commitments above it completes the identity-leaf preimage —
/// `keccak256(DOMAIN_IDENTITY_LEAF ‖ serial ‖ keysHash)`, the exact
/// `certHash` inside the wallet's CREATE2 derivation — which this
/// ledger writes into the IDENTITY tree (tree 8) at open. Without it
/// the account exists on Final Chain but no execution chain would ever
/// admit its creation.
bytes32 serial;
address owner;
bool pqEnabled;
/// @dev The chains this account exists on at genesis, and its account on
/// each (`AccountStateLeaf.deployedChains`). Granted here because an
/// account with no row could be created nowhere; the holder extends the
/// table afterwards with `SET_CHAIN_ACCOUNT`. Empty is refused for the
/// same reason a zero KEM version is — it means the opener never
/// decided, not that it decided "none".
FinalStateTrees.ChainAccount[] deployedChains;
uint64 delayMs;
address[] guardians;
uint16 threshold;
uint16 cancelThreshold;
}
// -------------------------------------------------------------- storage
/// @notice Where every co-signer, key and role is resolved. Immutable, so
/// the genesis quorum can never be pointed at a registry from calldata.
FinalIdentityRegistry public immutable registry;
/// @notice Tree 1. Written on every accepted transition.
FinalStateTrees public immutable trees;
/// @notice The role that may attest a genesis.
uint256 public openerRole;
/// @notice How many attestations one genesis needs. Zero refuses every open.
uint256 public openThreshold;
/// @notice Bound into every genesis digest. One per `openAccount` call.
uint64 public openNonce;
mapping(address => Account) private _accounts;
mapping(address => address[]) private _guardians;
mapping(address => address[]) private _pendingGuardians;
/// @notice Next authorization nonce, per account and per action.
/// @dev Per ACTION, not per account. The record `version` advances on every
/// accepted transition, so binding an authorization to it would let anyone
/// who can move the state cheaply invalidate everyone else's in-flight
/// signatures — and `TRANSFER_OWNER` sits with the LIVE key, so a thief
/// holding it could race transfers to keep guardians' freeze signatures
/// perpetually stale, griefing away the exact defence aimed at them.
mapping(address => mapping(uint8 => uint64)) public nonceOf;
/// @notice Per-chain inactivity threshold, in seconds, by registry slot.
/// @dev Held HERE and nowhere else. The execution chains carry no threshold
/// at all, which is the point: five chains holding five copies of one
/// decision is five chances for them to disagree about when an account is
/// abandoned. Zero means the chain does not accrue dormancy.
/// @dev `uint64`, and the widening is required rather than tidy: the
/// two-year default is 63,072,000,000 ms, which does not fit `uint32`
/// at all. In seconds it did, which is why it was one.
mapping(uint8 => uint64) public inactivityThresholdOf;
/// @notice Chains an account is known to exist on, as OBSERVED by a relayer
/// (`WalletCreated` seen on that chain). Off-leaf, deliberately: this is the
/// fan-out's target set, not an authorization, and putting it in the leaf
/// would bump `version` on every new deployment.
mapping(address => bytes32[]) private _deployments;
mapping(address => mapping(bytes32 => bool)) private _hasDeployment;
/// @notice The holder's `(chainRef, account)` table — the leaf's
/// `deployedChains`. Granted at genesis, extended by `SET_CHAIN_ACCOUNT`.
/// Rows are replaced, never removed: removing one strands assets at an
/// account that can no longer be resolved.
mapping(address => FinalStateTrees.ChainAccount[]) private _chainAccounts;
/// @notice Every opened account, in the order it was opened.
address[] private _wallets;
/// @dev The owner → wallets index behind tree 8's branch 2: every account
/// an owner holds, in the order they were opened or transferred in.
/// The tree commits to this array (`FinalStateTrees.ownerIndexLeafHash`);
/// `walletsByOwner` is the readable half a reader asks first.
mapping(address owner => address[]) private _walletsByOwner;
/// @dev Position + 1 of a wallet in its owner's array; 0 = not indexed.
mapping(address wallet => uint256) private _ownerSlotPlusOne;
/// @notice The restore lane's replay counter (`restoreAccounts`).
uint64 public restoreNonce;
/// @notice True once `sealRestore` has run: the lane that re-creates
/// registrations after a redeploy is closed for the life of this
/// ledger, and every account from then on enters through `openAccount`.
bool public restoreSealed;
// --------------------------------------------------------------- events
event AccountOpened(address indexed wallet, address indexed owner, bool pqEnabled);
event AccountRestored(address indexed wallet, address indexed owner, uint64 version);
event RestoreSealed();
event RequestApplied(address indexed wallet, Action indexed action, Actor actor, uint64 version);
event DeploymentObserved(address indexed wallet, bytes32 indexed chainRef);
/// @notice A row of the account's `deployedChains` table was set or replaced.
event ChainAccountSet(address indexed wallet, bytes32 indexed chainRef, bytes32 account);
event LedgerConfigured(uint256 openerRole, uint256 openThreshold);
// --------------------------------------------------------------- errors
error NotAuthorized(address caller);
error UnknownAccount(address wallet);
event ActivityRecorded(address indexed wallet, uint64 at);
event DormancyRefreshed(address indexed wallet, uint32 dormantChains, uint64 version);
error AccountAlreadyOpen(address wallet);
/// @notice `restoreAccounts` after `sealRestore`.
error RestoreIsSealed();
/// @notice A restored record that is not an opened account, or names the zero wallet.
error InvalidRestore(address wallet);
/// @notice Genesis carried no chain row — the account could exist on no
/// chain at all, which is an opener that never decided, not a decision.
error NoChainGranted(address wallet);
/// @notice A genesis with no certificate serial. The admission leaf needs
/// it, and zero is the shape of an opener that never resolved it.
error ZeroSerial(address wallet);
/// @notice A `deployedChains` row names the zero chain or the zero account.
error InvalidChainAccount(bytes32 chainRef, bytes32 account);
error LedgerNotConfigured();
error ThresholdUnreachable(uint256 live, uint256 required);
error NonceMismatch(uint64 expected, uint64 supplied);
error RequestExpired(uint64 expiresAt, uint256 nowSeconds);
error ExpiryTooFar(uint64 span, uint64 cap);
error NoCredential();
error CredentialNotPermitted(string why);
error AmbiguousCredential();
error KeyCommitmentMismatch();
error SignatureInvalid();
error WrongAlgorithmForSlot(uint8 supplied);
error MalformedBlob();
error NotAGuardian(address who);
error DuplicateGuardian(address who);
error InvalidTransition(string why);
error InvalidDelay(uint64 delayMs);
error InvalidGuardianSet(string why);
error DelayNotElapsed(uint64 readyAt, uint256 nowSeconds);
// ---------------------------------------------------------- constructor
/**
* @dev The precompile probe is the point of having a constructor at all: a
* ledger deployed where SLH-DSA cannot be verified would accept no
* credential it was ever given, and the first symptom would be an account
* plane that silently refuses every holder.
*/
constructor(FinalIdentityRegistry registry_, FinalStateTrees trees_) {
FinalChainPrecompiles.assertAvailable();
registry = registry_;
trees = trees_;
}
/**
* @notice Set which role may attest a genesis, and how many attestations.
* @dev The registry's bootstrap admin alone while its window is open, the
* sealed `ROLE_REGISTRAR` quorum afterwards — the same window and quorum
* the registry and the trees use. `approvals` is empty during bootstrap.
* Re-callable, because a co-signer set that grows or shrinks has to be able
* to move its threshold.
*/
function configure(
uint256 role,
uint256 k,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
if (registry.bootstrapSealed() || msg.sender != registry.bootstrapAdmin()) {
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);
}
openerRole = role;
openThreshold = k;
emit LedgerConfigured(role, k);
}
// ---------------------------------------------------------------- opens
/**
* @notice Register accounts at genesis, under a PQ quorum.
*
* @dev The genesis guardian set lands immediately, and that is the one
* exception to the delay rule: there is no outgoing set to cancel it, and a
* delay here would protect nobody while leaving a fresh account with no
* guardians for its first day — which is when it is least able to defend
* itself.
*/
function openAccount(
Genesis[] calldata batch,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
uint256 k = openThreshold;
if (k == 0) revert LedgerNotConfigured();
uint64 n = openNonce;
FinalPqQuorum.require_(
registry,
approvals,
FinalPqQuorum.digest(address(this), ACTION_OPEN, anchorBlock, keccak256(abi.encode(n, batch))),
openerRole,
k,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
false
);
openNonce = n + 1;
FinalStateTrees.AccountStateLeaf[] memory leaves =
new FinalStateTrees.AccountStateLeaf[](batch.length);
bytes32[] memory identityKeys = new bytes32[](batch.length);
bytes32[] memory identityLeaves = new bytes32[](batch.length);
for (uint256 i = 0; i < batch.length; i++) {
leaves[i] = _open(batch[i]);
identityKeys[i] = trees.identityKeyFor(batch[i].wallet);
identityLeaves[i] = _identityLeafOf(batch[i]);
}
trees.setAccountStatesAsWriter(leaves);
// The admission half, same-tx: without its tree-8 leaf the account
// exists here and is creatable nowhere. Write-once by construction —
// the certificate IS the address, so no later mutation moves it.
trees.setLeavesAsWriter(TREE_IDENTITY_ID, BRANCH_MAIN_ID, identityKeys, identityLeaves);
// The owner index, same-tx: one leaf per owner touched by the batch.
address[] memory owners = new address[](batch.length);
for (uint256 i = 0; i < batch.length; i++) owners[i] = batch[i].owner;
_writeOwnerIndex(owners);
}
// -------------------------------------------------------------- restore
/// @notice Storage words one `Account` occupies (28 packed fields → 15
/// slots). Pinned by test: a field added to the struct that does
/// not fit the last slot is a constant bump here or a truncated
/// restore.
uint256 public constant ACCOUNT_WORDS = 15;
/**
* @notice One account as the redeploy tooling carries it: the record's
* storage words verbatim (`accountWords`), the lists beside it, and
* its tree-8 admission leaf — what `exportLedgerAccounts.cjs` writes
* before a redeploy and `restoreAccounts` replays after it.
* @dev Words rather than a decoded struct, deliberately: a calldata
* `Account` copied field by field is ~3 KB of bytecode this ledger does
* not have under EIP-170, and the words ARE the record — same source,
* same layout, byte-exact. `identityLeaf` is carried verbatim rather
* than re-derived: the admission leaf commits to the GENESIS
* certificate keys, and a rotated account's live keys are not those.
* `nonces` are carried because the request digest binds the chain and
* not this contract's address — a reset would replay every request the
* holder ever signed.
*/
struct Restored {
address wallet;
bytes32[ACCOUNT_WORDS] words;
address[] guardians;
address[] pendingGuardians;
uint64[ACTION_COUNT] nonces;
bytes32[] deployments;
FinalStateTrees.ChainAccount[] chainAccounts;
bytes32 identityLeaf;
}
/// @notice The raw storage words of `wallet`'s record — the export's input.
function accountWords(address wallet) external view returns (bytes32[ACCOUNT_WORDS] memory words) {
if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
Account storage a = _accounts[wallet];
uint256 base;
assembly { base := a.slot }
for (uint256 i = 0; i < ACCOUNT_WORDS; i++) {
bytes32 w;
assembly { w := sload(add(base, i)) }
words[i] = w;
}
}
/**
* @notice Re-create one registration exported from a previous ledger, verbatim.
*
* @dev Ruled 2026-09-03/04: every account registration is saved before a
* redeploy of the state plane and restored after it, and the redeploy is
* NO-WIPE — same chain, same identities, new ledger. `openAccount` cannot
* do this: it recreates a GENESIS (version 1, no rotation, no guardian
* change, zero nonces), so a rotated account restored through it would
* honour keys its holder already retired. This writes the record as it
* was — keys, versions, nonces, pending changes, chain accounts,
* deployments, dormancy — and the tree leaves the plane derives from it:
* tree 1 from the record (`_leafOf`), tree 8 branch 1 verbatim, tree 8
* branch 2 (the owner index) rebuilt.
*
* Same authority as an open (the opener quorum, its own nonce — one round
* per record, so a refused record names itself), only for wallets this
* ledger does not know, and only until `sealRestore`: the
* lane exists for the restore step of a redeploy and for nothing after.
* The restore tool verifies every record against the export before the
* fleet is pointed here; the trees' roots are new by construction (the
* shape may change across a redeploy) and are anchored at a higher epoch.
*/
function restoreAccount(
Restored calldata r,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
if (restoreSealed) revert RestoreIsSealed();
uint256 k = openThreshold;
if (k == 0) revert LedgerNotConfigured();
uint64 n = restoreNonce;
FinalPqQuorum.require_(
registry,
approvals,
FinalPqQuorum.digest(address(this), ACTION_RESTORE, anchorBlock, keccak256(abi.encode(n, r))),
openerRole,
k,
FinalPqQuorum.ALG_ML_DSA_87,
anchorBlock,
false
);
restoreNonce = n + 1;
address wallet = r.wallet;
if (wallet == address(0)) revert InvalidRestore(wallet);
Account storage a = _accounts[wallet];
if (a.opened) revert AccountAlreadyOpen(wallet);
uint256 base;
assembly { base := a.slot }
for (uint256 j = 0; j < ACCOUNT_WORDS; j++) {
bytes32 w = r.words[j];
assembly { sstore(add(base, j), w) }
}
if (!a.opened || a.serial == bytes32(0) || a.owner == address(0)) revert InvalidRestore(wallet);
_guardians[wallet] = r.guardians;
_pendingGuardians[wallet] = r.pendingGuardians;
for (uint8 j = 0; j < ACTION_COUNT; j++) nonceOf[wallet][j] = r.nonces[j];
for (uint256 j = 0; j < r.deployments.length; j++) {
bytes32 ref = r.deployments[j];
if (_hasDeployment[wallet][ref]) continue;
_hasDeployment[wallet][ref] = true;
_deployments[wallet].push(ref);
}
for (uint256 j = 0; j < r.chainAccounts.length; j++) {
_setChainAccount(wallet, r.chainAccounts[j].chainRef, r.chainAccounts[j].account);
}
_wallets.push(wallet);
_indexOwner(wallet, a.owner);
emit AccountRestored(wallet, a.owner, a.version);
FinalStateTrees.AccountStateLeaf[] memory one = new FinalStateTrees.AccountStateLeaf[](1);
one[0] = _leafOf(wallet);
trees.setAccountStatesAsWriter(one);
bytes32[] memory keys = new bytes32[](1);
bytes32[] memory leaves = new bytes32[](1);
keys[0] = trees.identityKeyFor(wallet);
leaves[0] = r.identityLeaf;
trees.setLeavesAsWriter(TREE_IDENTITY_ID, BRANCH_MAIN_ID, keys, leaves);
address[] memory owners = new address[](1);
owners[0] = a.owner;
_writeOwnerIndex(owners);
}
/// @notice Close the restore lane for good. The configuration authority's
/// call, run once the restored roots have been verified.
function sealRestore(uint64 anchorBlock, FinalPqQuorum.Approval[] calldata approvals) external {
if (registry.bootstrapSealed() || msg.sender != registry.bootstrapAdmin()) {
registry.requireRegistrarQuorum(
ACTION_SEAL_RESTORE, keccak256(abi.encode(address(this))), anchorBlock, approvals
);
}
restoreSealed = true;
emit RestoreSealed();
}
// ---------------------------------------------------------- owner index
/// @notice Every account `owner` holds, in index order — the readable half
/// of tree 8's branch 2, whose leaf commits to exactly this array.
function walletsByOwner(address owner) external view returns (address[] memory) {
return _walletsByOwner[owner];
}
/// @dev Add `wallet` to `owner`'s array (position remembered for removal).
function _indexOwner(address wallet, address owner) private {
address[] storage list = _walletsByOwner[owner];
list.push(wallet);
_ownerSlotPlusOne[wallet] = list.length;
}
/// @dev Remove `wallet` from `owner`'s array: swap the last in, pop. Order
/// within an owner's array is not a promise — the leaf is recomputed
/// over the array as it stands.
function _unindexOwner(address wallet, address owner) private {
uint256 pos = _ownerSlotPlusOne[wallet];
if (pos == 0) return;
address[] storage list = _walletsByOwner[owner];
uint256 last = list.length - 1;
if (pos - 1 != last) {
address moved = list[last];
list[pos - 1] = moved;
_ownerSlotPlusOne[moved] = pos;
}
list.pop();
_ownerSlotPlusOne[wallet] = 0;
}
/// @dev Write the branch-2 leaf of every owner in `owners`. A repeated
/// owner is the same leaf written twice — cheaper than a dedupe here.
function _writeOwnerIndex(address[] memory owners) private {
bytes32[] memory keys = new bytes32[](owners.length);
bytes32[] memory hashes = new bytes32[](owners.length);
for (uint256 i = 0; i < owners.length; i++) {
keys[i] = keccak256(abi.encode(DOMAIN_OWNER_INDEX_KEY, owners[i]));
hashes[i] = keccak256(abi.encode(DOMAIN_OWNER_INDEX_LEAF, owners[i], _walletsByOwner[owners[i]]));
}
trees.setLeavesAsWriter(TREE_IDENTITY_ID, BRANCH_OWNER_INDEX_ID, keys, hashes);
}
/// @dev The identity leaf the execution chains' gateways verify at
/// creation: `keccak256(DOMAIN_IDENTITY_LEAF ‖ serial ‖ keysHash)` with
/// `keysHash` the issuer's six-commitment fold — byte-identical to
/// `IdentityRootModule.identityLeafHash(serial, keysHash)` over the same
/// commitments, and to what `FinalIdentityRegistry.identityTreeLeafOf`
/// derives for a service.
function _identityLeafOf(Genesis calldata g) private pure returns (bytes32) {
bytes32 keysHash = keccak256(
abi.encodePacked(
g.liveAccess, g.liveTransaction, g.recoveryAccess, g.recoveryTransaction, g.liveKem, g.recoveryKem
)
);
return keccak256(abi.encodePacked(DOMAIN_IDENTITY_LEAF, g.serial, keysHash));
}
function _open(Genesis calldata g) private returns (FinalStateTrees.AccountStateLeaf memory) {
Account storage a = _accounts[g.wallet];
if (a.opened) revert AccountAlreadyOpen(g.wallet);
uint64 delay = g.delayMs == 0 ? DEFAULT_DELAY_MS : g.delayMs;
if (delay < MIN_DELAY_MS || delay > MAX_DELAY_MS) revert InvalidDelay(delay);
if (g.deployedChains.length == 0) revert NoChainGranted(g.wallet);
// A zero serial is an opener that never decided, not one that decided
// "none" — and the admission leaf it derives would name a certificate
// that cannot exist.
if (g.serial == bytes32(0)) revert ZeroSerial(g.wallet);
uint16 cancelThreshold = g.cancelThreshold == 0
? _cancelThresholdFor(uint16(g.guardians.length), g.threshold)
: g.cancelThreshold;
_assertGuardianSet(g.wallet, g.guardians, g.threshold, cancelThreshold);
a.opened = true;
a.liveAccess = g.liveAccess;
a.liveTransaction = g.liveTransaction;
a.recoveryAccess = g.recoveryAccess;
a.recoveryTransaction = g.recoveryTransaction;
a.serial = g.serial;
a.liveKem = g.liveKem;
a.recoveryKem = g.recoveryKem;
// Generation ONE, not zero. Zero has to stay unreachable so a reader can
// tell "this account has no KEM key" from "this is its first" — and an
// envelope header carrying zero is then a sealer that never resolved
// the version rather than one that resolved it to the genesis key.
a.kemVersion = 1;
a.owner = g.owner;
a.pqEnabled = g.pqEnabled;
for (uint256 i = 0; i < g.deployedChains.length; i++) {
_setChainAccount(g.wallet, g.deployedChains[i].chainRef, g.deployedChains[i].account);
}
a.version = 1;
a.delayMs = delay;
a.threshold = g.threshold;
a.cancelThreshold = cancelThreshold;
_guardians[g.wallet] = g.guardians;
_wallets.push(g.wallet);
_indexOwner(g.wallet, g.owner);
emit AccountOpened(g.wallet, g.owner, g.pqEnabled);
return _leafOf(g.wallet);
}
// ------------------------------------------------------------- requests
/**
* @notice Authorize and apply one request.
*
* @dev **Permissionless.** Whoever submits pays gas and carries no
* authority: the credential decides, and it is verified here rather than by
* a process that then tells everyone what it concluded. That is the whole
* difference between this and the journal it replaces.
*
* Verify, apply, publish — in that order, in one transaction. There is no
* window in which the record and the tree disagree, which is what the
* journal's trial-then-append-then-install dance existed to approximate.
*/
/// @notice Record evidence that this account's holder is alive.
///
/// @dev **Permissionless and monotone FORWARD.** Anyone may push the stamp
/// later; nobody may push it back. That asymmetry is the whole design:
/// understating liveness is the only dangerous direction, because it
/// manufactures dormancy against a holder who is still there — and this
/// rule puts that out of reach of anyone, including us, since the holder can
/// always stamp it here directly. Overstating merely delays a legitimate
/// recovery, which fails safe.
///
/// So there is nothing to authorize and no one to trust: a hostile stamper
/// can only make an account look MORE alive, and a lazy one is corrected by
/// the next party who cares.
///
/// A future timestamp is refused. Otherwise one call could push an account
/// permanently out of dormancy, which is the same seizure-proofing failure
/// in the opposite direction.
function recordActivity(address wallet) external {
Account storage a = _accounts[wallet];
if (!a.opened) revert UnknownAccount(wallet);
if (a.lastActivityAt >= FinalChainTime.nowMs()) return;
a.lastActivityAt = FinalChainTime.nowMs();
emit ActivityRecorded(wallet, a.lastActivityAt);
}
/// @notice Recompute which chains consider this account dormant, and
/// publish the verdict if it changed.
///
/// @dev **The leaf carries the VERDICT, not the clock.** `lastActivityAt`
/// moves whenever the holder acts anywhere; putting it in the leaf would
/// bump the account's version on every transaction, move tree 1 every time,
/// and age every outstanding proof. The bitmap moves only when a chain
/// crosses its threshold — the same rarity as a freeze — so tree 1 keeps the
/// cadence it was designed for.
///
/// Permissionless for the same reason as `recordActivity`: it derives
/// entirely from state this contract already holds, so the caller chooses
/// nothing. Publishing only on a CHANGE is what keeps a caller from
/// rewriting tree 1 at will.
/// @param slots Registry slots to evaluate. Explicit rather than a sweep,
/// because the set of chains is not this contract's to enumerate.
function refreshDormancy(address wallet, uint8[] calldata slots) external {
Account storage a = _accounts[wallet];
if (!a.opened) revert UnknownAccount(wallet);
uint32 next = a.dormantChains;
for (uint256 i = 0; i < slots.length; i++) {
uint8 slot = slots[i];
uint64 threshold = inactivityThresholdOf[slot];
uint32 bit = uint32(1) << slot;
// A zero threshold means the chain does not accrue dormancy at all,
// and clears any bit already set — otherwise disabling the policy
// would leave accounts stranded dormant with no way back.
bool dormant = threshold != 0
&& FinalChainTime.nowMs() >= uint256(a.lastActivityAt) + threshold;
next = dormant ? (next | bit) : (next & ~bit);
}
if (next == a.dormantChains) return;
a.dormantChains = next;
a.version += 1;
FinalStateTrees.AccountStateLeaf[] memory one = new FinalStateTrees.AccountStateLeaf[](1);
one[0] = _leafOf(wallet);
trees.setAccountStatesAsWriter(one);
emit DormancyRefreshed(wallet, next, a.version);
}
function submitRequest(
Request calldata request,
Credential calldata credential,
GuardianAuth[] calldata guardianAuths
) external returns (uint64 version) {
Account storage a = _accounts[request.wallet];
if (!a.opened) revert UnknownAccount(request.wallet);
// Nonce first: it is the cheapest check and the one that makes a
// replayed request indistinguishable from a stale one to everything
// below.
uint64 expected = nonceOf[request.wallet][uint8(request.action)];
if (request.nonce != expected) revert NonceMismatch(expected, request.nonce);
if (request.expiresAt <= FinalChainTime.nowMs()) revert RequestExpired(request.expiresAt, FinalChainTime.nowMs());
uint64 span = request.expiresAt - FinalChainTime.nowMs();
if (span > MAX_REQUEST_TTL_MS) revert ExpiryTooFar(span, MAX_REQUEST_TTL_MS);
bytes32 digest = requestDigest(request);
(Actor actor, uint256 guardianCount) = _establishActor(a, request, credential, guardianAuths, digest);
_apply(a, request, actor, guardianCount);
a.version += 1;
// Only on acceptance. A refused transition must leave the authorization
// spendable — nothing happened, and burning the nonce would mean a
// mis-ordered request costs the holder a trip back to their cold key.
nonceOf[request.wallet][uint8(request.action)] = expected + 1;
FinalStateTrees.AccountStateLeaf[] memory one = new FinalStateTrees.AccountStateLeaf[](1);
one[0] = _leafOf(request.wallet);
trees.setAccountStatesAsWriter(one);
emit RequestApplied(request.wallet, request.action, actor, a.version);
return a.version;
}
/**
* @notice Record that an account exists on `chainRef`.
* @dev A `ROLE_RELAYER` observation of a `WalletCreated` event on some other
* chain, and NOT an authorization: it records where an account exists so the
* fan-out knows where to write. Add-only and idempotent, so a relayer can
* omit a chain and cannot remove one — the reconciler re-derives the set by
* code probe and adds whatever was missed.
*
* `msg.sender` is a type-0x46 sender, derived from the relayer's
* transaction key, so the role is resolved through the registry's sender
* binding rather than looked up on the sender itself.
*/
function observeDeployment(address wallet, bytes32 chainRef) external {
if (!registry.senderHasRole(msg.sender, registry.ROLE_RELAYER())) revert NotAuthorized(msg.sender);
if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
if (_hasDeployment[wallet][chainRef]) return;
_hasDeployment[wallet][chainRef] = true;
_deployments[wallet].push(chainRef);
emit DeploymentObserved(wallet, chainRef);
}
// ------------------------------------------------------------- the digest
/// @notice The EIP-712 digest a request is authorized under.
/// @dev Pure and public, so a holder's client, a co-signer and this contract
/// derive one value. `payloadHash` is `keccak256` of the ABI-encoded
/// arguments exactly as the caller supplied them — the encoding is what
/// stops a signature being moved onto different arguments, so it is bound
/// byte for byte rather than re-derived from decoded fields.
function requestDigest(Request calldata request) public view returns (bytes32) {
bytes32 structHash = keccak256(
abi.encode(
REQUEST_TYPEHASH,
request.wallet,
keccak256(bytes(_actionName(request.action))),
keccak256(request.payload),
request.nonce,
request.expiresAt
)
);
return keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash));
}
/// @notice The EIP-712 domain separator for this chain.
function domainSeparator() public view returns (bytes32) {
return keccak256(
abi.encode(
EIP712_DOMAIN_TYPEHASH,
DOMAIN_NAME,
DOMAIN_VERSION,
block.chainid,
DOMAIN_ACCOUNT_STATE_REQUEST
)
);
}
/// @dev The canonical action string the digest hashes. Restated from
/// `accountState.js:ACTION` and pinned against it by the parity suite — a
/// mismatch here is a signature no client can produce, with nothing naming
/// the cause.
function _actionName(Action action) private pure returns (string memory) {
if (action == Action.FREEZE) return "freeze";
if (action == Action.UNFREEZE) return "unfreeze";
if (action == Action.INITIATE_ROTATION) return "initiate-rotation";
if (action == Action.CANCEL_ROTATION) return "cancel-rotation";
if (action == Action.FINALIZE_ROTATION) return "finalize-rotation";
if (action == Action.INITIATE_GUARDIAN_CHANGE) return "initiate-guardian-change";
if (action == Action.CANCEL_GUARDIAN_CHANGE) return "cancel-guardian-change";
if (action == Action.FINALIZE_GUARDIAN_CHANGE) return "finalize-guardian-change";
if (action == Action.TRANSFER_OWNER) return "transfer-owner";
if (action == Action.ENABLE_PQ) return "enable-pq";
return "set-chain-account";
}
// ------------------------------------------------------------- the actor
/// @dev Which credential each action is considered under. An early refusal
/// and a statement of intent; the transition below is what actually
/// enforces authority.
function _expectedActor(Action action) private pure returns (Actor) {
if (
action == Action.FREEZE || action == Action.CANCEL_ROTATION
|| action == Action.CANCEL_GUARDIAN_CHANGE
) return Actor.GUARDIANS;
if (action == Action.TRANSFER_OWNER || action == Action.ENABLE_PQ || action == Action.SET_CHAIN_ACCOUNT) {
return Actor.LIVE_KEY;
}
if (action == Action.FINALIZE_ROTATION || action == Action.FINALIZE_GUARDIAN_CHANGE) {
// Permissionless by design — the delay has run, the outcome is
// already determined, and requiring the initiator to come back
// would let an attacker win by keeping them away from their cold
// key.
return Actor.NONE;
}
return Actor.RECOVERY_KEY;
}
function _establishActor(
Account storage a,
Request calldata request,
Credential calldata credential,
GuardianAuth[] calldata guardianAuths,
bytes32 digest
) private view returns (Actor, uint256) {
Actor wanted = _expectedActor(request.action);
if (wanted == Actor.NONE) return (Actor.NONE, 0);
if (wanted == Actor.GUARDIANS) {
if (credential.pqBlob.length != 0 || credential.ownerSignature.length != 0) {
// The ACCOUNT's own key, offered for a guardian action. Refused
// rather than ignored: freeze and cancel exist BECAUSE a key may
// be the compromised party, and accepting one here would hand
// the attacker both halves.
revert CredentialNotPermitted("guardian action");
}
return (Actor.GUARDIANS, _countGuardians(a, request.wallet, digest, guardianAuths));
}
if (guardianAuths.length != 0) revert CredentialNotPermitted("not a guardian action");
return (_resolveKeyActor(a, credential, digest), 0);
}
/**
* @dev Establish which key signed, by matching its commitment.
*
* The order of the two comparisons does not matter and must not: an account
* whose live and recovery access commitments are EQUAL has no second
* credential at all, and resolving that to whichever branch ran first would
* silently grant the live key the recovery key's powers.
*/
function _resolveKeyActor(Account storage a, Credential calldata credential, bytes32 digest)
private
view
returns (Actor)
{
if (credential.pqBlob.length == 0 && credential.ownerSignature.length == 0) revert NoCredential();
if (credential.ownerSignature.length != 0) {
// The pre-PQ live credential. Refused once the account has migrated:
// a migrated account's ECDSA owner is a settlement destination, not
// an authority, and leaving this path open would keep the weaker
// credential live forever behind the stronger one.
if (a.pqEnabled) revert CredentialNotPermitted("account is PQ");
if (_recover(digest, credential.ownerSignature) != a.owner) revert SignatureInvalid();
return Actor.LIVE_KEY;
}
(bytes memory publicKey, bytes memory signature) = _decodeAccessBlob(credential.pqBlob);
bytes32 presented = keccak256(publicKey);
if (a.liveAccess == a.recoveryAccess) revert AmbiguousCredential();
Actor actor;
if (presented == a.recoveryAccess) actor = Actor.RECOVERY_KEY;
else if (presented == a.liveAccess && a.pqEnabled) actor = Actor.LIVE_KEY;
else revert KeyCommitmentMismatch();
if (!FinalChainPrecompiles.verifySlhDsa(publicKey, abi.encodePacked(digest), signature)) {
revert SignatureInvalid();
}
return actor;
}
/**
* @dev Count distinct guardian authorizations over `digest`.
*
* Distinctness is checked on the RESOLVED guardian, never on anything the
* request labels itself with, so one guardian cannot reach a threshold by
* submitting N times — the failure that turns an M-of-N into a 1-of-N
* without changing a single visible parameter.
*/
function _countGuardians(
Account storage a,
address wallet,
bytes32 digest,
GuardianAuth[] calldata auths
) private view returns (uint256 count) {
address[] storage members = _guardians[wallet];
address[] memory seen = new address[](auths.length);
for (uint256 i = 0; i < auths.length; i++) {
address who = _resolveGuardian(auths[i], digest);
bool isMember;
for (uint256 m = 0; m < members.length; m++) {
if (members[m] == who) { isMember = true; break; }
}
if (!isMember) revert NotAGuardian(who);
for (uint256 s = 0; s < count; s++) {
if (seen[s] == who) revert DuplicateGuardian(who);
}
seen[count] = who;
count += 1;
}
a; // silence the unused-parameter warning without widening the signature
}
function _resolveGuardian(GuardianAuth calldata auth, bytes32 digest) private view returns (address) {
if (auth.guardian == address(0)) {
if (auth.signature.length == 0) revert NoCredential();
return _recover(digest, auth.signature);
}
Account storage g = _accounts[auth.guardian];
// Not an account here. Only the EOA form can speak for it, and that form
// names nobody — so an explicit guardian with no record is a claim this
// cannot check rather than one it should take on faith.
if (!g.opened) revert UnknownAccount(auth.guardian);
// A frozen guardian is one whose own live key is under suspicion.
// Letting it cancel someone else's rotation is precisely the move a
// compromised guardian would make.
if (g.frozen) revert CredentialNotPermitted("guardian is frozen");
if (auth.pqBlob.length != 0) {
if (!g.pqEnabled) revert CredentialNotPermitted("guardian is not PQ");
// The LIVE access key, not the recovery one. Acting as a guardian is
// an ordinary action for that account; its recovery pair authorizes
// rotating its own credentials and nothing else.
(bytes memory publicKey, bytes memory signature) = _decodeAccessBlob(auth.pqBlob);
if (keccak256(publicKey) != g.liveAccess) revert KeyCommitmentMismatch();
if (!FinalChainPrecompiles.verifySlhDsa(publicKey, abi.encodePacked(digest), signature)) {
revert SignatureInvalid();
}
return auth.guardian;
}
if (auth.signature.length != 0) {
if (g.pqEnabled) revert CredentialNotPermitted("guardian is PQ");
if (_recover(digest, auth.signature) != g.owner) revert SignatureInvalid();
return auth.guardian;
}
revert NoCredential();
}
/**
* @dev Decode a PQ blob and enforce the slot whitelist.
*
* The ACCESS CLASS, not a named slot. Both access slots carry the same
* algorithm, so naming one of them would work by coincidence. What this
* enforces is that an ML-DSA transaction key — which verifies perfectly well
* under its own algorithm — is still refused: a key that signs spends does
* not get to rotate a credential set, its own or anyone else's.
*/
function _decodeAccessBlob(bytes calldata blob) private pure returns (bytes memory, bytes memory) {
if (blob.length < 96) revert MalformedBlob();
(uint8 algorithmId, bytes memory publicKey, bytes memory signature) =
abi.decode(blob, (uint8, bytes, bytes));
if (algorithmId != ALG_SLH_DSA_SHAKE_256S) revert WrongAlgorithmForSlot(algorithmId);
return (publicKey, signature);
}
/// @dev `ecrecover` with the malleability and zero-address cases closed.
function _recover(bytes32 digest, bytes memory signature) private pure returns (address) {
if (signature.length != 65) revert SignatureInvalid();
bytes32 r;
bytes32 s;
uint8 v;
assembly ("memory-safe") {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
revert SignatureInvalid();
}
address who = ecrecover(digest, v, r, s);
if (who == address(0)) revert SignatureInvalid();
return who;
}
// --------------------------------------------------------- the transitions
function _apply(Account storage a, Request calldata request, Actor actor, uint256 guardianCount)
private
{
Action action = request.action;
if (action == Action.FREEZE) {
_requireGuardians(actor, guardianCount, a.threshold);
if (a.frozen) revert InvalidTransition("already frozen");
a.frozen = true;
} else if (action == Action.UNFREEZE) {
// Guardians deliberately cannot reach this. Freezing stops spending
// and its misuse is denial of service; lifting a freeze un-protects
// an account that may be mid-theft, which is not bounded at all.
_requireRecovery(actor);
if (!a.frozen) revert InvalidTransition("not frozen");
a.frozen = false;
} else if (action == Action.INITIATE_ROTATION) {
_requireRecovery(actor);
if (a.rotationPending) revert InvalidTransition("rotation already pending");
(bytes32 newAccess, bytes32 newTransaction, bytes32 newKem) =
abi.decode(request.payload, (bytes32, bytes32, bytes32));
if (newAccess == bytes32(0) || newTransaction == bytes32(0) || newKem == bytes32(0)) {
revert InvalidTransition("rotation needs all three commitments");
}
a.rotationPending = true;
a.pendingRecoveryAccess = newAccess;
a.pendingRecoveryTransaction = newTransaction;
a.pendingRecoveryKem = newKem;
a.rotationInitiatedAt = FinalChainTime.nowMs();
a.rotationCancels = 0;
} else if (action == Action.CANCEL_ROTATION) {
// The tie-breaker for a compromised recovery key, at a HIGHER
// threshold than freeze: cancelling can block the holder's remedy.
_requireGuardians(actor, guardianCount, a.cancelThreshold);
if (!a.rotationPending) revert InvalidTransition("no rotation pending");
if (a.rotationCancels >= MAX_ROTATION_CANCELS) {
revert InvalidTransition("cancel budget exhausted; the rotation proceeds");
}
a.rotationCancels += 1;
// Restart the clock rather than dropping the request. Dropping it
// would make each cancel a full re-initiation by the recovery key,
// which for a holder rotating a stolen key means going back to
// their cold key every time a hostile guardian objects.
a.rotationInitiatedAt = FinalChainTime.nowMs();
} else if (action == Action.FINALIZE_ROTATION) {
if (!a.rotationPending) revert InvalidTransition("no rotation pending");
uint64 readyAt = a.rotationInitiatedAt + a.delayMs;
if (FinalChainTime.nowMs() < readyAt) revert DelayNotElapsed(readyAt, FinalChainTime.nowMs());
// **Promotion, atomically.** The committed recovery pair becomes
// live and a freshly generated pair becomes the new recovery, in one
// step — so the account is never without a spare, and the live key
// never gets to touch either slot.
a.liveAccess = a.recoveryAccess;
a.liveTransaction = a.recoveryTransaction;
a.liveKem = a.recoveryKem;
// The KEM generation moves with the key, in the same statement
// group. A promotion that advanced the key and not the counter
// would leave every in-flight envelope claiming a version that no
// longer describes what it was sealed to — which is the silent
// failure the counter exists to make visible.
a.kemVersion += 1;
a.recoveryAccess = a.pendingRecoveryAccess;
a.recoveryTransaction = a.pendingRecoveryTransaction;
a.recoveryKem = a.pendingRecoveryKem;
a.rotationPending = false;
a.pendingRecoveryAccess = bytes32(0);
a.pendingRecoveryTransaction = bytes32(0);
a.pendingRecoveryKem = bytes32(0);
a.rotationCancels = 0;
} else if (action == Action.INITIATE_GUARDIAN_CHANGE) {
_requireRecovery(actor);
// **A pending rotation pins the guardian set.** Without this, a
// compromised recovery key swaps the cancellers out and then rotates
// — two transactions, nobody legitimate left to object, and the
// cancel power never engages.
if (a.rotationPending) revert InvalidTransition("guardian set pinned by a pending rotation");
if (a.guardianChangePending) revert InvalidTransition("guardian change already pending");
(address[] memory guardians, uint16 threshold, uint16 cancelThreshold) =
abi.decode(request.payload, (address[], uint16, uint16));
_assertGuardianSet(request.wallet, guardians, threshold, cancelThreshold);
_pendingGuardians[request.wallet] = guardians;
a.pendingThreshold = threshold;
a.pendingCancelThreshold = cancelThreshold;
a.guardianChangeInitiatedAt = FinalChainTime.nowMs();
a.guardianChangePending = true;
} else if (action == Action.CANCEL_GUARDIAN_CHANGE) {
// Cancelled by the OUTGOING set — the people being removed. That is
// the point: if the recovery key is the compromised credential, the
// outgoing guardians are the only party with both the standing and
// the motive to object to their own removal.
_requireGuardians(actor, guardianCount, a.cancelThreshold);
if (!a.guardianChangePending) revert InvalidTransition("no guardian change pending");
a.guardianChangePending = false;
delete _pendingGuardians[request.wallet];
} else if (action == Action.FINALIZE_GUARDIAN_CHANGE) {
if (!a.guardianChangePending) revert InvalidTransition("no guardian change pending");
if (a.rotationPending) revert InvalidTransition("guardian set pinned by a pending rotation");
uint64 readyAt = a.guardianChangeInitiatedAt + a.delayMs;
if (FinalChainTime.nowMs() < readyAt) revert DelayNotElapsed(readyAt, FinalChainTime.nowMs());
_guardians[request.wallet] = _pendingGuardians[request.wallet];
a.threshold = a.pendingThreshold;
a.cancelThreshold = a.pendingCancelThreshold;
a.guardianChangePending = false;
delete _pendingGuardians[request.wallet];
} else if (action == Action.TRANSFER_OWNER) {
// Selling an account and rotating a compromised key are different
// operations. Transfer keeps an arbitrary target and stays with the
// live key; constraining it to promotion would break ordinary use.
if (actor != Actor.LIVE_KEY) revert CredentialNotPermitted("owner transfer needs the live key");
if (a.frozen) revert InvalidTransition("a frozen account cannot transfer ownership");
address newOwner = abi.decode(request.payload, (address));
if (newOwner == address(0)) revert InvalidTransition("newOwner required");
address oldOwner = a.owner;
a.owner = newOwner;
_unindexOwner(request.wallet, oldOwner);
_indexOwner(request.wallet, newOwner);
address[] memory touched = new address[](2);
touched[0] = oldOwner;
touched[1] = newOwner;
_writeOwnerIndex(touched);
} else if (action == Action.SET_CHAIN_ACCOUNT) {
// An ordinary action of the account, so the LIVE key — and, like
// every other action, nothing a frozen account may do: a row added
// mid-freeze is a destination an attacker holding the live key
// chose, and settlement toward it is exactly what the freeze stops.
if (actor != Actor.LIVE_KEY) revert CredentialNotPermitted("chain account needs the live key");
if (a.frozen) revert InvalidTransition("a frozen account cannot change its chain table");
(bytes32 chainRef, bytes32 account) = abi.decode(request.payload, (bytes32, bytes32));
_setChainAccount(request.wallet, chainRef, account);
} else {
// **Migration takes no key material, and that is the whole point.**
// The four commitments were fixed at issuance and are already in
// this record, so there is nothing to supply and nothing to get
// wrong. An enable that took key arguments would let one account
// acquire a different PQ identity per chain — the divergence this
// plane exists to make impossible.
//
// Authorized by the LIVE key, which for a pre-PQ account is its
// ECDSA owner: `_resolveKeyActor` resolves an owner signature to
// `LIVE_KEY` only while `!pqEnabled`, and resolves the PQ live
// credential to `LIVE_KEY` only while `pqEnabled`, so exactly one
// credential can reach this and it is the right one.
if (actor != Actor.LIVE_KEY) revert CredentialNotPermitted("PQ migration needs the live key");
if (a.pqEnabled) revert InvalidTransition("already PQ");
a.pqEnabled = true;
// The sentinel is written HERE rather than resolved by each
// consumer. A post-PQ account's owner is the same constant
// everywhere by construction; leaving each chain to substitute it
// would put one conditional on every authorization path and give a
// future consumer somewhere to disagree.
a.owner = FINAL_PQ_NATIVE_OWNER;
}
}
/// @dev Set or replace the row for `chainRef`. Zero in either half is
/// refused: a zero chain names nothing and a zero account is the
/// settlement contract's "unspecified", which this table exists to
/// resolve rather than restate.
function _setChainAccount(address wallet, bytes32 chainRef, bytes32 account) private {
if (chainRef == bytes32(0) || account == bytes32(0)) revert InvalidChainAccount(chainRef, account);
FinalStateTrees.ChainAccount[] storage rows = _chainAccounts[wallet];
for (uint256 i = 0; i < rows.length; i++) {
if (rows[i].chainRef == chainRef) {
rows[i].account = account;
emit ChainAccountSet(wallet, chainRef, account);
return;
}
}
rows.push(FinalStateTrees.ChainAccount({chainRef: chainRef, account: account}));
emit ChainAccountSet(wallet, chainRef, account);
}
function _requireRecovery(Actor actor) private pure {
if (actor != Actor.RECOVERY_KEY) revert CredentialNotPermitted("recovery key only");
}
function _requireGuardians(Actor actor, uint256 count, uint16 threshold) private pure {
if (actor != Actor.GUARDIANS) revert CredentialNotPermitted("guardians only");
if (threshold == 0) revert CredentialNotPermitted("no guardian set configured");
if (count < threshold) revert NotAGuardian(address(0));
}
// ------------------------------------------------------- guardian shape
/// @notice A strict majority, and never below the freeze threshold plus one.
/// @dev The asymmetry is what bounds a bad guardian set. Freezing is
/// fail-closed and self-harming at worst, so a low bar is fine; cancelling
/// can block the holder's remedy, so one malicious guardian must not be able
/// to do it alone.
function _cancelThresholdFor(uint16 count, uint16 freezeThreshold) private pure returns (uint16) {
if (count == 0) return 0;
uint16 majority = count / 2 + 1;
return majority > freezeThreshold + 1 ? majority : freezeThreshold + 1;
}
/**
* @dev Structural checks on a guardian set.
*
* These bound SHAPE, not independence — three addresses one party controls
* pass every rule, and nothing on chain can see that either. They remove the
* configurations that are wrong on their face; the threshold asymmetry and
* the bounded cancel are what make a badly-chosen set survivable.
*/
function _assertGuardianSet(
address wallet,
address[] memory guardians,
uint16 threshold,
uint16 cancelThreshold
) private pure {
uint256 n = guardians.length;
if (threshold == 0) {
if (n != 0) revert InvalidGuardianSet("guardians supplied with a zero threshold");
return;
}
if (n < threshold) revert InvalidGuardianSet("threshold exceeds the guardian count");
if (n >= 3 && threshold < 2) revert InvalidGuardianSet("three or more guardians need a threshold of at least 2");
if (cancelThreshold <= threshold) revert InvalidGuardianSet("cancel threshold must exceed the freeze threshold");
if (cancelThreshold > n) revert InvalidGuardianSet("cancel threshold exceeds the guardian count");
for (uint256 i = 0; i < n; i++) {
address g = guardians[i];
if (g == address(0)) revert InvalidGuardianSet("zero address is not a guardian");
if (g == wallet) revert InvalidGuardianSet("a wallet cannot guard itself");
for (uint256 j = i + 1; j < n; j++) {
if (guardians[j] == g) revert InvalidGuardianSet("duplicate guardian");
}
}
}
// ---------------------------------------------------------------- views
/// @notice One account's full record.
function accountOf(address wallet) external view returns (Account memory) {
if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
return _accounts[wallet];
}
/// @notice Whether an account exists here at all.
/**
* @notice Which generation of this account's encapsulation key is current.
*
* @dev What a sealer writes into an intent header's `kemKeyVersion`, and
* what a recipient compares against to decide which stage's key an envelope
* was sealed to.
*
* A dedicated getter rather than reaching through `recordOf`, because this
* is read on the composing path for every intent and `recordOf` returns the
* whole account plus four arrays. Zero means the account was never opened —
* `_open` starts at one — so a caller cannot mistake "no such account" for
* "the first key".
*/
function kemVersionOf(address wallet) external view returns (uint16) {
return _accounts[wallet].kemVersion;
}
/// @notice The genesis certificate serial — the raw half of the tree-8
/// admission leaf's preimage. Zero for an account that was never opened.
/// @dev What a plane-migration snapshot captures beside `leafOf`: the
/// derived admission hash is not invertible, so a re-open on a fresh
/// plane needs this to rebuild the same leaf.
function serialOf(address wallet) external view returns (bytes32) {
return _accounts[wallet].serial;
}
function isOpen(address wallet) external view returns (bool) {
return _accounts[wallet].opened;
}
/// @notice The live guardian set.
function guardiansOf(address wallet) external view returns (address[] memory) {
return _guardians[wallet];
}
/// @notice The guardian set a pending change would install.
function pendingGuardiansOf(address wallet) external view returns (address[] memory) {
return _pendingGuardians[wallet];
}
/// @notice Chains this account is known to exist on — the fan-out target set.
function deploymentsOf(address wallet) external view returns (bytes32[] memory) {
return _deployments[wallet];
}
/// @notice The holder's `(chainRef, account)` table — the leaf's `deployedChains`.
function chainAccountsOf(address wallet) external view returns (FinalStateTrees.ChainAccount[] memory) {
return _chainAccounts[wallet];
}
/// @notice The account `wallet` is on `chainRef`, or zero if it has no row there.
/// @dev What a zero settlement beneficiary toward `chainRef` resolves to,
/// and what the co-signers check at admission before the source lock.
function accountOn(address wallet, bytes32 chainRef) external view returns (bytes32) {
FinalStateTrees.ChainAccount[] storage rows = _chainAccounts[wallet];
for (uint256 i = 0; i < rows.length; i++) {
if (rows[i].chainRef == chainRef) return rows[i].account;
}
return bytes32(0);
}
/// @notice Every per-action nonce for one account, in `Action` order.
/// @dev One call rather than eleven. A reader that fetched them separately
/// would also be fetching them at eleven different blocks.
function noncesOf(address wallet) public view returns (uint64[11] memory out) {
for (uint8 i = 0; i < ACTION_COUNT; i++) out[i] = nonceOf[wallet][i];
}
/// @notice How many accounts exist.
/// @notice Everything the ledger holds for one account, in one read — the
/// account plane's mirror (`chainLedger.js`) and the redeploy export
/// read this; `accountWords` carries the record verbatim for restore.
function recordOf(address wallet)
external
view
returns (
Account memory account,
address[] memory guardians,
address[] memory pendingGuardians,
uint64[11] memory nonces,
bytes32[] memory deployments,
FinalStateTrees.ChainAccount[] memory chainAccounts
)
{
if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
return (
_accounts[wallet],
_guardians[wallet],
_pendingGuardians[wallet],
noncesOf(wallet),
_deployments[wallet],
_chainAccounts[wallet]
);
}
function walletCount() external view returns (uint256) {
return _wallets.length;
}
/// @notice A page of accounts, in the order they were opened.
/// @dev Paged because the publisher iterates every account and this chain
/// caps a call's gas like any other; an unbounded getter would stop working
/// at exactly the size where it starts to matter.
function walletsBetween(uint256 from, uint256 to) external view returns (address[] memory page) {
if (to > _wallets.length) to = _wallets.length;
if (from > to) from = to;
page = new address[](to - from);
for (uint256 i = from; i < to; i++) page[i - from] = _wallets[i];
}
/// @notice The published leaf for an account — exactly the committed
/// fields, in `FinalWalletFactory.AccountStateLeaf` order.
function leafOf(address wallet) external view returns (FinalStateTrees.AccountStateLeaf memory) {
if (!_accounts[wallet].opened) revert UnknownAccount(wallet);
return _leafOf(wallet);
}
function _leafOf(address wallet) private view returns (FinalStateTrees.AccountStateLeaf memory) {
Account storage a = _accounts[wallet];
return FinalStateTrees.AccountStateLeaf({
wallet: wallet,
liveAccess: a.liveAccess,
liveTransaction: a.liveTransaction,
recoveryAccess: a.recoveryAccess,
recoveryTransaction: a.recoveryTransaction,
liveKem: a.liveKem,
recoveryKem: a.recoveryKem,
owner: a.owner,
pqEnabled: a.pqEnabled,
frozen: a.frozen,
deployedChains: _chainAccounts[wallet],
dormantChains: a.dormantChains,
version: a.version
});
}
}
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
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
import {FinalChainPrecompiles} from "./FinalChainPrecompiles.sol";
import {FinalChainTime} from "./FinalChainTime.sol";
/**
* @title FinalCertificate
* @notice Reads a Final Certificate (`.fcert`, schema v3) on chain.
*
* @dev Final Chain only — it needs the SHA3-256 precompile, because the schema
* hashes with FIPS-202 SHA3 and the EVM has `keccak256`, which is a different
* function.
*
* ## Why the chain parses this at all
*
* `FinalIdentityRegistry.registerWithCertificate` used to take the TBS bytes
* AND the public keys as separate arguments. It derived `certHash` from the
* bytes, which sounds like verification and is not: nothing compared the keys
* to the certificate, so a registrar could bind any certificate to any keypair.
* The registry would then 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 way for
* two arguments to disagree.
*
* ## The SubjectKeyId check
*
* The schema defines `SubjectKeyId` as SHA3-256 of the `PublicKeyBlock`. Having
* parsed the block, this recomputes that digest and compares. The field is
* inside the TBS, so it is covered by the CA's signatures — which makes the
* check a statement about what the CA attested, not merely about internal
* consistency of bytes the caller supplied.
*
* ## What this does NOT do
*
* It does not verify the CA's signatures over the TBS, and it does not walk the
* chain to the root. Both are possible here — the precompiles verify ML-DSA-87
* and SLH-DSA-SHAKE-256s — and both are deliberately out of scope for the
* registry's bootstrap path, where the registrar is the party that issued the
* certificate in the first place. `verifyIssuerSignatures` below is provided for
* callers that need it, and the identity registry uses it once a CA is itself
* registered.
*/
library FinalCertificate {
/// `"PQCF"`.
uint32 internal constant MAGIC = 0x50514346;
/// The current wire generation — v5's `Version = 2` (chain-attested
/// issuance; ruled 2026-09-01). The v4 wire (`Version = 1`) stays
/// PARSEABLE so pre-cutover artifacts still read; encoders write 2.
/// fails to parse rather than being reinterpreted: `pqKeysHash` and every
/// wallet address derive from this exact layout.
uint32 internal constant VERSION = 2;
/// The v4 generation, accepted on parse for pre-cutover artifacts.
uint32 internal constant VERSION_V4 = 1;
/// @notice The 0x0102 Institution identity extension (issuer profile).
uint16 internal constant EXT_INSTITUTION = 0x0102;
/// Algorithm ids ARE the FIPS numbers, in one space for signatures and KEMs
/// — the same ids the quorum wire format and the backend registry use, and
/// the numbers the precompile addresses end in.
/// ML-KEM-1024 (FIPS 203), the lattice half of the encapsulation pair.
uint16 internal constant ALG_ML_KEM_1024 = 0x0003;
/// ML-DSA-87 (FIPS 204). Transaction class.
uint16 internal constant ALG_ML_DSA_87 = 0x0004;
/// SLH-DSA-SHAKE-256s (FIPS 205). Access class, and the seal.
uint16 internal constant ALG_SLH_DSA_SHAKE_256S = 0x0005;
/// FN-DSA (FIPS 206). Reserved: no implementation, never accepted.
uint16 internal constant ALG_FN_DSA = 0x0006;
/// HQC-5 (FIPS 207), the code-based half of the encapsulation pair.
uint16 internal constant ALG_HQC_5 = 0x0007;
/// Certificate signing. Says which key to verify WITH; it grants nothing —
/// that comes from `Depth` and `MaxDelegationDepth`.
uint16 internal constant PURPOSE_CERT_SIGNING = 0x0004;
/// The wallet's four slots, in two stages of two.
///
/// A certificate carries ONE stage, never all four. The stage is what gets
/// 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.
///
/// 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;
uint16 internal constant PURPOSE_ACTIVE_ACCESS = 0x0011;
uint16 internal constant PURPOSE_RECOVERY_TX = 0x0012;
uint16 internal constant PURPOSE_RECOVERY_ACCESS = 0x0013;
/// @dev v4's encapsulation purposes. Parsed, and each stage's pair is
/// resolved alongside its signing pair — `FinalIdentityRegistry` then
/// stores them so a sender can encapsulate to a registered party
/// without a second lookup somewhere less authoritative.
///
/// They were declared and skipped for one release, which is how the
/// registry's four encapsulation-key mappings ended up read in three
/// places and written in none: `kemCommitments` hashed the empty
/// string for every account and `kemKeysOf` returned nothing.
uint16 internal constant PURPOSE_ACTIVE_KEM = 0x0014;
uint16 internal constant PURPOSE_RECOVERY_KEM = 0x0015;
/// @dev The seal: a second SLH-DSA-SHAKE-256s key, distinct from the access
/// key, that co-signs execution-class quorum decisions. Carried by
/// SERVICE certificates only — a user's wallet never seals — and
/// optional in the schema, so a certificate without it parses
/// unchanged. Outside `keysHash`: a seal is operational, rotated by
/// issuing a new live certificate, and it must not move a wallet
/// address it plays no part in.
uint16 internal constant PURPOSE_ACTIVE_SEAL = 0x0016;
/// @dev A purpose no certificate can carry, so `parse` can be told "this
/// stage has no encapsulation slot" without a second boolean. `0xffff`
/// is outside the registry and reserved by being used here.
uint16 internal constant NO_KEM_PURPOSE = 0xffff;
/// Nanoseconds per second. The schema's validity fields are nanoseconds and
/// `block.timestamp` is seconds; a comparison across the two units is a bug
/// waiting for the first certificate anybody actually checks.
/// @dev The schema stamps validity in NANOseconds and this chain's clock is
/// MILLIseconds, so a certificate converts down by 1e6 rather than by
/// 1e9. It was 1e9 — seconds — which made every `notBefore` look 1000x
/// too small against `block.timestamp` and every certificate
/// permanently "already valid", including one issued for the future.
uint64 internal constant NS_PER_MILLISECOND = FinalChainTime.NS_PER_MILLISECOND;
/// @notice What the chain keeps out of one certificate.
struct Parsed {
bytes32 certHash;
bytes32 serial;
/// keccak256 of the IssuerDN bytes, for the chain-issuer pin: a
/// chain-attested certificate carries the ruled constant DN and the
/// registry compares hashes rather than strings.
bytes32 issuerDnHash;
/// The SubjectDN bytes verbatim — the jurisdiction rule reads its
/// `C=` component at issuer registration.
bytes subjectDn;
/// The 0x0102 Institution extension VALUE, when present; empty
/// otherwise. Issuer registration parses jurisdiction out of it.
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;
uint8 depth;
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;
}
error BadMagic(uint32 got);
error BadVersion(uint32 got);
error Truncated(uint256 needed, uint256 got);
error SubjectKeyIdMismatch(bytes32 derived, bytes32 declared);
error MissingSlot(uint16 purpose);
error WrongAlgorithmForSlot(uint16 purpose, uint16 algorithm);
error DuplicateKey(uint16 purpose, uint16 algorithm);
error KeysNotSorted();
error BadKeyLength(uint16 algorithm, uint256 length);
error InvalidDepth(uint8 depth, uint8 maxDelegationDepth);
error ValidityInverted(uint64 notBefore, uint64 notAfter);
/**
* @notice Parse and self-check a `TBSCertificate`.
* @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.
*
* @dev Checking for a CAPABILITY rather than a type is the 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.
*/
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 generations. v4 artifacts predate chain-attested issuance
// and still parse — supersession is handled at admission (PoP and the
// chain-issuer pins), not by refusing to read history.
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: `activeTransaction` + `activeAccess`.
/// @dev `external`, like the other three entry points below: the registry
/// sits against the EIP-170 ceiling and the TBS parser is its single
/// largest inlined dependency, so the four doors it actually calls are
/// DEPLOY-LINKED — the library is one more contract in the plane's fixed
/// nonce-0 deploy order (doctrine §2 of `arch/final-chain-regenesis.md`),
/// its address baked immutably into the registry's bytecode. A linked
/// library is code, not a key: nothing can repoint it after deployment.
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, an ordinary action for that
/// account, uses the live access key. Keeping the two stages in separate
/// certificates is what makes that boundary something a verifier can see.
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 CA certificate, whose two keys are both cert-signing.
/// @dev No encapsulation purpose: a CA signs and is never sealed to, so
/// `PURPOSE_ACTIVE_KEM` is passed as a value the loop can never match. A
/// CA certificate carrying encapsulation keys would parse them into slots
/// `_write` then discards, which is a shape worth refusing to have.
function parseCa(bytes calldata tbs) external view returns (Parsed memory) {
return parse(tbs, PURPOSE_CERT_SIGNING, PURPOSE_CERT_SIGNING, NO_KEM_PURPOSE);
}
/**
* @notice Verify a CA's dual signature over `tbs`.
* @dev Both must verify, not either. Two signatures under two different
* hardness assumptions is the entire reason the schema carries two, and
* accepting one would collapse that to whichever family breaks first.
*/
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);
}
function _need(bytes calldata tbs, uint256 upto) private pure {
if (tbs.length < upto) revert Truncated(upto, tbs.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);
}
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
//
// @author Final DeFi
// @version 1.0.0
pragma solidity ^0.8.20;
/**
* @title FinalChainPrecompiles
* @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 `final-reth`, the node binary in `FinalBackend/vendor/reth/final/`, 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
pragma solidity ^0.8.20;
/**
* @title FinalChainTime
* @notice **On Final Chain, `block.timestamp` is MILLISECONDS.**
*
* @dev Every other EVM chain stamps seconds. This one cannot: it mints a block
* every 100 ms and Ethereum requires block timestamps to strictly increase, so
* a second-denominated clock would run out of distinct values ten times over
* per second. Milliseconds is the deliberate choice, and it is a property of
* the CHAIN — `final-reth` — not of any contract here.
*
* Every duration on this chain is therefore in milliseconds, and this library
* exists so that is stated in one place instead of assumed in fifteen.
*
* ## How this was found, which is the reason for the naming rules below
*
* It was not found by the test suite. Foundry's `block.timestamp` is seconds,
* so all 1249 tests agreed with the contracts and every one of them was wrong
* about the chain they deploy to. It was found the first time anything
* exercised a deadline against the real chain — a posted intent, which reverted
* `DeadlinePassed` against a header whose deadline had been computed from wall
* time.
*
* What was actually broken was worse than a posting. `rotationInitiatedAt` is
* written from `block.timestamp` and compared against `rotationInitiatedAt +
* delaySeconds`: a millisecond clock plus a second-denominated delay. The
* 24-hour default recovery delay elapsed in **86 seconds**, and the two-year
* dormancy threshold in about seventeen hours. That delay is the thing standing
* between a stolen recovery key and an account.
*
* Nothing had noticed because nothing time-dependent had ever run: `walletCount`
* is 0, `FinalBundleLog.size` is 0, and no intent had been posted.
*
* ## The naming rule
*
* A field or constant carrying a duration or an instant on this chain ends in
* `Ms`. Not decoration — the bug was a field named `delaySeconds` that held
* milliseconds, and a name that lies is how the next reader reintroduces it.
* `SECONDS` names are gone from `contracts/finalchain/` and must not come back.
*
* Solidity's `hours` / `days` suffixes are still the clearest way to write a
* duration, so they are written as `24 hours * MS_PER_SECOND` rather than as a
* literal: the intent stays readable and the unit stays explicit.
*/
library FinalChainTime {
/// @notice Milliseconds per second. The whole conversion, named once.
uint64 internal constant MS_PER_SECOND = 1_000;
/// @notice Milliseconds per nanosecond divisor — the certificate schema
/// stamps validity in NANOseconds, so a certificate converts down to this
/// chain's clock rather than up.
uint64 internal constant NS_PER_MILLISECOND = 1_000_000;
/// @notice This chain's clock, stated as a function so a caller reads the
/// unit rather than remembering it.
/// @dev No arithmetic. It exists to make `FinalChainTime.nowMs()` the thing
/// people write, which is self-describing where `block.timestamp` is not.
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
//
// @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";
/**
* @title FinalIdentityRegistry
* @notice Who every party in the system IS, on chain, with its certificate.
*
* @dev Final Chain only. Every service, every co-signer, every certificate
* authority and every operator has one record here, and that record carries the
* party's actual public keys — not commitments to them.
*
* ## Why the full key and not a hash
*
* 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: 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` reads them from storage, and
* "which key is co-signer 3" has exactly one answer. That question previously
* had three: an environment variable, an on-chain roster, and a Secret Manager
* entry, with nothing comparing them. Every configuration failure in this
* program has been those three disagreeing.
*
* ## The certificate is the record, not a pointer to one
*
* `certHash` is `SHA3-256(TBSCertificate)` — the certificate's own identity per
* the v3 schema, and the handle revocation is keyed on. The schema says
* revocation exists "on Final Chain only"; this is that place.
*
`registerWithCertificate` takes the TBS bytes and **reads everything out of
* them**: the digest, the serial, the key identifiers, the depth pair, the
* validity window and both public keys. It takes no key arguments at all.
*
* That is a correction, and the version it replaces is worth naming because it
* looked right. It took the TBS *and* the keys, derived `certHash` from the
* TBS, and never compared the two — so a registrar could bind any certificate
* to any keypair, and the registry would hold a key the certificate does not
* contain. Every signature that key produced would then verify against a
* certificate that never authorised it.
*
* ## The root is the first record on this chain, not a file somewhere
*
* The schema says Final Chain is the only root CA and that "the root is pinned,
* not distributed" — chain validation terminates at Final Chain **by identity**,
* never by finding a self-signed certificate in a local store.
*
* `registerRoot` is that pin, and it is the only entry point that accepts a
* certificate without checking an issuer's signature. It takes a depth-0,
* self-issued certificate from the bootstrap admin, once. Everything after it
* is `registerWithCertificate`, which **verifies the issuer's ML-DSA and
* SLH-DSA signatures on chain, through the precompiles**, against the issuer's
* own registered keys, and checks that the child's `AuthorityKeyId` is the
* issuer's `SubjectKeyId` and that the issuer's depth admits it.
*
* So there is no path by which a key enters this registry unattested. Not
* "a registrar should only register certified keys" — a registrar *cannot*
* register anything else.
*
* ## Roles are a bitmask
*
* One party is legitimately several things — a co-signer that is also a
* publisher, an operator that is also a guardian. A single enum would force
* either duplicate records for one key (two sources of truth about one party)
* or a role hierarchy nobody agrees on. A mask has neither problem, and a
* quorum asks "does this account carry ROLE_X" rather than "is this account an
* X", which is the same distinction the certificate schema draws when it says
* verifiers check for capabilities and never for types.
*
* ## 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, an LMS key,
* the registrar threshold itself) and every state-plane configuration change
* that routes 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 exception, and it is the only one: while it is
* open the bootstrap admin writes alone, because every roster has to be
* installed by someone before it can install itself.
*
* ## The sender is not the account
*
* Final Chain transactions are type 0x46, signed by ML-DSA-87, and the node
* derives `msg.sender` from the key: `keccak256(0x04 ‖ publicKey)[12:]`. That
* address pays gas and holds no authority. {accountOfSender} binds it to the
* identity whose `activeTransaction` 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 rather than the roster.
*/
/// @dev Domain for a stage's encapsulation commitment. Byte-equal to
/// `FinalWalletFactory.DOMAIN_KEM_BUNDLE` and to `DOMAIN_KEM_BUNDLE_PREIMAGE` in
/// the issuer; three derivations of one word, and a mismatch in any of them is a
/// certificate that verifies nowhere.
bytes32 constant DOMAIN_KEM_BUNDLE = keccak256("FINAL_KEM_BUNDLE_v01");
/// @dev Tree 8's leaf domain — byte-equal to
/// `IdentityRootModule.DOMAIN_IDENTITY_LEAF` on every execution chain.
/// Restated rather than imported because the module lives on other chains and
/// there is no import that would make them one value; the cross-contract
/// parity test pins the pair. The `_PQ_` spelling is historical and FROZEN:
/// the premined vanity certificates were mined against this exact constant,
/// and the leaf it derives is the `certHash` inside every wallet's CREATE2
/// derivation.
bytes32 constant DOMAIN_IDENTITY_LEAF = keccak256("FINAL_IDENTITY_LEAF_PQ_v01");
/// @dev D7 (ruled 2026-09-01): ISSUER records project into tree 8 under their
/// own domain — `DOMAIN_ISSUER_LEAF ‖ certHash ‖ version ‖ issuerTreeRoot` —
/// so an issuer is stapleable for offline licence verification while the
/// distinct domain keeps its leaf out of wallet admission (the gateway folds
/// with the wallet domain, so an issuer leaf can never satisfy
/// `verifyIdentityCert`). `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 the fixed-depth insertion-ordered state
/// trees cannot prove non-inclusion.
bytes32 constant DOMAIN_ISSUER_LEAF = keccak256("FINAL_ISSUER_LEAF_v01");
/// @dev Chain-issuer constants (ruled 2026-09-01, amended same day: C-less).
/// The chain is the issuer but holds no keypair, so every chain-attested
/// certificate carries these two NAMED values in its issuer fields — required
/// by the wire format, verifying nothing, covered by `certHash`. The DN is
/// deliberately env-agnostic AND jurisdiction-silent: the issuer is the
/// worldwide network, not a legal entity, and an env-specific DN would fork
/// `certHash` per environment. Reference implementation:
/// `dashboard/public/fcert.js` (`CHAIN_ISSUER_DN`, `CHAIN_AUTHORITY_KEY_ID`);
/// `docs/developers/certificate-schema.md` § Chain-issuer constants.
bytes32 constant CHAIN_ISSUER_DN_HASH = keccak256("CN=Final Chain,O=Final DeFi");
/// @dev `SHA3-256(utf8("FINAL_CHAIN_AUTHORITY_v01"))` — a DOMAIN constant, not
/// a key digest (the chain has no PublicKeyBlock). Precomputed because the
/// mock SHA3 precompile under Foundry is deliberately not the real function;
/// pinned against `hashlib.sha3_256` and the dashboard's value by test.
/// Zero-length AuthorityKeyId stays reserved for the retired genesis root
/// alone and is admitted nowhere.
bytes32 constant CHAIN_AUTHORITY_KEY_ID =
0x9a6a5d8139ad2d28957698330aaa691017dba7dc80eb7cbec585239fb680bbab;
/// @notice The identity tree's projection door on `FinalStateTrees`. 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. Same pattern as `IChainSource` on the trees side.
interface IIdentityLeafSink {
function syncIdentityLeaves(address[] calldata accounts) external;
}
/// @notice `FinalRevocationLog`'s recording door, same narrow-interface
/// reasoning. `recorded` is read first so a fingerprint someone already
/// recorded permissionlessly cannot revert the registry mutation feeding it.
interface IRevocationRecorder {
function record(bytes32 signerId) external;
function recorded(bytes32 signerId) external view returns (bool);
}
contract FinalIdentityRegistry {
// ---------------------------------------------------------------- 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
/// @dev One per membership mutation, so an approval to grant a role can
/// never be replayed as one to revoke. The registry is its own verifying
/// contract for these.
bytes32 public constant DOMAIN_REGISTER_WALLET = keccak256("FINAL_REGISTRY_REGISTER_WALLET_v01");
bytes32 public constant DOMAIN_REGISTER_ISSUER = keccak256("FINAL_REGISTRY_REGISTER_ISSUER_v01");
/// @notice The admission proof-of-possession digest domain (schema §v5).
/// 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.
bytes32 public constant DOMAIN_IDENTITY_ADMISSION = keccak256("FINAL_IDENTITY_ADMISSION_v01");
/// @notice Root-plane global certificate revocation (D5).
bytes32 public constant DOMAIN_REVOKE_CERTIFICATE =
keccak256("FINAL_REGISTRY_REVOKE_CERTIFICATE_v01");
/// @notice The ISSUING identity's certificate-revocation digest domain.
bytes32 public constant DOMAIN_ISSUER_CERT_REVOCATION =
keccak256("FINAL_ISSUER_CERT_REVOCATION_v01");
bytes32 public constant DOMAIN_REGISTER_LMS_KEY = keccak256("FINAL_REGISTRY_REGISTER_LMS_KEY_v01");
bytes32 public constant DOMAIN_SET_ROLES = keccak256("FINAL_REGISTRY_SET_ROLES_v01");
bytes32 public constant DOMAIN_REVOKE = keccak256("FINAL_REGISTRY_REVOKE_v01");
bytes32 public constant DOMAIN_SET_REGISTRAR_THRESHOLD =
keccak256("FINAL_REGISTRY_SET_REGISTRAR_THRESHOLD_v01");
/// @dev The algorithm id the sender derivation is domain-separated by:
/// ML-DSA-87, FIPS 204, the only algorithm the transaction envelope admits.
uint8 private constant ENVELOPE_ALG_ML_DSA_87 = 4;
// ------------------------------------------------------------- storage
/**
* @notice One party's on-chain identity.
* @dev `version` increments on every mutation and is what a rotation is:
* the record is replaced, not appended to, and the version is how a reader
* on another chain knows which of two copies it saw 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;
/// Seconds since epoch. The schema's TBS is nanoseconds; the conversion
/// happens off chain because block timestamps are seconds and a
/// comparison across units is a bug waiting for a leap.
/// @dev MILLISECONDS — this chain's clock. See `FinalChainTime`.
uint64 notBefore;
/// Seconds since epoch, or 0 for "never expires" — which the schema
/// allows and personal identity certificates use.
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;
}
/**
* @notice A hash-based (LMS) signing key held by a registered account.
*
* The protocol plane's quorums verify LMS, not ML-DSA: an execution chain
* has no PQ precompiles, so `FinalRootAuthority` checks a keccak hash loop
* instead (`arch/hash-based-authority.md`). Those keys are the authority
* over `masterRoot`, and therefore over PQ execution — which makes "who
* holds signer 0x39bb…?" 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 LMS signing key for an account, if it holds one.
/// @dev One slot per (account, chain) — LMS-01. `nextLeaf` on an
/// authority is a complete single-use counter 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.
mapping(address account => mapping(uint64 chainId => LmsKey)) private _lmsKey;
/// @notice Which account a signer fingerprint belongs to. This is the
/// lookup the whole record exists for: a gateway roster names fingerprints
/// and nothing else, so without it the keys are unattributable.
/// @dev What a fingerprint is bound to: the account that holds it and the
/// chain it signs for — one slot, written once at registration and left in
/// place when superseded (attribution is history). The chain names the
/// (account, chain) slot `lmsSignerIsLive` resolves against.
// NOTE: this contract sits ~13 bytes under EIP-170 (24,563 of 24,576 at
// the pinned optimizer settings). The next feature here pays for itself
// in bytecode first — see the LMS-binding merge and the off-chain
// zero-chain check for what that looks like.
struct LmsBinding {
address account;
uint64 chainId;
}
mapping(bytes32 signerId => LmsBinding) private _lmsBinding;
/// @notice The identity record for an account.
mapping(address account => Identity) private _identity;
/// The four slots, verbatim. All four are stored in full because the
/// precompiles verify against a KEY, not a commitment — and a key that
/// arrived in calldata proves nothing about who signed.
///
/// A CA has two keys, not 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;
mapping(address account => bytes) private _activeAccessKey;
mapping(address account => bytes) private _recoveryTransactionKey;
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. Empty for every identity
/// whose certificate carries no `PURPOSE_ACTIVE_SEAL` entry: users, CAs.
mapping(address account => bytes) private _activeSealKey;
/// @notice Encapsulation keys, per stage. Two algorithms each — ML-KEM-1024
/// (lattice) and HQC-5 (code-based) — so a break in either family leaves the
/// other standing, the same reasoning that pairs ML-DSA with SLH-DSA above.
/// @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;
mapping(address account => bytes) private _activeKemHqc;
mapping(address account => bytes) private _recoveryKemMlKem;
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
event IdentityRegistered(
address indexed account, bytes32 indexed certHash, uint256 roles, uint64 version
);
event IdentityRolesChanged(address indexed account, uint256 previousRoles, uint256 newRoles);
event LmsKeyRegistered(
address indexed account,
bytes32 indexed signerId,
uint64 indexed chainId,
bytes16 keyId,
uint8 height,
bytes32 root,
uint64 version
);
event IdentityRevoked(address indexed account, bytes32 indexed certHash);
/// @notice One revocation-lane entry: `revoker` is `address(0)` for the
/// root plane, the issuing identity otherwise.
event CertificateRevoked(bytes32 indexed certHash, address indexed revoker);
event BootstrapSealed(address indexed sealedBy);
/// @notice The one-shot state-plane wiring landed.
event StatePlaneWired(address stateTrees, address revocationLog);
event RegistrarThresholdSet(uint256 threshold);
/// @notice A registrar quorum authorized an action. `nonce` is the value
/// the approvals were made over; the next action needs the next one.
event RegistrarQuorumApproved(
address indexed verifyingContract, bytes32 indexed actionDomain, uint64 nonce, uint256 valid
);
// -------------------------------------------------------------- errors
error NotAuthorized(address caller);
error BootstrapAlreadySealed();
error UnknownAccount(address account);
/// @notice A certificate's encapsulation key failed the chain's own
/// well-formedness check. Names the algorithm, because the pair is stored
/// together and "one of these two" is not an actionable answer.
error MalformedEncapsulationKey(address account, uint16 algorithmId);
error CertificateAlreadyBound(bytes32 certHash, address boundTo);
error CertificateIsRevoked(bytes32 certHash);
error VersionNotNewer(uint64 current, uint64 offered);
error IssuerNotACertificateAuthority(address issuer);
error IssuerMayNotSign(address issuer, uint8 depth, uint8 maxDelegationDepth);
error WrongDepth(uint8 got, uint8 want);
error DelegationWidened(uint8 child, uint8 issuer);
error AuthorityKeyIdMismatch(bytes32 got, bytes32 want);
error StagesDisagree(bytes32 liveSerial, bytes32 recoverySerial);
/// @notice `height` outside 1..24. See `FinalLms.MAX_HEIGHT`.
error LmsHeightOutOfRange(uint8 height);
/// @notice A zero root commits to no tree.
error LmsRootIsZero();
/// @notice This fingerprint already belongs to a different account.
error LmsKeyAlreadyBound(bytes32 signerId, address boundTo);
/// @notice Two identities cannot share a transaction key: the sender it
/// derives would be attributable to both.
error SenderAlreadyBound(address sender, address boundTo);
/// @notice Fewer registrars able to seal than the threshold asks for.
error RegistrarThresholdUnreachable(uint256 sealable, uint256 threshold);
error RegistrarThresholdIsZero();
/// @notice {wireStatePlane} ran already, or was handed a zero address.
error StatePlaneAlreadyWired();
error ZeroStatePlane();
/// @notice The holder's admission proof of possession did not verify —
/// one family failed, or the digest was built over the wrong nonce.
error AdmissionProofInvalid(address account);
/// @notice The certificate does not carry the ruled chain-issuer
/// AuthorityKeyId — it is not a chain-attested certificate.
error NotChainAttested(bytes32 authorityKeyId);
/// @notice The certificate's IssuerDN is not the ruled constant.
error WrongIssuerDn(bytes32 issuerDnHash);
/// @notice A chain-attested end entity sits at depth 1 with
/// `maxDelegationDepth == depth`; anything else is not an end entity.
error NotAnEndEntity(uint8 depth, uint8 maxDelegationDepth);
/// @notice An issuer that cannot sign is an end entity wearing a profile.
error IssuerCannotSign(uint8 depth, uint8 maxDelegationDepth);
/// @notice Third-party issuers carry a real `NotAfter` (ruling 3) —
/// expiry is the passive half of their lifecycle.
error IssuerMustExpire();
/// @notice An issuer validity window past the ~2-year ceiling (ruling 3).
error IssuerValidityTooLong(uint64 notBefore, uint64 notAfter);
/// @notice An institution registration without a real ISO 3166 `C=` in
/// its subject DN, or with a jurisdiction that does not match its
/// Institution extension. Only the trust root is jurisdiction-silent.
error JurisdictionMissing();
error JurisdictionMismatch();
// --------------------------------------------------------- constructor
/**
* @param admin The bootstrap registrar. Genesis names the chain deployer.
* @dev The precompile probe is the point of the constructor. This contract
* is meaningless on a chain that cannot verify PQ signatures, and deploying
* it there would produce a registry full of keys nothing can check.
*/
constructor(address admin) {
FinalChainPrecompiles.assertAvailable();
bootstrapAdmin = admin;
}
// ----------------------------------------------------------- authority
/**
* @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
* pretending otherwise produced the one roster that could not be
* bootstrapped in `FinalRootAuthority`. It is closed by
* `sealBootstrap`, which is irreversible.
*
* While it 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.
*/
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 state-plane contracts.
* @dev `msg.sender` — the calling contract — is the verifying contract the
* digest binds and the counter it burns, so an approval collected for the
* trees' configuration cannot be spent on the bundle log'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 the quorum.
*/
function requireRegistrarQuorum(
bytes32 actionDomain,
bytes32 payloadDigest,
uint64 anchorBlock,
FinalPqQuorum.Approval[] calldata approvals
) external {
_requireRegistrarQuorum(msg.sender, actionDomain, payloadDigest, anchorBlock, approvals);
}
/// @dev The digest is `FinalPqQuorum.digest(verifyingContract, actionDomain,
/// anchorBlock, keccak256(abi.encode(nonce, payloadDigest)))`; the seal is
/// required — membership is the hybrid class.
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 Bootstrap admin while the window is open; the current registrar
* quorum afterwards, so a registrar set that grows or shrinks can move it.
* Refuses a threshold the sealable registrars cannot meet, and refuses zero:
* both are a registry that can never be written to again.
*/
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.
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`. Restated rather
* than imported because the two live on different chains and there is no
* import that 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.
*/
function lmsSignerId(bytes16 keyId, uint8 height, bytes32 root) public pure returns (bytes32) {
return keccak256(abi.encode(keyId, height, root));
}
/**
* @notice Record the LMS signing key an already-registered account holds.
* @dev Membership-gated, same as 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-per-key-they-hold.
*
* @param account Must already be registered and not revoked.
* @param version Strictly increasing. 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; see
* `FinalPqQuorum`. 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, chain) — LMS-01 made the same
// operator a different signer on every chain, so chain B starting at
// version 1 says nothing about chain A 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.
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. Zeroes for a fingerprint never registered.
/// @dev The revocation log's permanence gate reads this to find the
/// (account, chain) SLOT a fingerprint belongs to — the slot's current key
/// is what separates a superseded fingerprint (permanent, recordable) from
/// a merely lapsed one (expiry, temporary, refused). Attribution is
/// history: the binding survives supersession, exactly as the mapping
/// behind {lmsSignerIsLive} does, because it IS that mapping.
function lmsBindingOf(bytes32 signerId) external view returns (address account, uint64 chainId) {
LmsBinding storage binding = _lmsBinding[signerId];
return (binding.account, binding.chainId);
}
/**
* @notice Is this signer fingerprint held by a live, unrevoked account?
* @dev The question a verifier actually has. A gateway roster names
* fingerprints and nothing else, so "is 0x39bb… still good?" is otherwise
* unanswerable from the state plane.
*/
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. The
/// count is of registrars that can SEAL — a certificate authority carrying
/// the role has no seal key and can never contribute an approval.
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 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 — both contracts take THIS registry as one — so the deploy
* tooling calls it in the same nonce-fixed block that deploys them, before
* any identity is registered.
*/
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_);
}
/// @dev Project `account`'s tree-8 leaf, same-tx. Skipped while the plane
/// is unwired — the 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 besides forgetting to call it.
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);
}
/// @dev Record a PERMANENTLY retired fingerprint, same-tx, unless the log
/// is unwired or someone already recorded it permissionlessly.
function _recordRevokedSigner(bytes32 signerId) private {
address log = revocationLog;
if (log == address(0)) return;
if (IRevocationRecorder(log).recorded(signerId)) return;
IRevocationRecorder(log).record(signerId);
}
// -------------------------------------------------------- registration
/// @notice The holder's admission proof of possession: both live-stage
/// families over the admission digest (schema §v5). There is no root
/// keypair and no CA signature any more — the chain admits, and the
/// "2 signatures at creation" are the HOLDER's, verified by the
/// precompiles inside this very transaction.
struct AdmissionProof {
bytes mlDsaSignature;
bytes slhDsaSignature;
}
/**
* @notice Register or rotate a Final Wallet identity from its two public
* certificates — CHAIN-ATTESTED (schema §v5, ruled 2026-09-01).
*
* @param account The wallet address the certificate set derives.
* @param liveTbs `live.pub.fcert` TBS — `activeTransaction` + `activeAccess`.
* @param recoveryTbs `recovery.pub.fcert` TBS — 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), verified in the precompiles inside this
* transaction. This replaced the CA signature: issuance authority
* is the registrar quorum, possession is this proof, and there is
* no root keypair anywhere.
* @param roles Capability bitmask. The one thing the certificates do not
* say, because capability is this system's decision.
* @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.
*
* @dev **Both stages, together.** A wallet has four keys in two stages and
* the recovery pair is PRE-COMMITTED — written at `initialize` from the same
* certificate set that determined the address, which is why PQ migration
* takes no key arguments. The two must share a `SerialNumber`: a serial is
* per certificate SET, so two stages disagreeing are two different wallets.
*
* **Chain-attested means pinned, per stage:** the ruled IssuerDN and
* AuthorityKeyId constants, depth exactly 1 (directly under the chain),
* and `maxDelegationDepth == depth` (an end entity signs nothing — the
* same immutable pair `identityTreeLeafOf` discriminates records 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 our own
* intermediate) that signs certificates OFF-chain with the keys
* registered here (D2: the superCA).
*
* @param account The issuer's account on this chain.
* @param tbs The single issuer certificate's TBS: two CERT_SIGNING keys
* (ML-DSA-87 + SLH-DSA-SHAKE-256s), 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;
* `address(0)` for an issuer hanging directly under the chain.
* @param proof The issuer's OWN two cert-signing keys over the admission
* digest (`recoveryCertHash` slot is zero — there is no recovery
* stage to bind).
*
* @dev Admission is chain-native like any identity: registrar quorum plus
* the holder's PoP. What the v4 delegation rules said survives verbatim as
* LINEAGE — a nested issuer's depth, delegation bound and AuthorityKeyId
* must chain to its registered parent — but no parent SIGNS anything; the
* chain's admission is the issuance.
*
* Ruling 3: a registered issuer always expires (`NotAfter` real, window
* bounded ~2 years) — the passive liveness touchpoint; renewal re-issues
* under the same registered keys with a version bump.
*
* The jurisdiction rule (ruled 2026-09-01, amended): only the trust root
* is jurisdiction-silent. An institution MUST carry its real ISO 3166
* `C=` in its subject DN, matching the `jurisdiction` field of its
* `0x0102` Institution extension — CA/Browser-Forum practice, enforced at
* the door because a verifier's legal recourse starts with knowing where
* an issuer answers for itself.
*/
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 Ruling 3's validity ceiling for registered issuers, in this
/// chain's milliseconds: two 366-day years.
uint64 public constant MAX_ISSUER_VALIDITY_MS = 2 * 366 days * 1000;
/// @dev The chain-attested end-entity pins, run once per 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);
}
}
/// @dev The v4 delegation rules, surviving as lineage: a nested issuer
/// chains to a registered, signing-capable parent one level up; a direct
/// issuer hangs under the chain at depth 1.
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);
}
}
/// @dev The jurisdiction rule: a real ISO 3166 alpha-2 `C=` in the subject
/// DN, equal to the Institution extension's `jurisdiction` field. The DN
/// is canonical comma-separated form, so `C=` matches at the start or
/// right after a comma; the component value is exactly two bytes.
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();
}
/// @dev Verify the holder's PoP: both live-stage families over the
/// admission digest, in the precompiles, inside this transaction. Burns
/// the gate nonce on the bootstrap path (the quorum path burned it in
/// `_requireRegistrarQuorum` already), so an admission is one-shot in
/// both regimes.
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;
}
}
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);
}
/**
* @dev Store one stage's encapsulation pair, or clear it.
*
* Empty is legitimate and is not the same as absent-and-wrong: a CA has no
* encapsulation stage, and a certificate issued before v4 carries none.
* `FinalCertificate.parse` 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 intent then never decrypts — the failure
* mode with no error attached, and the one this whole pairing exists to
* avoid.
*/
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.
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);
}
/// @dev Once sealed, no mutation may leave the registrar quorum unreachable
/// — that is the one change nothing could ever undo. Checked after the
/// write so the count reflects it.
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.
/// @param chainIds The chains whose LMS-key slots this account holds — the
/// registrars supply the list (the digest binds it) because a mapping
/// cannot enumerate its own keys. Each named slot's current fingerprint is
/// recorded into the revocation log same-tx; a chain with no slot is
/// skipped, and a fingerprint missed by an incomplete list stays
/// permanently recordable through the log's permissionless door, since a
/// revoked account never regains standing.
/// @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.
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` (D5).
*
* @dev The half of the one revocation lane that gates registration and
* covers break-glass: any certificate — registered, off-chain-issued, or
* never seen — can be killed by handle under the registrar quorum. When
* the handle is a registered identity's CURRENT certificate the identity
* falls with it (flag, roles, same-tx projection), so a break-glass by
* handle is never weaker than {revoke} — it only skips the LMS-slot
* enumeration, which stays permanently recordable through the revocation
* log's permissionless door.
*/
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 "Sub-issuer and us alike" (D5) — but SCOPED: this records WHO
* revoked, and a verifier honours the entry only when the revoker is the
* certificate's own issuer (which the verifier knows — it holds the
* cert). 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, the
* chain, the handle and the issuer's own gate nonce. 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 the lane models one.
*/
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. `registered` is the field to branch on.
function identityOf(address account) external view returns (Identity memory) {
return _identity[account];
}
/// @notice `activeTransaction` — ML-DSA-87. What a quorum verifies against.
function activeTransactionKeyOf(address account) external view returns (bytes memory) {
return _activeTransactionKey[account];
}
/// @notice `activeAccess` — SLH-DSA-SHAKE-256s. Identity, and guardianship.
function activeAccessKeyOf(address account) external view returns (bytes memory) {
return _activeAccessKey[account];
}
/// @notice `activeSeal` — SLH-DSA-SHAKE-256s. What `FinalPqQuorum` verifies
/// an execution-class approval's `seal` against. Empty when the identity
/// carries no seal, in which case it cannot take part in a sealed quorum.
function activeSealKeyOf(address account) external view returns (bytes memory) {
return _activeSealKey[account];
}
/// @notice `recoveryTransaction`. Authorizes rotating this account's own
/// credentials and nothing else. Empty for a CA.
function recoveryTransactionKeyOf(address account) external view returns (bytes memory) {
return _recoveryTransactionKey[account];
}
/// @notice `recoveryAccess`. Empty for a CA.
function recoveryAccessKeyOf(address account) external view returns (bytes memory) {
return _recoveryAccessKey[account];
}
/// @notice The four commitments, in the order tree 1's leaf wants them.
/// @dev keccak, not SHA3 — these feed `FinalWalletFactory.accountStateLeafHash`,
/// which every other chain verifies with, and that one hashes with keccak.
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 the wallet's CREATE2 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 (`minePqVanityCerts.cjs` is the reference encoder;
* the parity test pins this function against the premined fixtures).
*
* 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
* CA. The CA exclusion is structural, not a role read: an end entity has
* `depth == maxDelegationDepth` (it issues nothing), a CA never does, and
* the depth pair is immutable per version where roles are not.
*
* Lives HERE rather than on `FinalStateTrees` (whose tree 8 consumes it)
* because every input is this contract's storage and the trees contract
* sits against EIP-170.
*/
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) {
// D7 (ruled 2026-09-01): an ISSUER exists in tree 8 under its own
// domain, so its record is stapleable for offline licence
// verification. `certHash` suffices (it covers the whole TBS and
// the verifier holds the cert), `version` makes supersession move
// the leaf, and the third word RESERVES the issuer's own
// certificate-tree anchor — zero until wired. The distinct domain
// does the wallet-admission exclusion the zero projection used to
// do; zero-on-revoke above is now 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 `AccountStateLeaf` order.
/// @dev One word per STAGE, over both of that stage's KEM public keys. The
/// pair is the unit — an account holds both or neither — so committing 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 registered before the encapsulation slots existed hashes the
/// empty string here rather than reverting: `syncIdentities` must keep
/// projecting it, and a leaf that cannot be built is a party that cannot be
/// revoked.
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 encapsulation keys themselves, for a party composing a message.
function kemKeysOf(address account)
external
view
returns (bytes memory activeMlKem, bytes memory activeHqc)
{
return (_activeKemMlKem[account], _activeKemHqc[account]);
}
// ------------------------------------------------------------- senders
/**
* @notice The Final Chain sender a transaction key produces.
* @dev `keccak256(uint8(4) ‖ publicKey)[12:]` — byte-identical to what the
* node derives from a type-0x46 envelope and to the backend's
* `pqTransaction.senderOf`. Pure, so a client can compute it from a
* certificate before the identity is registered.
*/
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, 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. False for a sender no identity claims.
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.
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.
function accountCount() external view returns (uint256) {
return _accounts.length;
}
/// @notice Registered account by index, in registration order.
function accountAt(uint256 index) external view returns (address) {
return _accounts[index];
}
/// @notice Every account carrying every bit in `roleMask`.
/// @dev A view, so the O(n) scan costs nothing. Callers that need this in a
/// transaction should pass the member list explicitly instead — see
/// `FinalPqQuorum`, which takes signers rather than searching for them.
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 that reverts forever with nothing naming the
* roster as the cause.
*/
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 should not be a universal pass.
*/
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.
function isActive(address account) public view returns (bool) {
Identity storage id = _identity[account];
return id.registered && !id.revoked && _withinValidity(id);
}
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;
}
}
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
//
// @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;
}
error ThresholdNotMet(uint256 valid, uint256 required);
error SignersNotAscending(address previous, address next);
error SignerLacksRole(address signer, uint256 roleMask);
error WrongAlgorithm(address signer, uint8 got, uint8 required);
error BadSignature(address signer, uint8 algorithm);
error BadSeal(address signer);
error AnchorAhead(uint64 anchorBlock, uint256 blockNumber);
error AnchorStale(uint64 anchorBlock, uint256 blockNumber);
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);
}
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
//
// @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";
/// @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 {
function enabledChainRefs() external view returns (bytes32[] memory);
}
/// @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 {
function slotKeyLeafOf(address member, uint64 slotIndex) external view returns (bytes32);
}
/// @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 {
function endpointLeafOf(bytes32 endpointId) external view returns (bytes32);
}
/**
* @title FinalStateTrees
* @notice The eight trees. Final Chain's state plane, and the source of truth
* every other chain projects from.
*
* @dev 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 |
*
* ## 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 (2026-09-04)
*
* 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 {
// ---------------------------------------------------------------- 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` — a
/// CONTINUOUS root over this tree replaces the cold-set snapshot the
/// retired `publishIdentityRoot.cjs` ceremony folded off-chain. 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 (WAL-02), 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.
uint8 public constant TREE_COUNT = 8;
/// @notice 2^24 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 in it.
/// A million rows per branch is far past where this design gets replaced
/// by Final Chain proper. Raising any of this later is a migration, not a
/// parameter change: the depth is in every root.
uint256 public constant DEPTH = 24;
uint256 public constant BRANCH_BITS = 4;
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.
uint256 public constant BRANCH_CAPACITY = 1 << BRANCH_DEPTH;
/// @notice Slots per tree, all branches together.
uint256 public constant CAPACITY = 1 << DEPTH;
/// @notice 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-28 tree
/// whose level-24 nodes are the eight tree roots, which is what lets one
/// path prove a leaf against it.
uint256 public constant FOREST_BITS = 4;
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 (user ruling 2026-09-05).
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;
/// @dev Must equal `FinalWalletFactory.DOMAIN_ACCOUNT_STATE_LEAF`. Pinned
/// by the cross-repo parity test; a field reordered on one side and not the
/// other is a root every chain rejects.
///
/// `v02`: `deployedChains` became the `(chainRef, account)` table. A v01
/// leaf and a v02 leaf never share a domain, so a proof built against the
/// retired plane cannot verify against this one by accident.
bytes32 public constant DOMAIN_ACCOUNT_STATE_LEAF =
keccak256("FINAL_ACCOUNT_STATE_LEAF_v02");
bytes32 private constant ACTION_SET_LEAVES = keccak256("FinalStateTrees.setLeaves.v01");
/// @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");
bytes32 public constant ACTION_SET_TREE_WRITER = keccak256("FINAL_STATE_TREES_SET_TREE_WRITER_v01");
bytes32 public constant ACTION_SET_CHAIN_SOURCE = keccak256("FINAL_STATE_TREES_SET_CHAIN_SOURCE_v01");
bytes32 public constant ACTION_SET_SLOT_KEY_SOURCE = keccak256("FINAL_STATE_TREES_SET_SLOT_KEY_SOURCE_v01");
bytes32 public constant ACTION_SET_ENDPOINT_SOURCE = keccak256("FINAL_STATE_TREES_SET_ENDPOINT_SOURCE_v01");
bytes32 public constant ACTION_SEED_COUNTERS = keccak256("FINAL_STATE_TREES_SEED_COUNTERS_v01");
bytes32 public constant ACTION_SET_TYPED_WRITER = keccak256("FINAL_STATE_TREES_SET_TYPED_WRITER_v01");
bytes32 public constant ACTION_SET_CONFIG = keccak256("FINAL_STATE_TREES_SET_CONFIG_v01");
/// @dev Key domains. Both are full-width hashes rather than the packed
/// address they 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");
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");
bytes32 private constant DOMAIN_SLOT_KEY = keccak256("FinalStateTrees.key.slotKey.v01");
bytes32 private constant DOMAIN_ENDPOINT_KEY = keccak256("FinalStateTrees.key.endpoint.v01");
bytes32 private constant DOMAIN_CONFIG_KEY = keccak256("FinalStateTrees.key.config.v01");
/// @notice Leaf domains for the owner index (tree 8, branch 2) and for
/// configuration rows (branch 0 of every tree). The config leaf binds the
/// tree too, so the same row in two trees is two different leaves.
bytes32 public constant DOMAIN_OWNER_INDEX_LEAF = keccak256("FINAL_OWNER_INDEX_LEAF_v01");
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;
/// Raw (untagged) leaf value by tree and slot.
mapping(uint8 => mapping(uint256 => bytes32)) private _leaf;
/// Internal nodes, levels 1..DEPTH. Level 0 is derived from `_leaf`.
mapping(uint8 => mapping(uint256 => mapping(uint256 => bytes32))) private _node;
/// Empty-subtree hash per level, computed once at construction — up to
/// the round root's height, since the forest's empty positions are empty
/// trees.
bytes32[ROUND_DEPTH + 1] private _zero;
/// Permanent slot for a key, 1-based so 0 means unassigned. The slot's top
/// `BRANCH_BITS` are the branch the key lives in.
mapping(uint8 => mapping(bytes32 => uint256)) private _slotPlusOne;
/// The key a slot was handed to — the reverse of `_slotPlusOne`, so any
/// branch enumerates on chain (`keyAt` over `0 .. branchSlotsUsed`) with
/// no log window. One extra word per NEW key, never per update.
mapping(uint8 => mapping(uint256 => bytes32)) private _keyAt;
/// @notice Slots handed out per tree, all branches together.
mapping(uint8 => uint256) public slotsUsed;
/// Slots handed out per branch — the next position in it.
mapping(uint8 => mapping(uint8 => uint256)) private _branchSlotsUsed;
/// The VALUE behind a configuration row (branch 0), by tree and key.
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 {
bytes32[TREE_COUNT + 1] roots;
bytes32 roundRoot;
uint64 blockNumber;
uint64 timestamp;
}
/// @notice Published rounds, 1-indexed. Round 0 is "nothing published".
mapping(uint64 => Round) private _rounds;
/// @notice Highest published round.
uint64 public round;
/// Tree versions as of the last published round.
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 grant 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
event LeavesSet(uint8 indexed treeId, uint256 count, bytes32 newRoot, uint64 treeVersion);
event RoundPublished(uint64 indexed round, uint64 blockNumber, uint64 timestamp);
event TreeConfigured(uint8 indexed treeId, uint256 writerRole, uint256 threshold);
event TreeWriterSet(uint8 indexed treeId, address writer);
event ChainSourceSet(address source);
event SlotKeySourceSet(address source);
event EndpointSourceSet(address source);
/// @notice A fresh plane took over the previous plane's counters.
event CountersSeeded(uint64 round, uint64[] versions);
event TypedWriterSet(address writer);
event ConfigSet(uint8 indexed treeId, bytes32 indexed key, bytes32 value);
// --------------------------------------------------------------- errors
error UnknownTree(uint8 treeId);
error LengthMismatch(uint256 keys, uint256 leaves);
error BranchFull(uint8 treeId, uint8 branch);
error UnknownBranch(uint8 branch);
/// @notice A key already holds a slot in another branch of this tree.
error BranchMismatch(uint8 treeId, bytes32 key, uint8 have, uint8 want);
/// @notice Branch 0 is written by `setConfig` alone.
error ConfigBranchReserved(uint8 treeId);
error SlotKeySourceUnset();
error EndpointSourceUnset();
/// @notice Counters can be seeded only into a plane that has published nothing.
error NotFresh();
error VersionCountMismatch(uint256 given);
error TreeNotConfigured(uint8 treeId);
error NothingToPublish();
error UnknownKey(uint8 treeId, bytes32 key);
error NotAuthorized(address caller);
error NoRounds();
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).
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.
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.
error InvalidChainAccount(bytes32 chainRef, bytes32 account);
// ---------------------------------------------------------- constructor
/**
* @param registry_ The identity registry. Every signer, key and role is
* resolved through it.
* @dev 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.
*/
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
/**
* @dev The configuration gate: 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.
*/
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.
* @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.
*/
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.
*/
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.
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.
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 Take over the previous plane's counters — one `treeVersion` per
* tree (index = treeId, 0 unused) and the published `round` — so a
* redeploy is monotonic for every consumer that compares them
* (rings, explorers, the round feed). NO-WIPE redeploy, ruled
* 2026-09-03. Past rounds' roots stay on the old plane:
* `roundRootAt` below the seed answers zero.
* @dev Configuration authority (bootstrap admin before the seal, registrar
* quorum after), and only while this plane has published nothing.
*/
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.
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.
*
* @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 the round root — one source for
* the fleet, the contracts and the explorer, where the fleet's environment
* used to be a second one.
*/
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 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.
*/
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) {
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 {
bytes32 chainRef;
bytes32 account;
}
/// @notice `FinalWalletFactory.AccountStateLeaf`, field for field.
struct AccountStateLeaf {
address wallet;
bytes32 liveAccess;
bytes32 liveTransaction;
bytes32 recoveryAccess;
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;
bytes32 recoveryKem;
address owner;
bool pqEnabled;
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 what a zero beneficiary resolves through; it
/// replaced a bitmask over registry slots that could only say "may
/// exist", never "as what".
ChainAccount[] deployedChains;
/// @dev Per-chain dormancy verdict, one bit per asset-registry chain
/// slot. Keeps the slot space the bitmask had.
uint32 dormantChains;
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.
*/
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.
*/
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 EOA.
*/
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.
/// Pinned against the factory by test. `deployedChains` rides through
/// `abi.encode` like every other field — head offset, then length and
/// rows — so the table is committed whole and in order.
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
)
);
}
/// @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.
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`).
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.
*/
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 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.
*/
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.
function rootsAt(uint64 which) external view returns (bytes32[TREE_COUNT + 1] memory) {
return _rounds[which].roots;
}
/// @notice One tree's root at one round.
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.
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.
*/
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.
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.
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.
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`.
function keyAt(uint8 treeId, uint256 slot) external view returns (bytes32) {
return _keyAt[treeId][slot];
}
/// @notice Slots handed out in one branch.
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.
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.
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.
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).
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.
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.
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)`.
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.
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.
*/
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.
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.
*/
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 A view, so the backend fetches a proof with one `eth_call` instead of
* rebuilding 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.
*/
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.
function emptyRoot(uint256 level) external view returns (bytes32) {
return _zero[level];
}
/// @notice The tree-1 key a wallet occupies.
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.
*/
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.
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.
*/
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.
*/
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
/// @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.
function _enabledChainRefs() private view returns (bytes32[] memory) {
address source = chainSource;
if (source == address(0)) return new bytes32[](0);
return IChainSource(source).enabledChainRefs();
}
function _assertTree(uint8 treeId) private pure {
if (treeId == 0 || treeId > TREE_COUNT) revert UnknownTree(treeId);
}
function _assertBranch(uint8 branch) private pure {
if (branch >= BRANCH_COUNT) revert UnknownBranch(branch);
}
/// @dev A branch a quorum or a writer may write: any but the config branch.
function _assertDataBranch(uint8 treeId, uint8 branch) private pure {
_assertBranch(branch);
if (branch == BRANCH_CONFIG) revert ConfigBranchReserved(treeId);
}
/// @dev Version + event, the tail of every write door.
function _bump(uint8 treeId, uint256 count) private {
uint64 v = treeVersion[treeId] + 1;
treeVersion[treeId] = v;
emit LeavesSet(treeId, count, liveRoot[treeId], v);
}
/// @dev `keccak256(0x01 ‖ lo ‖ hi)`, the pair sorted — the one node hash.
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));
}
/// @dev The sibling path from a slot up `height` levels.
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;
}
}
/// @dev The forest's leaves: the tree roots at their positions, the
/// empty tree at the rest.
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];
}
}
/// @dev Fold a power-of-two level to its root, in place.
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];
}
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;
}
/// @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.
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;
}
}
abi
[
{
"type": "constructor",
"inputs": [
{
"name": "registry_",
"type": "address",
"internalType": "contract FinalIdentityRegistry"
},
{
"name": "trees_",
"type": "address",
"internalType": "contract FinalStateTrees"
}
],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "ACCOUNT_WORDS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "ACTION_CONFIGURE",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "ACTION_COUNT",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "ACTION_SEAL_RESTORE",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "ALG_SLH_DSA_SHAKE_256S",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "DEFAULT_DELAY_MS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint64",
"internalType": "uint64"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "DOMAIN_ACCOUNT_STATE_REQUEST",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "FINAL_PQ_NATIVE_OWNER",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "MAX_DELAY_MS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint64",
"internalType": "uint64"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "MAX_REQUEST_TTL_MS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint64",
"internalType": "uint64"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "MAX_ROTATION_CANCELS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "MIN_DELAY_MS",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint64",
"internalType": "uint64"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "accountOf",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalAccountLedger.Account",
"components": [
{
"name": "opened",
"type": "bool",
"internalType": "bool"
},
{
"name": "liveAccess",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "liveTransaction",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryAccess",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryTransaction",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "serial",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "liveKem",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryKem",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "kemVersion",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "dormantChains",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "lastActivityAt",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "pqEnabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "frozen",
"type": "bool",
"internalType": "bool"
},
{
"name": "version",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "delayMs",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "threshold",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "cancelThreshold",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "rotationPending",
"type": "bool",
"internalType": "bool"
},
{
"name": "rotationCancels",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "rotationInitiatedAt",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "pendingRecoveryAccess",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "pendingRecoveryTransaction",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "pendingRecoveryKem",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "guardianChangePending",
"type": "bool",
"internalType": "bool"
},
{
"name": "pendingThreshold",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "pendingCancelThreshold",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "guardianChangeInitiatedAt",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "accountOn",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "accountWords",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "words",
"type": "bytes32[15]",
"internalType": "bytes32[15]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "chainAccountsOf",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "tuple[]",
"internalType": "struct FinalStateTrees.ChainAccount[]",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "bytes32",
"internalType": "bytes32"
}
]
}
],
"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": "deploymentsOf",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "bytes32[]",
"internalType": "bytes32[]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "domainSeparator",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "guardiansOf",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "address[]",
"internalType": "address[]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "inactivityThresholdOf",
"inputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"outputs": [
{
"name": "",
"type": "uint64",
"internalType": "uint64"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "isOpen",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "kemVersionOf",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint16",
"internalType": "uint16"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "leafOf",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "tuple",
"internalType": "struct FinalStateTrees.AccountStateLeaf",
"components": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
},
{
"name": "liveAccess",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "liveTransaction",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryAccess",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryTransaction",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "liveKem",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryKem",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "pqEnabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "frozen",
"type": "bool",
"internalType": "bool"
},
{
"name": "deployedChains",
"type": "tuple[]",
"internalType": "struct FinalStateTrees.ChainAccount[]",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"name": "dormantChains",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "version",
"type": "uint64",
"internalType": "uint64"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "nonceOf",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
},
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"outputs": [
{
"name": "",
"type": "uint64",
"internalType": "uint64"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "noncesOf",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "out",
"type": "uint64[11]",
"internalType": "uint64[11]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "observeDeployment",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
},
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "openAccount",
"inputs": [
{
"name": "batch",
"type": "tuple[]",
"internalType": "struct FinalAccountLedger.Genesis[]",
"components": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
},
{
"name": "liveAccess",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "liveTransaction",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryAccess",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryTransaction",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "liveKem",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryKem",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "serial",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "pqEnabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "deployedChains",
"type": "tuple[]",
"internalType": "struct FinalStateTrees.ChainAccount[]",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"name": "delayMs",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "guardians",
"type": "address[]",
"internalType": "address[]"
},
{
"name": "threshold",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "cancelThreshold",
"type": "uint16",
"internalType": "uint16"
}
]
},
{
"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": "openNonce",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint64",
"internalType": "uint64"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "openThreshold",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "openerRole",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "pendingGuardiansOf",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "address[]",
"internalType": "address[]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "recordActivity",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "recordOf",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "account",
"type": "tuple",
"internalType": "struct FinalAccountLedger.Account",
"components": [
{
"name": "opened",
"type": "bool",
"internalType": "bool"
},
{
"name": "liveAccess",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "liveTransaction",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryAccess",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryTransaction",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "serial",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "liveKem",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "recoveryKem",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "kemVersion",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "dormantChains",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "lastActivityAt",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "pqEnabled",
"type": "bool",
"internalType": "bool"
},
{
"name": "frozen",
"type": "bool",
"internalType": "bool"
},
{
"name": "version",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "delayMs",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "threshold",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "cancelThreshold",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "rotationPending",
"type": "bool",
"internalType": "bool"
},
{
"name": "rotationCancels",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "rotationInitiatedAt",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "pendingRecoveryAccess",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "pendingRecoveryTransaction",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "pendingRecoveryKem",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "guardianChangePending",
"type": "bool",
"internalType": "bool"
},
{
"name": "pendingThreshold",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "pendingCancelThreshold",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "guardianChangeInitiatedAt",
"type": "uint64",
"internalType": "uint64"
}
]
},
{
"name": "guardians",
"type": "address[]",
"internalType": "address[]"
},
{
"name": "pendingGuardians",
"type": "address[]",
"internalType": "address[]"
},
{
"name": "nonces",
"type": "uint64[11]",
"internalType": "uint64[11]"
},
{
"name": "deployments",
"type": "bytes32[]",
"internalType": "bytes32[]"
},
{
"name": "chainAccounts",
"type": "tuple[]",
"internalType": "struct FinalStateTrees.ChainAccount[]",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "bytes32",
"internalType": "bytes32"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "refreshDormancy",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
},
{
"name": "slots",
"type": "uint8[]",
"internalType": "uint8[]"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "registry",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract FinalIdentityRegistry"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "requestDigest",
"inputs": [
{
"name": "request",
"type": "tuple",
"internalType": "struct FinalAccountLedger.Request",
"components": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
},
{
"name": "action",
"type": "uint8",
"internalType": "enum FinalAccountLedger.Action"
},
{
"name": "nonce",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "expiresAt",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "payload",
"type": "bytes",
"internalType": "bytes"
}
]
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "restoreAccount",
"inputs": [
{
"name": "r",
"type": "tuple",
"internalType": "struct FinalAccountLedger.Restored",
"components": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
},
{
"name": "words",
"type": "bytes32[15]",
"internalType": "bytes32[15]"
},
{
"name": "guardians",
"type": "address[]",
"internalType": "address[]"
},
{
"name": "pendingGuardians",
"type": "address[]",
"internalType": "address[]"
},
{
"name": "nonces",
"type": "uint64[11]",
"internalType": "uint64[11]"
},
{
"name": "deployments",
"type": "bytes32[]",
"internalType": "bytes32[]"
},
{
"name": "chainAccounts",
"type": "tuple[]",
"internalType": "struct FinalStateTrees.ChainAccount[]",
"components": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"name": "identityLeaf",
"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": "restoreNonce",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint64",
"internalType": "uint64"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "restoreSealed",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "sealRestore",
"inputs": [
{
"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": "serialOf",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "submitRequest",
"inputs": [
{
"name": "request",
"type": "tuple",
"internalType": "struct FinalAccountLedger.Request",
"components": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
},
{
"name": "action",
"type": "uint8",
"internalType": "enum FinalAccountLedger.Action"
},
{
"name": "nonce",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "expiresAt",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "payload",
"type": "bytes",
"internalType": "bytes"
}
]
},
{
"name": "credential",
"type": "tuple",
"internalType": "struct FinalAccountLedger.Credential",
"components": [
{
"name": "pqBlob",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "ownerSignature",
"type": "bytes",
"internalType": "bytes"
}
]
},
{
"name": "guardianAuths",
"type": "tuple[]",
"internalType": "struct FinalAccountLedger.GuardianAuth[]",
"components": [
{
"name": "guardian",
"type": "address",
"internalType": "address"
},
{
"name": "pqBlob",
"type": "bytes",
"internalType": "bytes"
},
{
"name": "signature",
"type": "bytes",
"internalType": "bytes"
}
]
}
],
"outputs": [
{
"name": "version",
"type": "uint64",
"internalType": "uint64"
}
],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "trees",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract FinalStateTrees"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "walletCount",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "walletsBetween",
"inputs": [
{
"name": "from",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "to",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "page",
"type": "address[]",
"internalType": "address[]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "walletsByOwner",
"inputs": [
{
"name": "owner",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "address[]",
"internalType": "address[]"
}
],
"stateMutability": "view"
},
{
"type": "event",
"name": "AccountOpened",
"inputs": [
{
"name": "wallet",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "owner",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "pqEnabled",
"type": "bool",
"indexed": false,
"internalType": "bool"
}
],
"anonymous": false
},
{
"type": "event",
"name": "AccountRestored",
"inputs": [
{
"name": "wallet",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "owner",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "version",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "ActivityRecorded",
"inputs": [
{
"name": "wallet",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "at",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "ChainAccountSet",
"inputs": [
{
"name": "wallet",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "chainRef",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
},
{
"name": "account",
"type": "bytes32",
"indexed": false,
"internalType": "bytes32"
}
],
"anonymous": false
},
{
"type": "event",
"name": "DeploymentObserved",
"inputs": [
{
"name": "wallet",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "chainRef",
"type": "bytes32",
"indexed": true,
"internalType": "bytes32"
}
],
"anonymous": false
},
{
"type": "event",
"name": "DormancyRefreshed",
"inputs": [
{
"name": "wallet",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "dormantChains",
"type": "uint32",
"indexed": false,
"internalType": "uint32"
},
{
"name": "version",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "LedgerConfigured",
"inputs": [
{
"name": "openerRole",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "openThreshold",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"type": "event",
"name": "RequestApplied",
"inputs": [
{
"name": "wallet",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "action",
"type": "uint8",
"indexed": true,
"internalType": "enum FinalAccountLedger.Action"
},
{
"name": "actor",
"type": "uint8",
"indexed": false,
"internalType": "enum FinalAccountLedger.Actor"
},
{
"name": "version",
"type": "uint64",
"indexed": false,
"internalType": "uint64"
}
],
"anonymous": false
},
{
"type": "event",
"name": "RestoreSealed",
"inputs": [],
"anonymous": false
},
{
"type": "error",
"name": "AccountAlreadyOpen",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "AmbiguousCredential",
"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": "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": "CredentialNotPermitted",
"inputs": [
{
"name": "why",
"type": "string",
"internalType": "string"
}
]
},
{
"type": "error",
"name": "DelayNotElapsed",
"inputs": [
{
"name": "readyAt",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "nowSeconds",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"type": "error",
"name": "DuplicateGuardian",
"inputs": [
{
"name": "who",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "ExpiryTooFar",
"inputs": [
{
"name": "span",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "cap",
"type": "uint64",
"internalType": "uint64"
}
]
},
{
"type": "error",
"name": "InvalidChainAccount",
"inputs": [
{
"name": "chainRef",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "account",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"type": "error",
"name": "InvalidDelay",
"inputs": [
{
"name": "delayMs",
"type": "uint64",
"internalType": "uint64"
}
]
},
{
"type": "error",
"name": "InvalidGuardianSet",
"inputs": [
{
"name": "why",
"type": "string",
"internalType": "string"
}
]
},
{
"type": "error",
"name": "InvalidRestore",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "InvalidTransition",
"inputs": [
{
"name": "why",
"type": "string",
"internalType": "string"
}
]
},
{
"type": "error",
"name": "KeyCommitmentMismatch",
"inputs": []
},
{
"type": "error",
"name": "LedgerNotConfigured",
"inputs": []
},
{
"type": "error",
"name": "MalformedBlob",
"inputs": []
},
{
"type": "error",
"name": "NoChainGranted",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "NoCredential",
"inputs": []
},
{
"type": "error",
"name": "NonceMismatch",
"inputs": [
{
"name": "expected",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "supplied",
"type": "uint64",
"internalType": "uint64"
}
]
},
{
"type": "error",
"name": "NotAGuardian",
"inputs": [
{
"name": "who",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "NotAuthorized",
"inputs": [
{
"name": "caller",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "PrecompileUnavailable",
"inputs": [
{
"name": "precompile",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "RequestExpired",
"inputs": [
{
"name": "expiresAt",
"type": "uint64",
"internalType": "uint64"
},
{
"name": "nowSeconds",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"type": "error",
"name": "RestoreIsSealed",
"inputs": []
},
{
"type": "error",
"name": "SignatureInvalid",
"inputs": []
},
{
"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": "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": "required",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"type": "error",
"name": "UnknownAccount",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
]
},
{
"type": "error",
"name": "WrongAlgorithm",
"inputs": [
{
"name": "signer",
"type": "address",
"internalType": "address"
},
{
"name": "got",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "required",
"type": "uint8",
"internalType": "uint8"
}
]
},
{
"type": "error",
"name": "WrongAlgorithmForSlot",
"inputs": [
{
"name": "supplied",
"type": "uint8",
"internalType": "uint8"
}
]
},
{
"type": "error",
"name": "ZeroSerial",
"inputs": [
{
"name": "wallet",
"type": "address",
"internalType": "address"
}
]
}
]read contract
bytecode · 25,951 bytes
0x60806040526004361015610011575f80fd5b5f5f3560e01c806303a2ce341461368d578063178bcc931461364957806323a647341461362a57806329b57c691461360d5780632cd0ef57146135ee5780632f7c88f5146135cb5780632fecac301461359357806333a7c02f1461356e57806334c209d91461257d57806338eb0788146125615780634217f75714612512578063499326fb146124ea57806351510e4a146124af5780635575e6831461247057806357f736951461242157806358137dff146123e45780635d419257146123c85780635e732005146123a857806365e84331146122c25780636b75b6a1146120405780636edb17dc14611ffd578063795e8d4a14611f455780637b10399914611f005780638086b8ba14611e9857806381c0dd5414611e7a57806382a4484714611e3f578063840b625b14611dff5780638af1bee614611db05780638f48dc3f14611d52578063909473a914611d1757806392880ad014611cfb57806394bc4e961461197557806396f191d414611129578063a7ce270314611102578063b19f4805146110c7578063b580a787146110a1578063bbc7ba351461105f578063bd252f1314611020578063bf6bff17146104ba578063c067611114610493578063c06ac6a31461034a578063c2eeb6701461032b578063e3b5908a146102f7578063f487885e146102da578063f698da25146102b7578063fc72c4ce1461029b5763fd6bc5471461021f575f80fd5b34610298576020366003190112610298576102386136ac565b90610241613c54565b506001600160a01b0382168082526003602052604082205460ff16156102855761028161026d84614e29565b604051918291602083526020830190613996565b0390f35b633131bf7960e21b825260045260249150fd5b80fd5b50346102985780600319360112610298576020604051600f8152f35b503461029857806003193601126102985760206102d2614a76565b604051908152f35b503461029857806003193601126102985760209054604051908152f35b50346102985760203660031901126102985761016061031c6103176136ac565b614a0a565b6103296040518092613966565bf35b50346102985780600319360112610298576040516236ee808152602090f35b5034610298576020366003190112610298576103646136ac565b9061036d6141da565b5061016060405161037e8282613b63565b3690376001600160a01b0382168082526003602052604082205490919060ff161561048157610435926104728284610281945260036020526040812085825260046020526104646104586040842097808552600560205261044961042861042261041c61041661041060406103f5818d209e614a0a565b9b8881526008602052818120988152600a60205220996142a2565b9d613b84565b9a613b84565b93613d74565b95613e2f565b976040519b8c809c6137de565b6105606103808c01526105608b01906136d6565b908982036103a08b01526136d6565b926103c0880190613966565b858203610520870152613742565b90838203610540850152613775565b602491633131bf7960e21b8252600452fd5b503461029857806003193601126102985760206001600160401b0360025416604051908152f35b5034610298576060366003190112610298576004356001600160401b03811161101c576104eb903690600401613712565b6104f69291926137b4565b6044356001600160401b03811161086257610515903690600401613712565b9091600154801561100d57600254936001600160401b038516936040518760608201876020840152604080840152526080810160808960051b830101908b908b8d6101de1990360301905b8c8110610ec05750505050926106459592826105926001600160401b0399979461063f9703601f198101835282613b63565b6020815191012060405160208101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301527f1d2159d826062d6d8bb06b1f7449d53275f95106855af24febedc5e55574135860808301528a871660a083015260c082015260c0815261061260e082613b63565b519020908b54927f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d54061516a565b50613bff565b16906001600160401b0319161760025561065e81613c3d565b9261066c6040519485613b63565b818452601f1961067b83613c3d565b01835b818110610ea957505061069082613ea1565b61069983613ea1565b906236ee80855b85811061086657508592919050867f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03163b1561086257836106fd9160405180938192632728f27160e21b835260048301613d15565b0381837f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03165af1908115610857578491610842575b50507f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03163b156108335760405163abf1570d60e01b81529183918391829161078e9190600484016147fd565b0381837f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03165af180156108375761081e575b50506107d382613ea1565b91835b8181106107ea57846107e78561555f565b80f35b806108046101006107fe60019486886149e7565b01613bd7565b61080e8287613d01565b90838060a01b03169052016107d6565b8161082891613b63565b6108335782846107c8565b8280fd5b6040513d84823e3d90fd5b8161084c91613b63565b61083357828761073a565b6040513d86823e3d90fd5b8380fd5b93909594916108768584846149e7565b9761087f613c54565b506001600160a01b036108918a613bd7565b1687526003602052604087209788549060ff8216610e85576101608b016001600160401b036108bf82613beb565b16610e7657506305265c00915b816001600160401b038416108015610e60575b610e44576108f16101408d018d61476f565b905015610e205760e08c0135908115610dfc579060016101a094939261098f8f8e6101c082019061ffff61092483615762565b1615159050610deb575061095661093f610180830183614681565b905061ffff61094f8b8501615762565b911661608d565b9788915b61098961096682613bd7565b91610981610978610180830183614681565b95909201615762565b933691614dc1565b90615b9c565b60ff1916178c5560208d013560018d015560408d013560028d015560608d013560038d015560808d013560048d015560058c015560a08c013560068c015560c08c013560078c015560088b01600161ffff198254161790556109f46101008d01613bd7565b60098c0180546001600160a01b0319166001600160a01b0392909216919091179055610a236101208d01615771565b60098c01805460ff60a01b191691151560a01b60ff60a01b16919091179055895b610a526101408e018e61476f565b9050811015610aa857600190610aa28e610a6b81613bd7565b906020610a9a85610a87610a8d82610a8761014088018861476f565b906147a4565b359461014081019061476f565b0135916153dc565b01610a44565b50949793959899906001600160401b03600a610b3594939d959d600160b01b8360b01b196009830154161760098201550191166001600160401b0319825416178155610b16610afa6101a08601615762565b825461ffff60401b191660409190911b61ffff60401b16178255565b805461ffff60501b191660509290921b61ffff60501b16919091179055565b610b43610180820182614681565b906001600160a01b03610b5584613bd7565b168a52600460205260408a20906001600160401b038311610dd757610b7a83836146e6565b908a5260208a208a5b838110610dbc5750505050610c2981610ba6610ba1610c2e94613bd7565b6147b4565b610bc5610bb282613bd7565b610bbf6101008401613bd7565b90615509565b610bce81613bd7565b610bdb6101008301613bd7565b7f568403fd429f133b4cc18a945d220c328c59a445a8122f240f1d74fd55fb69376020610c0b6101208601615771565b60405190151581526001600160a01b039384169490931692a3613bd7565b614e29565b610c38828a613d01565b52610c438189613d01565b50610c57610c528288886149e7565b613bd7565b6040516382edfbd960e01b81526001600160a01b0391821660048201529190602090839060249082907f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8165afa8015610db1578890610d7b575b60019250610cbf8286613d01565b52610ccb8188886149e7565b60405160208101906020830135825260408301356040820152606083013560608201526080830135608082015260a083013560a082015260c083013560c082015260c08152610d1b60e082613b63565b5190206040519060e060208301937fcc25d3fea88291f95ddfb5590a6b760f02245a0e4ca7c0b69285c6cd26543afd855201356040830152606082015260608152610d67608082613b63565b519020610d748287613d01565b52016106a0565b506020823d8211610da9575b81610d9460209383613b63565b81010312610da55760019151610cb1565b5f80fd5b3d9150610d87565b6040513d8a823e3d90fd5b6001906020610dca85613bd7565b9401938184015501610b83565b634e487b7160e01b8b52604160045260248bfd5b610df490615762565b97889161095a565b60248b610e088f613bd7565b6316efda7d60e21b82526001600160a01b0316600452fd5b60248a610e2c8e613bd7565b633aa293db60e11b82526001600160a01b0316600452fd5b6310b0f87560e11b8a526001600160401b03831660045260248afd5b50639a7ec8006001600160401b038416116108df565b610e7f90613beb565b916108cc565b602489610e918d613bd7565b633b49009360e11b82526001600160a01b0316600452fd5b602090610eb4613c54565b8282890101520161067e565b90919293607f19868203018452843583811215611009578f01906001600160a01b03610eeb836136c2565b1681526020820135602082015260408201356040820152606082013560608201526080820135608082015260a082013560a082015260c082013560c082015260e082013560e082015260018060a01b03610f4861010084016136c2565b16610100820152610120820135801515809103611005576001928260209392610120859401526101c061ffff610ff582610fd9610fa0610f8c610140890189614614565b6101e06101408a01526101e0890191614648565b6001600160401b03610fb56101608a016137ca565b16610160880152610fca6101808901896145a1565b908883036101808a01526145d5565b9583610fe86101a083016149d8565b166101a0870152016149d8565b1691015296019401929101610560565b8f80fd5b8e80fd5b631d087e6160e21b8652600486fd5b5080fd5b50346102985760203660031901126102985760043560ff811680910361101c576040826001600160401b03926020945260078452205416604051908152f35b50346102985760203660031901126102985760209061ffff906008906040906001600160a01b0361108e6136ac565b1681526003855220015416604051908152f35b5034610298576040366003190112610298576107e76110be6136ac565b60243590614833565b503461029857806003193601126102985760206040517f3154287b2470d9f05573ebd18908404f28e212134930a0f6df0005bc02e1c5158152f35b503461029857806003193601126102985760206001600160401b03600e5416604051908152f35b5034610298576060366003190112610298576001600160401b03600435116102985761040060043536036003190112610298576111646137b4565b6044356001600160401b03811161083357611183903690600401613712565b91600e5460ff8160401c166119665760015493841561100d57604080516001600160401b038416602082015280820191909152926001600160a01b036111cc60048035016136c2565b166060850152602460043501956101e0876080870137610204600435019561123b6112136111ff896004356004016145a1565b6104006102608b01526104608a01916145d5565b611228610224600435016004356004016145a1565b898303605f19016102808b0152906145d5565b93600435610244018a6102a089015b600b82106119405750505061126a6103a4600435016004356004016145a1565b888703605f19016104008a015280875290956001600160fb1b03821161193c576113a5966112ce9260051b8091602084013760206112b36103c460043501600435600401614614565b939092018b81038201605f19016104208d0152019190614648565b966112f1816103e46004350135998a61044083015203601f198101835282613b63565b6020815191012060405160208101917fd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f31267583524660408301523060608301527f8ff45d05bf7eaecf1e3489de0ad3d898e5ab54735cd0ca116506a6c8a7438c9560808301526001600160401b03871660a083015260c082015260c0815261137860e082613b63565b519020908a54927f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d54061516a565b506001600160401b036113b9818316613bff565b16906001600160401b03191617600e556113d7600435600401613bd7565b6001600160a01b0381169290919083156118db578386526003602052604086209460ff86541661192857865b600f811061191057505060ff855416158015611904575b80156118ef575b6118db5761143490600435600401614681565b90848752600460205260408720906001600160401b0383116118ac5761145a83836146e6565b90875260208720875b8381106118c0575050505061148361022460043501600435600401614681565b90848752600560205260408720906001600160401b0383116118ac576114a983836146e6565b90875260208720875b8381106118915750505050845b60ff8116600b8110156115215760ff91816114ea6114e56001946102446004350161472a565b613beb565b90878a52600660205260408a20905f526020526001600160401b0360405f2091166001600160401b031982541617905501166114bf565b50509290845b61153c6103a460043501600435600401614681565b90508110156115c757806115676001926115616103a460043501600435600401614681565b90613f70565b3585885260096020526040882081895260205260ff6040892054166115c1576115bb90868952600960205260408920818a52602052604089208460ff1982541617905586895260086020526040892061473b565b01611527565b506115bb565b50838593845b856115e36103c46004350160043560040161476f565b905082101561163757508061163161160c600193610a876103c46004350160043560040161476f565b35602061162884610a876103c46004350160043560040161476f565b013590876153dc565b016115cd565b80949150600986611647846147b4565b018054909290611660906001600160a01b031682615509565b8254847fcfe82510d1c464fb22d59e8531313b14d3894bb1dfdec9de06b77b65afa87a766020604051936001600160401b038160b01c16855260018060a01b031693a36116b46116ae613cb4565b91614e29565b6116bd82613cf4565b526116c781613cf4565b507f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b031690813b1561083357826117199160405180938192632728f27160e21b835260048301613d15565b038183865af1908115611886578391611871575b505060409384519061173f8683613b63565b60018252601f198601968736602085013786519161175d8884613b63565b60018352883660208501378751906382edfbd960e01b82526004820152602081602481885afa908115611867578691611832575b5061179b84613cf4565b526117a582613cf4565b52823b15610862576117cf92849283885180968195829463abf1570d60e01b8452600484016147fd565b03925af1801561182857611813575b50506107e7926117f083519384613b63565b60018352366020840137546001600160a01b031661180d82613cf4565b5261555f565b8161181d91613b63565b6108625783856117de565b84513d84823e3d90fd5b9550506020853d60201161185f575b8161184e60209383613b63565b81010312610da5578894518a611791565b3d9150611841565b88513d88823e3d90fd5b8161187b91613b63565b61101c57818761172d565b6040513d85823e3d90fd5b600190602061189f85613bd7565b94019381840155016114b2565b634e487b7160e01b88526041600452602488fd5b60019060206118ce85613bd7565b9401938184015501611463565b6308e1960960e41b86526004849052602486fd5b5060098501546001600160a01b031615611421565b5060058501541561141a565b8061191d600192846141c9565b358189015501611403565b633b49009360e11b87526004859052602487fd5b8b80fd5b6020806001926001600160401b03611957876137ca565b1681520193019101909161124a565b630eace7db60e21b8552600485fd5b5034610298576080366003190112610298576004356024356044356001600160401b038116809103610862576064356001600160401b038111611cf7576119c0903690600401613712565b6040516328305db160e21b81527f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b0316939290602081600481885afa908115610db1578891611cc8575b508015611c62575b611adf575b50505081611a62575b50816040917f4bd8fc893261db17fa12018122920c62b43b0dc5e8b6b08723911b819f53b1189385558060015582519182526020820152a180f35b60206024916040519283809263342f616360e01b82528760048301525afa908115610857578491611aad575b5081811015611a2757633770da3360e11b845260045260245250604490fd5b90506020813d602011611ad7575b81611ac860209383613b63565b81010312610da557515f611a8e565b3d9150611abb565b604051602081019087825286604082015260408152611aff606082613b63565b519020843b15611c5e57908288949392604051946322f3f44760e11b865260848601917f3154287b2470d9f05573ebd18908404f28e212134930a0f6df0005bc02e1c515600488015260248701526044860152608060648601525260a4830160a060048460051b8601010192828690607e19813603015b838310611bb557505050505050818082859350038183875af1801561083757611ba0575b80611a1e565b81611baa91613b63565b61086257835f611b9a565b919395909294969750609f1960031989830301018652863582811215611c5a576001916020918291611c47918701906001600160a01b03611bf5836136c2565b16815260ff611c05858401613958565b1684820152611c39611c2e611c1d6040850185613f1f565b608060408601526080850191613f50565b926060810190613f1f565b916060818503910152613f50565b98019601930190918b9796959492611b76565b8c80fd5b8780fd5b5060405163f5778b0360e01b8152602081600481885afa908115610db1578891611c99575b506001600160a01b0316331415611a19565b611cbb915060203d602011611cc1575b611cb38183613b63565b810190613f00565b5f611c87565b503d611ca9565b611cea915060203d602011611cf0575b611ce28183613b63565b810190613ee8565b5f611a11565b503d611cd8565b8480fd5b5034610298578060031936011261029857602060405160058152f35b503461029857806003193601126102985760206040517f720d0d938a4dc8953abf8fbbdb7f8551022f52b67ff459f9772a21aa44137c0f8152f35b503461029857604036600319011261029857611d6c6136ac565b906024359160ff8316830361101c5760ff9160409160018060a01b0316815260066020522091165f5260205260206001600160401b0360405f205416604051908152f35b50346102985760203660031901126102985761028190611deb906040906001600160a01b03611ddd6136ac565b168152600560205220613b84565b6040519182916020835260208301906136d6565b503461029857602036600319011261029857600435906001600160401b0382116102985760a060031983360301126102985760206102d28360040161449d565b50346102985760203660031901126102985761028190611deb906040906001600160a01b03611e6c6136ac565b168152600460205220613b84565b50346102985780600319360112610298576020600154604051908152f35b503461029857602036600319011261029857611eb26136ac565b611eba6141da565b506001600160a01b03168082526003602052604082205490919060ff16156104815760408161038093611ef393526003602052206142a2565b61032960405180926137de565b50346102985780600319360112610298576040517f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b03168152602090f35b503461029857602036600319011261029857611f5f6136ac565b6040516101e09291611f718483613b63565b833683376001600160a01b03168083526003602052604083205460ff1615611feb578252600360205260408220825b600f8110611fd357505060405191825b600f8210611fbd57505050f35b6020806001928551815201930191019091611fb0565b80600191830154611fe482866141c9565b5201611fa0565b633131bf7960e21b8352600452602482fd5b5034610298576040366003190112610298576120176136ac565b602435906001600160401b0382116108335761203a6107e7923690600401613712565b91613f8e565b503461029857604036600319011261029857600435906001600160401b038216809203610298576024356001600160401b03811161101c57612086903690600401613712565b6040516328305db160e21b81527f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b03169290602081600481875afa9081156122985785916122a3575b508015612242575b612123575b83600160401b68ff000000000000000019600e541617600e557f9488b39bb791360e870cc4e5329751ad18c820062978074594e27a8fdf4944fc8180a180f35b60405160208101903082526020815261213d604082613b63565b519020833b15611cf7578290604051966322f3f44760e11b885260848801917fa6ed41d5df38aac26edd8244ab692c2c45399b5e8d5e222559b3ef5e6703b85b60048a015260248901526044880152608060648801525260a4850160a060048460051b8801010192828690607e19813603015b8383106121f357505050505050838092818580978582965003925af18015610837576121de575b80806120e3565b816121e891613b63565b61029857805f6121d7565b909192939495609f196003198b83030101865286358281121561223e576001916020918291612230918701906001600160a01b03611bf5836136c2565b9801960194930191906121b0565b8980fd5b5060405163f5778b0360e01b8152602081600481875afa908115612298578591612279575b506001600160a01b03163314156120de565b612292915060203d602011611cc157611cb38183613b63565b5f612267565b6040513d87823e3d90fd5b6122bc915060203d602011611cf057611ce28183613b63565b5f6120d6565b503461029857604036600319011261029857600435602435908091600b548082116123a0575b50809111612398575b6123036122fe8383613e94565b613ea1565b9180600b54905b8381106123275760405160208082528190610281908201886136d6565b8181101561238457600b86527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db981015460019190600388901b1c6001600160a01b031661237d6123778684613e94565b88613d01565b520161230a565b634e487b7160e01b86526032600452602486fd5b9050806122f1565b90505f6122e8565b503461029857806003193601126102985760206040516446494e414c8152f35b50346102985780600319360112610298576020604051600b8152f35b5034610298576020366003190112610298576020906005906040906001600160a01b0361240f6136ac565b16815260038452200154604051908152f35b5034610298576020366003190112610298576102819061245c906040906001600160a01b0361244e6136ac565b168152600a60205220613e2f565b604051918291602083526020830190613775565b50346102985760203660031901126102985760209060ff906040906001600160a01b0361249b6136ac565b168152600384522054166040519015158152f35b503461029857806003193601126102985760206040517fa6ed41d5df38aac26edd8244ab692c2c45399b5e8d5e222559b3ef5e6703b85b8152f35b50346102985760403660031901126102985760206102d26125096136ac565b60243590613dd5565b5034610298576020366003190112610298576102819061254d906040906001600160a01b0361253f6136ac565b168152600860205220613d74565b604051918291602083526020830190613742565b5034610298578060031936011261029857602060405160028152f35b5034610da5576060366003190112610da557600435906001600160401b038211610da557816004019060a06003198436030112610da557602435906001600160401b038211610da55760406003198336030112610da5576044356001600160401b038111610da5576125f3903690600401613712565b90949093906001600160a01b0361260983613bd7565b165f52600360205260405f2060ff81541615613545576001600160a01b0361263084613bd7565b165f52600660205260405f2094602483013595600b871015610da55760ff87165f526020526001600160401b0360405f205416966044840198886001600160401b0361267b8c613beb565b160361351c576001600160401b039899506064850161269981613beb565b8a42169a8b911611156134f4576001600160401b036126b88b92613beb565b16036001600160401b038111612bb55763240c8400906001600160401b03168181116134df575050906126fa92916126ef8761449d565b926004018786614b28565b90965f938761293e57505061271c9061ffff600a84015460401c169088615a9b565b600981019081549060ff8260a81c166129075760ff60a81b19909116600160a81b17909155612791906009905b019661278c6127646001600160401b038a5460b01c16613bff565b895467ffffffffffffffff60b01b191660b09190911b67ffffffffffffffff60b01b16178955565b613bff565b906001600160a01b036127a384613bd7565b165f52600660205260405f2090610da55760ff85165f526020526001600160401b0360405f2091166001600160401b03198254161790556127e2613cb4565b6127ee610c2983613bd7565b6127f782613cf4565b5261280181613cf4565b507f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b0316803b15610da557604051632728f27160e21b8152915f9183918290849082906128589060048301613d15565b03925af180156128fc576128e5575b506128796001600160401b0391613bd7565b945460b01c16926040519160048210156128d1575081526020818101849052936001600160a01b0316907f564e8f9360b1445a420f5373633054ebbd6fed89fbb8c83cf1f33ee7269f573890604090a3604051908152f35b634e487b7160e01b81526021600452602490fd5b6128f29192505f90613b63565b5f90612879612867565b6040513d5f823e3d90fd5b60405163517a083560e11b815260206004820152600e60248201526d30b63932b0b23c90333937bd32b760911b6044820152606490fd5b5f945090600188036129b25750505061295686615b50565b600981019081549060ff8260a81c161561297f576127919260099260ff60a81b19169055612749565b60405163517a083560e11b815260206004820152600a6024820152693737ba10333937bd32b760b11b6044820152606490fd5b919350915f93600288145f14612b2357506129cc88615b50565b600a8101805460ff8160601c16612add576129ec6084606096018861441a565b9080969181010312610da5578435936040602087013596013585158015612ad5575b8015612acd575b612a7b5761279196600996612a6b94600160601b9060ff60601b1916178655600b870155600c860155600d85015582908154906001600160401b0360701b9060701b16906001600160401b0360701b1916179055565b805460ff60681b19169055612749565b60405163517a083560e11b8152602060048201526024808201527f726f746174696f6e206e6565647320616c6c20746872656520636f6d6d69746d604482015263656e747360e01b6064820152608490fd5b508015612a15565b508615612a0e565b60405163517a083560e11b815260206004820152601860248201527f726f746174696f6e20616c72656164792070656e64696e6700000000000000006044820152606490fd5b5f9450919260038803612c625750600a810191612b4b83549161ffff8360501c16908b615a9b565b60ff8160601c1615612c265760681c60ff166002811015612bc95760010160ff8111612bb557825468ffffffffffffffffff60681b191660689190911b60ff60681b161760709390931b67ffffffffffffffff60701b16929092179055612791906009905b612749565b634e487b7160e01b5f52601160045260245ffd5b60405163517a083560e11b815260206004820152602e60248201527f63616e63656c20627564676574206578686175737465643b2074686520726f7460448201526d6174696f6e2070726f636565647360901b6064820152608490fd5b60405163517a083560e11b81526020600482015260136024820152726e6f20726f746174696f6e2070656e64696e6760681b6044820152606490fd5b935091905f93600488145f14612d44575050600a820190815460ff8160601c1615612c2657612ca1816001600160401b03808094169160701c16613c1d565b16808210612d2f575050816009915f6003612791950180546001850155816004850180546002870155816007870193845460068901556008880161ffff612cea81835416614e15565b825461ffff19169116179055600b88018054909155600c88018054909355600d88018054909555865460ff60601b19168755555555805460ff60681b19169055612749565b63be20035760e01b5f5260045260245260445ffd5b5f9450919060058803612f60575050612d5c87615b50565b60ff600a83015460601c16612f0857600e82019060ff825416612ec2576084612d8691018561441a565b8101606082820312610da55781356001600160401b038111610da55782019080601f83011215610da557816020612dbf93359101614dc1565b90612dd86040612dd1602084016149d8565b92016149d8565b91612ded838383612de88b613bd7565b615b9c565b6001600160a01b03612dfe88613bd7565b165f52600560205260405f208151916001600160401b038311612eae57602090612e2884846146e6565b01905f5260205f205f5b838110612e915750505050916001612791949264ffff000000600995845462ffff0060ff199260081b16906cffffffffffffffffffffffff00191617169160181b16176cffffffffffffffff00000000004260281b1617179055612749565b82516001600160a01b031681830155602090920191600101612e32565b634e487b7160e01b5f52604160045260245ffd5b60405163517a083560e11b815260206004820152601f60248201527f677561726469616e206368616e676520616c72656164792070656e64696e67006044820152606490fd5b60405163517a083560e11b815260206004820152602960248201527f677561726469616e207365742070696e6e656420627920612070656e64696e67604482015268103937ba30ba34b7b760b91b6064820152608490fd5b5f9450919060068803613012575050612f869061ffff600a84015460501c169088615a9b565b600e81019081549060ff821615612fcc5760ff19909116909155612791906009906001600160a01b03612fb886613bd7565b165f526005602052612bb060405f206146b6565b60405163517a083560e11b815260206004820152601a60248201527f6e6f20677561726469616e206368616e67652070656e64696e670000000000006044820152606490fd5b5f94509150600787036131095750600e82019081549060ff821615612fcc57600a840191825460ff8160601c16612f08576001600160401b0391828061305d93169160281c16613c1d565b16808210612d2f575050916009916130f06127919460018060a01b0361308289613bd7565b165f9081526005602052604090206130b6906001600160a01b036130a58b613bd7565b165f52600460205260405f20614d5d565b8254815461ffff60401b191660389190911b61ffff60401b161781558254815461ffff60501b191660389190911b61ffff60501b16179055565b805460ff191690556001600160a01b03612fb886613bd7565b5f9350919050600886036133045760048710156132f057600287036132a057600981019081549260ff8460a81c166132475761314a6084602092018761441a565b9080929181010312610da557356001600160a01b0381169390849003610da557831561320d5782546001600160a01b0319166001600160a01b0385811691909117909355909116906131a48261319f87613bd7565b6159b2565b6131b6836131b187613bd7565b615509565b604051916131c5606084613b63565b6002835260403660208501376131da83613cf4565b528151600110156131f957612bb082612791946040600995015261555f565b634e487b7160e01b5f52603260045260245ffd5b60405163517a083560e11b81526020600482015260116024820152701b995dd3dddb995c881c995c5d5a5c9959607a1b6044820152606490fd5b60405163517a083560e11b815260206004820152602a60248201527f612066726f7a656e206163636f756e742063616e6e6f74207472616e736665726044820152690206f776e6572736869760b41b6064820152608490fd5b60405163765a8bc960e11b815260206004820152602160248201527f6f776e6572207472616e73666572206e6565647320746865206c697665206b656044820152607960f81b6064820152608490fd5b634e487b7160e01b5f52602160045260245ffd5b5f925090600a860361340c5760048710156132f057600287036133c75760ff600983015460a81c1661336a5761333f6084604092018561441a565b9080929181010312610da55761279191612bb08260206009940135903561336588613bd7565b6153dc565b60405163517a083560e11b815260206004820152602e60248201527f612066726f7a656e206163636f756e742063616e6e6f74206368616e6765206960448201526d747320636861696e207461626c6560901b6064820152608490fd5b606460405163765a8bc960e11b815260206004820152602060248201527f636861696e206163636f756e74206e6565647320746865206c697665206b65796044820152fd5b5060048610156132f0576002860361349957600981019081549060ff8260a01c166134665761279192740100000000000000000000000000000046494e414c6009936affffffffffffffffffffff60a81b16179055612749565b60405163517a083560e11b815260206004820152600a602482015269616c726561647920505160b01b6044820152606490fd5b60405163765a8bc960e11b815260206004820152601f60248201527f5051206d6967726174696f6e206e6565647320746865206c697665206b6579006044820152606490fd5b630181da8960e31b5f5260045260245260445ffd5b6001600160401b036135068b92613beb565b6365d3805160e11b5f521660045260245260445ffd5b6001600160401b038961352e8c613beb565b90631b3b434760e21b5f526004521660245260445ffd5b61354e83613bd7565b633131bf7960e21b5f9081526001600160a01b0391909116600452602490fd5b34610da5575f366003190112610da557602060ff600e5460401c166040519015158152f35b34610da5576020366003190112610da5576001600160a01b036135b46136ac565b165f52600c602052610281611deb60405f20613b84565b34610da5576020366003190112610da5576135ec6135e76136ac565b613a52565b005b34610da5575f366003190112610da557604051639a7ec8008152602090f35b34610da5575f366003190112610da5576020600b54604051908152f35b34610da5575f366003190112610da5576040516305265c008152602090f35b34610da5575f366003190112610da5576040517f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b03168152602090f35b34610da5575f366003190112610da55760405163240c84008152602090f35b600435906001600160a01b0382168203610da557565b35906001600160a01b0382168203610da557565b90602080835192838152019201905f5b8181106136f35750505090565b82516001600160a01b03168452602093840193909201916001016136e6565b9181601f84011215610da5578235916001600160401b038311610da5576020808501948460051b010111610da557565b90602080835192838152019201905f5b81811061375f5750505090565b8251845260209384019390920191600101613752565b90602080835192838152019201905f5b8181106137925750505090565b8251805185526020908101518186015260409094019390920191600101613785565b602435906001600160401b0382168203610da557565b35906001600160401b0382168203610da557565b6001600160401b0361036080928051151585526020810151602086015260408101516040860152606081015160608601526080810151608086015260a081015160a086015260c081015160c086015260e081015160e086015261ffff6101008201511661010086015263ffffffff61012082015116610120860152826101408201511661014086015260018060a01b036101608201511661016086015261018081015115156101808601526101a081015115156101a0860152826101c0820151166101c0860152826101e0820151166101e086015261ffff6102008201511661020086015261ffff61022082015116610220860152610240810151151561024086015260ff6102608201511661026086015282610280820151166102808601526102a08101516102a08601526102c08101516102c08601526102e08101516102e0860152610300810151151561030086015261ffff6103208201511661032086015261ffff61034082015116610340860152015116910152565b359060ff82168203610da557565b905f905b600b821061397757505050565b6020806001926001600160401b0386511681520193019101909161396a565b9060018060a01b0382511681526020820151602082015260408201516040820152606082015160608201526080820151608082015260a082015160a082015260c082015160c082015260018060a01b0360e08301511660e0820152610100820151151561010082015261012082015115156101208201526101806001600160401b0381613a366101408601516101a06101408701526101a0860190613775565b9463ffffffff6101608201511661016086015201511691015290565b6001600160a01b03165f818152600360205260409020805460ff1615613afd5760080180546001600160401b0342166001600160401b038260301c161015613af8577fbc7135101c9542d8a73b8e35f3dc1d780bd3932fb2c6f7f7b40b27b32d5b0eb8916020916dffffffffffffffff0000000000004260301b16906dffffffffffffffff0000000000001916178091556001600160401b036040519160301c168152a2565b505050565b50633131bf7960e21b5f5260045260245ffd5b6101a081019081106001600160401b03821117612eae57604052565b604081019081106001600160401b03821117612eae57604052565b61038081019081106001600160401b03821117612eae57604052565b90601f801991011681019081106001600160401b03821117612eae57604052565b90604051918281549182825260208201905f5260205f20925f5b818110613bb5575050613bb392500383613b63565b565b84546001600160a01b0316835260019485019487945060209093019201613b9e565b356001600160a01b0381168103610da55790565b356001600160401b0381168103610da55790565b6001600160401b036001911601906001600160401b038211612bb557565b906001600160401b03809116911601906001600160401b038211612bb557565b6001600160401b038111612eae5760051b60200190565b60405190613c6182613b10565b5f610180838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e082015282610100820152826101208201526060610140820152826101608201520152565b60408051909190613cc58382613b63565b6001815291601f1901825f5b828110613cdd57505050565b602090613ce8613c54565b82828501015201613cd1565b8051156131f95760200190565b80518210156131f95760209160051b010190565b602081016020825282518091526040820191602060408360051b8301019401925f915b838310613d4757505050505090565b9091929394602080613d65600193603f198682030187528951613996565b97019301930191939290613d38565b90604051918281549182825260208201905f5260205f20925f5b818110613da3575050613bb392500383613b63565b8454835260019485019487945060209093019201613d8e565b80548210156131f9575f5260205f209060011b01905f90565b6001600160a01b03165f908152600a6020526040812080549290915b838110613e0057505050505f90565b81613e0b8285613dbc565b505414613e1a57600101613df1565b905060019250613e2991613dbc565b50015490565b908154613e3b81613c3d565b92613e496040519485613b63565b81845260208401905f5260205f205f915b838310613e675750505050565b60026020600192604051613e7a81613b2c565b855481528486015483820152815201920192019190613e5a565b91908203918211612bb557565b90613eab82613c3d565b613eb86040519182613b63565b8281528092613ec9601f1991613c3d565b0190602036910137565b80548210156131f9575f5260205f2001905f90565b90816020910312610da557518015158103610da55790565b90816020910312610da557516001600160a01b0381168103610da55790565b9035601e1982360301811215610da55701602081359101916001600160401b038211610da5578136038313610da557565b908060209392818452848401375f828201840152601f01601f1916010190565b91908110156131f95760051b0190565b3560ff81168103610da55790565b6001600160a01b0381165f81815260036020526040902080549194939160ff16156141b657600881018054601081901c63ffffffff169586945f915b8083106141245750505063ffffffff841695861461411b5765ffffffff000060099460101b169065ffffffff0000191617905501916140406140186001600160401b03855460b01c16613bff565b845467ffffffffffffffff60b01b191660b09190911b67ffffffffffffffff60b01b16178455565b61405161404b613cb4565b92614e29565b61405a83613cf4565b5261406482613cf4565b507f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b0316803b15610da557604051632728f27160e21b8152925f9184918290849082906140bb9060048301613d15565b03925af180156128fc577f1b02e1ac9b990e23331c3ec991471a8235ccf6f92c91382bf942bd6d047a5358936040936001600160401b039261410b575b505460b01c1682519182526020820152a2565b5f61411591613b63565b5f6140f8565b50505050505050565b90919560ff61413c614137898587613f70565b613f80565b1690815f52600760205263ffffffff60016001600160401b0360405f205416931b16908215158061418a575b600193501561417c57175b96019190613fca565b63ffffffff91191616614173565b506001600160401b038660301c16928301809311612bb5576001926001600160401b0342161015614168565b84633131bf7960e21b5f5260045260245ffd5b90600f8110156131f95760051b0190565b604051906141e782613b47565b5f610360838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e08201528261020082015282610220820152826102408201528261026082015282610280820152826102a0820152826102c0820152826102e08201528261030082015282610320820152826103408201520152565b906040516142af81613b47565b6103606001600160401b03600e839560ff8154161515855260018101546020860152600281015460408601526003810154606086015260048101546080860152600581015460a0860152600681015460c0860152600781015460e086015282600882015461ffff811661010088015263ffffffff8160101c1661012088015260301c1661014086015282600982015460018060a01b03811661016088015260ff8160a01c16151561018088015260ff8160a81c1615156101a088015260b01c166101c086015282600a8201548181166101e088015261ffff8160401c1661020088015261ffff8160501c1661022088015260ff8160601c16151561024088015260ff8160681c1661026088015260701c16610280860152600b8101546102a0860152600c8101546102c0860152600d8101546102e0860152015460ff8116151561030085015261ffff8160081c1661032085015261ffff8160181c1661034085015260281c16910152565b903590601e1981360301821215610da557018035906001600160401b038211610da557602001918136038313610da557565b6001600160401b038111612eae57601f01601f191660200190565b9291926144738261444c565b916144816040519384613b63565b829481845281830111610da5578281602093845f960137010152565b6144a681613bd7565b906020810135600b811015610da5576144c66001600160401b0391614f0a565b6020815191012091816144e66144df608084018461441a565b3691614467565b602081519101209161450660606144ff60408401613beb565b9201613beb565b926040519560208701977fb898ee3e5af9db61371c27766584da3118def5d64a9cc094018ecc9465856c75895260018060a01b03166040880152606087015260808601521660a08401521660c082015260c0815261456560e082613b63565b519020614570614a76565b9060405190602082019261190160f01b8452602283015260428201526042815261459b606282613b63565b51902090565b9035601e1982360301811215610da55701602081359101916001600160401b038211610da5578160051b36038313610da557565b916020908281520191905f5b8181106145ee5750505090565b909192602080600192838060a01b03614606886136c2565b1681520194019291016145e1565b9035601e1982360301811215610da55701602081359101916001600160401b038211610da5578160061b36038313610da557565b916020908281520191905f5b8181106146615750505090565b823584526020808401359085015260409384019390920191600101614654565b903590601e1981360301821215610da557018035906001600160401b038211610da557602001918160051b36038313610da557565b8054905f8155816146c5575050565b5f5260205f205f5b8281106146d957505050565b5f828201556001016146cd565b90600160401b8111612eae5781549181815582821061470457505050565b5f528060205f20019103905f5b82811061471d57505050565b5f82820155600101614711565b90600b8110156131f95760051b0190565b8054600160401b811015612eae5761475891600182018155613ed3565b819291549060031b91821b915f19901b1916179055565b903590601e1981360301821215610da557018035906001600160401b038211610da557602001918160061b36038313610da557565b91908110156131f95760061b0190565b600b5490600160401b821015612eae576147d9826001613bb39401600b55600b613ed3565b81546001600160a01b0393841660039290921b91821b9390911b1916919091179055565b9091614822614830936008845260016020850152608060408501526080840190613742565b916060818403910152613742565b90565b604051630827e01160e21b81527f0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d5406001600160a01b031690602081600481855afa9081156128fc575f916149a4575b5060405163b3c2628360e01b815233600482015260248101919091529060209082908180604481015b03915afa9081156128fc575f91614985575b5015614972576001600160a01b03165f8181526003602052604090205460ff161561496057805f52600960205260405f20825f5260205260ff60405f20541661495c57805f52600960205260405f20825f5260205260405f20600160ff19825416179055805f5260086020526149368260405f2061473b565b7f01dd03ab26e4cf04b9c9b50f382960c7f90d82ca1dd926bbc6a375c9f7c364ee5f80a3565b5050565b633131bf7960e21b5f5260045260245ffd5b634a0bfec160e01b5f523360045260245ffd5b61499e915060203d602011611cf057611ce28183613b63565b5f6148bd565b90506020813d6020116149d0575b816149bf60209383613b63565b81010312610da557516148ab614882565b3d91506149b2565b359061ffff82168203610da557565b91908110156131f95760051b810135906101de1981360301821215610da5570190565b6040519091610160614a1c8184613b63565b36833781925f5b60ff8116600b811015614a6f5760ff91600191828060a01b0385165f52600660205260405f20815f52602052614a676001600160401b0360405f205416918861472a565b520116614a23565b5050509050565b60405160208101907fa604fff5a27d5951f334ccda7abff3286a8af29caeeb196a6f2b40a1dce7612b82527fc619505002d5007634d27320f0048d5d1c5afce34ccad1dd6375aa12660719e660408201527f2fc2a6c36092b11f026ace43ab546acdba21ec549e096cc237386bf84f6963ae60608201524660808201527f720d0d938a4dc8953abf8fbbdb7f8551022f52b67ff459f9772a21aa44137c0f60a082015260a0815261459b60c082613b63565b94939192906020810135600b811015610da557614b449061577e565b60048110156132f0578015614d5057600314614bac575050614b6e57614b699261582a565b905f90565b60405163765a8bc960e11b81526020600482015260156024820152743737ba10309033bab0b93234b0b71030b1ba34b7b760591b6044820152606490fd5b91939592909450614bbd818061441a565b158015929150614d37575b50614cff57614bd690613bd7565b6001600160a01b03165f90815260046020526040812090949093614bf984613ea1565b915f96865494605e1984360301915b878a1015614cf0578960051b85013583811215610da55784614c2b9187016160d4565b6001600160a01b03165f805b828c8b8310614cbb575b50505015614ca9575f5b828110614c745750614c5d8288613d01565b5260018101809111612bb557600190990198614c08565b816001600160a01b03614c87838b613d01565b511614614c9657600101614c4b565b50636c2d22d760e01b5f5260045260245ffd5b6302333ca160e51b5f5260045260245ffd5b82614cc591613ed3565b905460039190911b1c6001600160a01b031614614ce457600101614c37565b505060015f828c614c41565b60039950975095505050505050565b60405163765a8bc960e11b815260206004820152600f60248201526e33bab0b93234b0b71030b1ba34b7b760891b6044820152606490fd5b614d469150602081019061441a565b905015155f614bc8565b505050505050505f905f90565b81811461495c578154916001600160401b038311612eae57614d7f83836146e6565b5f5260205f20905f5260205f208154915f925b848410614da0575050505050565b600191820180546001600160a01b0390921684860155939091019290614d92565b929190614dcd81613c3d565b93614ddb6040519586613b63565b602085838152019160051b8101928311610da557905b828210614dfd57505050565b60208091614e0a846136c2565b815201910190614df1565b61ffff60019116019061ffff8211612bb557565b614e31613c54565b5060018060a01b0316805f5260036020526001600160401b0360405f206001810154614ef1600283015492600381015460048201546006830154906007840154926009850154978a5f52600a60205263ffffffff600860405f2097015460101c16976040519b614ea08d613b10565b8c5260208c015260408b015260608a0152608089015260a088015260c087015260018060a01b03841660e087015260ff8460a01c16151561010087015260ff8460a81c161515610120870152613e2f565b61014085015261016084015260b01c1661018082015290565b600b8110156132f0578015615145576001811461511e57600281146150ee57600381146150c057600481146150905760058114615054576006811461501f5760078114614fe35760088114614fb657600914614f8f57604051614f6e604082613b63565b60118152701cd95d0b58da185a5b8b5858d8dbdd5b9d607a1b602082015290565b604051614f9d604082613b63565b6009815268656e61626c652d707160b81b602082015290565b50604051614fc5604082613b63565b600e81526d3a3930b739b332b916b7bbb732b960911b602082015290565b50604051614ff2604082613b63565b601881527f66696e616c697a652d677561726469616e2d6368616e67650000000000000000602082015290565b5060405161502e604082613b63565b601681527563616e63656c2d677561726469616e2d6368616e676560501b602082015290565b50604051615063604082613b63565b601881527f696e6974696174652d677561726469616e2d6368616e67650000000000000000602082015290565b5060405161509f604082613b63565b60118152703334b730b634bd3296b937ba30ba34b7b760791b602082015290565b506040516150cf604082613b63565b600f81526e31b0b731b2b616b937ba30ba34b7b760891b602082015290565b506040516150fd604082613b63565b601181527034b734ba34b0ba3296b937ba30ba34b7b760791b602082015290565b5060405161512d604082613b63565b6008815267756e667265657a6560c01b602082015290565b50604051615154604082613b63565b6006815265667265657a6560d01b602082015290565b94939195965f9785156153cd576001600160401b03164381116153b7576102586151948243613e94565b116153a15750604051936020850152602084526151b2604085613b63565b5f945f985b888a1015615377578960051b840135607e1985360301811215610da5578401966151e088613bd7565b6001600160a01b03918216911681101561534b57506151fe87613bd7565b9661523f60208761520e84613bd7565b604051632e4bfa5160e11b81526001600160a01b039091166004820152602481019190915291829081906044820190565b03816001600160a01b038e165afa9081156128fc575f9161532d575b5015615306576020810190600460ff61527384613f80565b16036152d35761528488828c615f52565b1561529f5750505f198114612bb5576001998a0199016151b7565b906152b46152ae60ff93613bd7565b91613f80565b9063bbf82ba360e01b5f5260018060a01b03166004521660245260445ffd5b906152e26152ae60ff93613bd7565b9063587548c360e11b5f5260018060a01b031660045216602452600460445260645ffd5b6153108691613bd7565b63ae8bb03960e01b5f5260018060a01b031660045260245260445ffd5b615345915060203d8111611cf057611ce28183613b63565b5f61525b565b61535488613bd7565b6311641feb60e21b5f9081526004929092526001600160a01b0316602452604490fd5b9850955095505050505080831061538b5750565b826305bc216760e51b5f5260045260245260445ffd5b630ed38fd160e41b5f526004524360245260445ffd5b637b51505560e01b5f526004524360245260445ffd5b631fc460bf60e11b5f5260045ffd5b81158015615501575b6154eb576001600160a01b03165f818152600a6020526040812093905b845481101561546457836154168287613dbc565b50541461542557600101615402565b908060016154586020947fdad5443e7da4ee0fe09d9ddadad5781fdbcdced9d75ef0e3ca82c3c231c9c3e5969798613dbc565b500155604051908152a3565b509091926040519161547583613b2c565b84835260208301918183528054600160401b811015612eae5761549d91600182018155613dbc565b6154d85760016020937fdad5443e7da4ee0fe09d9ddadad5781fdbcdced9d75ef0e3ca82c3c231c9c3e59551835551910155604051908152a3565b634e487b7160e01b5f525f60045260245ffd5b5063483f415960e01b5f5260045260245260445ffd5b5082156153e5565b6001600160a01b039091165f908152600c602052604090208054600160401b811015612eae57826147d98260016155439401855584613ed3565b546001600160a01b039091165f908152600d6020526040902055565b9061556a8251613ea1565b6155748351613ea1565b5f935b80518510156156c2576001600160a01b036155928683613d01565b511660405160208101917fd9678eabd6141fe616532b9df9bfdaedfa09cd81dd3444e4a52f5d3dc6e8872a83526040820152604081526155d3606082613b63565b5190206155e08685613d01565b526001600160a01b036155f38683613d01565b5116946001600160a01b036156088284613d01565b51165f52600c60205260405f2095604051602081019160808201907f9c57ef476e1983208f620f721cb1c8eb1297e8e0315ae8704308bcddc11b864e84526040830152606080830152885480915260a08201985f5260205f20905f905b8082106156a057505050615688816001959697989903601f198101835282613b63565b5190206156958286613d01565b520193929190615577565b82546001600160a01b03168b526020909a019960019283019290910190615665565b509192507f0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c86001600160a01b031691823b15610da557615747926157355f80946040519687958694859363abf1570d60e01b85526008600486015260026024860152608060448601526084850190613742565b83810360031901606485015290613742565b03925af180156128fc576157585750565b5f613bb391613b63565b3561ffff81168103610da55790565b358015158103610da55790565b600b811015806132f0578115801561581e575b81811561580e575b5061580757806132f0576008821480156157fb575b8181156157eb575b506157e4576132f057600481149081156157d9575b506157d557600190565b5f90565b60079150145f6157cb565b5050600290565b90506132f057600a8214816157b6565b50505f600982146157ae565b5050600390565b90506132f0576006821481615799565b50505f60038214615791565b9190615836818061441a565b9050158061599c575b61598d5760208101615851818361441a565b905061591557506158658161586b9261441a565b90616359565b90928351602085012060018201549060038301548083146159065781036158cb575050506158b4916001935b604051916020830152602082526158af604083613b63565b6163eb565b156158bc5790565b6337e8456b60e01b5f5260045ffd5b1490816158f4575b50156158e5576158b491600293615897565b6385f83a9760e01b5f5260045ffd5b60ff91506009015460a01c165f6158d3565b637154496160e11b5f5260045ffd5b9260099092919201549260ff8460a01c16615957576144df61593a916159409461441a565b906162bf565b6001600160a01b039182169116036158bc57600290565b60405163765a8bc960e11b815260206004820152600d60248201526c6163636f756e7420697320505160981b6044820152606490fd5b63a8bd36f560e01b5f5260045ffd5b506159aa602082018261441a565b90501561583f565b6001600160a01b03165f818152600d602052604090205490918115613af8576001600160a01b03165f908152600c6020526040902080549091905f198101908111612bb5575f19820190828211612bb557808203615a5b575b50505080548015615a47575f190190615a248282613ed3565b81549060018060a01b039060031b1b19169055555f52600d6020525f6040812055565b634e487b7160e01b5f52603160045260245ffd5b6147d991615a6c615a879286613ed3565b905460039190911b1c6001600160a01b031692839186613ed3565b5f52600d60205260405f20555f8080615a0b565b91909160048110156132f057600303615b195761ffff16908115615ad35710615ac057565b6302333ca160e51b5f525f60045260245ffd5b60405163765a8bc960e11b815260206004820152601a60248201527f6e6f20677561726469616e2073657420636f6e666967757265640000000000006044820152606490fd5b60405163765a8bc960e11b815260206004820152600e60248201526d677561726469616e73206f6e6c7960901b6044820152606490fd5b60048110156132f057600103615b6257565b60405163765a8bc960e11b81526020600482015260116024820152707265636f76657279206b6579206f6e6c7960781b6044820152606490fd5b90919261ffff83519416908115615e8f57818510615e3d57600385101580615e33575b615dce5761ffff1690811115615d6e578310615d14575f5b838110615be45750505050565b6001600160a01b03615bf68285613d01565b51168015615cce576001600160a01b0383168114615c885760018201808311612bb5575b858110615c2b575050600101615bd7565b816001600160a01b03615c3e8388613d01565b511614615c4d57600101615c1a565b604051631a3e09c760e01b8152602060048201526012602482015271323ab83634b1b0ba329033bab0b93234b0b760711b6044820152606490fd5b604051631a3e09c760e01b815260206004820152601c60248201527f612077616c6c65742063616e6e6f7420677561726420697473656c66000000006044820152606490fd5b604051631a3e09c760e01b815260206004820152601e60248201527f7a65726f2061646472657373206973206e6f74206120677561726469616e00006044820152606490fd5b604051631a3e09c760e01b815260206004820152602b60248201527f63616e63656c207468726573686f6c642065786365656473207468652067756160448201526a1c991a585b8818dbdd5b9d60aa1b6064820152608490fd5b604051631a3e09c760e01b815260206004820152603160248201527f63616e63656c207468726573686f6c64206d757374206578636565642074686560448201527008199c99595e99481d1a1c995cda1bdb19607a1b6064820152608490fd5b604051631a3e09c760e01b815260206004820152603660248201527f7468726565206f72206d6f726520677561726469616e73206e65656420612074604482015275343932b9b437b6321037b31030ba103632b0b9ba101960511b6064820152608490fd5b5060028210615bbf565b604051631a3e09c760e01b8152602060048201526024808201527f7468726573686f6c6420657863656564732074686520677561726469616e20636044820152631bdd5b9d60e21b6064820152608490fd5b50505050615e9957565b604051631a3e09c760e01b815260206004820152602860248201527f677561726469616e7320737570706c69656420776974682061207a65726f20746044820152671a1c995cda1bdb1960c21b6064820152608490fd5b602081830312610da5578051906001600160401b038211610da5570181601f82011215610da557805190615f238261444c565b92615f316040519485613b63565b82845260208383010111610da557815f9260208093018386015e8301015290565b9160208201600460ff615f6483613f80565b161461600c5760ff615f77600592613f80565b1614615f84575050505f90565b5f615f8e83613bd7565b604051639e5adaeb60e01b81526001600160a01b0391821660048201529485916024918391165afa9182156128fc57614830935f93615fe0575b506144df816040615fda93019061441a565b916163eb565b615fda9193506160046144df913d805f833e615ffc8183613b63565b810190615ef0565b939150615fc8565b505f61601783613bd7565b60405163b7af85d760e01b81526001600160a01b0391821660048201529485916024918391165afa9182156128fc57614830935f93616069575b506144df81604061606393019061441a565b916164da565b6160639193506160856144df913d805f833e615ffc8183613b63565b939150616051565b61ffff8116156160ce57617fff6160a79160011c16614e15565b9061ffff6160b482614e15565b1661ffff831611156160c4575090565b6148309150614e15565b50505f90565b906001600160a01b036160e683613bd7565b161561629a576001600160a01b036160fd83613bd7565b165f52600360205260405f209060ff825416156135455760098201549160ff8360a81c1661625f5760208401616133818661441a565b90506161cc575050604083019061614a828561441a565b905061615f5763a8bd36f560e01b5f5260045ffd5b60ff8360a01c166161955761593a6144df61617a938661441a565b6001600160a01b039182169116036158bc5761483090613bd7565b60405163765a8bc960e11b815260206004820152600e60248201526d677561726469616e20697320505160901b6044820152606490fd5b909260a01c60ff1615616224576158656161e6918561441a565b919092600184516020860120910154036158e55761621692604051916020830152602082526158af604083613b63565b156158bc5761483090613bd7565b60405163765a8bc960e11b8152602060048201526012602482015271677561726469616e206973206e6f7420505160701b6044820152606490fd5b60405163765a8bc960e11b815260206004820152601260248201527133bab0b93234b0b71034b990333937bd32b760711b6044820152606490fd5b604082016162a8818461441a565b90501561598d576144df61593a916148309461441a565b60418251036158bc57602082015190606060408401519301515f1a907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116158bc576020935f93608093604051938452868401526040830152606082015282805260015afa156128fc575f516001600160a01b038116156158bc5790565b9080601f83011215610da55781602061483093359101614467565b9190606081106163dc57820191606081840312610da55761637981613958565b9260208201356001600160401b038111610da5578161639991840161633e565b916040810135916001600160401b038311610da55760ff926163bb920161633e565b9316600581036163ca57509190565b630a6b407960e41b5f5260045260245ffd5b6398f4350560e01b5f5260045ffd5b60408151148015906164cd575b6164c657602061644b5f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282613b63565b51906102055afa3d156164bf573d6164628161444c565b906164706040519283613b63565b81523d5f602083013e5b816164b3575b81616489575090565b90506020815191015190602081106164a2575b50151590565b5f199060200360031b1b165f61649c565b80516020149150616480565b606061647a565b5050505f90565b50617460835114156163f8565b610a20815114801590616552575b6164c657602061653b5f948286958160405195869481808701998051918291018b5e8601908282018b8152815193849201905e010190878252805192839101825e0185815203601f198101835282613b63565b51906102045afa3d156164bf573d6164628161444c565b50611213835114156164e856
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 | PUSH1 | 0x80 |
| 0002 | PUSH1 | 0x40 |
| 0004 | MSTORE | |
| 0005 | PUSH1 | 0x04 |
| 0007 | CALLDATASIZE | |
| 0008 | LT | |
| 0009 | ISZERO | |
| 000a | PUSH2 | 0x0011 |
| 000d | JUMPI | |
| 000e | PUSH0 | |
| 000f | DUP1 | |
| 0010 | REVERT | |
| 0011 | JUMPDEST | |
| 0012 | PUSH0 | |
| 0013 | PUSH0 | |
| 0014 | CALLDATALOAD | |
| 0015 | PUSH1 | 0xe0 |
| 0017 | SHR | |
| 0018 | DUP1 | |
| 0019 | PUSH4 | 0x03a2ce34 |
| 001e | EQ | |
| 001f | PUSH2 | 0x368d |
| 0022 | JUMPI | |
| 0023 | DUP1 | |
| 0024 | PUSH4 | 0x178bcc93 |
| 0029 | EQ | |
| 002a | PUSH2 | 0x3649 |
| 002d | JUMPI | |
| 002e | DUP1 | |
| 002f | PUSH4 | 0x23a64734 |
| 0034 | EQ | |
| 0035 | PUSH2 | 0x362a |
| 0038 | JUMPI | |
| 0039 | DUP1 | |
| 003a | PUSH4 | 0x29b57c69 |
| 003f | EQ | |
| 0040 | PUSH2 | 0x360d |
| 0043 | JUMPI | |
| 0044 | DUP1 | |
| 0045 | PUSH4 | 0x2cd0ef57 |
| 004a | EQ | |
| 004b | PUSH2 | 0x35ee |
| 004e | JUMPI | |
| 004f | DUP1 | |
| 0050 | PUSH4 | 0x2f7c88f5 |
| 0055 | EQ | |
| 0056 | PUSH2 | 0x35cb |
| 0059 | JUMPI | |
| 005a | DUP1 | |
| 005b | PUSH4 | 0x2fecac30 |
| 0060 | EQ | |
| 0061 | PUSH2 | 0x3593 |
| 0064 | JUMPI | |
| 0065 | DUP1 | |
| 0066 | PUSH4 | 0x33a7c02f |
| 006b | EQ | |
| 006c | PUSH2 | 0x356e |
| 006f | JUMPI | |
| 0070 | DUP1 | |
| 0071 | PUSH4 | 0x34c209d9 |
| 0076 | EQ | |
| 0077 | PUSH2 | 0x257d |
| 007a | JUMPI | |
| 007b | DUP1 | |
| 007c | PUSH4 | 0x38eb0788 |
| 0081 | EQ | |
| 0082 | PUSH2 | 0x2561 |
| 0085 | JUMPI | |
| 0086 | DUP1 | |
| 0087 | PUSH4 | 0x4217f757 |
| 008c | EQ | |
| 008d | PUSH2 | 0x2512 |
| 0090 | JUMPI | |
| 0091 | DUP1 | |
| 0092 | PUSH4 | 0x499326fb |
| 0097 | EQ | |
| 0098 | PUSH2 | 0x24ea |
| 009b | JUMPI | |
| 009c | DUP1 | |
| 009d | PUSH4 | 0x51510e4a |
| 00a2 | EQ | |
| 00a3 | PUSH2 | 0x24af |
| 00a6 | JUMPI | |
| 00a7 | DUP1 | |
| 00a8 | PUSH4 | 0x5575e683 |
| 00ad | EQ | |
| 00ae | PUSH2 | 0x2470 |
| 00b1 | JUMPI | |
| 00b2 | DUP1 | |
| 00b3 | PUSH4 | 0x57f73695 |
| 00b8 | EQ | |
| 00b9 | PUSH2 | 0x2421 |
| 00bc | JUMPI | |
| 00bd | DUP1 | |
| 00be | PUSH4 | 0x58137dff |
| 00c3 | EQ | |
| 00c4 | PUSH2 | 0x23e4 |
| 00c7 | JUMPI | |
| 00c8 | DUP1 | |
| 00c9 | PUSH4 | 0x5d419257 |
| 00ce | EQ | |
| 00cf | PUSH2 | 0x23c8 |
| 00d2 | JUMPI | |
| 00d3 | DUP1 | |
| 00d4 | PUSH4 | 0x5e732005 |
| 00d9 | EQ | |
| 00da | PUSH2 | 0x23a8 |
| 00dd | JUMPI | |
| 00de | DUP1 | |
| 00df | PUSH4 | 0x65e84331 |
| 00e4 | EQ | |
| 00e5 | PUSH2 | 0x22c2 |
| 00e8 | JUMPI | |
| 00e9 | DUP1 | |
| 00ea | PUSH4 | 0x6b75b6a1 |
| 00ef | EQ | |
| 00f0 | PUSH2 | 0x2040 |
| 00f3 | JUMPI | |
| 00f4 | DUP1 | |
| 00f5 | PUSH4 | 0x6edb17dc |
| 00fa | EQ | |
| 00fb | PUSH2 | 0x1ffd |
| 00fe | JUMPI | |
| 00ff | DUP1 | |
| 0100 | PUSH4 | 0x795e8d4a |
| 0105 | EQ | |
| 0106 | PUSH2 | 0x1f45 |
| 0109 | JUMPI | |
| 010a | DUP1 | |
| 010b | PUSH4 | 0x7b103999 |
| 0110 | EQ | |
| 0111 | PUSH2 | 0x1f00 |
| 0114 | JUMPI | |
| 0115 | DUP1 | |
| 0116 | PUSH4 | 0x8086b8ba |
| 011b | EQ | |
| 011c | PUSH2 | 0x1e98 |
| 011f | JUMPI | |
| 0120 | DUP1 | |
| 0121 | PUSH4 | 0x81c0dd54 |
| 0126 | EQ | |
| 0127 | PUSH2 | 0x1e7a |
| 012a | JUMPI | |
| 012b | DUP1 | |
| 012c | PUSH4 | 0x82a44847 |
| 0131 | EQ | |
| 0132 | PUSH2 | 0x1e3f |
| 0135 | JUMPI | |
| 0136 | DUP1 | |
| 0137 | PUSH4 | 0x840b625b |
| 013c | EQ | |
| 013d | PUSH2 | 0x1dff |
| 0140 | JUMPI | |
| 0141 | DUP1 | |
| 0142 | PUSH4 | 0x8af1bee6 |
| 0147 | EQ | |
| 0148 | PUSH2 | 0x1db0 |
| 014b | JUMPI | |
| 014c | DUP1 | |
| 014d | PUSH4 | 0x8f48dc3f |
| 0152 | EQ | |
| 0153 | PUSH2 | 0x1d52 |
| 0156 | JUMPI | |
| 0157 | DUP1 | |
| 0158 | PUSH4 | 0x909473a9 |
| 015d | EQ | |
| 015e | PUSH2 | 0x1d17 |
| 0161 | JUMPI | |
| 0162 | DUP1 | |
| 0163 | PUSH4 | 0x92880ad0 |
| 0168 | EQ | |
| 0169 | PUSH2 | 0x1cfb |
| 016c | JUMPI | |
| 016d | DUP1 | |
| 016e | PUSH4 | 0x94bc4e96 |
| 0173 | EQ | |
| 0174 | PUSH2 | 0x1975 |
| 0177 | JUMPI | |
| 0178 | DUP1 | |
| 0179 | PUSH4 | 0x96f191d4 |
| 017e | EQ | |
| 017f | PUSH2 | 0x1129 |
| 0182 | JUMPI | |
| 0183 | DUP1 | |
| 0184 | PUSH4 | 0xa7ce2703 |
| 0189 | EQ | |
| 018a | PUSH2 | 0x1102 |
| 018d | JUMPI | |
| 018e | DUP1 | |
| 018f | PUSH4 | 0xb19f4805 |
| 0194 | EQ | |
| 0195 | PUSH2 | 0x10c7 |
| 0198 | JUMPI | |
| 0199 | DUP1 | |
| 019a | PUSH4 | 0xb580a787 |
| 019f | EQ | |
| 01a0 | PUSH2 | 0x10a1 |
| 01a3 | JUMPI | |
| 01a4 | DUP1 | |
| 01a5 | PUSH4 | 0xbbc7ba35 |
| 01aa | EQ | |
| 01ab | PUSH2 | 0x105f |
| 01ae | JUMPI | |
| 01af | DUP1 | |
| 01b0 | PUSH4 | 0xbd252f13 |
| 01b5 | EQ | |
| 01b6 | PUSH2 | 0x1020 |
| 01b9 | JUMPI | |
| 01ba | DUP1 | |
| 01bb | PUSH4 | 0xbf6bff17 |
| 01c0 | EQ | |
| 01c1 | PUSH2 | 0x04ba |
| 01c4 | JUMPI | |
| 01c5 | DUP1 | |
| 01c6 | PUSH4 | 0xc0676111 |
| 01cb | EQ | |
| 01cc | PUSH2 | 0x0493 |
| 01cf | JUMPI | |
| 01d0 | DUP1 | |
| 01d1 | PUSH4 | 0xc06ac6a3 |
| 01d6 | EQ | |
| 01d7 | PUSH2 | 0x034a |
| 01da | JUMPI | |
| 01db | DUP1 | |
| 01dc | PUSH4 | 0xc2eeb670 |
| 01e1 | EQ | |
| 01e2 | PUSH2 | 0x032b |
| 01e5 | JUMPI | |
| 01e6 | DUP1 | |
| 01e7 | PUSH4 | 0xe3b5908a |
| 01ec | EQ | |
| 01ed | PUSH2 | 0x02f7 |
| 01f0 | JUMPI | |
| 01f1 | DUP1 | |
| 01f2 | PUSH4 | 0xf487885e |
| 01f7 | EQ | |
| 01f8 | PUSH2 | 0x02da |
| 01fb | JUMPI | |
| 01fc | DUP1 | |
| 01fd | PUSH4 | 0xf698da25 |
| 0202 | EQ | |
| 0203 | PUSH2 | 0x02b7 |
| 0206 | JUMPI | |
| 0207 | DUP1 | |
| 0208 | PUSH4 | 0xfc72c4ce |
| 020d | EQ | |
| 020e | PUSH2 | 0x029b |
| 0211 | JUMPI | |
| 0212 | PUSH4 | 0xfd6bc547 |
| 0217 | EQ | |
| 0218 | PUSH2 | 0x021f |
| 021b | JUMPI | |
| 021c | PUSH0 | |
| 021d | DUP1 | |
| 021e | REVERT | |
| 021f | JUMPDEST | |
| 0220 | CALLVALUE | |
| 0221 | PUSH2 | 0x0298 |
| 0224 | JUMPI | |
| 0225 | PUSH1 | 0x20 |
| 0227 | CALLDATASIZE | |
| 0228 | PUSH1 | 0x03 |
| 022a | NOT | |
| 022b | ADD | |
| 022c | SLT | |
| 022d | PUSH2 | 0x0298 |
| 0230 | JUMPI | |
| 0231 | PUSH2 | 0x0238 |
| 0234 | PUSH2 | 0x36ac |
| 0237 | JUMP | |
| 0238 | JUMPDEST | |
| 0239 | SWAP1 | |
| 023a | PUSH2 | 0x0241 |
| 023d | PUSH2 | 0x3c54 |
| 0240 | JUMP | |
| 0241 | JUMPDEST | |
| 0242 | POP | |
| 0243 | PUSH1 | 0x01 |
| 0245 | PUSH1 | 0x01 |
| 0247 | PUSH1 | 0xa0 |
| 0249 | SHL | |
| 024a | SUB | |
| 024b | DUP3 | |
| 024c | AND | |
| 024d | DUP1 | |
| 024e | DUP3 | |
| 024f | MSTORE | |
| 0250 | PUSH1 | 0x03 |
| 0252 | PUSH1 | 0x20 |
| 0254 | MSTORE | |
| 0255 | PUSH1 | 0x40 |
| 0257 | DUP3 | |
| 0258 | KECCAK256 | |
| 0259 | SLOAD | |
| 025a | PUSH1 | 0xff |
| 025c | AND | |
| 025d | ISZERO | |
| 025e | PUSH2 | 0x0285 |
| 0261 | JUMPI | |
| 0262 | PUSH2 | 0x0281 |
| 0265 | PUSH2 | 0x026d |
| 0268 | DUP5 | |
| 0269 | PUSH2 | 0x4e29 |
| 026c | JUMP | |
| 026d | JUMPDEST | |
| 026e | PUSH1 | 0x40 |
| 0270 | MLOAD | |
| 0271 | SWAP2 | |
| 0272 | DUP3 | |
| 0273 | SWAP2 | |
| 0274 | PUSH1 | 0x20 |
| 0276 | DUP4 | |
| 0277 | MSTORE | |
| 0278 | PUSH1 | 0x20 |
| 027a | DUP4 | |
| 027b | ADD | |
| 027c | SWAP1 | |
| 027d | PUSH2 | 0x3996 |
| 0280 | JUMP | |
| 0281 | JUMPDEST | |
| 0282 | SUB | |
| 0283 | SWAP1 | |
| 0284 | RETURN | |
| 0285 | JUMPDEST | |
| 0286 | PUSH4 | 0x3131bf79 |
| 028b | PUSH1 | 0xe2 |
| 028d | SHL | |
| 028e | DUP3 | |
| 028f | MSTORE | |
| 0290 | PUSH1 | 0x04 |
| 0292 | MSTORE | |
| 0293 | PUSH1 | 0x24 |
| 0295 | SWAP2 | |
| 0296 | POP | |
| 0297 | REVERT | |
| 0298 | JUMPDEST | |
| 0299 | DUP1 | |
| 029a | REVERT | |
| 029b | JUMPDEST | |
| 029c | POP | |
| 029d | CALLVALUE | |
| 029e | PUSH2 | 0x0298 |
| 02a1 | JUMPI | |
| 02a2 | DUP1 | |
| 02a3 | PUSH1 | 0x03 |
| 02a5 | NOT | |
| 02a6 | CALLDATASIZE | |
| 02a7 | ADD | |
| 02a8 | SLT | |
| 02a9 | PUSH2 | 0x0298 |
| 02ac | JUMPI | |
| 02ad | PUSH1 | 0x20 |
| 02af | PUSH1 | 0x40 |
| 02b1 | MLOAD | |
| 02b2 | PUSH1 | 0x0f |
| 02b4 | DUP2 | |
| 02b5 | MSTORE | |
| 02b6 | RETURN | |
| 02b7 | JUMPDEST | |
| 02b8 | POP | |
| 02b9 | CALLVALUE | |
| 02ba | PUSH2 | 0x0298 |
| 02bd | JUMPI | |
| 02be | DUP1 | |
| 02bf | PUSH1 | 0x03 |
| 02c1 | NOT | |
| 02c2 | CALLDATASIZE | |
| 02c3 | ADD | |
| 02c4 | SLT | |
| 02c5 | PUSH2 | 0x0298 |
| 02c8 | JUMPI | |
| 02c9 | PUSH1 | 0x20 |
| 02cb | PUSH2 | 0x02d2 |
| 02ce | PUSH2 | 0x4a76 |
| 02d1 | JUMP | |
| 02d2 | JUMPDEST | |
| 02d3 | PUSH1 | 0x40 |
| 02d5 | MLOAD | |
| 02d6 | SWAP1 | |
| 02d7 | DUP2 | |
| 02d8 | MSTORE | |
| 02d9 | RETURN | |
| 02da | JUMPDEST | |
| 02db | POP | |
| 02dc | CALLVALUE | |
| 02dd | PUSH2 | 0x0298 |
| 02e0 | JUMPI | |
| 02e1 | DUP1 | |
| 02e2 | PUSH1 | 0x03 |
| 02e4 | NOT | |
| 02e5 | CALLDATASIZE | |
| 02e6 | ADD | |
| 02e7 | SLT | |
| 02e8 | PUSH2 | 0x0298 |
| 02eb | JUMPI | |
| 02ec | PUSH1 | 0x20 |
| 02ee | SWAP1 | |
| 02ef | SLOAD | |
| 02f0 | PUSH1 | 0x40 |
| 02f2 | MLOAD | |
| 02f3 | SWAP1 | |
| 02f4 | DUP2 | |
| 02f5 | MSTORE | |
| 02f6 | RETURN | |
| 02f7 | JUMPDEST | |
| 02f8 | POP | |
| 02f9 | CALLVALUE | |
| 02fa | PUSH2 | 0x0298 |
| 02fd | JUMPI | |
| 02fe | PUSH1 | 0x20 |
| 0300 | CALLDATASIZE | |
| 0301 | PUSH1 | 0x03 |
| 0303 | NOT | |
| 0304 | ADD | |
| 0305 | SLT | |
| 0306 | PUSH2 | 0x0298 |
| 0309 | JUMPI | |
| 030a | PUSH2 | 0x0160 |
| 030d | PUSH2 | 0x031c |
| 0310 | PUSH2 | 0x0317 |
| 0313 | PUSH2 | 0x36ac |
| 0316 | JUMP | |
| 0317 | JUMPDEST | |
| 0318 | PUSH2 | 0x4a0a |
| 031b | JUMP | |
| 031c | JUMPDEST | |
| 031d | PUSH2 | 0x0329 |
| 0320 | PUSH1 | 0x40 |
| 0322 | MLOAD | |
| 0323 | DUP1 | |
| 0324 | SWAP3 | |
| 0325 | PUSH2 | 0x3966 |
| 0328 | JUMP | |
| 0329 | JUMPDEST | |
| 032a | RETURN | |
| 032b | JUMPDEST | |
| 032c | POP | |
| 032d | CALLVALUE | |
| 032e | PUSH2 | 0x0298 |
| 0331 | JUMPI | |
| 0332 | DUP1 | |
| 0333 | PUSH1 | 0x03 |
| 0335 | NOT | |
| 0336 | CALLDATASIZE | |
| 0337 | ADD | |
| 0338 | SLT | |
| 0339 | PUSH2 | 0x0298 |
| 033c | JUMPI | |
| 033d | PUSH1 | 0x40 |
| 033f | MLOAD | |
| 0340 | PUSH3 | 0x36ee80 |
| 0344 | DUP2 | |
| 0345 | MSTORE | |
| 0346 | PUSH1 | 0x20 |
| 0348 | SWAP1 | |
| 0349 | RETURN | |
| 034a | JUMPDEST | |
| 034b | POP | |
| 034c | CALLVALUE | |
| 034d | PUSH2 | 0x0298 |
| 0350 | JUMPI | |
| 0351 | PUSH1 | 0x20 |
| 0353 | CALLDATASIZE | |
| 0354 | PUSH1 | 0x03 |
| 0356 | NOT | |
| 0357 | ADD | |
| 0358 | SLT | |
| 0359 | PUSH2 | 0x0298 |
| 035c | JUMPI | |
| 035d | PUSH2 | 0x0364 |
| 0360 | PUSH2 | 0x36ac |
| 0363 | JUMP | |
| 0364 | JUMPDEST | |
| 0365 | SWAP1 | |
| 0366 | PUSH2 | 0x036d |
| 0369 | PUSH2 | 0x41da |
| 036c | JUMP | |
| 036d | JUMPDEST | |
| 036e | POP | |
| 036f | PUSH2 | 0x0160 |
| 0372 | PUSH1 | 0x40 |
| 0374 | MLOAD | |
| 0375 | PUSH2 | 0x037e |
| 0378 | DUP3 | |
| 0379 | DUP3 | |
| 037a | PUSH2 | 0x3b63 |
| 037d | JUMP | |
| 037e | JUMPDEST | |
| 037f | CALLDATASIZE | |
| 0380 | SWAP1 | |
| 0381 | CALLDATACOPY | |
| 0382 | PUSH1 | 0x01 |
| 0384 | PUSH1 | 0x01 |
| 0386 | PUSH1 | 0xa0 |
| 0388 | SHL | |
| 0389 | SUB | |
| 038a | DUP3 | |
| 038b | AND | |
| 038c | DUP1 | |
| 038d | DUP3 | |
| 038e | MSTORE | |
| 038f | PUSH1 | 0x03 |
| 0391 | PUSH1 | 0x20 |
| 0393 | MSTORE | |
| 0394 | PUSH1 | 0x40 |
| 0396 | DUP3 | |
| 0397 | KECCAK256 | |
| 0398 | SLOAD | |
| 0399 | SWAP1 | |
| 039a | SWAP2 | |
| 039b | SWAP1 | |
| 039c | PUSH1 | 0xff |
| 039e | AND | |
| 039f | ISZERO | |
| 03a0 | PUSH2 | 0x0481 |
| 03a3 | JUMPI | |
| 03a4 | PUSH2 | 0x0435 |
| 03a7 | SWAP3 | |
| 03a8 | PUSH2 | 0x0472 |
| 03ab | DUP3 | |
| 03ac | DUP5 | |
| 03ad | PUSH2 | 0x0281 |
| 03b0 | SWAP5 | |
| 03b1 | MSTORE | |
| 03b2 | PUSH1 | 0x03 |
| 03b4 | PUSH1 | 0x20 |
| 03b6 | MSTORE | |
| 03b7 | PUSH1 | 0x40 |
| 03b9 | DUP2 | |
| 03ba | KECCAK256 | |
| 03bb | DUP6 | |
| 03bc | DUP3 | |
| 03bd | MSTORE | |
| 03be | PUSH1 | 0x04 |
| 03c0 | PUSH1 | 0x20 |
| 03c2 | MSTORE | |
| 03c3 | PUSH2 | 0x0464 |
| 03c6 | PUSH2 | 0x0458 |
| 03c9 | PUSH1 | 0x40 |
| 03cb | DUP5 | |
| 03cc | KECCAK256 | |
| 03cd | SWAP8 | |
| 03ce | DUP1 | |
| 03cf | DUP6 | |
| 03d0 | MSTORE | |
| 03d1 | PUSH1 | 0x05 |
| 03d3 | PUSH1 | 0x20 |
| 03d5 | MSTORE | |
| 03d6 | PUSH2 | 0x0449 |
| 03d9 | PUSH2 | 0x0428 |
| 03dc | PUSH2 | 0x0422 |
| 03df | PUSH2 | 0x041c |
| 03e2 | PUSH2 | 0x0416 |
| 03e5 | PUSH2 | 0x0410 |
| 03e8 | PUSH1 | 0x40 |
| 03ea | PUSH2 | 0x03f5 |
| 03ed | DUP2 | |
| 03ee | DUP14 | |
| 03ef | KECCAK256 | |
| 03f0 | SWAP15 | |
| 03f1 | PUSH2 | 0x4a0a |
| 03f4 | JUMP | |
| 03f5 | JUMPDEST | |
| 03f6 | SWAP12 | |
| 03f7 | DUP9 | |
| 03f8 | DUP2 | |
| 03f9 | MSTORE | |
| 03fa | PUSH1 | 0x08 |
| 03fc | PUSH1 | 0x20 |
| 03fe | MSTORE | |
| 03ff | DUP2 | |
| 0400 | DUP2 | |
| 0401 | KECCAK256 | |
| 0402 | SWAP9 | |
| 0403 | DUP2 | |
| 0404 | MSTORE | |
| 0405 | PUSH1 | 0x0a |
| 0407 | PUSH1 | 0x20 |
| 0409 | MSTORE | |
| 040a | KECCAK256 | |
| 040b | SWAP10 | |
| 040c | PUSH2 | 0x42a2 |
| 040f | JUMP | |
| 0410 | JUMPDEST | |
| 0411 | SWAP14 | |
| 0412 | PUSH2 | 0x3b84 |
| 0415 | JUMP | |
| 0416 | JUMPDEST | |
| 0417 | SWAP11 | |
| 0418 | PUSH2 | 0x3b84 |
| 041b | JUMP | |
| 041c | JUMPDEST | |
| 041d | SWAP4 | |
| 041e | PUSH2 | 0x3d74 |
| 0421 | JUMP | |
| 0422 | JUMPDEST | |
| 0423 | SWAP6 | |
| 0424 | PUSH2 | 0x3e2f |
| 0427 | JUMP | |
| 0428 | JUMPDEST | |
| 0429 | SWAP8 | |
| 042a | PUSH1 | 0x40 |
| 042c | MLOAD | |
| 042d | SWAP12 | |
| 042e | DUP13 | |
| 042f | DUP1 | |
| 0430 | SWAP13 | |
| 0431 | PUSH2 | 0x37de |
| 0434 | JUMP | |
| 0435 | JUMPDEST | |
| 0436 | PUSH2 | 0x0560 |
| 0439 | PUSH2 | 0x0380 |
| 043c | DUP13 | |
| 043d | ADD | |
| 043e | MSTORE | |
| 043f | PUSH2 | 0x0560 |
| 0442 | DUP12 | |
| 0443 | ADD | |
| 0444 | SWAP1 | |
| 0445 | PUSH2 | 0x36d6 |
| 0448 | JUMP | |
| 0449 | JUMPDEST | |
| 044a | SWAP1 | |
| 044b | DUP10 | |
| 044c | DUP3 | |
| 044d | SUB | |
| 044e | PUSH2 | 0x03a0 |
| 0451 | DUP12 | |
| 0452 | ADD | |
| 0453 | MSTORE | |
| 0454 | PUSH2 | 0x36d6 |
| 0457 | JUMP | |
| 0458 | JUMPDEST | |
| 0459 | SWAP3 | |
| 045a | PUSH2 | 0x03c0 |
| 045d | DUP9 | |
| 045e | ADD | |
| 045f | SWAP1 | |
| 0460 | PUSH2 | 0x3966 |
| 0463 | JUMP | |
| 0464 | JUMPDEST | |
| 0465 | DUP6 | |
| 0466 | DUP3 | |
| 0467 | SUB | |
| 0468 | PUSH2 | 0x0520 |
| 046b | DUP8 | |
| 046c | ADD | |
| 046d | MSTORE | |
| 046e | PUSH2 | 0x3742 |
| 0471 | JUMP | |
| 0472 | JUMPDEST | |
| 0473 | SWAP1 | |
| 0474 | DUP4 | |
| 0475 | DUP3 | |
| 0476 | SUB | |
| 0477 | PUSH2 | 0x0540 |
| 047a | DUP6 | |
| 047b | ADD | |
| 047c | MSTORE | |
| 047d | PUSH2 | 0x3775 |
| 0480 | JUMP | |
| 0481 | JUMPDEST | |
| 0482 | PUSH1 | 0x24 |
| 0484 | SWAP2 | |
| 0485 | PUSH4 | 0x3131bf79 |
| 048a | PUSH1 | 0xe2 |
| 048c | SHL | |
| 048d | DUP3 | |
| 048e | MSTORE | |
| 048f | PUSH1 | 0x04 |
| 0491 | MSTORE | |
| 0492 | REVERT | |
| 0493 | JUMPDEST | |
| 0494 | POP | |
| 0495 | CALLVALUE | |
| 0496 | PUSH2 | 0x0298 |
| 0499 | JUMPI | |
| 049a | DUP1 | |
| 049b | PUSH1 | 0x03 |
| 049d | NOT | |
| 049e | CALLDATASIZE | |
| 049f | ADD | |
| 04a0 | SLT | |
| 04a1 | PUSH2 | 0x0298 |
| 04a4 | JUMPI | |
| 04a5 | PUSH1 | 0x20 |
| 04a7 | PUSH1 | 0x01 |
| 04a9 | PUSH1 | 0x01 |
| 04ab | PUSH1 | 0x40 |
| 04ad | SHL | |
| 04ae | SUB | |
| 04af | PUSH1 | 0x02 |
| 04b1 | SLOAD | |
| 04b2 | AND | |
| 04b3 | PUSH1 | 0x40 |
| 04b5 | MLOAD | |
| 04b6 | SWAP1 | |
| 04b7 | DUP2 | |
| 04b8 | MSTORE | |
| 04b9 | RETURN | |
| 04ba | JUMPDEST | |
| 04bb | POP | |
| 04bc | CALLVALUE | |
| 04bd | PUSH2 | 0x0298 |
| 04c0 | JUMPI | |
| 04c1 | PUSH1 | 0x60 |
| 04c3 | CALLDATASIZE | |
| 04c4 | PUSH1 | 0x03 |
| 04c6 | NOT | |
| 04c7 | ADD | |
| 04c8 | SLT | |
| 04c9 | PUSH2 | 0x0298 |
| 04cc | JUMPI | |
| 04cd | PUSH1 | 0x04 |
| 04cf | CALLDATALOAD | |
| 04d0 | PUSH1 | 0x01 |
| 04d2 | PUSH1 | 0x01 |
| 04d4 | PUSH1 | 0x40 |
| 04d6 | SHL | |
| 04d7 | SUB | |
| 04d8 | DUP2 | |
| 04d9 | GT | |
| 04da | PUSH2 | 0x101c |
| 04dd | JUMPI | |
| 04de | PUSH2 | 0x04eb |
| 04e1 | SWAP1 | |
| 04e2 | CALLDATASIZE | |
| 04e3 | SWAP1 | |
| 04e4 | PUSH1 | 0x04 |
| 04e6 | ADD | |
| 04e7 | PUSH2 | 0x3712 |
| 04ea | JUMP | |
| 04eb | JUMPDEST | |
| 04ec | PUSH2 | 0x04f6 |
| 04ef | SWAP3 | |
| 04f0 | SWAP2 | |
| 04f1 | SWAP3 | |
| 04f2 | PUSH2 | 0x37b4 |
| 04f5 | JUMP | |
| 04f6 | JUMPDEST | |
| 04f7 | PUSH1 | 0x44 |
| 04f9 | CALLDATALOAD | |
| 04fa | PUSH1 | 0x01 |
| 04fc | PUSH1 | 0x01 |
| 04fe | PUSH1 | 0x40 |
| 0500 | SHL | |
| 0501 | SUB | |
| 0502 | DUP2 | |
| 0503 | GT | |
| 0504 | PUSH2 | 0x0862 |
| 0507 | JUMPI | |
| 0508 | PUSH2 | 0x0515 |
| 050b | SWAP1 | |
| 050c | CALLDATASIZE | |
| 050d | SWAP1 | |
| 050e | PUSH1 | 0x04 |
| 0510 | ADD | |
| 0511 | PUSH2 | 0x3712 |
| 0514 | JUMP | |
| 0515 | JUMPDEST | |
| 0516 | SWAP1 | |
| 0517 | SWAP2 | |
| 0518 | PUSH1 | 0x01 |
| 051a | SLOAD | |
| 051b | DUP1 | |
| 051c | ISZERO | |
| 051d | PUSH2 | 0x100d |
| 0520 | JUMPI | |
| 0521 | PUSH1 | 0x02 |
| 0523 | SLOAD | |
| 0524 | SWAP4 | |
| 0525 | PUSH1 | 0x01 |
| 0527 | PUSH1 | 0x01 |
| 0529 | PUSH1 | 0x40 |
| 052b | SHL | |
| 052c | SUB | |
| 052d | DUP6 | |
| 052e | AND | |
| 052f | SWAP4 | |
| 0530 | PUSH1 | 0x40 |
| 0532 | MLOAD | |
| 0533 | DUP8 | |
| 0534 | PUSH1 | 0x60 |
| 0536 | DUP3 | |
| 0537 | ADD | |
| 0538 | DUP8 | |
| 0539 | PUSH1 | 0x20 |
| 053b | DUP5 | |
| 053c | ADD | |
| 053d | MSTORE | |
| 053e | PUSH1 | 0x40 |
| 0540 | DUP1 | |
| 0541 | DUP5 | |
| 0542 | ADD | |
| 0543 | MSTORE | |
| 0544 | MSTORE | |
| 0545 | PUSH1 | 0x80 |
| 0547 | DUP2 | |
| 0548 | ADD | |
| 0549 | PUSH1 | 0x80 |
| 054b | DUP10 | |
| 054c | PUSH1 | 0x05 |
| 054e | SHL | |
| 054f | DUP4 | |
| 0550 | ADD | |
| 0551 | ADD | |
| 0552 | SWAP1 | |
| 0553 | DUP12 | |
| 0554 | SWAP1 | |
| 0555 | DUP12 | |
| 0556 | DUP14 | |
| 0557 | PUSH2 | 0x01de |
| 055a | NOT | |
| 055b | SWAP1 | |
| 055c | CALLDATASIZE | |
| 055d | SUB | |
| 055e | ADD | |
| 055f | SWAP1 | |
| 0560 | JUMPDEST | |
| 0561 | DUP13 | |
| 0562 | DUP2 | |
| 0563 | LT | |
| 0564 | PUSH2 | 0x0ec0 |
| 0567 | JUMPI | |
| 0568 | POP | |
| 0569 | POP | |
| 056a | POP | |
| 056b | POP | |
| 056c | SWAP3 | |
| 056d | PUSH2 | 0x0645 |
| 0570 | SWAP6 | |
| 0571 | SWAP3 | |
| 0572 | DUP3 | |
| 0573 | PUSH2 | 0x0592 |
| 0576 | PUSH1 | 0x01 |
| 0578 | PUSH1 | 0x01 |
| 057a | PUSH1 | 0x40 |
| 057c | SHL | |
| 057d | SUB | |
| 057e | SWAP10 | |
| 057f | SWAP8 | |
| 0580 | SWAP5 | |
| 0581 | PUSH2 | 0x063f |
| 0584 | SWAP8 | |
| 0585 | SUB | |
| 0586 | PUSH1 | 0x1f |
| 0588 | NOT | |
| 0589 | DUP2 | |
| 058a | ADD | |
| 058b | DUP4 | |
| 058c | MSTORE | |
| 058d | DUP3 | |
| 058e | PUSH2 | 0x3b63 |
| 0591 | JUMP | |
| 0592 | JUMPDEST | |
| 0593 | PUSH1 | 0x20 |
| 0595 | DUP2 | |
| 0596 | MLOAD | |
| 0597 | SWAP2 | |
| 0598 | ADD | |
| 0599 | KECCAK256 | |
| 059a | PUSH1 | 0x40 |
| 059c | MLOAD | |
| 059d | PUSH1 | 0x20 |
| 059f | DUP2 | |
| 05a0 | ADD | |
| 05a1 | SWAP2 | |
| 05a2 | PUSH32 | 0xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675 |
| 05c3 | DUP4 | |
| 05c4 | MSTORE | |
| 05c5 | CHAINID | |
| 05c6 | PUSH1 | 0x40 |
| 05c8 | DUP4 | |
| 05c9 | ADD | |
| 05ca | MSTORE | |
| 05cb | ADDRESS | |
| 05cc | PUSH1 | 0x60 |
| 05ce | DUP4 | |
| 05cf | ADD | |
| 05d0 | MSTORE | |
| 05d1 | PUSH32 | 0x1d2159d826062d6d8bb06b1f7449d53275f95106855af24febedc5e555741358 |
| 05f2 | PUSH1 | 0x80 |
| 05f4 | DUP4 | |
| 05f5 | ADD | |
| 05f6 | MSTORE | |
| 05f7 | DUP11 | |
| 05f8 | DUP8 | |
| 05f9 | AND | |
| 05fa | PUSH1 | 0xa0 |
| 05fc | DUP4 | |
| 05fd | ADD | |
| 05fe | MSTORE | |
| 05ff | PUSH1 | 0xc0 |
| 0601 | DUP3 | |
| 0602 | ADD | |
| 0603 | MSTORE | |
| 0604 | PUSH1 | 0xc0 |
| 0606 | DUP2 | |
| 0607 | MSTORE | |
| 0608 | PUSH2 | 0x0612 |
| 060b | PUSH1 | 0xe0 |
| 060d | DUP3 | |
| 060e | PUSH2 | 0x3b63 |
| 0611 | JUMP | |
| 0612 | JUMPDEST | |
| 0613 | MLOAD | |
| 0614 | SWAP1 | |
| 0615 | KECCAK256 | |
| 0616 | SWAP1 | |
| 0617 | DUP12 | |
| 0618 | SLOAD | |
| 0619 | SWAP3 | |
| 061a | PUSH32 | 0x0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d540 |
| 063b | PUSH2 | 0x516a |
| 063e | JUMP | |
| 063f | JUMPDEST | |
| 0640 | POP | |
| 0641 | PUSH2 | 0x3bff |
| 0644 | JUMP | |
| 0645 | JUMPDEST | |
| 0646 | AND | |
| 0647 | SWAP1 | |
| 0648 | PUSH1 | 0x01 |
| 064a | PUSH1 | 0x01 |
| 064c | PUSH1 | 0x40 |
| 064e | SHL | |
| 064f | SUB | |
| 0650 | NOT | |
| 0651 | AND | |
| 0652 | OR | |
| 0653 | PUSH1 | 0x02 |
| 0655 | SSTORE | |
| 0656 | PUSH2 | 0x065e |
| 0659 | DUP2 | |
| 065a | PUSH2 | 0x3c3d |
| 065d | JUMP | |
| 065e | JUMPDEST | |
| 065f | SWAP3 | |
| 0660 | PUSH2 | 0x066c |
| 0663 | PUSH1 | 0x40 |
| 0665 | MLOAD | |
| 0666 | SWAP5 | |
| 0667 | DUP6 | |
| 0668 | PUSH2 | 0x3b63 |
| 066b | JUMP | |
| 066c | JUMPDEST | |
| 066d | DUP2 | |
| 066e | DUP5 | |
| 066f | MSTORE | |
| 0670 | PUSH1 | 0x1f |
| 0672 | NOT | |
| 0673 | PUSH2 | 0x067b |
| 0676 | DUP4 | |
| 0677 | PUSH2 | 0x3c3d |
| 067a | JUMP | |
| 067b | JUMPDEST | |
| 067c | ADD | |
| 067d | DUP4 | |
| 067e | JUMPDEST | |
| 067f | DUP2 | |
| 0680 | DUP2 | |
| 0681 | LT | |
| 0682 | PUSH2 | 0x0ea9 |
| 0685 | JUMPI | |
| 0686 | POP | |
| 0687 | POP | |
| 0688 | PUSH2 | 0x0690 |
| 068b | DUP3 | |
| 068c | PUSH2 | 0x3ea1 |
| 068f | JUMP | |
| 0690 | JUMPDEST | |
| 0691 | PUSH2 | 0x0699 |
| 0694 | DUP4 | |
| 0695 | PUSH2 | 0x3ea1 |
| 0698 | JUMP | |
| 0699 | JUMPDEST | |
| 069a | SWAP1 | |
| 069b | PUSH3 | 0x36ee80 |
| 069f | DUP6 | |
| 06a0 | JUMPDEST | |
| 06a1 | DUP6 | |
| 06a2 | DUP2 | |
| 06a3 | LT | |
| 06a4 | PUSH2 | 0x0866 |
| 06a7 | JUMPI | |
| 06a8 | POP | |
| 06a9 | DUP6 | |
| 06aa | SWAP3 | |
| 06ab | SWAP2 | |
| 06ac | SWAP1 | |
| 06ad | POP | |
| 06ae | DUP7 | |
| 06af | PUSH32 | 0x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8 |
| 06d0 | PUSH1 | 0x01 |
| 06d2 | PUSH1 | 0x01 |
| 06d4 | PUSH1 | 0xa0 |
| 06d6 | SHL | |
| 06d7 | SUB | |
| 06d8 | AND | |
| 06d9 | EXTCODESIZE | |
| 06da | ISZERO | |
| 06db | PUSH2 | 0x0862 |
| 06de | JUMPI | |
| 06df | DUP4 | |
| 06e0 | PUSH2 | 0x06fd |
| 06e3 | SWAP2 | |
| 06e4 | PUSH1 | 0x40 |
| 06e6 | MLOAD | |
| 06e7 | DUP1 | |
| 06e8 | SWAP4 | |
| 06e9 | DUP2 | |
| 06ea | SWAP3 | |
| 06eb | PUSH4 | 0x2728f271 |
| 06f0 | PUSH1 | 0xe2 |
| 06f2 | SHL | |
| 06f3 | DUP4 | |
| 06f4 | MSTORE | |
| 06f5 | PUSH1 | 0x04 |
| 06f7 | DUP4 | |
| 06f8 | ADD | |
| 06f9 | PUSH2 | 0x3d15 |
| 06fc | JUMP | |
| 06fd | JUMPDEST | |
| 06fe | SUB | |
| 06ff | DUP2 | |
| 0700 | DUP4 | |
| 0701 | PUSH32 | 0x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8 |
| 0722 | PUSH1 | 0x01 |
| 0724 | PUSH1 | 0x01 |
| 0726 | PUSH1 | 0xa0 |
| 0728 | SHL | |
| 0729 | SUB | |
| 072a | AND | |
| 072b | GAS | |
| 072c | CALL | |
| 072d | SWAP1 | |
| 072e | DUP2 | |
| 072f | ISZERO | |
| 0730 | PUSH2 | 0x0857 |
| 0733 | JUMPI | |
| 0734 | DUP5 | |
| 0735 | SWAP2 | |
| 0736 | PUSH2 | 0x0842 |
| 0739 | JUMPI | |
| 073a | JUMPDEST | |
| 073b | POP | |
| 073c | POP | |
| 073d | PUSH32 | 0x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8 |
| 075e | PUSH1 | 0x01 |
| 0760 | PUSH1 | 0x01 |
| 0762 | PUSH1 | 0xa0 |
| 0764 | SHL | |
| 0765 | SUB | |
| 0766 | AND | |
| 0767 | EXTCODESIZE | |
| 0768 | ISZERO | |
| 0769 | PUSH2 | 0x0833 |
| 076c | JUMPI | |
| 076d | PUSH1 | 0x40 |
| 076f | MLOAD | |
| 0770 | PUSH4 | 0xabf1570d |
| 0775 | PUSH1 | 0xe0 |
| 0777 | SHL | |
| 0778 | DUP2 | |
| 0779 | MSTORE | |
| 077a | SWAP2 | |
| 077b | DUP4 | |
| 077c | SWAP2 | |
| 077d | DUP4 | |
| 077e | SWAP2 | |
| 077f | DUP3 | |
| 0780 | SWAP2 | |
| 0781 | PUSH2 | 0x078e |
| 0784 | SWAP2 | |
| 0785 | SWAP1 | |
| 0786 | PUSH1 | 0x04 |
| 0788 | DUP5 | |
| 0789 | ADD | |
| 078a | PUSH2 | 0x47fd |
| 078d | JUMP | |
| 078e | JUMPDEST | |
| 078f | SUB | |
| 0790 | DUP2 | |
| 0791 | DUP4 | |
| 0792 | PUSH32 | 0x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8 |
| 07b3 | PUSH1 | 0x01 |
| 07b5 | PUSH1 | 0x01 |
| 07b7 | PUSH1 | 0xa0 |
| 07b9 | SHL | |
| 07ba | SUB | |
| 07bb | AND | |
| 07bc | GAS | |
| 07bd | CALL | |
| 07be | DUP1 | |
| 07bf | ISZERO | |
| 07c0 | PUSH2 | 0x0837 |
| 07c3 | JUMPI | |
| 07c4 | PUSH2 | 0x081e |
| 07c7 | JUMPI | |
| 07c8 | JUMPDEST | |
| 07c9 | POP | |
| 07ca | POP | |
| 07cb | PUSH2 | 0x07d3 |
| 07ce | DUP3 | |
| 07cf | PUSH2 | 0x3ea1 |
| 07d2 | JUMP | |
| 07d3 | JUMPDEST | |
| 07d4 | SWAP2 | |
| 07d5 | DUP4 | |
| 07d6 | JUMPDEST | |
| 07d7 | DUP2 | |
| 07d8 | DUP2 | |
| 07d9 | LT | |
| 07da | PUSH2 | 0x07ea |
| 07dd | JUMPI | |
| 07de | DUP5 | |
| 07df | PUSH2 | 0x07e7 |
| 07e2 | DUP6 | |
| 07e3 | PUSH2 | 0x555f |
| 07e6 | JUMP | |
| 07e7 | JUMPDEST | |
| 07e8 | DUP1 | |
| 07e9 | RETURN | |
| 07ea | JUMPDEST | |
| 07eb | DUP1 | |
| 07ec | PUSH2 | 0x0804 |
| 07ef | PUSH2 | 0x0100 |
| 07f2 | PUSH2 | 0x07fe |
| 07f5 | PUSH1 | 0x01 |
| 07f7 | SWAP5 | |
| 07f8 | DUP7 | |
| 07f9 | DUP9 | |
| 07fa | PUSH2 | 0x49e7 |
| 07fd | JUMP | |
| 07fe | JUMPDEST | |
| 07ff | ADD | |
| 0800 | PUSH2 | 0x3bd7 |
| 0803 | JUMP | |
| 0804 | JUMPDEST | |
| 0805 | PUSH2 | 0x080e |
| 0808 | DUP3 | |
| 0809 | DUP8 | |
| 080a | PUSH2 | 0x3d01 |
| 080d | JUMP | |
| 080e | JUMPDEST | |
| 080f | SWAP1 | |
| 0810 | DUP4 | |
| 0811 | DUP1 | |
| 0812 | PUSH1 | 0xa0 |
| 0814 | SHL | |
| 0815 | SUB | |
| 0816 | AND | |
| 0817 | SWAP1 | |
| 0818 | MSTORE | |
| 0819 | ADD | |
| 081a | PUSH2 | 0x07d6 |
| 081d | JUMP | |
| 081e | JUMPDEST | |
| 081f | DUP2 | |
| 0820 | PUSH2 | 0x0828 |
| 0823 | SWAP2 | |
| 0824 | PUSH2 | 0x3b63 |
| 0827 | JUMP | |
| 0828 | JUMPDEST | |
| 0829 | PUSH2 | 0x0833 |
| 082c | JUMPI | |
| 082d | DUP3 | |
| 082e | DUP5 | |
| 082f | PUSH2 | 0x07c8 |
| 0832 | JUMP | |
| 0833 | JUMPDEST | |
| 0834 | DUP3 | |
| 0835 | DUP1 | |
| 0836 | REVERT | |
| 0837 | JUMPDEST | |
| 0838 | PUSH1 | 0x40 |
| 083a | MLOAD | |
| 083b | RETURNDATASIZE | |
| 083c | DUP5 | |
| 083d | DUP3 | |
| 083e | RETURNDATACOPY | |
| 083f | RETURNDATASIZE | |
| 0840 | SWAP1 | |
| 0841 | REVERT | |
| 0842 | JUMPDEST | |
| 0843 | DUP2 | |
| 0844 | PUSH2 | 0x084c |
| 0847 | SWAP2 | |
| 0848 | PUSH2 | 0x3b63 |
| 084b | JUMP | |
| 084c | JUMPDEST | |
| 084d | PUSH2 | 0x0833 |
| 0850 | JUMPI | |
| 0851 | DUP3 | |
| 0852 | DUP8 | |
| 0853 | PUSH2 | 0x073a |
| 0856 | JUMP | |
| 0857 | JUMPDEST | |
| 0858 | PUSH1 | 0x40 |
| 085a | MLOAD | |
| 085b | RETURNDATASIZE | |
| 085c | DUP7 | |
| 085d | DUP3 | |
| 085e | RETURNDATACOPY | |
| 085f | RETURNDATASIZE | |
| 0860 | SWAP1 | |
| 0861 | REVERT | |
| 0862 | JUMPDEST | |
| 0863 | DUP4 | |
| 0864 | DUP1 | |
| 0865 | REVERT | |
| 0866 | JUMPDEST | |
| 0867 | SWAP4 | |
| 0868 | SWAP1 | |
| 0869 | SWAP6 | |
| 086a | SWAP5 | |
| 086b | SWAP2 | |
| 086c | PUSH2 | 0x0876 |
| 086f | DUP6 | |
| 0870 | DUP5 | |
| 0871 | DUP5 | |
| 0872 | PUSH2 | 0x49e7 |
| 0875 | JUMP | |
| 0876 | JUMPDEST | |
| 0877 | SWAP8 | |
| 0878 | PUSH2 | 0x087f |
| 087b | PUSH2 | 0x3c54 |
| 087e | JUMP | |
| 087f | JUMPDEST | |
| 0880 | POP | |
| 0881 | PUSH1 | 0x01 |
| 0883 | PUSH1 | 0x01 |
| 0885 | PUSH1 | 0xa0 |
| 0887 | SHL | |
| 0888 | SUB | |
| 0889 | PUSH2 | 0x0891 |
| 088c | DUP11 | |
| 088d | PUSH2 | 0x3bd7 |
| 0890 | JUMP | |
| 0891 | JUMPDEST | |
| 0892 | AND | |
| 0893 | DUP8 | |
| 0894 | MSTORE | |
| 0895 | PUSH1 | 0x03 |
| 0897 | PUSH1 | 0x20 |
| 0899 | MSTORE | |
| 089a | PUSH1 | 0x40 |
| 089c | DUP8 | |
| 089d | KECCAK256 | |
| 089e | SWAP8 | |
| 089f | DUP9 | |
| 08a0 | SLOAD | |
| 08a1 | SWAP1 | |
| 08a2 | PUSH1 | 0xff |
| 08a4 | DUP3 | |
| 08a5 | AND | |
| 08a6 | PUSH2 | 0x0e85 |
| 08a9 | JUMPI | |
| 08aa | PUSH2 | 0x0160 |
| 08ad | DUP12 | |
| 08ae | ADD | |
| 08af | PUSH1 | 0x01 |
| 08b1 | PUSH1 | 0x01 |
| 08b3 | PUSH1 | 0x40 |
| 08b5 | SHL | |
| 08b6 | SUB | |
| 08b7 | PUSH2 | 0x08bf |
| 08ba | DUP3 | |
| 08bb | PUSH2 | 0x3beb |
| 08be | JUMP | |
| 08bf | JUMPDEST | |
| 08c0 | AND | |
| 08c1 | PUSH2 | 0x0e76 |
| 08c4 | JUMPI | |
| 08c5 | POP | |
| 08c6 | PUSH4 | 0x05265c00 |
| 08cb | SWAP2 | |
| 08cc | JUMPDEST | |
| 08cd | DUP2 | |
| 08ce | PUSH1 | 0x01 |
| 08d0 | PUSH1 | 0x01 |
| 08d2 | PUSH1 | 0x40 |
| 08d4 | SHL | |
| 08d5 | SUB | |
| 08d6 | DUP5 | |
| 08d7 | AND | |
| 08d8 | LT | |
| 08d9 | DUP1 | |
| 08da | ISZERO | |
| 08db | PUSH2 | 0x0e60 |
| 08de | JUMPI | |
| 08df | JUMPDEST | |
| 08e0 | PUSH2 | 0x0e44 |
| 08e3 | JUMPI | |
| 08e4 | PUSH2 | 0x08f1 |
| 08e7 | PUSH2 | 0x0140 |
| 08ea | DUP14 | |
| 08eb | ADD | |
| 08ec | DUP14 | |
| 08ed | PUSH2 | 0x476f |
| 08f0 | JUMP | |
| 08f1 | JUMPDEST | |
| 08f2 | SWAP1 | |
| 08f3 | POP | |
| 08f4 | ISZERO | |
| 08f5 | PUSH2 | 0x0e20 |
| 08f8 | JUMPI | |
| 08f9 | PUSH1 | 0xe0 |
| 08fb | DUP13 | |
| 08fc | ADD | |
| 08fd | CALLDATALOAD | |
| 08fe | SWAP1 | |
| 08ff | DUP2 | |
| 0900 | ISZERO | |
| 0901 | PUSH2 | 0x0dfc |
| 0904 | JUMPI | |
| 0905 | SWAP1 | |
| 0906 | PUSH1 | 0x01 |
| 0908 | PUSH2 | 0x01a0 |
| 090b | SWAP5 | |
| 090c | SWAP4 | |
| 090d | SWAP3 | |
| 090e | PUSH2 | 0x098f |
| 0911 | DUP16 | |
| 0912 | DUP15 | |
| 0913 | PUSH2 | 0x01c0 |
| 0916 | DUP3 | |
| 0917 | ADD | |
| 0918 | SWAP1 | |
| 0919 | PUSH2 | 0xffff |
| 091c | PUSH2 | 0x0924 |
| 091f | DUP4 | |
| 0920 | PUSH2 | 0x5762 |
| 0923 | JUMP | |
| 0924 | JUMPDEST | |
| 0925 | AND | |
| 0926 | ISZERO | |
| 0927 | ISZERO | |
| 0928 | SWAP1 | |
| 0929 | POP | |
| 092a | PUSH2 | 0x0deb |
| 092d | JUMPI | |
| 092e | POP | |
| 092f | PUSH2 | 0x0956 |
| 0932 | PUSH2 | 0x093f |
| 0935 | PUSH2 | 0x0180 |
| 0938 | DUP4 | |
| 0939 | ADD | |
| 093a | DUP4 | |
| 093b | PUSH2 | 0x4681 |
| 093e | JUMP | |
| 093f | JUMPDEST | |
| 0940 | SWAP1 | |
| 0941 | POP | |
| 0942 | PUSH2 | 0xffff |
| 0945 | PUSH2 | 0x094f |
| 0948 | DUP12 | |
| 0949 | DUP6 | |
| 094a | ADD | |
| 094b | PUSH2 | 0x5762 |
| 094e | JUMP | |
| 094f | JUMPDEST | |
| 0950 | SWAP2 | |
| 0951 | AND | |
| 0952 | PUSH2 | 0x608d |
| 0955 | JUMP | |
| 0956 | JUMPDEST | |
| 0957 | SWAP8 | |
| 0958 | DUP9 | |
| 0959 | SWAP2 | |
| 095a | JUMPDEST | |
| 095b | PUSH2 | 0x0989 |
| 095e | PUSH2 | 0x0966 |
| 0961 | DUP3 | |
| 0962 | PUSH2 | 0x3bd7 |
| 0965 | JUMP | |
| 0966 | JUMPDEST | |
| 0967 | SWAP2 | |
| 0968 | PUSH2 | 0x0981 |
| 096b | PUSH2 | 0x0978 |
| 096e | PUSH2 | 0x0180 |
| 0971 | DUP4 | |
| 0972 | ADD | |
| 0973 | DUP4 | |
| 0974 | PUSH2 | 0x4681 |
| 0977 | JUMP | |
| 0978 | JUMPDEST | |
| 0979 | SWAP6 | |
| 097a | SWAP1 | |
| 097b | SWAP3 | |
| 097c | ADD | |
| 097d | PUSH2 | 0x5762 |
| 0980 | JUMP | |
| 0981 | JUMPDEST | |
| 0982 | SWAP4 | |
| 0983 | CALLDATASIZE | |
| 0984 | SWAP2 | |
| 0985 | PUSH2 | 0x4dc1 |
| 0988 | JUMP | |
| 0989 | JUMPDEST | |
| 098a | SWAP1 | |
| 098b | PUSH2 | 0x5b9c |
| 098e | JUMP | |
| 098f | JUMPDEST | |
| 0990 | PUSH1 | 0xff |
| 0992 | NOT | |
| 0993 | AND | |
| 0994 | OR | |
| 0995 | DUP13 | |
| 0996 | SSTORE | |
| 0997 | PUSH1 | 0x20 |
| 0999 | DUP14 | |
| 099a | ADD | |
| 099b | CALLDATALOAD | |
| 099c | PUSH1 | 0x01 |
| 099e | DUP14 | |
| 099f | ADD | |
| 09a0 | SSTORE | |
| 09a1 | PUSH1 | 0x40 |
| 09a3 | DUP14 | |
| 09a4 | ADD | |
| 09a5 | CALLDATALOAD | |
| 09a6 | PUSH1 | 0x02 |
| 09a8 | DUP14 | |
| 09a9 | ADD | |
| 09aa | SSTORE | |
| 09ab | PUSH1 | 0x60 |
| 09ad | DUP14 | |
| 09ae | ADD | |
| 09af | CALLDATALOAD | |
| 09b0 | PUSH1 | 0x03 |
| 09b2 | DUP14 | |
| 09b3 | ADD | |
| 09b4 | SSTORE | |
| 09b5 | PUSH1 | 0x80 |
| 09b7 | DUP14 | |
| 09b8 | ADD | |
| 09b9 | CALLDATALOAD | |
| 09ba | PUSH1 | 0x04 |
| 09bc | DUP14 | |
| 09bd | ADD | |
| 09be | SSTORE | |
| 09bf | PUSH1 | 0x05 |
| 09c1 | DUP13 | |
| 09c2 | ADD | |
| 09c3 | SSTORE | |
| 09c4 | PUSH1 | 0xa0 |
| 09c6 | DUP13 | |
| 09c7 | ADD | |
| 09c8 | CALLDATALOAD | |
| 09c9 | PUSH1 | 0x06 |
| 09cb | DUP13 | |
| 09cc | ADD | |
| 09cd | SSTORE | |
| 09ce | PUSH1 | 0xc0 |
| 09d0 | DUP13 | |
| 09d1 | ADD | |
| 09d2 | CALLDATALOAD | |
| 09d3 | PUSH1 | 0x07 |
| 09d5 | DUP13 | |
| 09d6 | ADD | |
| 09d7 | SSTORE | |
| 09d8 | PUSH1 | 0x08 |
| 09da | DUP12 | |
| 09db | ADD | |
| 09dc | PUSH1 | 0x01 |
| 09de | PUSH2 | 0xffff |
| 09e1 | NOT | |
| 09e2 | DUP3 | |
| 09e3 | SLOAD | |
| 09e4 | AND | |
| 09e5 | OR | |
| 09e6 | SWAP1 | |
| 09e7 | SSTORE | |
| 09e8 | PUSH2 | 0x09f4 |
| 09eb | PUSH2 | 0x0100 |
| 09ee | DUP14 | |
| 09ef | ADD | |
| 09f0 | PUSH2 | 0x3bd7 |
| 09f3 | JUMP | |
| 09f4 | JUMPDEST | |
| 09f5 | PUSH1 | 0x09 |
| 09f7 | DUP13 | |
| 09f8 | ADD | |
| 09f9 | DUP1 | |
| 09fa | SLOAD | |
| 09fb | PUSH1 | 0x01 |
| 09fd | PUSH1 | 0x01 |
| 09ff | PUSH1 | 0xa0 |
| 0a01 | SHL | |
| 0a02 | SUB | |
| 0a03 | NOT | |
| 0a04 | AND | |
| 0a05 | PUSH1 | 0x01 |
| 0a07 | PUSH1 | 0x01 |
| 0a09 | PUSH1 | 0xa0 |
| 0a0b | SHL | |
| 0a0c | SUB | |
| 0a0d | SWAP3 | |
| 0a0e | SWAP1 | |
| 0a0f | SWAP3 | |
| 0a10 | AND | |
| 0a11 | SWAP2 | |
| 0a12 | SWAP1 | |
| 0a13 | SWAP2 | |
| 0a14 | OR | |
| 0a15 | SWAP1 | |
| 0a16 | SSTORE | |
| 0a17 | PUSH2 | 0x0a23 |
| 0a1a | PUSH2 | 0x0120 |
| 0a1d | DUP14 | |
| 0a1e | ADD | |
| 0a1f | PUSH2 | 0x5771 |
| 0a22 | JUMP | |
| 0a23 | JUMPDEST | |
| 0a24 | PUSH1 | 0x09 |
| 0a26 | DUP13 | |
| 0a27 | ADD | |
| 0a28 | DUP1 | |
| 0a29 | SLOAD | |
| 0a2a | PUSH1 | 0xff |
| 0a2c | PUSH1 | 0xa0 |
| 0a2e | SHL | |
| 0a2f | NOT | |
| 0a30 | AND | |
| 0a31 | SWAP2 | |
| 0a32 | ISZERO | |
| 0a33 | ISZERO | |
| 0a34 | PUSH1 | 0xa0 |
| 0a36 | SHL | |
| 0a37 | PUSH1 | 0xff |
| 0a39 | PUSH1 | 0xa0 |
| 0a3b | SHL | |
| 0a3c | AND | |
| 0a3d | SWAP2 | |
| 0a3e | SWAP1 | |
| 0a3f | SWAP2 | |
| 0a40 | OR | |
| 0a41 | SWAP1 | |
| 0a42 | SSTORE | |
| 0a43 | DUP10 | |
| 0a44 | JUMPDEST | |
| 0a45 | PUSH2 | 0x0a52 |
| 0a48 | PUSH2 | 0x0140 |
| 0a4b | DUP15 | |
| 0a4c | ADD | |
| 0a4d | DUP15 | |
| 0a4e | PUSH2 | 0x476f |
| 0a51 | JUMP | |
| 0a52 | JUMPDEST | |
| 0a53 | SWAP1 | |
| 0a54 | POP | |
| 0a55 | DUP2 | |
| 0a56 | LT | |
| 0a57 | ISZERO | |
| 0a58 | PUSH2 | 0x0aa8 |
| 0a5b | JUMPI | |
| 0a5c | PUSH1 | 0x01 |
| 0a5e | SWAP1 | |
| 0a5f | PUSH2 | 0x0aa2 |
| 0a62 | DUP15 | |
| 0a63 | PUSH2 | 0x0a6b |
| 0a66 | DUP2 | |
| 0a67 | PUSH2 | 0x3bd7 |
| 0a6a | JUMP | |
| 0a6b | JUMPDEST | |
| 0a6c | SWAP1 | |
| 0a6d | PUSH1 | 0x20 |
| 0a6f | PUSH2 | 0x0a9a |
| 0a72 | DUP6 | |
| 0a73 | PUSH2 | 0x0a87 |
| 0a76 | PUSH2 | 0x0a8d |
| 0a79 | DUP3 | |
| 0a7a | PUSH2 | 0x0a87 |
| 0a7d | PUSH2 | 0x0140 |
| 0a80 | DUP9 | |
| 0a81 | ADD | |
| 0a82 | DUP9 | |
| 0a83 | PUSH2 | 0x476f |
| 0a86 | JUMP | |
| 0a87 | JUMPDEST | |
| 0a88 | SWAP1 | |
| 0a89 | PUSH2 | 0x47a4 |
| 0a8c | JUMP | |
| 0a8d | JUMPDEST | |
| 0a8e | CALLDATALOAD | |
| 0a8f | SWAP5 | |
| 0a90 | PUSH2 | 0x0140 |
| 0a93 | DUP2 | |
| 0a94 | ADD | |
| 0a95 | SWAP1 | |
| 0a96 | PUSH2 | 0x476f |
| 0a99 | JUMP | |
| 0a9a | JUMPDEST | |
| 0a9b | ADD | |
| 0a9c | CALLDATALOAD | |
| 0a9d | SWAP2 | |
| 0a9e | PUSH2 | 0x53dc |
| 0aa1 | JUMP | |
| 0aa2 | JUMPDEST | |
| 0aa3 | ADD | |
| 0aa4 | PUSH2 | 0x0a44 |
| 0aa7 | JUMP | |
| 0aa8 | JUMPDEST | |
| 0aa9 | POP | |
| 0aaa | SWAP5 | |
| 0aab | SWAP8 | |
| 0aac | SWAP4 | |
| 0aad | SWAP6 | |
| 0aae | SWAP9 | |
| 0aaf | SWAP10 | |
| 0ab0 | SWAP1 | |
| 0ab1 | PUSH1 | 0x01 |
| 0ab3 | PUSH1 | 0x01 |
| 0ab5 | PUSH1 | 0x40 |
| 0ab7 | SHL | |
| 0ab8 | SUB | |
| 0ab9 | PUSH1 | 0x0a |
| 0abb | PUSH2 | 0x0b35 |
| 0abe | SWAP5 | |
| 0abf | SWAP4 | |
| 0ac0 | SWAP14 | |
| 0ac1 | SWAP6 | |
| 0ac2 | SWAP14 | |
| 0ac3 | PUSH1 | 0x01 |
| 0ac5 | PUSH1 | 0xb0 |
| 0ac7 | SHL | |
| 0ac8 | DUP4 | |
| 0ac9 | PUSH1 | 0xb0 |
| 0acb | SHL | |
| 0acc | NOT | |
| 0acd | PUSH1 | 0x09 |
| 0acf | DUP4 | |
| 0ad0 | ADD | |
| 0ad1 | SLOAD | |
| 0ad2 | AND | |
| 0ad3 | OR | |
| 0ad4 | PUSH1 | 0x09 |
| 0ad6 | DUP3 | |
| 0ad7 | ADD | |
| 0ad8 | SSTORE | |
| 0ad9 | ADD | |
| 0ada | SWAP2 | |
| 0adb | AND | |
| 0adc | PUSH1 | 0x01 |
| 0ade | PUSH1 | 0x01 |
| 0ae0 | PUSH1 | 0x40 |
| 0ae2 | SHL | |
| 0ae3 | SUB | |
| 0ae4 | NOT | |
| 0ae5 | DUP3 | |
| 0ae6 | SLOAD | |
| 0ae7 | AND | |
| 0ae8 | OR | |
| 0ae9 | DUP2 | |
| 0aea | SSTORE | |
| 0aeb | PUSH2 | 0x0b16 |
| 0aee | PUSH2 | 0x0afa |
| 0af1 | PUSH2 | 0x01a0 |
| 0af4 | DUP7 | |
| 0af5 | ADD | |
| 0af6 | PUSH2 | 0x5762 |
| 0af9 | JUMP | |
| 0afa | JUMPDEST | |
| 0afb | DUP3 | |
| 0afc | SLOAD | |
| 0afd | PUSH2 | 0xffff |
| 0b00 | PUSH1 | 0x40 |
| 0b02 | SHL | |
| 0b03 | NOT | |
| 0b04 | AND | |
| 0b05 | PUSH1 | 0x40 |
| 0b07 | SWAP2 | |
| 0b08 | SWAP1 | |
| 0b09 | SWAP2 | |
| 0b0a | SHL | |
| 0b0b | PUSH2 | 0xffff |
| 0b0e | PUSH1 | 0x40 |
| 0b10 | SHL | |
| 0b11 | AND | |
| 0b12 | OR | |
| 0b13 | DUP3 | |
| 0b14 | SSTORE | |
| 0b15 | JUMP | |
| 0b16 | JUMPDEST | |
| 0b17 | DUP1 | |
| 0b18 | SLOAD | |
| 0b19 | PUSH2 | 0xffff |
| 0b1c | PUSH1 | 0x50 |
| 0b1e | SHL | |
| 0b1f | NOT | |
| 0b20 | AND | |
| 0b21 | PUSH1 | 0x50 |
| 0b23 | SWAP3 | |
| 0b24 | SWAP1 | |
| 0b25 | SWAP3 | |
| 0b26 | SHL | |
| 0b27 | PUSH2 | 0xffff |
| 0b2a | PUSH1 | 0x50 |
| 0b2c | SHL | |
| 0b2d | AND | |
| 0b2e | SWAP2 | |
| 0b2f | SWAP1 | |
| 0b30 | SWAP2 | |
| 0b31 | OR | |
| 0b32 | SWAP1 | |
| 0b33 | SSTORE | |
| 0b34 | JUMP | |
| 0b35 | JUMPDEST | |
| 0b36 | PUSH2 | 0x0b43 |
| 0b39 | PUSH2 | 0x0180 |
| 0b3c | DUP3 | |
| 0b3d | ADD | |
| 0b3e | DUP3 | |
| 0b3f | PUSH2 | 0x4681 |
| 0b42 | JUMP | |
| 0b43 | JUMPDEST | |
| 0b44 | SWAP1 | |
| 0b45 | PUSH1 | 0x01 |
| 0b47 | PUSH1 | 0x01 |
| 0b49 | PUSH1 | 0xa0 |
| 0b4b | SHL | |
| 0b4c | SUB | |
| 0b4d | PUSH2 | 0x0b55 |
| 0b50 | DUP5 | |
| 0b51 | PUSH2 | 0x3bd7 |
| 0b54 | JUMP | |
| 0b55 | JUMPDEST | |
| 0b56 | AND | |
| 0b57 | DUP11 | |
| 0b58 | MSTORE | |
| 0b59 | PUSH1 | 0x04 |
| 0b5b | PUSH1 | 0x20 |
| 0b5d | MSTORE | |
| 0b5e | PUSH1 | 0x40 |
| 0b60 | DUP11 | |
| 0b61 | KECCAK256 | |
| 0b62 | SWAP1 | |
| 0b63 | PUSH1 | 0x01 |
| 0b65 | PUSH1 | 0x01 |
| 0b67 | PUSH1 | 0x40 |
| 0b69 | SHL | |
| 0b6a | SUB | |
| 0b6b | DUP4 | |
| 0b6c | GT | |
| 0b6d | PUSH2 | 0x0dd7 |
| 0b70 | JUMPI | |
| 0b71 | PUSH2 | 0x0b7a |
| 0b74 | DUP4 | |
| 0b75 | DUP4 | |
| 0b76 | PUSH2 | 0x46e6 |
| 0b79 | JUMP | |
| 0b7a | JUMPDEST | |
| 0b7b | SWAP1 | |
| 0b7c | DUP11 | |
| 0b7d | MSTORE | |
| 0b7e | PUSH1 | 0x20 |
| 0b80 | DUP11 | |
| 0b81 | KECCAK256 | |
| 0b82 | DUP11 | |
| 0b83 | JUMPDEST | |
| 0b84 | DUP4 | |
| 0b85 | DUP2 | |
| 0b86 | LT | |
| 0b87 | PUSH2 | 0x0dbc |
| 0b8a | JUMPI | |
| 0b8b | POP | |
| 0b8c | POP | |
| 0b8d | POP | |
| 0b8e | POP | |
| 0b8f | PUSH2 | 0x0c29 |
| 0b92 | DUP2 | |
| 0b93 | PUSH2 | 0x0ba6 |
| 0b96 | PUSH2 | 0x0ba1 |
| 0b99 | PUSH2 | 0x0c2e |
| 0b9c | SWAP5 | |
| 0b9d | PUSH2 | 0x3bd7 |
| 0ba0 | JUMP | |
| 0ba1 | JUMPDEST | |
| 0ba2 | PUSH2 | 0x47b4 |
| 0ba5 | JUMP | |
| 0ba6 | JUMPDEST | |
| 0ba7 | PUSH2 | 0x0bc5 |
| 0baa | PUSH2 | 0x0bb2 |
| 0bad | DUP3 | |
| 0bae | PUSH2 | 0x3bd7 |
| 0bb1 | JUMP | |
| 0bb2 | JUMPDEST | |
| 0bb3 | PUSH2 | 0x0bbf |
| 0bb6 | PUSH2 | 0x0100 |
| 0bb9 | DUP5 | |
| 0bba | ADD | |
| 0bbb | PUSH2 | 0x3bd7 |
| 0bbe | JUMP | |
| 0bbf | JUMPDEST | |
| 0bc0 | SWAP1 | |
| 0bc1 | PUSH2 | 0x5509 |
| 0bc4 | JUMP | |
| 0bc5 | JUMPDEST | |
| 0bc6 | PUSH2 | 0x0bce |
| 0bc9 | DUP2 | |
| 0bca | PUSH2 | 0x3bd7 |
| 0bcd | JUMP | |
| 0bce | JUMPDEST | |
| 0bcf | PUSH2 | 0x0bdb |
| 0bd2 | PUSH2 | 0x0100 |
| 0bd5 | DUP4 | |
| 0bd6 | ADD | |
| 0bd7 | PUSH2 | 0x3bd7 |
| 0bda | JUMP | |
| 0bdb | JUMPDEST | |
| 0bdc | PUSH32 | 0x568403fd429f133b4cc18a945d220c328c59a445a8122f240f1d74fd55fb6937 |
| 0bfd | PUSH1 | 0x20 |
| 0bff | PUSH2 | 0x0c0b |
| 0c02 | PUSH2 | 0x0120 |
| 0c05 | DUP7 | |
| 0c06 | ADD | |
| 0c07 | PUSH2 | 0x5771 |
| 0c0a | JUMP | |
| 0c0b | JUMPDEST | |
| 0c0c | PUSH1 | 0x40 |
| 0c0e | MLOAD | |
| 0c0f | SWAP1 | |
| 0c10 | ISZERO | |
| 0c11 | ISZERO | |
| 0c12 | DUP2 | |
| 0c13 | MSTORE | |
| 0c14 | PUSH1 | 0x01 |
| 0c16 | PUSH1 | 0x01 |
| 0c18 | PUSH1 | 0xa0 |
| 0c1a | SHL | |
| 0c1b | SUB | |
| 0c1c | SWAP4 | |
| 0c1d | DUP5 | |
| 0c1e | AND | |
| 0c1f | SWAP5 | |
| 0c20 | SWAP1 | |
| 0c21 | SWAP4 | |
| 0c22 | AND | |
| 0c23 | SWAP3 | |
| 0c24 | LOG3 | |
| 0c25 | PUSH2 | 0x3bd7 |
| 0c28 | JUMP | |
| 0c29 | JUMPDEST | |
| 0c2a | PUSH2 | 0x4e29 |
| 0c2d | JUMP | |
| 0c2e | JUMPDEST | |
| 0c2f | PUSH2 | 0x0c38 |
| 0c32 | DUP3 | |
| 0c33 | DUP11 | |
| 0c34 | PUSH2 | 0x3d01 |
| 0c37 | JUMP | |
| 0c38 | JUMPDEST | |
| 0c39 | MSTORE | |
| 0c3a | PUSH2 | 0x0c43 |
| 0c3d | DUP2 | |
| 0c3e | DUP10 | |
| 0c3f | PUSH2 | 0x3d01 |
| 0c42 | JUMP | |
| 0c43 | JUMPDEST | |
| 0c44 | POP | |
| 0c45 | PUSH2 | 0x0c57 |
| 0c48 | PUSH2 | 0x0c52 |
| 0c4b | DUP3 | |
| 0c4c | DUP9 | |
| 0c4d | DUP9 | |
| 0c4e | PUSH2 | 0x49e7 |
| 0c51 | JUMP | |
| 0c52 | JUMPDEST | |
| 0c53 | PUSH2 | 0x3bd7 |
| 0c56 | JUMP | |
| 0c57 | JUMPDEST | |
| 0c58 | PUSH1 | 0x40 |
| 0c5a | MLOAD | |
| 0c5b | PUSH4 | 0x82edfbd9 |
| 0c60 | PUSH1 | 0xe0 |
| 0c62 | SHL | |
| 0c63 | DUP2 | |
| 0c64 | MSTORE | |
| 0c65 | PUSH1 | 0x01 |
| 0c67 | PUSH1 | 0x01 |
| 0c69 | PUSH1 | 0xa0 |
| 0c6b | SHL | |
| 0c6c | SUB | |
| 0c6d | SWAP2 | |
| 0c6e | DUP3 | |
| 0c6f | AND | |
| 0c70 | PUSH1 | 0x04 |
| 0c72 | DUP3 | |
| 0c73 | ADD | |
| 0c74 | MSTORE | |
| 0c75 | SWAP2 | |
| 0c76 | SWAP1 | |
| 0c77 | PUSH1 | 0x20 |
| 0c79 | SWAP1 | |
| 0c7a | DUP4 | |
| 0c7b | SWAP1 | |
| 0c7c | PUSH1 | 0x24 |
| 0c7e | SWAP1 | |
| 0c7f | DUP3 | |
| 0c80 | SWAP1 | |
| 0c81 | PUSH32 | 0x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8 |
| 0ca2 | AND | |
| 0ca3 | GAS | |
| 0ca4 | STATICCALL | |
| 0ca5 | DUP1 | |
| 0ca6 | ISZERO | |
| 0ca7 | PUSH2 | 0x0db1 |
| 0caa | JUMPI | |
| 0cab | DUP9 | |
| 0cac | SWAP1 | |
| 0cad | PUSH2 | 0x0d7b |
| 0cb0 | JUMPI | |
| 0cb1 | JUMPDEST | |
| 0cb2 | PUSH1 | 0x01 |
| 0cb4 | SWAP3 | |
| 0cb5 | POP | |
| 0cb6 | PUSH2 | 0x0cbf |
| 0cb9 | DUP3 | |
| 0cba | DUP7 | |
| 0cbb | PUSH2 | 0x3d01 |
| 0cbe | JUMP | |
| 0cbf | JUMPDEST | |
| 0cc0 | MSTORE | |
| 0cc1 | PUSH2 | 0x0ccb |
| 0cc4 | DUP2 | |
| 0cc5 | DUP9 | |
| 0cc6 | DUP9 | |
| 0cc7 | PUSH2 | 0x49e7 |
| 0cca | JUMP | |
| 0ccb | JUMPDEST | |
| 0ccc | PUSH1 | 0x40 |
| 0cce | MLOAD | |
| 0ccf | PUSH1 | 0x20 |
| 0cd1 | DUP2 | |
| 0cd2 | ADD | |
| 0cd3 | SWAP1 | |
| 0cd4 | PUSH1 | 0x20 |
| 0cd6 | DUP4 | |
| 0cd7 | ADD | |
| 0cd8 | CALLDATALOAD | |
| 0cd9 | DUP3 | |
| 0cda | MSTORE | |
| 0cdb | PUSH1 | 0x40 |
| 0cdd | DUP4 | |
| 0cde | ADD | |
| 0cdf | CALLDATALOAD | |
| 0ce0 | PUSH1 | 0x40 |
| 0ce2 | DUP3 | |
| 0ce3 | ADD | |
| 0ce4 | MSTORE | |
| 0ce5 | PUSH1 | 0x60 |
| 0ce7 | DUP4 | |
| 0ce8 | ADD | |
| 0ce9 | CALLDATALOAD | |
| 0cea | PUSH1 | 0x60 |
| 0cec | DUP3 | |
| 0ced | ADD | |
| 0cee | MSTORE | |
| 0cef | PUSH1 | 0x80 |
| 0cf1 | DUP4 | |
| 0cf2 | ADD | |
| 0cf3 | CALLDATALOAD | |
| 0cf4 | PUSH1 | 0x80 |
| 0cf6 | DUP3 | |
| 0cf7 | ADD | |
| 0cf8 | MSTORE | |
| 0cf9 | PUSH1 | 0xa0 |
| 0cfb | DUP4 | |
| 0cfc | ADD | |
| 0cfd | CALLDATALOAD | |
| 0cfe | PUSH1 | 0xa0 |
| 0d00 | DUP3 | |
| 0d01 | ADD | |
| 0d02 | MSTORE | |
| 0d03 | PUSH1 | 0xc0 |
| 0d05 | DUP4 | |
| 0d06 | ADD | |
| 0d07 | CALLDATALOAD | |
| 0d08 | PUSH1 | 0xc0 |
| 0d0a | DUP3 | |
| 0d0b | ADD | |
| 0d0c | MSTORE | |
| 0d0d | PUSH1 | 0xc0 |
| 0d0f | DUP2 | |
| 0d10 | MSTORE | |
| 0d11 | PUSH2 | 0x0d1b |
| 0d14 | PUSH1 | 0xe0 |
| 0d16 | DUP3 | |
| 0d17 | PUSH2 | 0x3b63 |
| 0d1a | JUMP | |
| 0d1b | JUMPDEST | |
| 0d1c | MLOAD | |
| 0d1d | SWAP1 | |
| 0d1e | KECCAK256 | |
| 0d1f | PUSH1 | 0x40 |
| 0d21 | MLOAD | |
| 0d22 | SWAP1 | |
| 0d23 | PUSH1 | 0xe0 |
| 0d25 | PUSH1 | 0x20 |
| 0d27 | DUP4 | |
| 0d28 | ADD | |
| 0d29 | SWAP4 | |
| 0d2a | PUSH32 | 0xcc25d3fea88291f95ddfb5590a6b760f02245a0e4ca7c0b69285c6cd26543afd |
| 0d4b | DUP6 | |
| 0d4c | MSTORE | |
| 0d4d | ADD | |
| 0d4e | CALLDATALOAD | |
| 0d4f | PUSH1 | 0x40 |
| 0d51 | DUP4 | |
| 0d52 | ADD | |
| 0d53 | MSTORE | |
| 0d54 | PUSH1 | 0x60 |
| 0d56 | DUP3 | |
| 0d57 | ADD | |
| 0d58 | MSTORE | |
| 0d59 | PUSH1 | 0x60 |
| 0d5b | DUP2 | |
| 0d5c | MSTORE | |
| 0d5d | PUSH2 | 0x0d67 |
| 0d60 | PUSH1 | 0x80 |
| 0d62 | DUP3 | |
| 0d63 | PUSH2 | 0x3b63 |
| 0d66 | JUMP | |
| 0d67 | JUMPDEST | |
| 0d68 | MLOAD | |
| 0d69 | SWAP1 | |
| 0d6a | KECCAK256 | |
| 0d6b | PUSH2 | 0x0d74 |
| 0d6e | DUP3 | |
| 0d6f | DUP8 | |
| 0d70 | PUSH2 | 0x3d01 |
| 0d73 | JUMP | |
| 0d74 | JUMPDEST | |
| 0d75 | MSTORE | |
| 0d76 | ADD | |
| 0d77 | PUSH2 | 0x06a0 |
| 0d7a | JUMP | |
| 0d7b | JUMPDEST | |
| 0d7c | POP | |
| 0d7d | PUSH1 | 0x20 |
| 0d7f | DUP3 | |
| 0d80 | RETURNDATASIZE | |
| 0d81 | DUP3 | |
| 0d82 | GT | |
| 0d83 | PUSH2 | 0x0da9 |
| 0d86 | JUMPI | |
| 0d87 | JUMPDEST | |
| 0d88 | DUP2 | |
| 0d89 | PUSH2 | 0x0d94 |
| 0d8c | PUSH1 | 0x20 |
| 0d8e | SWAP4 | |
| 0d8f | DUP4 | |
| 0d90 | PUSH2 | 0x3b63 |
| 0d93 | JUMP | |
| 0d94 | JUMPDEST | |
| 0d95 | DUP2 | |
| 0d96 | ADD | |
| 0d97 | SUB | |
| 0d98 | SLT | |
| 0d99 | PUSH2 | 0x0da5 |
| 0d9c | JUMPI | |
| 0d9d | PUSH1 | 0x01 |
| 0d9f | SWAP2 | |
| 0da0 | MLOAD | |
| 0da1 | PUSH2 | 0x0cb1 |
| 0da4 | JUMP | |
| 0da5 | JUMPDEST | |
| 0da6 | PUSH0 | |
| 0da7 | DUP1 | |
| 0da8 | REVERT | |
| 0da9 | JUMPDEST | |
| 0daa | RETURNDATASIZE | |
| 0dab | SWAP2 | |
| 0dac | POP | |
| 0dad | PUSH2 | 0x0d87 |
| 0db0 | JUMP | |
| 0db1 | JUMPDEST | |
| 0db2 | PUSH1 | 0x40 |
| 0db4 | MLOAD | |
| 0db5 | RETURNDATASIZE | |
| 0db6 | DUP11 | |
| 0db7 | DUP3 | |
| 0db8 | RETURNDATACOPY | |
| 0db9 | RETURNDATASIZE | |
| 0dba | SWAP1 | |
| 0dbb | REVERT | |
| 0dbc | JUMPDEST | |
| 0dbd | PUSH1 | 0x01 |
| 0dbf | SWAP1 | |
| 0dc0 | PUSH1 | 0x20 |
| 0dc2 | PUSH2 | 0x0dca |
| 0dc5 | DUP6 | |
| 0dc6 | PUSH2 | 0x3bd7 |
| 0dc9 | JUMP | |
| 0dca | JUMPDEST | |
| 0dcb | SWAP5 | |
| 0dcc | ADD | |
| 0dcd | SWAP4 | |
| 0dce | DUP2 | |
| 0dcf | DUP5 | |
| 0dd0 | ADD | |
| 0dd1 | SSTORE | |
| 0dd2 | ADD | |
| 0dd3 | PUSH2 | 0x0b83 |
| 0dd6 | JUMP | |
| 0dd7 | JUMPDEST | |
| 0dd8 | PUSH4 | 0x4e487b71 |
| 0ddd | PUSH1 | 0xe0 |
| 0ddf | SHL | |
| 0de0 | DUP12 | |
| 0de1 | MSTORE | |
| 0de2 | PUSH1 | 0x41 |
| 0de4 | PUSH1 | 0x04 |
| 0de6 | MSTORE | |
| 0de7 | PUSH1 | 0x24 |
| 0de9 | DUP12 | |
| 0dea | REVERT | |
| 0deb | JUMPDEST | |
| 0dec | PUSH2 | 0x0df4 |
| 0def | SWAP1 | |
| 0df0 | PUSH2 | 0x5762 |
| 0df3 | JUMP | |
| 0df4 | JUMPDEST | |
| 0df5 | SWAP8 | |
| 0df6 | DUP9 | |
| 0df7 | SWAP2 | |
| 0df8 | PUSH2 | 0x095a |
| 0dfb | JUMP | |
| 0dfc | JUMPDEST | |
| 0dfd | PUSH1 | 0x24 |
| 0dff | DUP12 | |
| 0e00 | PUSH2 | 0x0e08 |
| 0e03 | DUP16 | |
| 0e04 | PUSH2 | 0x3bd7 |
| 0e07 | JUMP | |
| 0e08 | JUMPDEST | |
| 0e09 | PUSH4 | 0x16efda7d |
| 0e0e | PUSH1 | 0xe2 |
| 0e10 | SHL | |
| 0e11 | DUP3 | |
| 0e12 | MSTORE | |
| 0e13 | PUSH1 | 0x01 |
| 0e15 | PUSH1 | 0x01 |
| 0e17 | PUSH1 | 0xa0 |
| 0e19 | SHL | |
| 0e1a | SUB | |
| 0e1b | AND | |
| 0e1c | PUSH1 | 0x04 |
| 0e1e | MSTORE | |
| 0e1f | REVERT | |
| 0e20 | JUMPDEST | |
| 0e21 | PUSH1 | 0x24 |
| 0e23 | DUP11 | |
| 0e24 | PUSH2 | 0x0e2c |
| 0e27 | DUP15 | |
| 0e28 | PUSH2 | 0x3bd7 |
| 0e2b | JUMP | |
| 0e2c | JUMPDEST | |
| 0e2d | PUSH4 | 0x3aa293db |
| 0e32 | PUSH1 | 0xe1 |
| 0e34 | SHL | |
| 0e35 | DUP3 | |
| 0e36 | MSTORE | |
| 0e37 | PUSH1 | 0x01 |
| 0e39 | PUSH1 | 0x01 |
| 0e3b | PUSH1 | 0xa0 |
| 0e3d | SHL | |
| 0e3e | SUB | |
| 0e3f | AND | |
| 0e40 | PUSH1 | 0x04 |
| 0e42 | MSTORE | |
| 0e43 | REVERT | |
| 0e44 | JUMPDEST | |
| 0e45 | PUSH4 | 0x10b0f875 |
| 0e4a | PUSH1 | 0xe1 |
| 0e4c | SHL | |
| 0e4d | DUP11 | |
| 0e4e | MSTORE | |
| 0e4f | PUSH1 | 0x01 |
| 0e51 | PUSH1 | 0x01 |
| 0e53 | PUSH1 | 0x40 |
| 0e55 | SHL | |
| 0e56 | SUB | |
| 0e57 | DUP4 | |
| 0e58 | AND | |
| 0e59 | PUSH1 | 0x04 |
| 0e5b | MSTORE | |
| 0e5c | PUSH1 | 0x24 |
| 0e5e | DUP11 | |
| 0e5f | REVERT | |
| 0e60 | JUMPDEST | |
| 0e61 | POP | |
| 0e62 | PUSH4 | 0x9a7ec800 |
| 0e67 | PUSH1 | 0x01 |
| 0e69 | PUSH1 | 0x01 |
| 0e6b | PUSH1 | 0x40 |
| 0e6d | SHL | |
| 0e6e | SUB | |
| 0e6f | DUP5 | |
| 0e70 | AND | |
| 0e71 | GT | |
| 0e72 | PUSH2 | 0x08df |
| 0e75 | JUMP | |
| 0e76 | JUMPDEST | |
| 0e77 | PUSH2 | 0x0e7f |
| 0e7a | SWAP1 | |
| 0e7b | PUSH2 | 0x3beb |
| 0e7e | JUMP | |
| 0e7f | JUMPDEST | |
| 0e80 | SWAP2 | |
| 0e81 | PUSH2 | 0x08cc |
| 0e84 | JUMP | |
| 0e85 | JUMPDEST | |
| 0e86 | PUSH1 | 0x24 |
| 0e88 | DUP10 | |
| 0e89 | PUSH2 | 0x0e91 |
| 0e8c | DUP14 | |
| 0e8d | PUSH2 | 0x3bd7 |
| 0e90 | JUMP | |
| 0e91 | JUMPDEST | |
| 0e92 | PUSH4 | 0x3b490093 |
| 0e97 | PUSH1 | 0xe1 |
| 0e99 | SHL | |
| 0e9a | DUP3 | |
| 0e9b | MSTORE | |
| 0e9c | PUSH1 | 0x01 |
| 0e9e | PUSH1 | 0x01 |
| 0ea0 | PUSH1 | 0xa0 |
| 0ea2 | SHL | |
| 0ea3 | SUB | |
| 0ea4 | AND | |
| 0ea5 | PUSH1 | 0x04 |
| 0ea7 | MSTORE | |
| 0ea8 | REVERT | |
| 0ea9 | JUMPDEST | |
| 0eaa | PUSH1 | 0x20 |
| 0eac | SWAP1 | |
| 0ead | PUSH2 | 0x0eb4 |
| 0eb0 | PUSH2 | 0x3c54 |
| 0eb3 | JUMP | |
| 0eb4 | JUMPDEST | |
| 0eb5 | DUP3 | |
| 0eb6 | DUP3 | |
| 0eb7 | DUP10 | |
| 0eb8 | ADD | |
| 0eb9 | ADD | |
| 0eba | MSTORE | |
| 0ebb | ADD | |
| 0ebc | PUSH2 | 0x067e |
| 0ebf | JUMP | |
| 0ec0 | JUMPDEST | |
| 0ec1 | SWAP1 | |
| 0ec2 | SWAP2 | |
| 0ec3 | SWAP3 | |
| 0ec4 | SWAP4 | |
| 0ec5 | PUSH1 | 0x7f |
| 0ec7 | NOT | |
| 0ec8 | DUP7 | |
| 0ec9 | DUP3 | |
| 0eca | SUB | |
| 0ecb | ADD | |
| 0ecc | DUP5 | |
| 0ecd | MSTORE | |
| 0ece | DUP5 | |
| 0ecf | CALLDATALOAD | |
| 0ed0 | DUP4 | |
| 0ed1 | DUP2 | |
| 0ed2 | SLT | |
| 0ed3 | ISZERO | |
| 0ed4 | PUSH2 | 0x1009 |
| 0ed7 | JUMPI | |
| 0ed8 | DUP16 | |
| 0ed9 | ADD | |
| 0eda | SWAP1 | |
| 0edb | PUSH1 | 0x01 |
| 0edd | PUSH1 | 0x01 |
| 0edf | PUSH1 | 0xa0 |
| 0ee1 | SHL | |
| 0ee2 | SUB | |
| 0ee3 | PUSH2 | 0x0eeb |
| 0ee6 | DUP4 | |
| 0ee7 | PUSH2 | 0x36c2 |
| 0eea | JUMP | |
| 0eeb | JUMPDEST | |
| 0eec | AND | |
| 0eed | DUP2 | |
| 0eee | MSTORE | |
| 0eef | PUSH1 | 0x20 |
| 0ef1 | DUP3 | |
| 0ef2 | ADD | |
| 0ef3 | CALLDATALOAD | |
| 0ef4 | PUSH1 | 0x20 |
| 0ef6 | DUP3 | |
| 0ef7 | ADD | |
| 0ef8 | MSTORE | |
| 0ef9 | PUSH1 | 0x40 |
| 0efb | DUP3 | |
| 0efc | ADD | |
| 0efd | CALLDATALOAD | |
| 0efe | PUSH1 | 0x40 |
| 0f00 | DUP3 | |
| 0f01 | ADD | |
| 0f02 | MSTORE | |
| 0f03 | PUSH1 | 0x60 |
| 0f05 | DUP3 | |
| 0f06 | ADD | |
| 0f07 | CALLDATALOAD | |
| 0f08 | PUSH1 | 0x60 |
| 0f0a | DUP3 | |
| 0f0b | ADD | |
| 0f0c | MSTORE | |
| 0f0d | PUSH1 | 0x80 |
| 0f0f | DUP3 | |
| 0f10 | ADD | |
| 0f11 | CALLDATALOAD | |
| 0f12 | PUSH1 | 0x80 |
| 0f14 | DUP3 | |
| 0f15 | ADD | |
| 0f16 | MSTORE | |
| 0f17 | PUSH1 | 0xa0 |
| 0f19 | DUP3 | |
| 0f1a | ADD | |
| 0f1b | CALLDATALOAD | |
| 0f1c | PUSH1 | 0xa0 |
| 0f1e | DUP3 | |
| 0f1f | ADD | |
| 0f20 | MSTORE | |
| 0f21 | PUSH1 | 0xc0 |
| 0f23 | DUP3 | |
| 0f24 | ADD | |
| 0f25 | CALLDATALOAD | |
| 0f26 | PUSH1 | 0xc0 |
| 0f28 | DUP3 | |
| 0f29 | ADD | |
| 0f2a | MSTORE | |
| 0f2b | PUSH1 | 0xe0 |
| 0f2d | DUP3 | |
| 0f2e | ADD | |
| 0f2f | CALLDATALOAD | |
| 0f30 | PUSH1 | 0xe0 |
| 0f32 | DUP3 | |
| 0f33 | ADD | |
| 0f34 | MSTORE | |
| 0f35 | PUSH1 | 0x01 |
| 0f37 | DUP1 | |
| 0f38 | PUSH1 | 0xa0 |
| 0f3a | SHL | |
| 0f3b | SUB | |
| 0f3c | PUSH2 | 0x0f48 |
| 0f3f | PUSH2 | 0x0100 |
| 0f42 | DUP5 | |
| 0f43 | ADD | |
| 0f44 | PUSH2 | 0x36c2 |
| 0f47 | JUMP | |
| 0f48 | JUMPDEST | |
| 0f49 | AND | |
| 0f4a | PUSH2 | 0x0100 |
| 0f4d | DUP3 | |
| 0f4e | ADD | |
| 0f4f | MSTORE | |
| 0f50 | PUSH2 | 0x0120 |
| 0f53 | DUP3 | |
| 0f54 | ADD | |
| 0f55 | CALLDATALOAD | |
| 0f56 | DUP1 | |
| 0f57 | ISZERO | |
| 0f58 | ISZERO | |
| 0f59 | DUP1 | |
| 0f5a | SWAP2 | |
| 0f5b | SUB | |
| 0f5c | PUSH2 | 0x1005 |
| 0f5f | JUMPI | |
| 0f60 | PUSH1 | 0x01 |
| 0f62 | SWAP3 | |
| 0f63 | DUP3 | |
| 0f64 | PUSH1 | 0x20 |
| 0f66 | SWAP4 | |
| 0f67 | SWAP3 | |
| 0f68 | PUSH2 | 0x0120 |
| 0f6b | DUP6 | |
| 0f6c | SWAP5 | |
| 0f6d | ADD | |
| 0f6e | MSTORE | |
| 0f6f | PUSH2 | 0x01c0 |
| 0f72 | PUSH2 | 0xffff |
| 0f75 | PUSH2 | 0x0ff5 |
| 0f78 | DUP3 | |
| 0f79 | PUSH2 | 0x0fd9 |
| 0f7c | PUSH2 | 0x0fa0 |
| 0f7f | PUSH2 | 0x0f8c |
| 0f82 | PUSH2 | 0x0140 |
| 0f85 | DUP10 | |
| 0f86 | ADD | |
| 0f87 | DUP10 | |
| 0f88 | PUSH2 | 0x4614 |
| 0f8b | JUMP | |
| 0f8c | JUMPDEST | |
| 0f8d | PUSH2 | 0x01e0 |
| 0f90 | PUSH2 | 0x0140 |
| 0f93 | DUP11 | |
| 0f94 | ADD | |
| 0f95 | MSTORE | |
| 0f96 | PUSH2 | 0x01e0 |
| 0f99 | DUP10 | |
| 0f9a | ADD | |
| 0f9b | SWAP2 | |
| 0f9c | PUSH2 | 0x4648 |
| 0f9f | JUMP | |
| 0fa0 | JUMPDEST | |
| 0fa1 | PUSH1 | 0x01 |
| 0fa3 | PUSH1 | 0x01 |
| 0fa5 | PUSH1 | 0x40 |
| 0fa7 | SHL | |
| 0fa8 | SUB | |
| 0fa9 | PUSH2 | 0x0fb5 |
| 0fac | PUSH2 | 0x0160 |
| 0faf | DUP11 | |
| 0fb0 | ADD | |
| 0fb1 | PUSH2 | 0x37ca |
| 0fb4 | JUMP | |
| 0fb5 | JUMPDEST | |
| 0fb6 | AND | |
| 0fb7 | PUSH2 | 0x0160 |
| 0fba | DUP9 | |
| 0fbb | ADD | |
| 0fbc | MSTORE | |
| 0fbd | PUSH2 | 0x0fca |
| 0fc0 | PUSH2 | 0x0180 |
| 0fc3 | DUP10 | |
| 0fc4 | ADD | |
| 0fc5 | DUP10 | |
| 0fc6 | PUSH2 | 0x45a1 |
| 0fc9 | JUMP | |
| 0fca | JUMPDEST | |
| 0fcb | SWAP1 | |
| 0fcc | DUP9 | |
| 0fcd | DUP4 | |
| 0fce | SUB | |
| 0fcf | PUSH2 | 0x0180 |
| 0fd2 | DUP11 | |
| 0fd3 | ADD | |
| 0fd4 | MSTORE | |
| 0fd5 | PUSH2 | 0x45d5 |
| 0fd8 | JUMP | |
| 0fd9 | JUMPDEST | |
| 0fda | SWAP6 | |
| 0fdb | DUP4 | |
| 0fdc | PUSH2 | 0x0fe8 |
| 0fdf | PUSH2 | 0x01a0 |
| 0fe2 | DUP4 | |
| 0fe3 | ADD | |
| 0fe4 | PUSH2 | 0x49d8 |
| 0fe7 | JUMP | |
| 0fe8 | JUMPDEST | |
| 0fe9 | AND | |
| 0fea | PUSH2 | 0x01a0 |
| 0fed | DUP8 | |
| 0fee | ADD | |
| 0fef | MSTORE | |
| 0ff0 | ADD | |
| 0ff1 | PUSH2 | 0x49d8 |
| 0ff4 | JUMP | |
| 0ff5 | JUMPDEST | |
| 0ff6 | AND | |
| 0ff7 | SWAP2 | |
| 0ff8 | ADD | |
| 0ff9 | MSTORE | |
| 0ffa | SWAP7 | |
| 0ffb | ADD | |
| 0ffc | SWAP5 | |
| 0ffd | ADD | |
| 0ffe | SWAP3 | |
| 0fff | SWAP2 | |
| 1000 | ADD | |
| 1001 | PUSH2 | 0x0560 |
| 1004 | JUMP | |
| 1005 | JUMPDEST | |
| 1006 | DUP16 | |
| 1007 | DUP1 | |
| 1008 | REVERT | |
| 1009 | JUMPDEST | |
| 100a | DUP15 | |
| 100b | DUP1 | |
| 100c | REVERT | |
| 100d | JUMPDEST | |
| 100e | PUSH4 | 0x1d087e61 |
| 1013 | PUSH1 | 0xe2 |
| 1015 | SHL | |
| 1016 | DUP7 | |
| 1017 | MSTORE | |
| 1018 | PUSH1 | 0x04 |
| 101a | DUP7 | |
| 101b | REVERT | |
| 101c | JUMPDEST | |
| 101d | POP | |
| 101e | DUP1 | |
| 101f | REVERT | |
| 1020 | JUMPDEST | |
| 1021 | POP | |
| 1022 | CALLVALUE | |
| 1023 | PUSH2 | 0x0298 |
| 1026 | JUMPI | |
| 1027 | PUSH1 | 0x20 |
| 1029 | CALLDATASIZE | |
| 102a | PUSH1 | 0x03 |
| 102c | NOT | |
| 102d | ADD | |
| 102e | SLT | |
| 102f | PUSH2 | 0x0298 |
| 1032 | JUMPI | |
| 1033 | PUSH1 | 0x04 |
| 1035 | CALLDATALOAD | |
| 1036 | PUSH1 | 0xff |
| 1038 | DUP2 | |
| 1039 | AND | |
| 103a | DUP1 | |
| 103b | SWAP2 | |
| 103c | SUB | |
| 103d | PUSH2 | 0x101c |
| 1040 | JUMPI | |
| 1041 | PUSH1 | 0x40 |
| 1043 | DUP3 | |
| 1044 | PUSH1 | 0x01 |
| 1046 | PUSH1 | 0x01 |
| 1048 | PUSH1 | 0x40 |
| 104a | SHL | |
| 104b | SUB | |
| 104c | SWAP3 | |
| 104d | PUSH1 | 0x20 |
| 104f | SWAP5 | |
| 1050 | MSTORE | |
| 1051 | PUSH1 | 0x07 |
| 1053 | DUP5 | |
| 1054 | MSTORE | |
| 1055 | KECCAK256 | |
| 1056 | SLOAD | |
| 1057 | AND | |
| 1058 | PUSH1 | 0x40 |
| 105a | MLOAD | |
| 105b | SWAP1 | |
| 105c | DUP2 | |
| 105d | MSTORE | |
| 105e | RETURN | |
| 105f | JUMPDEST | |
| 1060 | POP | |
| 1061 | CALLVALUE | |
| 1062 | PUSH2 | 0x0298 |
| 1065 | JUMPI | |
| 1066 | PUSH1 | 0x20 |
| 1068 | CALLDATASIZE | |
| 1069 | PUSH1 | 0x03 |
| 106b | NOT | |
| 106c | ADD | |
| 106d | SLT | |
| 106e | PUSH2 | 0x0298 |
| 1071 | JUMPI | |
| 1072 | PUSH1 | 0x20 |
| 1074 | SWAP1 | |
| 1075 | PUSH2 | 0xffff |
| 1078 | SWAP1 | |
| 1079 | PUSH1 | 0x08 |
| 107b | SWAP1 | |
| 107c | PUSH1 | 0x40 |
| 107e | SWAP1 | |
| 107f | PUSH1 | 0x01 |
| 1081 | PUSH1 | 0x01 |
| 1083 | PUSH1 | 0xa0 |
| 1085 | SHL | |
| 1086 | SUB | |
| 1087 | PUSH2 | 0x108e |
| 108a | PUSH2 | 0x36ac |
| 108d | JUMP | |
| 108e | JUMPDEST | |
| 108f | AND | |
| 1090 | DUP2 | |
| 1091 | MSTORE | |
| 1092 | PUSH1 | 0x03 |
| 1094 | DUP6 | |
| 1095 | MSTORE | |
| 1096 | KECCAK256 | |
| 1097 | ADD | |
| 1098 | SLOAD | |
| 1099 | AND | |
| 109a | PUSH1 | 0x40 |
| 109c | MLOAD | |
| 109d | SWAP1 | |
| 109e | DUP2 | |
| 109f | MSTORE | |
| 10a0 | RETURN | |
| 10a1 | JUMPDEST | |
| 10a2 | POP | |
| 10a3 | CALLVALUE | |
| 10a4 | PUSH2 | 0x0298 |
| 10a7 | JUMPI | |
| 10a8 | PUSH1 | 0x40 |
| 10aa | CALLDATASIZE | |
| 10ab | PUSH1 | 0x03 |
| 10ad | NOT | |
| 10ae | ADD | |
| 10af | SLT | |
| 10b0 | PUSH2 | 0x0298 |
| 10b3 | JUMPI | |
| 10b4 | PUSH2 | 0x07e7 |
| 10b7 | PUSH2 | 0x10be |
| 10ba | PUSH2 | 0x36ac |
| 10bd | JUMP | |
| 10be | JUMPDEST | |
| 10bf | PUSH1 | 0x24 |
| 10c1 | CALLDATALOAD | |
| 10c2 | SWAP1 | |
| 10c3 | PUSH2 | 0x4833 |
| 10c6 | JUMP | |
| 10c7 | JUMPDEST | |
| 10c8 | POP | |
| 10c9 | CALLVALUE | |
| 10ca | PUSH2 | 0x0298 |
| 10cd | JUMPI | |
| 10ce | DUP1 | |
| 10cf | PUSH1 | 0x03 |
| 10d1 | NOT | |
| 10d2 | CALLDATASIZE | |
| 10d3 | ADD | |
| 10d4 | SLT | |
| 10d5 | PUSH2 | 0x0298 |
| 10d8 | JUMPI | |
| 10d9 | PUSH1 | 0x20 |
| 10db | PUSH1 | 0x40 |
| 10dd | MLOAD | |
| 10de | PUSH32 | 0x3154287b2470d9f05573ebd18908404f28e212134930a0f6df0005bc02e1c515 |
| 10ff | DUP2 | |
| 1100 | MSTORE | |
| 1101 | RETURN | |
| 1102 | JUMPDEST | |
| 1103 | POP | |
| 1104 | CALLVALUE | |
| 1105 | PUSH2 | 0x0298 |
| 1108 | JUMPI | |
| 1109 | DUP1 | |
| 110a | PUSH1 | 0x03 |
| 110c | NOT | |
| 110d | CALLDATASIZE | |
| 110e | ADD | |
| 110f | SLT | |
| 1110 | PUSH2 | 0x0298 |
| 1113 | JUMPI | |
| 1114 | PUSH1 | 0x20 |
| 1116 | PUSH1 | 0x01 |
| 1118 | PUSH1 | 0x01 |
| 111a | PUSH1 | 0x40 |
| 111c | SHL | |
| 111d | SUB | |
| 111e | PUSH1 | 0x0e |
| 1120 | SLOAD | |
| 1121 | AND | |
| 1122 | PUSH1 | 0x40 |
| 1124 | MLOAD | |
| 1125 | SWAP1 | |
| 1126 | DUP2 | |
| 1127 | MSTORE | |
| 1128 | RETURN | |
| 1129 | JUMPDEST | |
| 112a | POP | |
| 112b | CALLVALUE | |
| 112c | PUSH2 | 0x0298 |
| 112f | JUMPI | |
| 1130 | PUSH1 | 0x60 |
| 1132 | CALLDATASIZE | |
| 1133 | PUSH1 | 0x03 |
| 1135 | NOT | |
| 1136 | ADD | |
| 1137 | SLT | |
| 1138 | PUSH2 | 0x0298 |
| 113b | JUMPI | |
| 113c | PUSH1 | 0x01 |
| 113e | PUSH1 | 0x01 |
| 1140 | PUSH1 | 0x40 |
| 1142 | SHL | |
| 1143 | SUB | |
| 1144 | PUSH1 | 0x04 |
| 1146 | CALLDATALOAD | |
| 1147 | GT | |
| 1148 | PUSH2 | 0x0298 |
| 114b | JUMPI | |
| 114c | PUSH2 | 0x0400 |
| 114f | PUSH1 | 0x04 |
| 1151 | CALLDATALOAD | |
| 1152 | CALLDATASIZE | |
| 1153 | SUB | |
| 1154 | PUSH1 | 0x03 |
| 1156 | NOT | |
| 1157 | ADD | |
| 1158 | SLT | |
| 1159 | PUSH2 | 0x0298 |
| 115c | JUMPI | |
| 115d | PUSH2 | 0x1164 |
| 1160 | PUSH2 | 0x37b4 |
| 1163 | JUMP | |
| 1164 | JUMPDEST | |
| 1165 | PUSH1 | 0x44 |
| 1167 | CALLDATALOAD | |
| 1168 | PUSH1 | 0x01 |
| 116a | PUSH1 | 0x01 |
| 116c | PUSH1 | 0x40 |
| 116e | SHL | |
| 116f | SUB | |
| 1170 | DUP2 | |
| 1171 | GT | |
| 1172 | PUSH2 | 0x0833 |
| 1175 | JUMPI | |
| 1176 | PUSH2 | 0x1183 |
| 1179 | SWAP1 | |
| 117a | CALLDATASIZE | |
| 117b | SWAP1 | |
| 117c | PUSH1 | 0x04 |
| 117e | ADD | |
| 117f | PUSH2 | 0x3712 |
| 1182 | JUMP | |
| 1183 | JUMPDEST | |
| 1184 | SWAP2 | |
| 1185 | PUSH1 | 0x0e |
| 1187 | SLOAD | |
| 1188 | PUSH1 | 0xff |
| 118a | DUP2 | |
| 118b | PUSH1 | 0x40 |
| 118d | SHR | |
| 118e | AND | |
| 118f | PUSH2 | 0x1966 |
| 1192 | JUMPI | |
| 1193 | PUSH1 | 0x01 |
| 1195 | SLOAD | |
| 1196 | SWAP4 | |
| 1197 | DUP5 | |
| 1198 | ISZERO | |
| 1199 | PUSH2 | 0x100d |
| 119c | JUMPI | |
| 119d | PUSH1 | 0x40 |
| 119f | DUP1 | |
| 11a0 | MLOAD | |
| 11a1 | PUSH1 | 0x01 |
| 11a3 | PUSH1 | 0x01 |
| 11a5 | PUSH1 | 0x40 |
| 11a7 | SHL | |
| 11a8 | SUB | |
| 11a9 | DUP5 | |
| 11aa | AND | |
| 11ab | PUSH1 | 0x20 |
| 11ad | DUP3 | |
| 11ae | ADD | |
| 11af | MSTORE | |
| 11b0 | DUP1 | |
| 11b1 | DUP3 | |
| 11b2 | ADD | |
| 11b3 | SWAP2 | |
| 11b4 | SWAP1 | |
| 11b5 | SWAP2 | |
| 11b6 | MSTORE | |
| 11b7 | SWAP3 | |
| 11b8 | PUSH1 | 0x01 |
| 11ba | PUSH1 | 0x01 |
| 11bc | PUSH1 | 0xa0 |
| 11be | SHL | |
| 11bf | SUB | |
| 11c0 | PUSH2 | 0x11cc |
| 11c3 | PUSH1 | 0x04 |
| 11c5 | DUP1 | |
| 11c6 | CALLDATALOAD | |
| 11c7 | ADD | |
| 11c8 | PUSH2 | 0x36c2 |
| 11cb | JUMP | |
| 11cc | JUMPDEST | |
| 11cd | AND | |
| 11ce | PUSH1 | 0x60 |
| 11d0 | DUP6 | |
| 11d1 | ADD | |
| 11d2 | MSTORE | |
| 11d3 | PUSH1 | 0x24 |
| 11d5 | PUSH1 | 0x04 |
| 11d7 | CALLDATALOAD | |
| 11d8 | ADD | |
| 11d9 | SWAP6 | |
| 11da | PUSH2 | 0x01e0 |
| 11dd | DUP8 | |
| 11de | PUSH1 | 0x80 |
| 11e0 | DUP8 | |
| 11e1 | ADD | |
| 11e2 | CALLDATACOPY | |
| 11e3 | PUSH2 | 0x0204 |
| 11e6 | PUSH1 | 0x04 |
| 11e8 | CALLDATALOAD | |
| 11e9 | ADD | |
| 11ea | SWAP6 | |
| 11eb | PUSH2 | 0x123b |
| 11ee | PUSH2 | 0x1213 |
| 11f1 | PUSH2 | 0x11ff |
| 11f4 | DUP10 | |
| 11f5 | PUSH1 | 0x04 |
| 11f7 | CALLDATALOAD | |
| 11f8 | PUSH1 | 0x04 |
| 11fa | ADD | |
| 11fb | PUSH2 | 0x45a1 |
| 11fe | JUMP | |
| 11ff | JUMPDEST | |
| 1200 | PUSH2 | 0x0400 |
| 1203 | PUSH2 | 0x0260 |
| 1206 | DUP12 | |
| 1207 | ADD | |
| 1208 | MSTORE | |
| 1209 | PUSH2 | 0x0460 |
| 120c | DUP11 | |
| 120d | ADD | |
| 120e | SWAP2 | |
| 120f | PUSH2 | 0x45d5 |
| 1212 | JUMP | |
| 1213 | JUMPDEST | |
| 1214 | PUSH2 | 0x1228 |
| 1217 | PUSH2 | 0x0224 |
| 121a | PUSH1 | 0x04 |
| 121c | CALLDATALOAD | |
| 121d | ADD | |
| 121e | PUSH1 | 0x04 |
| 1220 | CALLDATALOAD | |
| 1221 | PUSH1 | 0x04 |
| 1223 | ADD | |
| 1224 | PUSH2 | 0x45a1 |
| 1227 | JUMP | |
| 1228 | JUMPDEST | |
| 1229 | DUP10 | |
| 122a | DUP4 | |
| 122b | SUB | |
| 122c | PUSH1 | 0x5f |
| 122e | NOT | |
| 122f | ADD | |
| 1230 | PUSH2 | 0x0280 |
| 1233 | DUP12 | |
| 1234 | ADD | |
| 1235 | MSTORE | |
| 1236 | SWAP1 | |
| 1237 | PUSH2 | 0x45d5 |
| 123a | JUMP | |
| 123b | JUMPDEST | |
| 123c | SWAP4 | |
| 123d | PUSH1 | 0x04 |
| 123f | CALLDATALOAD | |
| 1240 | PUSH2 | 0x0244 |
| 1243 | ADD | |
| 1244 | DUP11 | |
| 1245 | PUSH2 | 0x02a0 |
| 1248 | DUP10 | |
| 1249 | ADD | |
| 124a | JUMPDEST | |
| 124b | PUSH1 | 0x0b |
| 124d | DUP3 | |
| 124e | LT | |
| 124f | PUSH2 | 0x1940 |
| 1252 | JUMPI | |
| 1253 | POP | |
| 1254 | POP | |
| 1255 | POP | |
| 1256 | PUSH2 | 0x126a |
| 1259 | PUSH2 | 0x03a4 |
| 125c | PUSH1 | 0x04 |
| 125e | CALLDATALOAD | |
| 125f | ADD | |
| 1260 | PUSH1 | 0x04 |
| 1262 | CALLDATALOAD | |
| 1263 | PUSH1 | 0x04 |
| 1265 | ADD | |
| 1266 | PUSH2 | 0x45a1 |
| 1269 | JUMP | |
| 126a | JUMPDEST | |
| 126b | DUP9 | |
| 126c | DUP8 | |
| 126d | SUB | |
| 126e | PUSH1 | 0x5f |
| 1270 | NOT | |
| 1271 | ADD | |
| 1272 | PUSH2 | 0x0400 |
| 1275 | DUP11 | |
| 1276 | ADD | |
| 1277 | MSTORE | |
| 1278 | DUP1 | |
| 1279 | DUP8 | |
| 127a | MSTORE | |
| 127b | SWAP1 | |
| 127c | SWAP6 | |
| 127d | PUSH1 | 0x01 |
| 127f | PUSH1 | 0x01 |
| 1281 | PUSH1 | 0xfb |
| 1283 | SHL | |
| 1284 | SUB | |
| 1285 | DUP3 | |
| 1286 | GT | |
| 1287 | PUSH2 | 0x193c |
| 128a | JUMPI | |
| 128b | PUSH2 | 0x13a5 |
| 128e | SWAP7 | |
| 128f | PUSH2 | 0x12ce |
| 1292 | SWAP3 | |
| 1293 | PUSH1 | 0x05 |
| 1295 | SHL | |
| 1296 | DUP1 | |
| 1297 | SWAP2 | |
| 1298 | PUSH1 | 0x20 |
| 129a | DUP5 | |
| 129b | ADD | |
| 129c | CALLDATACOPY | |
| 129d | PUSH1 | 0x20 |
| 129f | PUSH2 | 0x12b3 |
| 12a2 | PUSH2 | 0x03c4 |
| 12a5 | PUSH1 | 0x04 |
| 12a7 | CALLDATALOAD | |
| 12a8 | ADD | |
| 12a9 | PUSH1 | 0x04 |
| 12ab | CALLDATALOAD | |
| 12ac | PUSH1 | 0x04 |
| 12ae | ADD | |
| 12af | PUSH2 | 0x4614 |
| 12b2 | JUMP | |
| 12b3 | JUMPDEST | |
| 12b4 | SWAP4 | |
| 12b5 | SWAP1 | |
| 12b6 | SWAP3 | |
| 12b7 | ADD | |
| 12b8 | DUP12 | |
| 12b9 | DUP2 | |
| 12ba | SUB | |
| 12bb | DUP3 | |
| 12bc | ADD | |
| 12bd | PUSH1 | 0x5f |
| 12bf | NOT | |
| 12c0 | ADD | |
| 12c1 | PUSH2 | 0x0420 |
| 12c4 | DUP14 | |
| 12c5 | ADD | |
| 12c6 | MSTORE | |
| 12c7 | ADD | |
| 12c8 | SWAP2 | |
| 12c9 | SWAP1 | |
| 12ca | PUSH2 | 0x4648 |
| 12cd | JUMP | |
| 12ce | JUMPDEST | |
| 12cf | SWAP7 | |
| 12d0 | PUSH2 | 0x12f1 |
| 12d3 | DUP2 | |
| 12d4 | PUSH2 | 0x03e4 |
| 12d7 | PUSH1 | 0x04 |
| 12d9 | CALLDATALOAD | |
| 12da | ADD | |
| 12db | CALLDATALOAD | |
| 12dc | SWAP10 | |
| 12dd | DUP11 | |
| 12de | PUSH2 | 0x0440 |
| 12e1 | DUP4 | |
| 12e2 | ADD | |
| 12e3 | MSTORE | |
| 12e4 | SUB | |
| 12e5 | PUSH1 | 0x1f |
| 12e7 | NOT | |
| 12e8 | DUP2 | |
| 12e9 | ADD | |
| 12ea | DUP4 | |
| 12eb | MSTORE | |
| 12ec | DUP3 | |
| 12ed | PUSH2 | 0x3b63 |
| 12f0 | JUMP | |
| 12f1 | JUMPDEST | |
| 12f2 | PUSH1 | 0x20 |
| 12f4 | DUP2 | |
| 12f5 | MLOAD | |
| 12f6 | SWAP2 | |
| 12f7 | ADD | |
| 12f8 | KECCAK256 | |
| 12f9 | PUSH1 | 0x40 |
| 12fb | MLOAD | |
| 12fc | PUSH1 | 0x20 |
| 12fe | DUP2 | |
| 12ff | ADD | |
| 1300 | SWAP2 | |
| 1301 | PUSH32 | 0xd850f5df47b124511e8e6ec99cf1a0beaf7c6237eff0a31305ce53d85f312675 |
| 1322 | DUP4 | |
| 1323 | MSTORE | |
| 1324 | CHAINID | |
| 1325 | PUSH1 | 0x40 |
| 1327 | DUP4 | |
| 1328 | ADD | |
| 1329 | MSTORE | |
| 132a | ADDRESS | |
| 132b | PUSH1 | 0x60 |
| 132d | DUP4 | |
| 132e | ADD | |
| 132f | MSTORE | |
| 1330 | PUSH32 | 0x8ff45d05bf7eaecf1e3489de0ad3d898e5ab54735cd0ca116506a6c8a7438c95 |
| 1351 | PUSH1 | 0x80 |
| 1353 | DUP4 | |
| 1354 | ADD | |
| 1355 | MSTORE | |
| 1356 | PUSH1 | 0x01 |
| 1358 | PUSH1 | 0x01 |
| 135a | PUSH1 | 0x40 |
| 135c | SHL | |
| 135d | SUB | |
| 135e | DUP8 | |
| 135f | AND | |
| 1360 | PUSH1 | 0xa0 |
| 1362 | DUP4 | |
| 1363 | ADD | |
| 1364 | MSTORE | |
| 1365 | PUSH1 | 0xc0 |
| 1367 | DUP3 | |
| 1368 | ADD | |
| 1369 | MSTORE | |
| 136a | PUSH1 | 0xc0 |
| 136c | DUP2 | |
| 136d | MSTORE | |
| 136e | PUSH2 | 0x1378 |
| 1371 | PUSH1 | 0xe0 |
| 1373 | DUP3 | |
| 1374 | PUSH2 | 0x3b63 |
| 1377 | JUMP | |
| 1378 | JUMPDEST | |
| 1379 | MLOAD | |
| 137a | SWAP1 | |
| 137b | KECCAK256 | |
| 137c | SWAP1 | |
| 137d | DUP11 | |
| 137e | SLOAD | |
| 137f | SWAP3 | |
| 1380 | PUSH32 | 0x0000000000000000000000003c0698e02a10fec9a5cd5939d0a0f2d484e8d540 |
| 13a1 | PUSH2 | 0x516a |
| 13a4 | JUMP | |
| 13a5 | JUMPDEST | |
| 13a6 | POP | |
| 13a7 | PUSH1 | 0x01 |
| 13a9 | PUSH1 | 0x01 |
| 13ab | PUSH1 | 0x40 |
| 13ad | SHL | |
| 13ae | SUB | |
| 13af | PUSH2 | 0x13b9 |
| 13b2 | DUP2 | |
| 13b3 | DUP4 | |
| 13b4 | AND | |
| 13b5 | PUSH2 | 0x3bff |
| 13b8 | JUMP | |
| 13b9 | JUMPDEST | |
| 13ba | AND | |
| 13bb | SWAP1 | |
| 13bc | PUSH1 | 0x01 |
| 13be | PUSH1 | 0x01 |
| 13c0 | PUSH1 | 0x40 |
| 13c2 | SHL | |
| 13c3 | SUB | |
| 13c4 | NOT | |
| 13c5 | AND | |
| 13c6 | OR | |
| 13c7 | PUSH1 | 0x0e |
| 13c9 | SSTORE | |
| 13ca | PUSH2 | 0x13d7 |
| 13cd | PUSH1 | 0x04 |
| 13cf | CALLDATALOAD | |
| 13d0 | PUSH1 | 0x04 |
| 13d2 | ADD | |
| 13d3 | PUSH2 | 0x3bd7 |
| 13d6 | JUMP | |
| 13d7 | JUMPDEST | |
| 13d8 | PUSH1 | 0x01 |
| 13da | PUSH1 | 0x01 |
| 13dc | PUSH1 | 0xa0 |
| 13de | SHL | |
| 13df | SUB | |
| 13e0 | DUP2 | |
| 13e1 | AND | |
| 13e2 | SWAP3 | |
| 13e3 | SWAP1 | |
| 13e4 | SWAP2 | |
| 13e5 | SWAP1 | |
| 13e6 | DUP4 | |
| 13e7 | ISZERO | |
| 13e8 | PUSH2 | 0x18db |
| 13eb | JUMPI | |
| 13ec | DUP4 | |
| 13ed | DUP7 | |
| 13ee | MSTORE | |
| 13ef | PUSH1 | 0x03 |
| 13f1 | PUSH1 | 0x20 |
| 13f3 | MSTORE | |
| 13f4 | PUSH1 | 0x40 |
| 13f6 | DUP7 | |
| 13f7 | KECCAK256 | |
| 13f8 | SWAP5 | |
| 13f9 | PUSH1 | 0xff |
| 13fb | DUP7 | |
| 13fc | SLOAD | |
| 13fd | AND | |
| 13fe | PUSH2 | 0x1928 |
| 1401 | JUMPI | |
| 1402 | DUP7 | |
| 1403 | JUMPDEST | |
| 1404 | PUSH1 | 0x0f |
| 1406 | DUP2 | |
| 1407 | LT | |
| 1408 | PUSH2 | 0x1910 |
| 140b | JUMPI | |
| 140c | POP | |
| 140d | POP | |
| 140e | PUSH1 | 0xff |
| 1410 | DUP6 | |
| 1411 | SLOAD | |
| 1412 | AND | |
| 1413 | ISZERO | |
| 1414 | DUP1 | |
| 1415 | ISZERO | |
| 1416 | PUSH2 | 0x1904 |
| 1419 | JUMPI | |
| 141a | JUMPDEST | |
| 141b | DUP1 | |
| 141c | ISZERO | |
| 141d | PUSH2 | 0x18ef |
| 1420 | JUMPI | |
| 1421 | JUMPDEST | |
| 1422 | PUSH2 | 0x18db |
| 1425 | JUMPI | |
| 1426 | PUSH2 | 0x1434 |
| 1429 | SWAP1 | |
| 142a | PUSH1 | 0x04 |
| 142c | CALLDATALOAD | |
| 142d | PUSH1 | 0x04 |
| 142f | ADD | |
| 1430 | PUSH2 | 0x4681 |
| 1433 | JUMP | |
| 1434 | JUMPDEST | |
| 1435 | SWAP1 | |
| 1436 | DUP5 | |
| 1437 | DUP8 | |
| 1438 | MSTORE | |
| 1439 | PUSH1 | 0x04 |
| 143b | PUSH1 | 0x20 |
| 143d | MSTORE | |
| 143e | PUSH1 | 0x40 |
| 1440 | DUP8 | |
| 1441 | KECCAK256 | |
| 1442 | SWAP1 | |
| 1443 | PUSH1 | 0x01 |
| 1445 | PUSH1 | 0x01 |
| 1447 | PUSH1 | 0x40 |
| 1449 | SHL | |
| 144a | SUB | |
| 144b | DUP4 | |
| 144c | GT | |
| 144d | PUSH2 | 0x18ac |
| 1450 | JUMPI | |
| 1451 | PUSH2 | 0x145a |
| 1454 | DUP4 | |
| 1455 | DUP4 | |
| 1456 | PUSH2 | 0x46e6 |
| 1459 | JUMP | |
| 145a | JUMPDEST | |
| 145b | SWAP1 | |
| 145c | DUP8 | |
| 145d | MSTORE | |
| 145e | PUSH1 | 0x20 |
| 1460 | DUP8 | |
| 1461 | KECCAK256 | |
| 1462 | DUP8 | |
| 1463 | JUMPDEST | |
| 1464 | DUP4 | |
| 1465 | DUP2 | |
| 1466 | LT | |
| 1467 | PUSH2 | 0x18c0 |
| 146a | JUMPI | |
| 146b | POP | |
| 146c | POP | |
| 146d | POP | |
| 146e | POP | |
| 146f | PUSH2 | 0x1483 |
| 1472 | PUSH2 | 0x0224 |
| 1475 | PUSH1 | 0x04 |
| 1477 | CALLDATALOAD | |
| 1478 | ADD | |
| 1479 | PUSH1 | 0x04 |
| 147b | CALLDATALOAD | |
| 147c | PUSH1 | 0x04 |
| 147e | ADD | |
| 147f | PUSH2 | 0x4681 |
| 1482 | JUMP | |
| 1483 | JUMPDEST | |
| 1484 | SWAP1 | |
| 1485 | DUP5 | |
| 1486 | DUP8 | |
| 1487 | MSTORE | |
| 1488 | PUSH1 | 0x05 |
| 148a | PUSH1 | 0x20 |
| 148c | MSTORE | |
| 148d | PUSH1 | 0x40 |
| 148f | DUP8 | |
| 1490 | KECCAK256 | |
| 1491 | SWAP1 | |
| 1492 | PUSH1 | 0x01 |
| 1494 | PUSH1 | 0x01 |
| 1496 | PUSH1 | 0x40 |
| 1498 | SHL | |
| 1499 | SUB | |
| 149a | DUP4 | |
| 149b | GT | |
| 149c | PUSH2 | 0x18ac |
| 149f | JUMPI | |
| 14a0 | PUSH2 | 0x14a9 |
| 14a3 | DUP4 | |
| 14a4 | DUP4 | |
| 14a5 | PUSH2 | 0x46e6 |
| 14a8 | JUMP | |
| 14a9 | JUMPDEST | |
| 14aa | SWAP1 | |
| 14ab | DUP8 | |
| 14ac | MSTORE | |
| 14ad | PUSH1 | 0x20 |
| 14af | DUP8 | |
| 14b0 | KECCAK256 | |
| 14b1 | DUP8 | |
| 14b2 | JUMPDEST | |
| 14b3 | DUP4 | |
| 14b4 | DUP2 | |
| 14b5 | LT | |
| 14b6 | PUSH2 | 0x1891 |
| 14b9 | JUMPI | |
| 14ba | POP | |
| 14bb | POP | |
| 14bc | POP | |
| 14bd | POP | |
| 14be | DUP5 | |
| 14bf | JUMPDEST | |
| 14c0 | PUSH1 | 0xff |
| 14c2 | DUP2 | |
| 14c3 | AND | |
| 14c4 | PUSH1 | 0x0b |
| 14c6 | DUP2 | |
| 14c7 | LT | |
| 14c8 | ISZERO | |
| 14c9 | PUSH2 | 0x1521 |
| 14cc | JUMPI | |
| 14cd | PUSH1 | 0xff |
| 14cf | SWAP2 | |
| 14d0 | DUP2 | |
| 14d1 | PUSH2 | 0x14ea |
| 14d4 | PUSH2 | 0x14e5 |
| 14d7 | PUSH1 | 0x01 |
| 14d9 | SWAP5 | |
| 14da | PUSH2 | 0x0244 |
| 14dd | PUSH1 | 0x04 |
| 14df | CALLDATALOAD | |
| 14e0 | ADD | |
| 14e1 | PUSH2 | 0x472a |
| 14e4 | JUMP | |
| 14e5 | JUMPDEST | |
| 14e6 | PUSH2 | 0x3beb |
| 14e9 | JUMP | |
| 14ea | JUMPDEST | |
| 14eb | SWAP1 | |
| 14ec | DUP8 | |
| 14ed | DUP11 | |
| 14ee | MSTORE | |
| 14ef | PUSH1 | 0x06 |
| 14f1 | PUSH1 | 0x20 |
| 14f3 | MSTORE | |
| 14f4 | PUSH1 | 0x40 |
| 14f6 | DUP11 | |
| 14f7 | KECCAK256 | |
| 14f8 | SWAP1 | |
| 14f9 | PUSH0 | |
| 14fa | MSTORE | |
| 14fb | PUSH1 | 0x20 |
| 14fd | MSTORE | |
| 14fe | PUSH1 | 0x01 |
| 1500 | PUSH1 | 0x01 |
| 1502 | PUSH1 | 0x40 |
| 1504 | SHL | |
| 1505 | SUB | |
| 1506 | PUSH1 | 0x40 |
| 1508 | PUSH0 | |
| 1509 | KECCAK256 | |
| 150a | SWAP2 | |
| 150b | AND | |
| 150c | PUSH1 | 0x01 |
| 150e | PUSH1 | 0x01 |
| 1510 | PUSH1 | 0x40 |
| 1512 | SHL | |
| 1513 | SUB | |
| 1514 | NOT | |
| 1515 | DUP3 | |
| 1516 | SLOAD | |
| 1517 | AND | |
| 1518 | OR | |
| 1519 | SWAP1 | |
| 151a | SSTORE | |
| 151b | ADD | |
| 151c | AND | |
| 151d | PUSH2 | 0x14bf |
| 1520 | JUMP | |
| 1521 | JUMPDEST | |
| 1522 | POP | |
| 1523 | POP | |
| 1524 | SWAP3 | |
| 1525 | SWAP1 | |
| 1526 | DUP5 | |
| 1527 | JUMPDEST | |
| 1528 | PUSH2 | 0x153c |
| 152b | PUSH2 | 0x03a4 |
| 152e | PUSH1 | 0x04 |
| 1530 | CALLDATALOAD | |
| 1531 | ADD | |
| 1532 | PUSH1 | 0x04 |
| 1534 | CALLDATALOAD | |
| 1535 | PUSH1 | 0x04 |
| 1537 | ADD | |
| 1538 | PUSH2 | 0x4681 |
| 153b | JUMP | |
| 153c | JUMPDEST | |
| 153d | SWAP1 | |
| 153e | POP | |
| 153f | DUP2 | |
| 1540 | LT | |
| 1541 | ISZERO | |
| 1542 | PUSH2 | 0x15c7 |
| 1545 | JUMPI | |
| 1546 | DUP1 | |
| 1547 | PUSH2 | 0x1567 |
| 154a | PUSH1 | 0x01 |
| 154c | SWAP3 | |
| 154d | PUSH2 | 0x1561 |
| 1550 | PUSH2 | 0x03a4 |
| 1553 | PUSH1 | 0x04 |
| 1555 | CALLDATALOAD | |
| 1556 | ADD | |
| 1557 | PUSH1 | 0x04 |
| 1559 | CALLDATALOAD | |
| 155a | PUSH1 | 0x04 |
| 155c | ADD | |
| 155d | PUSH2 | 0x4681 |
| 1560 | JUMP | |
| 1561 | JUMPDEST | |
| 1562 | SWAP1 | |
| 1563 | PUSH2 | 0x3f70 |
| 1566 | JUMP | |
| 1567 | JUMPDEST | |
| 1568 | CALLDATALOAD | |
| 1569 | DUP6 | |
| 156a | DUP9 | |
| 156b | MSTORE | |
| 156c | PUSH1 | 0x09 |
| 156e | PUSH1 | 0x20 |
| 1570 | MSTORE | |
| 1571 | PUSH1 | 0x40 |
| 1573 | DUP9 | |
| 1574 | KECCAK256 | |
| 1575 | DUP2 | |
| 1576 | DUP10 | |
| 1577 | MSTORE | |
| 1578 | PUSH1 | 0x20 |
| 157a | MSTORE | |
| 157b | PUSH1 | 0xff |
| 157d | PUSH1 | 0x40 |
| 157f | DUP10 | |
| 1580 | KECCAK256 | |
| 1581 | SLOAD | |
| 1582 | AND | |
| 1583 | PUSH2 | 0x15c1 |
| 1586 | JUMPI | |
| 1587 | PUSH2 | 0x15bb |
| 158a | SWAP1 | |
| 158b | DUP7 | |
| 158c | DUP10 | |
| 158d | MSTORE | |
| 158e | PUSH1 | 0x09 |
| 1590 | PUSH1 | 0x20 |
| 1592 | MSTORE | |
| 1593 | PUSH1 | 0x40 |
| 1595 | DUP10 | |
| 1596 | KECCAK256 | |
| 1597 | DUP2 | |
| 1598 | DUP11 | |
| 1599 | MSTORE | |
| 159a | PUSH1 | 0x20 |
| 159c | MSTORE | |
| 159d | PUSH1 | 0x40 |
| 159f | DUP10 | |
| 15a0 | KECCAK256 | |
| 15a1 | DUP5 | |
| 15a2 | PUSH1 | 0xff |
| 15a4 | NOT | |
| 15a5 | DUP3 | |
| 15a6 | SLOAD | |
| 15a7 | AND | |
| 15a8 | OR | |
| 15a9 | SWAP1 | |
| 15aa | SSTORE | |
| 15ab | DUP7 | |
| 15ac | DUP10 | |
| 15ad | MSTORE | |
| 15ae | PUSH1 | 0x08 |
| 15b0 | PUSH1 | 0x20 |
| 15b2 | MSTORE | |
| 15b3 | PUSH1 | 0x40 |
| 15b5 | DUP10 | |
| 15b6 | KECCAK256 | |
| 15b7 | PUSH2 | 0x473b |
| 15ba | JUMP | |
| 15bb | JUMPDEST | |
| 15bc | ADD | |
| 15bd | PUSH2 | 0x1527 |
| 15c0 | JUMP | |
| 15c1 | JUMPDEST | |
| 15c2 | POP | |
| 15c3 | PUSH2 | 0x15bb |
| 15c6 | JUMP | |
| 15c7 | JUMPDEST | |
| 15c8 | POP | |
| 15c9 | DUP4 | |
| 15ca | DUP6 | |
| 15cb | SWAP4 | |
| 15cc | DUP5 | |
| 15cd | JUMPDEST | |
| 15ce | DUP6 | |
| 15cf | PUSH2 | 0x15e3 |
| 15d2 | PUSH2 | 0x03c4 |
| 15d5 | PUSH1 | 0x04 |
| 15d7 | CALLDATALOAD | |
| 15d8 | ADD | |
| 15d9 | PUSH1 | 0x04 |
| 15db | CALLDATALOAD | |
| 15dc | PUSH1 | 0x04 |
| 15de | ADD | |
| 15df | PUSH2 | 0x476f |
| 15e2 | JUMP | |
| 15e3 | JUMPDEST | |
| 15e4 | SWAP1 | |
| 15e5 | POP | |
| 15e6 | DUP3 | |
| 15e7 | LT | |
| 15e8 | ISZERO | |
| 15e9 | PUSH2 | 0x1637 |
| 15ec | JUMPI | |
| 15ed | POP | |
| 15ee | DUP1 | |
| 15ef | PUSH2 | 0x1631 |
| 15f2 | PUSH2 | 0x160c |
| 15f5 | PUSH1 | 0x01 |
| 15f7 | SWAP4 | |
| 15f8 | PUSH2 | 0x0a87 |
| 15fb | PUSH2 | 0x03c4 |
| 15fe | PUSH1 | 0x04 |
| 1600 | CALLDATALOAD | |
| 1601 | ADD | |
| 1602 | PUSH1 | 0x04 |
| 1604 | CALLDATALOAD | |
| 1605 | PUSH1 | 0x04 |
| 1607 | ADD | |
| 1608 | PUSH2 | 0x476f |
| 160b | JUMP | |
| 160c | JUMPDEST | |
| 160d | CALLDATALOAD | |
| 160e | PUSH1 | 0x20 |
| 1610 | PUSH2 | 0x1628 |
| 1613 | DUP5 | |
| 1614 | PUSH2 | 0x0a87 |
| 1617 | PUSH2 | 0x03c4 |
| 161a | PUSH1 | 0x04 |
| 161c | CALLDATALOAD | |
| 161d | ADD | |
| 161e | PUSH1 | 0x04 |
| 1620 | CALLDATALOAD | |
| 1621 | PUSH1 | 0x04 |
| 1623 | ADD | |
| 1624 | PUSH2 | 0x476f |
| 1627 | JUMP | |
| 1628 | JUMPDEST | |
| 1629 | ADD | |
| 162a | CALLDATALOAD | |
| 162b | SWAP1 | |
| 162c | DUP8 | |
| 162d | PUSH2 | 0x53dc |
| 1630 | JUMP | |
| 1631 | JUMPDEST | |
| 1632 | ADD | |
| 1633 | PUSH2 | 0x15cd |
| 1636 | JUMP | |
| 1637 | JUMPDEST | |
| 1638 | DUP1 | |
| 1639 | SWAP5 | |
| 163a | SWAP2 | |
| 163b | POP | |
| 163c | PUSH1 | 0x09 |
| 163e | DUP7 | |
| 163f | PUSH2 | 0x1647 |
| 1642 | DUP5 | |
| 1643 | PUSH2 | 0x47b4 |
| 1646 | JUMP | |
| 1647 | JUMPDEST | |
| 1648 | ADD | |
| 1649 | DUP1 | |
| 164a | SLOAD | |
| 164b | SWAP1 | |
| 164c | SWAP3 | |
| 164d | SWAP1 | |
| 164e | PUSH2 | 0x1660 |
| 1651 | SWAP1 | |
| 1652 | PUSH1 | 0x01 |
| 1654 | PUSH1 | 0x01 |
| 1656 | PUSH1 | 0xa0 |
| 1658 | SHL | |
| 1659 | SUB | |
| 165a | AND | |
| 165b | DUP3 | |
| 165c | PUSH2 | 0x5509 |
| 165f | JUMP | |
| 1660 | JUMPDEST | |
| 1661 | DUP3 | |
| 1662 | SLOAD | |
| 1663 | DUP5 | |
| 1664 | PUSH32 | 0xcfe82510d1c464fb22d59e8531313b14d3894bb1dfdec9de06b77b65afa87a76 |
| 1685 | PUSH1 | 0x20 |
| 1687 | PUSH1 | 0x40 |
| 1689 | MLOAD | |
| 168a | SWAP4 | |
| 168b | PUSH1 | 0x01 |
| 168d | PUSH1 | 0x01 |
| 168f | PUSH1 | 0x40 |
| 1691 | SHL | |
| 1692 | SUB | |
| 1693 | DUP2 | |
| 1694 | PUSH1 | 0xb0 |
| 1696 | SHR | |
| 1697 | AND | |
| 1698 | DUP6 | |
| 1699 | MSTORE | |
| 169a | PUSH1 | 0x01 |
| 169c | DUP1 | |
| 169d | PUSH1 | 0xa0 |
| 169f | SHL | |
| 16a0 | SUB | |
| 16a1 | AND | |
| 16a2 | SWAP4 | |
| 16a3 | LOG3 | |
| 16a4 | PUSH2 | 0x16b4 |
| 16a7 | PUSH2 | 0x16ae |
| 16aa | PUSH2 | 0x3cb4 |
| 16ad | JUMP | |
| 16ae | JUMPDEST | |
| 16af | SWAP2 | |
| 16b0 | PUSH2 | 0x4e29 |
| 16b3 | JUMP | |
| 16b4 | JUMPDEST | |
| 16b5 | PUSH2 | 0x16bd |
| 16b8 | DUP3 | |
| 16b9 | PUSH2 | 0x3cf4 |
| 16bc | JUMP | |
| 16bd | JUMPDEST | |
| 16be | MSTORE | |
| 16bf | PUSH2 | 0x16c7 |
| 16c2 | DUP2 | |
| 16c3 | PUSH2 | 0x3cf4 |
| 16c6 | JUMP | |
| 16c7 | JUMPDEST | |
| 16c8 | POP | |
| 16c9 | PUSH32 | 0x0000000000000000000000000636a51e796ba8311016fae2a74670d2fdeb33c8 |
| 16ea | PUSH1 | 0x01 |
| 16ec | PUSH1 | 0x01 |
| 16ee | PUSH1 | 0xa0 |
| 16f0 | SHL | |
| 16f1 | SUB | |
| 16f2 | AND | |
| 16f3 | SWAP1 | |
| 16f4 | DUP2 | |
| 16f5 | EXTCODESIZE | |
| 16f6 | ISZERO | |
| 16f7 | PUSH2 | 0x0833 |
| 16fa | JUMPI | |
| 16fb | DUP3 | |
| 16fc | PUSH2 | 0x1719 |
| 16ff | SWAP2 | |
| 1700 | PUSH1 | 0x40 |
| 1702 | MLOAD | |
| 1703 | DUP1 | |
| 1704 | SWAP4 | |
| 1705 | DUP2 | |
| 1706 | SWAP3 | |
| 1707 | PUSH4 | 0x2728f271 |
| 170c | PUSH1 | 0xe2 |
| 170e | SHL | |
| 170f | DUP4 | |
| 1710 | MSTORE | |
| 1711 | PUSH1 | 0x04 |
| 1713 | DUP4 | |
| 1714 | ADD | |
| 1715 | PUSH2 | 0x3d15 |
| 1718 | JUMP | |
| 1719 | JUMPDEST | |
| 171a | SUB | |
| 171b | DUP2 | |
| 171c | DUP4 | |
| 171d | DUP7 | |
| 171e | GAS | |
| 171f | CALL | |
| 1720 | SWAP1 | |
| 1721 | DUP2 | |
| 1722 | ISZERO | |
| 1723 | PUSH2 | 0x1886 |
| 1726 | JUMPI | |
| 1727 | DUP4 | |
| 1728 | SWAP2 | |
| 1729 | PUSH2 | 0x1871 |
| 172c | JUMPI | |
| 172d | JUMPDEST | |
| 172e | POP | |
| 172f | POP | |
| 1730 | PUSH1 | 0x40 |
| 1732 | SWAP4 | |
| 1733 | DUP5 | |
| 1734 | MLOAD | |
| 1735 | SWAP1 | |
| 1736 | PUSH2 | 0x173f |
| 1739 | DUP7 | |
| 173a | DUP4 | |
| 173b | PUSH2 | 0x3b63 |
| 173e | JUMP | |
| 173f | JUMPDEST | |
| 1740 | PUSH1 | 0x01 |
| 1742 | DUP3 | |
| 1743 | MSTORE | |
| 1744 | PUSH1 | 0x1f |
| 1746 | NOT | |
| 1747 | DUP7 | |
| 1748 | ADD | |
| 1749 | SWAP7 | |
| 174a | DUP8 | |
| 174b | CALLDATASIZE | |
| 174c | PUSH1 | 0x20 |
| 174e | DUP6 | |
| 174f | ADD | |
| 1750 | CALLDATACOPY | |
| 1751 | DUP7 | |
| 1752 | MLOAD | |
| 1753 | SWAP2 | |
| 1754 | PUSH2 | 0x175d |
| 1757 | DUP9 | |
| 1758 | DUP5 | |
| 1759 | PUSH2 | 0x3b63 |
| 175c | JUMP | |
| 175d | JUMPDEST | |
| 175e | PUSH1 | 0x01 |
| 1760 | DUP4 | |
| 1761 | MSTORE | |
| 1762 | DUP9 | |
| 1763 | CALLDATASIZE | |
| 1764 | PUSH1 | 0x20 |
| 1766 | DUP6 | |
| 1767 | ADD | |
| 1768 | CALLDATACOPY | |
| 1769 | DUP8 | |
| 176a | MLOAD | |
| 176b | SWAP1 | |
| 176c | PUSH4 | 0x82edfbd9 |
| 1771 | PUSH1 | 0xe0 |
| 1773 | SHL | |
| 1774 | DUP3 | |
| 1775 | MSTORE | |
| 1776 | PUSH1 | 0x04 |
| 1778 | DUP3 | |
| 1779 | ADD | |
| 177a | MSTORE | |
| 177b | PUSH1 | 0x20 |
| 177d | DUP2 | |
| 177e | PUSH1 | 0x24 |
| 1780 | DUP2 | |
| 1781 | DUP9 | |
| 1782 | GAS | |
| 1783 | STATICCALL | |
| 1784 | SWAP1 | |
| 1785 | DUP2 | |
| 1786 | ISZERO | |
| 1787 | PUSH2 | 0x1867 |
| 178a | JUMPI | |
| 178b | DUP7 | |
| 178c | SWAP2 | |
| 178d | PUSH2 | 0x1832 |
| 1790 | JUMPI | |
| 1791 | JUMPDEST | |
| 1792 | POP | |
| 1793 | PUSH2 | 0x179b |
| 1796 | DUP5 | |
| 1797 | PUSH2 | 0x3cf4 |
| 179a | JUMP | |
| 179b | JUMPDEST | |
| 179c | MSTORE | |
| 179d | PUSH2 | 0x17a5 |
| 17a0 | DUP3 | |
| 17a1 | PUSH2 | 0x3cf4 |
| 17a4 | JUMP | |
| 17a5 | JUMPDEST | |
| 17a6 | MSTORE | |
| 17a7 | DUP3 | |
| 17a8 | EXTCODESIZE | |
| 17a9 | ISZERO | |
| 17aa | PUSH2 | 0x0862 |
| 17ad | JUMPI | |
| 17ae | PUSH2 | 0x17cf |
| 17b1 | SWAP3 | |
| 17b2 | DUP5 | |
| 17b3 | SWAP3 | |
| 17b4 | DUP4 | |
| 17b5 | DUP9 | |
| 17b6 | MLOAD | |
| 17b7 | DUP1 | |
| 17b8 | SWAP7 | |
| 17b9 | DUP2 | |
| 17ba | SWAP6 | |
| 17bb | DUP3 | |
| 17bc | SWAP5 | |
| 17bd | PUSH4 | 0xabf1570d |
| 17c2 | PUSH1 | 0xe0 |
| 17c4 | SHL | |
| 17c5 | DUP5 | |
| 17c6 | MSTORE | |
| 17c7 | PUSH1 | 0x04 |
| 17c9 | DUP5 | |
| 17ca | ADD | |
| 17cb | PUSH2 | 0x47fd |
| 17ce | JUMP | |
| 17cf | JUMPDEST | |
| 17d0 | SUB | |
| 17d1 | SWAP3 | |
| 17d2 | GAS | |
| 17d3 | CALL | |
| 17d4 | DUP1 | |
| 17d5 | ISZERO | |
| 17d6 | PUSH2 | 0x1828 |
| 17d9 | JUMPI | |
| 17da | PUSH2 | 0x1813 |
| 17dd | JUMPI | |
| 17de | JUMPDEST | |
| 17df | POP | |
| 17e0 | POP | |
| 17e1 | PUSH2 | 0x07e7 |
| 17e4 | SWAP3 | |
| 17e5 | PUSH2 | 0x17f0 |
| 17e8 | DUP4 | |
| 17e9 | MLOAD | |
| 17ea | SWAP4 | |
| 17eb | DUP5 | |
| 17ec | PUSH2 | 0x3b63 |
| 17ef | JUMP | |
| 17f0 | JUMPDEST | |
| 17f1 | PUSH1 | 0x01 |
| 17f3 | DUP4 | |
| 17f4 | MSTORE | |
| 17f5 | CALLDATASIZE | |
| 17f6 | PUSH1 | 0x20 |
| 17f8 | DUP5 | |
| 17f9 | ADD | |
| 17fa | CALLDATACOPY | |
| 17fb | SLOAD | |
| 17fc | PUSH1 | 0x01 |
| 17fe | PUSH1 | 0x01 |
| 1800 | PUSH1 | 0xa0 |
| 1802 | SHL | |
| 1803 | SUB | |
| 1804 | AND | |
| 1805 | PUSH2 | 0x180d |
| 1808 | DUP3 | |
| 1809 | PUSH2 | 0x3cf4 |
| 180c | JUMP | |
| 180d | JUMPDEST | |
| 180e | MSTORE | |
| 180f | PUSH2 | 0x555f |
| 1812 | JUMP | |
| 1813 | JUMPDEST | |
| 1814 | DUP2 | |
| 1815 | PUSH2 | 0x181d |
| 1818 | SWAP2 | |
| 1819 | PUSH2 | 0x3b63 |
| 181c | JUMP | |
| 181d | JUMPDEST | |
| 181e | PUSH2 | 0x0862 |
| 1821 | JUMPI | |
| 1822 | DUP4 | |
| 1823 | DUP6 | |
| 1824 | PUSH2 | 0x17de |
| 1827 | JUMP | |
| 1828 | JUMPDEST | |
| 1829 | DUP5 | |
| 182a | MLOAD | |
| 182b | RETURNDATASIZE | |
| 182c | DUP5 | |
| 182d | DUP3 | |
| 182e | RETURNDATACOPY | |
| 182f | RETURNDATASIZE | |
| 1830 | SWAP1 | |
| 1831 | REVERT | |
| 1832 | JUMPDEST | |
| 1833 | SWAP6 | |
| 1834 | POP | |
| 1835 | POP | |
| 1836 | PUSH1 | 0x20 |
| 1838 | DUP6 | |
| 1839 | RETURNDATASIZE | |
| 183a | PUSH1 | 0x20 |
| 183c | GT | |
| 183d | PUSH2 | 0x185f |
| 1840 | JUMPI | |
| 1841 | JUMPDEST | |
| 1842 | DUP2 | |
| 1843 | PUSH2 | 0x184e |
| 1846 | PUSH1 | 0x20 |
| 1848 | SWAP4 | |
| 1849 | DUP4 | |
| 184a | PUSH2 | 0x3b63 |
| 184d | JUMP | |
| 184e | JUMPDEST | |
| 184f | DUP2 | |
| 1850 | ADD | |
| 1851 | SUB | |
| 1852 | SLT | |
| 1853 | PUSH2 | 0x0da5 |
| 1856 | JUMPI | |
| 1857 | DUP9 | |
| 1858 | SWAP5 | |
| 1859 | MLOAD | |
| 185a | DUP11 | |
| 185b | PUSH2 | 0x1791 |
| 185e | JUMP | |
| 185f | JUMPDEST | |
| 1860 | RETURNDATASIZE | |
| 1861 | SWAP2 | |
| 1862 | POP | |
| 1863 | PUSH2 | 0x1841 |
| 1866 | JUMP | |
| 1867 | JUMPDEST | |
| 1868 | DUP9 | |
| 1869 | MLOAD | |
| 186a | RETURNDATASIZE | |
| 186b | DUP9 | |
| 186c | DUP3 | |
| 186d | RETURNDATACOPY | |
| 186e | RETURNDATASIZE | |
| 186f | SWAP1 | |
| 1870 | REVERT | |
| 1871 | JUMPDEST | |
| 1872 | DUP2 | |
| 1873 | PUSH2 | 0x187b |
| 1876 | SWAP2 | |
| 1877 | PUSH2 | 0x3b63 |
| 187a | JUMP | |
| 187b | JUMPDEST | |
| 187c | PUSH2 | 0x101c |
| 187f | JUMPI | |
| 1880 | DUP2 | |
| 1881 | DUP8 | |
| 1882 | PUSH2 | 0x172d |
| 1885 | JUMP | |
| 1886 | JUMPDEST | |
| 1887 | PUSH1 | 0x40 |
| 1889 | MLOAD | |
| 188a | RETURNDATASIZE | |
| 188b | DUP6 | |
| 188c | DUP3 | |
| 188d | RETURNDATACOPY | |
| 188e | RETURNDATASIZE | |
| 188f | SWAP1 | |
| 1890 | REVERT | |
| 1891 | JUMPDEST | |
| 1892 | PUSH1 | 0x01 |
| 1894 | SWAP1 | |
| 1895 | PUSH1 | 0x20 |
| 1897 | PUSH2 | 0x189f |
| 189a | DUP6 | |
| 189b | PUSH2 | 0x3bd7 |
| 189e | JUMP | |
| 189f | JUMPDEST | |
| 18a0 | SWAP5 | |
| 18a1 | ADD | |
| 18a2 | SWAP4 | |
| 18a3 | DUP2 | |
| 18a4 | DUP5 | |
| 18a5 | ADD | |
| 18a6 | SSTORE | |
| 18a7 | ADD | |
| 18a8 | PUSH2 | 0x14b2 |
| 18ab | JUMP | |
| 18ac | JUMPDEST | |
| 18ad | PUSH4 | 0x4e487b71 |
| 18b2 | PUSH1 | 0xe0 |
| 18b4 | SHL | |
| 18b5 | DUP9 | |
| 18b6 | MSTORE | |
| 18b7 | PUSH1 | 0x41 |
| 18b9 | PUSH1 | 0x04 |
| 18bb | MSTORE | |
| 18bc | PUSH1 | 0x24 |
| 18be | DUP9 | |
| 18bf | REVERT | |
| 18c0 | JUMPDEST | |
| 18c1 | PUSH1 | 0x01 |
| 18c3 | SWAP1 | |
| 18c4 | PUSH1 | 0x20 |
| 18c6 | PUSH2 | 0x18ce |
| 18c9 | DUP6 | |
| 18ca | PUSH2 | 0x3bd7 |
| 18cd | JUMP | |
| 18ce | JUMPDEST | |
| 18cf | SWAP5 | |
| 18d0 | ADD | |
| 18d1 | SWAP4 | |
| 18d2 | DUP2 | |
| 18d3 | DUP5 | |
| 18d4 | ADD | |
| 18d5 | SSTORE | |
| 18d6 | ADD | |
| 18d7 | PUSH2 | 0x1463 |
| 18da | JUMP | |
| 18db | JUMPDEST | |
| 18dc | PUSH4 | 0x08e19609 |
| 18e1 | PUSH1 | 0xe4 |
| 18e3 | SHL | |
| 18e4 | DUP7 | |
| 18e5 | MSTORE | |
| 18e6 | PUSH1 | 0x04 |
| 18e8 | DUP5 | |
| 18e9 | SWAP1 | |
| 18ea | MSTORE | |
| 18eb | PUSH1 | 0x24 |
| 18ed | DUP7 | |
| 18ee | REVERT | |
| 18ef | JUMPDEST | |
| 18f0 | POP | |
| 18f1 | PUSH1 | 0x09 |
| 18f3 | DUP6 | |
| 18f4 | ADD | |
| 18f5 | SLOAD | |
| 18f6 | PUSH1 | 0x01 |
| 18f8 | PUSH1 | 0x01 |
| 18fa | PUSH1 | 0xa0 |
| 18fc | SHL | |
| 18fd | SUB | |
| 18fe | AND | |
| 18ff | ISZERO | |
| 1900 | PUSH2 | 0x1421 |
| 1903 | JUMP | |
| 1904 | JUMPDEST | |
| 1905 | POP | |
| 1906 | PUSH1 | 0x05 |
| 1908 | DUP6 | |
| 1909 | ADD | |
| 190a | SLOAD | |
| 190b | ISZERO | |
| 190c | PUSH2 | 0x141a |
| 190f | JUMP | |
| 1910 | JUMPDEST | |
| 1911 | DUP1 | |
| 1912 | PUSH2 | 0x191d |
| 1915 | PUSH1 | 0x01 |
| 1917 | SWAP3 | |
| 1918 | DUP5 | |
| 1919 | PUSH2 | 0x41c9 |
| 191c | JUMP | |
| 191d | JUMPDEST | |
| 191e | CALLDATALOAD | |
| 191f | DUP2 | |
| 1920 | DUP10 | |
| 1921 | ADD | |
| 1922 | SSTORE | |
| 1923 | ADD | |
| 1924 | PUSH2 | 0x1403 |
| 1927 | JUMP | |
| 1928 | JUMPDEST | |
| 1929 | PUSH4 | 0x3b490093 |
| 192e | PUSH1 | 0xe1 |
| 1930 | SHL | |
| 1931 | DUP8 | |
| 1932 | MSTORE | |
| 1933 | PUSH1 | 0x04 |
| 1935 | DUP6 | |
| 1936 | SWAP1 | |
| 1937 | MSTORE | |
| 1938 | PUSH1 | 0x24 |
| 193a | DUP8 | |
| 193b | REVERT | |
| 193c | JUMPDEST | |
| 193d | DUP12 | |
| 193e | DUP1 | |
| 193f | REVERT | |
| 1940 | JUMPDEST | |
| 1941 | PUSH1 | 0x20 |
| 1943 | DUP1 | |
| 1944 | PUSH1 | 0x01 |
| 1946 | SWAP3 | |
| 1947 | PUSH1 | 0x01 |
| 1949 | PUSH1 | 0x01 |
| 194b | PUSH1 | 0x40 |
| 194d | SHL | |
| 194e | SUB | |
| 194f | PUSH2 | 0x1957 |