From 50e99ddd50837085e4940f510e0a9fea8556c37c Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 28 May 2026 02:59:22 +0500 Subject: [PATCH 01/24] feat: add interfold token vesting escrow --- agent/flow-trace/02_TOKENS_AND_ACTIVATION.md | 59 +- .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 17 +- .../06_DEACTIVATION_AND_COMPLETION.md | 14 +- packages/enclave-contracts/README.md | 4 + .../IBondingRegistry.json | 226 ++++++- .../contracts/interfaces/IBondingRegistry.sol | 100 +++ .../interfaces/IInterfoldVestingEscrow.sol | 109 ++++ .../interfaces/ILicenseBondReceiver.sol | 43 ++ .../contracts/registry/BondingRegistry.sol | 576 ++++++++++++++++-- .../token/InterfoldVestingEscrow.sol | 354 +++++++++++ .../deployAndSave/interfoldVestingEscrow.ts | 105 ++++ .../scripts/deployEnclave.ts | 88 +++ packages/enclave-contracts/scripts/index.ts | 1 + .../test/Registry/BondingRegistry.spec.ts | 30 +- .../test/Token/InterfoldVestingEscrow.spec.ts | 288 +++++++++ 15 files changed, 1914 insertions(+), 100 deletions(-) create mode 100644 packages/enclave-contracts/contracts/interfaces/IInterfoldVestingEscrow.sol create mode 100644 packages/enclave-contracts/contracts/interfaces/ILicenseBondReceiver.sol create mode 100644 packages/enclave-contracts/contracts/token/InterfoldVestingEscrow.sol create mode 100644 packages/enclave-contracts/scripts/deployAndSave/interfoldVestingEscrow.ts create mode 100644 packages/enclave-contracts/test/Token/InterfoldVestingEscrow.spec.ts diff --git a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md index 5bebb4ec5e..b2f1c23708 100644 --- a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md +++ b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md @@ -71,16 +71,29 @@ User runs: enclave ciphernode license bond --amount 50000 │ │ │ amount │ │ │ │ ) │ │ │ │ → ENCL tokens move from operator → contract │ -│ │ │ 3. operators[msg.sender].licenseBond += amount │ -│ │ │ 4. _updateOperatorStatus(msg.sender) │ +│ │ │ 3. Record LicenseBondSource { │ +│ │ │ amount, withdrawalAddress: msg.sender, │ +│ │ │ sourceId: 0, sequence │ +│ │ │ } │ +│ │ │ 4. operators[msg.sender].licenseBond += amount │ +│ │ │ 5. _updateOperatorStatus(msg.sender) │ │ │ │ → May activate if all conditions now met │ -│ │ │ 5. Emit LicenseBondUpdated(msg.sender, newBond) │ +│ │ │ 6. Emit LicenseBondSourceAdded and │ +│ │ │ LicenseBondUpdated(msg.sender, newBond) │ │ │ │ } │ │ │ └──────────────────────────────────────────────────────┘ │ │ └─ OUTPUT: "Transaction hash: 0x..." ``` +### Delegated / locked ENCL bonding + +`BondingRegistry.bondLicenseFor(operator, amount, withdrawalAddress, sourceId)` lets a funder supply +ENCL while crediting another operator. `InterfoldVestingEscrow` uses this path so locked allocations +can run nodes without first transferring unrestricted ENCL to the beneficiary. Each ENCL bond source +keeps its own withdrawal address and LIFO sequence. The legacy `bondLicense(amount)` path is +equivalent to `bondLicenseFor(msg.sender, amount, msg.sender, 0)`. + ### Activation check after bonding: ``` @@ -187,23 +200,16 @@ User runs: enclave ciphernode license unbond --amount 10000 │ │ │ 2. require(operators[msg.sender].licenseBond │ │ │ │ >= amount) │ │ │ │ 3. operators[msg.sender].licenseBond -= amount │ -│ │ │ 4. _exits.queueLicensesForExit( │ -│ │ │ msg.sender, exitDelay, amount │ +│ │ │ 4. _queueLicenseExitFromSources( │ +│ │ │ msg.sender, amount │ │ │ │ ) │ -│ │ │ │ │ -│ │ │ │ ┌─ ExitQueueLib ─────────────────────────┐ │ -│ │ │ │ │ Creates ExitTranche { │ │ -│ │ │ │ │ unlockTimestamp: now + exitDelay, │ │ -│ │ │ │ │ ticketAmount: 0, │ │ -│ │ │ │ │ licenseAmount: 10000 │ │ -│ │ │ │ │ } │ │ -│ │ │ │ │ Merges into last tranche if same │ │ -│ │ │ │ │ unlock time, else appends new tranche │ │ -│ │ │ │ │ Updates pendingTotals │ │ -│ │ │ │ └────────────────────────────────────────┘ │ +│ │ │ → Pops active LicenseBondSource entries LIFO │ +│ │ │ → Queues PendingLicenseBondSource entries │ +│ │ │ preserving withdrawalAddress + sourceId │ │ │ │ 5. _updateOperatorStatus(msg.sender) │ │ │ │ → May DEACTIVATE if bond drops below threshold │ -│ │ │ 6. Emit LicenseBondUpdated(msg.sender, newBond) │ +│ │ │ 6. Emit LicenseBondSourceQueuedForExit and │ +│ │ │ LicenseBondUpdated(msg.sender, newBond) │ │ │ │ } │ │ │ └───────────────────────────────────────────────────────┘ │ @@ -268,9 +274,9 @@ User runs: enclave ciphernode license claim [--max-ticket 50] [--max-license 100 │ │ ┌─── ON-CHAIN ─────────────────────────────────────────┐ │ │ │ │ │ │ │ claimExits(maxTicket, maxLicense) { │ -│ │ │ 1. (ticketAmount, licenseAmount) = │ +│ │ │ 1. (ticketAmount, _) = │ │ │ │ _exits.claimAssets( │ -│ │ │ msg.sender, maxTicket, maxLicense │ +│ │ │ msg.sender, maxTicket, 0 │ │ │ │ ) │ │ │ │ │ │ │ │ │ │ ┌─ ExitQueueLib.claimAssets() ───────────┐ │ @@ -278,7 +284,7 @@ User runs: enclave ciphernode license claim [--max-ticket 50] [--max-license 100 │ │ │ │ │ for each tranche where │ │ │ │ │ │ │ block.timestamp >= unlockTimestamp: │ │ │ │ │ │ │ take min(wanted, available) │ │ -│ │ │ │ │ from ticketAmount & licenseAmount │ │ +│ │ │ │ │ from ticketAmount │ │ │ │ │ │ │ Skip locked tranches (future unlock) │ │ │ │ │ │ │ Clean up empty tranches │ │ │ │ │ │ │ Update pendingTotals │ │ @@ -294,15 +300,16 @@ User runs: enclave ciphernode license claim [--max-ticket 50] [--max-license 100 │ │ │ │ │ underlying.safeTransfer(to, amount) │ │ │ │ │ │ └────────────────────────────────────────┘ │ │ │ │ │ -│ │ │ 3. if licenseAmount > 0: │ -│ │ │ licenseToken.safeTransfer( │ -│ │ │ msg.sender, licenseAmount │ +│ │ │ 3. licenseAmount = _claimLicenseExits( │ +│ │ │ msg.sender, maxLicense │ │ │ │ ) │ -│ │ │ → ENCL tokens returned to operator │ +│ │ │ → Each ENCL source pays its withdrawalAddress │ +│ │ │ → Receiver callback gets (operator, amount, │ +│ │ │ sourceId) when supported │ │ │ │ } │ │ │ └───────────────────────────────────────────────────────┘ │ -└─ Operator receives back their USDC and/or ENCL tokens +└─ Operator receives back USDC; ENCL goes to each source's withdrawal address ``` --- @@ -346,7 +353,7 @@ active = registered CLAIM EXITS ─────────── After exitDelay seconds: - ENCL → returned from ExitQueue + ENCL → returned to source withdrawal address USDC → paid out from ETK.payableBalance ``` diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index e5d9481e81..a47069070d 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -664,15 +664,16 @@ _executeSlash(proposalId): │ │ │ │ ┌─── BondingRegistry.slashLicenseBond() ───────────────┐ │ │ │ │ -│ │ │ 1. Slash from ACTIVE bond first: │ -│ │ │ slashFromActive = min(amount, licenseBond) │ -│ │ │ operators[op].licenseBond -= slashFromActive │ +│ │ │ 1. Compute active + pending ENCL source total │ │ │ │ │ -│ │ │ 2. Remaining from EXIT QUEUE: │ -│ │ │ _exits.slashPendingAssets( │ -│ │ │ operator, 0, remaining, │ -│ │ │ includeLockedAssets=true │ -│ │ │ ) │ +│ │ │ 2. _slashLicenseSourcesLifo(operator, amount): │ +│ │ │ Compare newest active source sequence with │ +│ │ │ newest pending-exit source sequence │ +│ │ │ Slash the newest source first │ +│ │ │ → Active slash decrements operators[op].licenseBond│ +│ │ │ → Pending slash decrements pending license totals │ +│ │ │ → Receiver callback gets (operator, amount, │ +│ │ │ sourceId) when supported │ │ │ │ │ │ │ │ 3. slashedLicenseBond += totalSlashed │ │ │ │ 4. _updateOperatorStatus(operator) │ diff --git a/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md b/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md index 111ce5aef6..e6094ad3a7 100644 --- a/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md +++ b/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md @@ -53,7 +53,9 @@ User runs: enclave ciphernode deactivate --license 20000 │ │ unbondLicense(20000): │ │ │ 1. require(amount != 0, sufficient bonded ENCL) │ │ │ 2. operators[op].licenseBond -= 20000 │ - │ │ 3. _exits.queueLicensesForExit(op, exitDelay, 20000)│ + │ │ 3. _queueLicenseExitFromSources(op, 20000) │ + │ │ → Queues source-aware ENCL exits preserving │ + │ │ withdrawalAddress + sourceId │ │ │ 4. _updateOperatorStatus(operator) │ │ │ → If licenseBond < │ │ │ (licenseRequiredBond * licenseActiveBps / 10000)│ @@ -72,8 +74,8 @@ User runs: enclave ciphernode deactivate --tickets 50 --license 20000 │ ├─ Calls removeTicketBalance(50) first └─ Then calls unbondLicense(20000) - → Both queued in ExitQueue with same exitDelay - → May merge into single tranche if same unlock time + → Tickets are queued in ExitQueueLib + → ENCL is queued in source-aware pending license exits ``` --- @@ -109,8 +111,9 @@ User runs: enclave ciphernode deregister │ │ _exits.queueAssetsForExit( │ │ │ op, exitDelay, │ │ │ fullTicketBalance, // tickets │ - │ │ licenseBondAmount // license │ + │ │ 0 // license handled below │ │ │ ) │ + │ │ _queueLicenseExitFromSources(op, licenseBondAmount)│ │ │ │ │ │ 8. Remove from Merkle tree: │ │ │ registry.removeCiphernode(msg.sender) │ @@ -234,7 +237,8 @@ Time ───────────────────────── │ │ (configured) │ │ │ Assets queued │ │ Assets claimable │ │ ETK burned │ Cannot cancel │ USDC returned │ -│ ENCL locked │ Can be slashed! │ ENCL returned │ +│ ENCL locked │ Can be slashed! │ ENCL returned to │ +│ │ │ withdrawal addr │ │ │ │ │ IMPORTANT: Even during the exit delay, slashing can still diff --git a/packages/enclave-contracts/README.md b/packages/enclave-contracts/README.md index 962dbe3b3c..09041f4682 100644 --- a/packages/enclave-contracts/README.md +++ b/packages/enclave-contracts/README.md @@ -56,6 +56,10 @@ directory, as well as to the `deployed_contracts.json` file. Be sure to configure your desired network in `hardhat.config.ts` before deploying. +For non-local networks, set `INTERFOLD_TGE_TIMESTAMP` to the agreed ENCL TGE +Unix timestamp before deploying. Local mock deployments default this timestamp +to the latest local block timestamp. + ## Localhost deployment If you are running Enclave locally, you can first start a local hardhat (or diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index b03dc84e7f..4bff4350b4 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -59,6 +59,11 @@ "name": "MaxAuthorizedDistributors", "type": "error" }, + { + "inputs": [], + "name": "MaxLicenseBondSources", + "type": "error" + }, { "inputs": [], "name": "NoPendingDeregistration", @@ -148,6 +153,197 @@ "name": "ConfigurationUpdated", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sourceId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } + ], + "name": "LicenseBondReceiverCallbackFailed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "funder", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "withdrawalAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sourceId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + } + ], + "name": "LicenseBondSourceAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "withdrawalAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sourceId", + "type": "bytes32" + } + ], + "name": "LicenseBondSourceClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "withdrawalAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "unlockTimestamp", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sourceId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + } + ], + "name": "LicenseBondSourceQueuedForExit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "withdrawalAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sourceId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + } + ], + "name": "LicenseBondSourceSlashed", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -414,6 +610,34 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "withdrawalAddress", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "sourceId", + "type": "bytes32" + } + ], + "name": "bondLicenseFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -1062,5 +1286,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-a6902ac15e994e1c055eb02433db296d1823fdfc" + "buildInfoId": "solc-0_8_28-f393c7cf878899096944bbd867df55a6fd9a4724" } \ No newline at end of file diff --git a/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol index ef92f2ab5c..0114b66b55 100644 --- a/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol @@ -46,6 +46,9 @@ interface IBondingRegistry { /// {MAX_AUTHORIZED_DISTRIBUTORS}. error MaxAuthorizedDistributors(); + /// @notice Thrown when an operator would exceed the live ENCL bond source cap. + error MaxLicenseBondSources(); + /// @notice Thrown when {renounceOwnership} is called. error RenounceOwnershipDisabled(); @@ -81,6 +84,88 @@ interface IBondingRegistry { bytes32 indexed reason ); + /** + * @notice Emitted when a license bond source is recorded for an operator. + * @param operator Address whose operator bond was credited + * @param funder Address that supplied the license tokens + * @param withdrawalAddress Address that receives this source if it exits + * @param amount Amount credited from this source + * @param sourceId Optional external source id for withdrawal receivers + * @param sequence Monotonic source sequence used for LIFO slashing + */ + event LicenseBondSourceAdded( + address indexed operator, + address indexed funder, + address indexed withdrawalAddress, + uint256 amount, + bytes32 sourceId, + uint64 sequence + ); + + /** + * @notice Emitted when a license bond source is queued for delayed exit. + * @param operator Address whose bond source was queued + * @param withdrawalAddress Address that will receive this source after unlock + * @param amount Amount queued from this source + * @param unlockTimestamp Timestamp when this source becomes claimable + * @param sourceId Optional external source id for withdrawal receivers + * @param sequence Original source sequence used for LIFO slashing + */ + event LicenseBondSourceQueuedForExit( + address indexed operator, + address indexed withdrawalAddress, + uint256 amount, + uint64 unlockTimestamp, + bytes32 sourceId, + uint64 sequence + ); + + /** + * @notice Emitted when a queued license bond source is paid to its withdrawal address. + * @param operator Address whose source was claimed + * @param withdrawalAddress Address that received license tokens + * @param amount Amount paid + * @param sourceId Optional external source id for withdrawal receivers + */ + event LicenseBondSourceClaimed( + address indexed operator, + address indexed withdrawalAddress, + uint256 amount, + bytes32 sourceId + ); + + /** + * @notice Emitted when a license bond source is slashed. + * @param operator Address whose source was slashed + * @param withdrawalAddress Withdrawal address attached to the source + * @param amount Amount slashed + * @param sourceId Optional external source id for withdrawal receivers + * @param sequence Original source sequence used for LIFO slashing + */ + event LicenseBondSourceSlashed( + address indexed operator, + address indexed withdrawalAddress, + uint256 amount, + bytes32 sourceId, + uint64 sequence + ); + + /** + * @notice Emitted when a withdrawal receiver callback fails. + * @param receiver Address that rejected or failed the callback + * @param operator Operator associated with the bond source + * @param amount Amount involved in the callback + * @param sourceId Optional external source id for withdrawal receivers + * @param selector Callback selector that failed + */ + event LicenseBondReceiverCallbackFailed( + address indexed receiver, + address indexed operator, + uint256 amount, + bytes32 sourceId, + bytes4 selector + ); + /** * @notice Emitted when operator requests deregistration from the protocol * @param operator Address of the operator @@ -364,6 +449,21 @@ interface IBondingRegistry { */ function bondLicense(uint256 amount) external; + /** + * @notice Bond license tokens supplied by the caller while crediting an operator. + * @param operator Address whose operator bond should be credited + * @param amount Amount of license tokens to pull from the caller + * @param withdrawalAddress Address that receives this source after unbond/decommission + * @param sourceId Optional external id passed back to withdrawal receivers + * @dev Requires caller approval for license token transfer. + */ + function bondLicenseFor( + address operator, + uint256 amount, + address withdrawalAddress, + bytes32 sourceId + ) external; + /** * @notice Unbond license tokens * @param amount Amount of license tokens to unbond diff --git a/packages/enclave-contracts/contracts/interfaces/IInterfoldVestingEscrow.sol b/packages/enclave-contracts/contracts/interfaces/IInterfoldVestingEscrow.sol new file mode 100644 index 0000000000..79d9599922 --- /dev/null +++ b/packages/enclave-contracts/contracts/interfaces/IInterfoldVestingEscrow.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +pragma solidity 0.8.28; + +import { + IERC165 +} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +interface IInterfoldVestingEscrow is IERC165 { + struct ScheduleInput { + address beneficiary; + uint256 totalAmount; + uint64 tokenHoldUntil; + uint64 tokenUnlockStart; + uint64 tokenUnlockEnd; + uint64 serviceStart; + uint64 serviceCliff; + uint64 serviceEnd; + bytes32 group; + } + + struct ScheduleView { + address beneficiary; + uint256 totalAmount; + uint256 claimedAmount; + uint256 bondedAmount; + uint256 slashedAmount; + uint64 tokenHoldUntil; + uint64 tokenUnlockStart; + uint64 tokenUnlockEnd; + uint64 serviceStart; + uint64 serviceCliff; + uint64 serviceEnd; + bytes32 group; + } + + event ScheduleCreated( + uint256 indexed scheduleId, + address indexed beneficiary, + bytes32 indexed group, + uint256 totalAmount, + uint64 tokenHoldUntil, + uint64 tokenUnlockStart, + uint64 tokenUnlockEnd, + uint64 serviceStart, + uint64 serviceCliff, + uint64 serviceEnd + ); + + event TokensClaimed( + uint256 indexed scheduleId, + address indexed beneficiary, + uint256 amount + ); + + event LockedTokensBonded( + uint256 indexed scheduleId, + address indexed beneficiary, + address indexed operator, + uint256 amount + ); + + event BondedTokensReturned( + uint256 indexed scheduleId, + address indexed operator, + uint256 amount + ); + + event BondedTokensSlashed( + uint256 indexed scheduleId, + address indexed operator, + uint256 amount + ); + + function createSchedule( + ScheduleInput calldata input + ) external returns (uint256 scheduleId); + + function batchCreateSchedules( + ScheduleInput[] calldata inputs + ) external returns (uint256[] memory scheduleIds); + + function claim(uint256 scheduleId, uint256 maxAmount) external; + + function bondLockedTokens( + uint256 scheduleId, + address operator, + uint256 amount + ) external; + + function vestedAmount( + uint256 scheduleId, + uint64 timestamp + ) external view returns (uint256); + + function claimableAmount( + uint256 scheduleId + ) external view returns (uint256); + + function bondableAmount(uint256 scheduleId) external view returns (uint256); + + function getSchedule( + uint256 scheduleId + ) external view returns (ScheduleView memory); +} diff --git a/packages/enclave-contracts/contracts/interfaces/ILicenseBondReceiver.sol b/packages/enclave-contracts/contracts/interfaces/ILicenseBondReceiver.sol new file mode 100644 index 0000000000..c497453d3d --- /dev/null +++ b/packages/enclave-contracts/contracts/interfaces/ILicenseBondReceiver.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +pragma solidity 0.8.28; + +import { + IERC165 +} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +/** + * @title ILicenseBondReceiver + * @notice Optional callback surface for contracts that receive ENCL license-bond withdrawals. + */ +interface ILicenseBondReceiver is IERC165 { + /** + * @notice Called by BondingRegistry after a queued ENCL bond source is returned. + * @param operator Operator whose bond source exited + * @param amount Amount returned to the receiver + * @param sourceId External source id supplied at bond time + * @return selector This function's selector on success + */ + function onLicenseBondReturned( + address operator, + uint256 amount, + bytes32 sourceId + ) external returns (bytes4 selector); + + /** + * @notice Called by BondingRegistry after a ENCL bond source is slashed. + * @param operator Operator whose bond source was slashed + * @param amount Amount slashed from the source + * @param sourceId External source id supplied at bond time + * @return selector This function's selector on success + */ + function onLicenseBondSlashed( + address operator, + uint256 amount, + bytes32 sourceId + ) external returns (bytes4 selector); +} diff --git a/packages/enclave-contracts/contracts/registry/BondingRegistry.sol b/packages/enclave-contracts/contracts/registry/BondingRegistry.sol index 0e5d09c30e..ae530545d5 100644 --- a/packages/enclave-contracts/contracts/registry/BondingRegistry.sol +++ b/packages/enclave-contracts/contracts/registry/BondingRegistry.sol @@ -24,6 +24,7 @@ import { ExitQueueLib } from "../lib/ExitQueueLib.sol"; import { IBondingRegistry } from "../interfaces/IBondingRegistry.sol"; import { ICiphernodeRegistry } from "../interfaces/ICiphernodeRegistry.sol"; +import { ILicenseBondReceiver } from "../interfaces/ILicenseBondReceiver.sol"; import { ISlashingManager } from "../interfaces/ISlashingManager.sol"; import { EnclaveTicketToken } from "../token/EnclaveTicketToken.sol"; @@ -117,6 +118,10 @@ contract BondingRegistry is /// @dev Default 8000 = 80%. Allows operators to unbond up to 20% while remaining active uint256 public licenseActiveBps; + /// @notice Maximum number of live ENCL license bond sources per operator. + /// @dev Bounds LIFO slashing and claim loops for delegated/locked-token bonds. + uint256 public constant MAX_LICENSE_BOND_SOURCES = 64; + /// @notice Number of currently active operators uint256 public numActiveOperators; @@ -134,9 +139,52 @@ contract BondingRegistry is bool active; } + /// @notice Active ENCL bond source credited to an operator. + /// @param amount Remaining active amount from this source + /// @param withdrawalAddress Address that receives this source after exit + /// @param sourceId Optional external id passed to withdrawal receivers + /// @param sequence Monotonic sequence used for LIFO slashing + struct LicenseBondSource { + uint256 amount; + address withdrawalAddress; + bytes32 sourceId; + uint64 sequence; + } + + /// @notice Pending ENCL bond source waiting through the exit delay. + /// @param unlockTimestamp Timestamp when this source becomes claimable + /// @param amount Remaining pending amount from this source + /// @param withdrawalAddress Address that receives this source after exit + /// @param sourceId Optional external id passed to withdrawal receivers + /// @param sequence Original active-source sequence used for LIFO slashing + struct PendingLicenseBondSource { + uint64 unlockTimestamp; + uint256 amount; + address withdrawalAddress; + bytes32 sourceId; + uint64 sequence; + } + /// @notice Maps operator address to their state data mapping(address operator => Operator data) internal operators; + /// @dev Active ENCL bond sources per operator. The tail is the newest source. + mapping(address operator => LicenseBondSource[] sources) + private _licenseSources; + + /// @dev Pending ENCL exit sources per operator. The head is used for claim scans. + mapping(address operator => PendingLicenseBondSource[] sources) + private _pendingLicenseSources; + + /// @dev Claim head for pending ENCL exits. + mapping(address operator => uint256 headIndex) private _pendingLicenseHead; + + /// @dev Aggregate pending ENCL exits per operator. + mapping(address operator => uint256 amount) private _pendingLicenseTotals; + + /// @dev Next ENCL bond source sequence per operator. Starts at 1. + mapping(address operator => uint64 sequence) private _nextLicenseSequence; + /// @notice Total slashed ticket balance available for treasury withdrawal uint256 public slashedTicketBalance; @@ -276,7 +324,8 @@ contract BondingRegistry is function pendingExits( address operator ) external view returns (uint256 ticket, uint256 license) { - return _exits.getPendingAmounts(operator); + (ticket, ) = _exits.getPendingAmounts(operator); + license = _pendingLicenseTotals[operator]; } /// @notice Preview how much an operator can currently claim @@ -286,7 +335,8 @@ contract BondingRegistry is function previewClaimable( address operator ) external view returns (uint256 ticket, uint256 license) { - return _exits.previewClaimableAmounts(operator); + (ticket, ) = _exits.previewClaimableAmounts(operator); + license = _previewClaimableLicense(operator); } /// @inheritdoc IBondingRegistry @@ -382,12 +432,10 @@ contract BondingRegistry is } if (ticketOut != 0 || licenseOut != 0) { - _exits.queueAssetsForExit( - msg.sender, - exitDelay, - ticketOut, - licenseOut - ); + _exits.queueAssetsForExit(msg.sender, exitDelay, ticketOut, 0); + if (licenseOut != 0) { + _queueLicenseExitFromSources(msg.sender, licenseOut); + } } // CiphernodeRegistry already emits an event when a ciphernode is removed @@ -444,23 +492,29 @@ contract BondingRegistry is function bondLicense( uint256 amount ) external nonReentrant noExitInProgress(msg.sender) { - require(amount != 0, ZeroAmount()); - - uint256 balanceBefore = licenseToken.balanceOf(address(this)); - licenseToken.safeTransferFrom(msg.sender, address(this), amount); - uint256 actualReceived = licenseToken.balanceOf(address(this)) - - balanceBefore; - - operators[msg.sender].licenseBond += actualReceived; - - emit LicenseBondUpdated( + _bondLicenseFrom( msg.sender, - int256(actualReceived), - operators[msg.sender].licenseBond, - REASON_BOND + msg.sender, + msg.sender, + amount, + bytes32(0) ); + } - _updateOperatorStatus(msg.sender); + /// @inheritdoc IBondingRegistry + function bondLicenseFor( + address operator, + uint256 amount, + address withdrawalAddress, + bytes32 sourceId + ) external nonReentrant noExitInProgress(operator) { + _bondLicenseFrom( + msg.sender, + operator, + withdrawalAddress, + amount, + sourceId + ); } /// @inheritdoc IBondingRegistry @@ -474,7 +528,7 @@ contract BondingRegistry is ); operators[msg.sender].licenseBond -= amount; - _exits.queueLicensesForExit(msg.sender, exitDelay, amount); + _queueLicenseExitFromSources(msg.sender, amount); emit LicenseBondUpdated( msg.sender, @@ -494,18 +548,16 @@ contract BondingRegistry is function claimExits( uint256 maxTicketAmount, uint256 maxLicenseAmount - ) external { - (uint256 ticketClaim, uint256 licenseClaim) = _exits.claimAssets( + ) external nonReentrant { + (uint256 ticketClaim, ) = _exits.claimAssets( msg.sender, maxTicketAmount, - maxLicenseAmount + 0 ); + uint256 licenseClaim = _claimLicenseExits(msg.sender, maxLicenseAmount); require(ticketClaim > 0 || licenseClaim > 0, ExitNotReady()); if (ticketClaim > 0) ticketToken.payout(msg.sender, ticketClaim); - if (licenseClaim > 0) { - _safeTransferLicenseWithDeltaCheck(msg.sender, licenseClaim); - } } // ====================== @@ -571,11 +623,11 @@ contract BondingRegistry is address operator, uint256 requestedSlashAmount, bytes32 slashReason - ) external onlySlashingManager { + ) external onlySlashingManager nonReentrant { require(requestedSlashAmount != 0, ZeroAmount()); Operator storage operatorData = operators[operator]; - (, uint256 pendingLicenseBalance) = _exits.getPendingAmounts(operator); + uint256 pendingLicenseBalance = _pendingLicenseTotals[operator]; uint256 totalAvailableBalance = operatorData.licenseBond + pendingLicenseBalance; uint256 actualSlashAmount = Math.min( @@ -585,25 +637,7 @@ contract BondingRegistry is if (actualSlashAmount == 0) return; - // Slash from active balance first - uint256 slashedFromActiveBalance = Math.min( - actualSlashAmount, - operatorData.licenseBond - ); - if (slashedFromActiveBalance > 0) { - operatorData.licenseBond -= slashedFromActiveBalance; - } - - // Slash remaining amount from pending queue - uint256 remainingToSlash = actualSlashAmount - slashedFromActiveBalance; - if (remainingToSlash > 0) { - _exits.slashPendingAssets( - operator, - 0, // ticketAmount - remainingToSlash, - true - ); - } + _slashLicenseSourcesLifo(operator, actualSlashAmount); slashedLicenseBond += actualSlashAmount; emit LicenseBondUpdated( @@ -827,6 +861,446 @@ contract BondingRegistry is // Internal Functions // ====================== + function _bondLicenseFrom( + address funder, + address operator, + address withdrawalAddress, + uint256 amount, + bytes32 sourceId + ) internal { + require(operator != address(0), ZeroAddress()); + require(withdrawalAddress != address(0), ZeroAddress()); + require(amount != 0, ZeroAmount()); + require( + _liveLicenseSourceCount(operator) < MAX_LICENSE_BOND_SOURCES, + MaxLicenseBondSources() + ); + + uint256 balanceBefore = licenseToken.balanceOf(address(this)); + licenseToken.safeTransferFrom(funder, address(this), amount); + uint256 actualReceived = licenseToken.balanceOf(address(this)) - + balanceBefore; + require(actualReceived != 0, ZeroAmount()); + + uint64 sequence = _nextLicenseSequence[operator]; + if (sequence == 0) sequence = 1; + _nextLicenseSequence[operator] = sequence + 1; + + _licenseSources[operator].push( + LicenseBondSource({ + amount: actualReceived, + withdrawalAddress: withdrawalAddress, + sourceId: sourceId, + sequence: sequence + }) + ); + + operators[operator].licenseBond += actualReceived; + + emit LicenseBondSourceAdded( + operator, + funder, + withdrawalAddress, + actualReceived, + sourceId, + sequence + ); + emit LicenseBondUpdated( + operator, + int256(actualReceived), + operators[operator].licenseBond, + REASON_BOND + ); + + _updateOperatorStatus(operator); + } + + function _queueLicenseExitFromSources( + address operator, + uint256 amount + ) internal { + if (amount == 0) return; + + uint64 currentTimestamp = uint64(block.timestamp); + require( + currentTimestamp <= (type(uint64).max - exitDelay), + ExitQueueLib.TimestampOverflow() + ); + uint64 unlockTimestamp = currentTimestamp + exitDelay; + uint256 remaining = amount; + LicenseBondSource[] storage sources = _licenseSources[operator]; + + while (remaining != 0) { + uint256 len = sources.length; + require(len != 0, InsufficientBalance()); + + LicenseBondSource storage source = sources[len - 1]; + uint256 amountToQueue = remaining < source.amount + ? remaining + : source.amount; + + if (amountToQueue != source.amount) { + require( + _liveLicenseSourceCount(operator) < + MAX_LICENSE_BOND_SOURCES, + MaxLicenseBondSources() + ); + } + + source.amount -= amountToQueue; + remaining -= amountToQueue; + + _pendingLicenseSources[operator].push( + PendingLicenseBondSource({ + unlockTimestamp: unlockTimestamp, + amount: amountToQueue, + withdrawalAddress: source.withdrawalAddress, + sourceId: source.sourceId, + sequence: source.sequence + }) + ); + _pendingLicenseTotals[operator] += amountToQueue; + + emit LicenseBondSourceQueuedForExit( + operator, + source.withdrawalAddress, + amountToQueue, + unlockTimestamp, + source.sourceId, + source.sequence + ); + + if (source.amount == 0) sources.pop(); + } + } + + function _claimLicenseExits( + address operator, + uint256 maxLicenseAmount + ) internal returns (uint256 claimedAmount) { + if (maxLicenseAmount == 0) return 0; + + PendingLicenseBondSource[] storage sources = _pendingLicenseSources[ + operator + ]; + uint256 head = _pendingLicenseHead[operator]; + uint256 len = sources.length; + uint256 remaining = maxLicenseAmount; + + for (uint256 i = head; i < len && remaining != 0; i++) { + PendingLicenseBondSource storage source = sources[i]; + if (source.amount == 0) { + if (i == head) head++; + continue; + } + if (block.timestamp < source.unlockTimestamp) continue; + + uint256 amountToClaim = remaining < source.amount + ? remaining + : source.amount; + + source.amount -= amountToClaim; + remaining -= amountToClaim; + claimedAmount += amountToClaim; + _pendingLicenseTotals[operator] -= amountToClaim; + + _safeTransferLicenseWithDeltaCheck( + source.withdrawalAddress, + amountToClaim + ); + emit LicenseBondSourceClaimed( + operator, + source.withdrawalAddress, + amountToClaim, + source.sourceId + ); + _notifyLicenseBondReturned( + source.withdrawalAddress, + operator, + amountToClaim, + source.sourceId + ); + + if (source.amount == 0 && i == head) head++; + } + + _pendingLicenseHead[operator] = head; + } + + function _previewClaimableLicense( + address operator + ) internal view returns (uint256 claimableAmount) { + PendingLicenseBondSource[] storage sources = _pendingLicenseSources[ + operator + ]; + uint256 head = _pendingLicenseHead[operator]; + uint256 len = sources.length; + + for (uint256 i = head; i < len; i++) { + PendingLicenseBondSource storage source = sources[i]; + if (block.timestamp >= source.unlockTimestamp) { + claimableAmount += source.amount; + } + } + } + + function _slashLicenseSourcesLifo( + address operator, + uint256 amount + ) internal { + uint256 remaining = amount; + + while (remaining != 0) { + ( + bool hasPending, + uint256 pendingIndex, + uint64 pendingSequence + ) = _latestPendingLicenseSource(operator); + LicenseBondSource[] storage activeSources = _licenseSources[ + operator + ]; + bool hasActive = activeSources.length != 0; + uint64 activeSequence = hasActive + ? activeSources[activeSources.length - 1].sequence + : 0; + + require(hasPending || hasActive, InsufficientBalance()); + + if ( + hasPending && (!hasActive || pendingSequence >= activeSequence) + ) { + uint256 slashed = _slashPendingLicenseSource( + operator, + pendingIndex, + remaining + ); + remaining -= slashed; + } else { + uint256 slashed = _slashActiveLicenseSource( + operator, + remaining + ); + remaining -= slashed; + } + } + } + + function _slashActiveLicenseSource( + address operator, + uint256 maxAmount + ) internal returns (uint256 slashedAmount) { + LicenseBondSource[] storage sources = _licenseSources[operator]; + uint256 sourceIndex = sources.length - 1; + LicenseBondSource storage source = sources[sourceIndex]; + slashedAmount = maxAmount < source.amount ? maxAmount : source.amount; + + source.amount -= slashedAmount; + operators[operator].licenseBond -= slashedAmount; + + emit LicenseBondSourceSlashed( + operator, + source.withdrawalAddress, + slashedAmount, + source.sourceId, + source.sequence + ); + _notifyLicenseBondSlashed( + source.withdrawalAddress, + operator, + slashedAmount, + source.sourceId + ); + + if (source.amount == 0) sources.pop(); + } + + function _slashPendingLicenseSource( + address operator, + uint256 sourceIndex, + uint256 maxAmount + ) internal returns (uint256 slashedAmount) { + PendingLicenseBondSource storage source = _pendingLicenseSources[ + operator + ][sourceIndex]; + slashedAmount = maxAmount < source.amount ? maxAmount : source.amount; + + source.amount -= slashedAmount; + _pendingLicenseTotals[operator] -= slashedAmount; + + emit LicenseBondSourceSlashed( + operator, + source.withdrawalAddress, + slashedAmount, + source.sourceId, + source.sequence + ); + _notifyLicenseBondSlashed( + source.withdrawalAddress, + operator, + slashedAmount, + source.sourceId + ); + + if ( + source.amount == 0 && sourceIndex == _pendingLicenseHead[operator] + ) { + _advancePendingLicenseHead(operator); + } + } + + function _latestPendingLicenseSource( + address operator + ) internal view returns (bool found, uint256 index, uint64 sequence) { + PendingLicenseBondSource[] storage sources = _pendingLicenseSources[ + operator + ]; + uint256 head = _pendingLicenseHead[operator]; + uint256 len = sources.length; + + for (uint256 i = len; i > head; i--) { + PendingLicenseBondSource storage source = sources[i - 1]; + if (source.amount != 0) { + return (true, i - 1, source.sequence); + } + } + + return (false, 0, 0); + } + + function _advancePendingLicenseHead(address operator) internal { + PendingLicenseBondSource[] storage sources = _pendingLicenseSources[ + operator + ]; + uint256 head = _pendingLicenseHead[operator]; + uint256 len = sources.length; + + while (head < len && sources[head].amount == 0) { + head++; + } + + _pendingLicenseHead[operator] = head; + } + + function _liveLicenseSourceCount( + address operator + ) internal view returns (uint256) { + return + _licenseSources[operator].length + + (_pendingLicenseSources[operator].length - + _pendingLicenseHead[operator]); + } + + function _notifyLicenseBondReturned( + address receiver, + address operator, + uint256 amount, + bytes32 sourceId + ) internal { + _notifyLicenseBondReceiver( + receiver, + operator, + amount, + sourceId, + ILicenseBondReceiver.onLicenseBondReturned.selector + ); + } + + function _notifyLicenseBondSlashed( + address receiver, + address operator, + uint256 amount, + bytes32 sourceId + ) internal { + _notifyLicenseBondReceiver( + receiver, + operator, + amount, + sourceId, + ILicenseBondReceiver.onLicenseBondSlashed.selector + ); + } + + function _notifyLicenseBondReceiver( + address receiver, + address operator, + uint256 amount, + bytes32 sourceId, + bytes4 selector + ) internal { + if (sourceId == bytes32(0) || receiver.code.length == 0) return; + + try + IERC165(receiver).supportsInterface( + type(ILicenseBondReceiver).interfaceId + ) + returns (bool supported) { + if (!supported) return; + } catch { + emit LicenseBondReceiverCallbackFailed( + receiver, + operator, + amount, + sourceId, + selector + ); + return; + } + + if (selector == ILicenseBondReceiver.onLicenseBondReturned.selector) { + try + ILicenseBondReceiver(receiver).onLicenseBondReturned( + operator, + amount, + sourceId + ) + returns (bytes4 returnedSelector) { + if (returnedSelector != selector) { + emit LicenseBondReceiverCallbackFailed( + receiver, + operator, + amount, + sourceId, + selector + ); + } + } catch { + emit LicenseBondReceiverCallbackFailed( + receiver, + operator, + amount, + sourceId, + selector + ); + } + } else { + try + ILicenseBondReceiver(receiver).onLicenseBondSlashed( + operator, + amount, + sourceId + ) + returns (bytes4 returnedSelector) { + if (returnedSelector != selector) { + emit LicenseBondReceiverCallbackFailed( + receiver, + operator, + amount, + sourceId, + selector + ); + } + } catch { + emit LicenseBondReceiverCallbackFailed( + receiver, + operator, + amount, + sourceId, + selector + ); + } + } + } + /// @dev Updates operator's active status based on current conditions /// @dev Operator is active if: registered, has minimum license bond, and has minimum tickets /// @param operator Address of the operator to update @@ -895,5 +1369,5 @@ contract BondingRegistry is /// @dev Reserved storage slots for future upgrades. // solhint-disable-next-line var-name-mixedcase - uint256[50] private __gap; + uint256[45] private __gap; } diff --git a/packages/enclave-contracts/contracts/token/InterfoldVestingEscrow.sol b/packages/enclave-contracts/contracts/token/InterfoldVestingEscrow.sol new file mode 100644 index 0000000000..75f3bd5235 --- /dev/null +++ b/packages/enclave-contracts/contracts/token/InterfoldVestingEscrow.sol @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +pragma solidity 0.8.28; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { + SafeERC20 +} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import { + ReentrancyGuard +} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import { + IERC165 +} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +import { IBondingRegistry } from "../interfaces/IBondingRegistry.sol"; +import { + IInterfoldVestingEscrow +} from "../interfaces/IInterfoldVestingEscrow.sol"; +import { ILicenseBondReceiver } from "../interfaces/ILicenseBondReceiver.sol"; + +/** + * @title InterfoldVestingEscrow + * @notice Multi-schedule protocol token vesting escrow for Interfold TGE allocations. + * @dev Schedules combine a token transfer lock and an optional service vesting curve. The + * releasable amount is the stricter of the two curves, less claimed and bonded amounts. + */ +contract InterfoldVestingEscrow is + IInterfoldVestingEscrow, + ILicenseBondReceiver, + Ownable2Step, + ReentrancyGuard +{ + using SafeERC20 for IERC20; + + error ZeroAddress(); + error ZeroAmount(); + error InvalidSchedule(); + error UnknownSchedule(); + error UnauthorizedBeneficiary(); + error NothingClaimable(); + error InsufficientUnbondedAllocation(); + error InsufficientEscrowBacking(); + error UnauthorizedBondingRegistry(); + error RenounceOwnershipDisabled(); + + IERC20 public immutable TOKEN; + IBondingRegistry public immutable BONDING_REGISTRY; + uint64 public immutable TGE_TIMESTAMP; + + uint256 public nextScheduleId; + uint256 public totalScheduled; + uint256 public totalClaimed; + uint256 public totalBonded; + uint256 public totalSlashed; + + mapping(uint256 scheduleId => ScheduleView schedule) private _schedules; + + constructor( + IERC20 token_, + IBondingRegistry bondingRegistry_, + uint64 tgeTimestamp_, + address initialOwner_ + ) Ownable(initialOwner_) { + if ( + address(token_) == address(0) || + address(bondingRegistry_) == address(0) || + initialOwner_ == address(0) + ) { + revert ZeroAddress(); + } + TOKEN = token_; + BONDING_REGISTRY = bondingRegistry_; + TGE_TIMESTAMP = tgeTimestamp_; + nextScheduleId = 1; + } + + function createSchedule( + ScheduleInput calldata input + ) external onlyOwner returns (uint256 scheduleId) { + scheduleId = _createSchedule(input); + } + + function batchCreateSchedules( + ScheduleInput[] calldata inputs + ) external onlyOwner returns (uint256[] memory scheduleIds) { + uint256 len = inputs.length; + scheduleIds = new uint256[](len); + for (uint256 i = 0; i < len; i++) { + scheduleIds[i] = _createSchedule(inputs[i]); + } + } + + function claim( + uint256 scheduleId, + uint256 maxAmount + ) external nonReentrant { + ScheduleView storage schedule = _schedule(scheduleId); + if (msg.sender != schedule.beneficiary) { + revert UnauthorizedBeneficiary(); + } + + uint256 amount = claimableAmount(scheduleId); + if (maxAmount < amount) amount = maxAmount; + if (amount == 0) revert NothingClaimable(); + + schedule.claimedAmount += amount; + totalClaimed += amount; + + TOKEN.safeTransfer(schedule.beneficiary, amount); + emit TokensClaimed(scheduleId, schedule.beneficiary, amount); + } + + function bondLockedTokens( + uint256 scheduleId, + address operator, + uint256 amount + ) external nonReentrant { + if (operator == address(0)) revert ZeroAddress(); + if (amount == 0) revert ZeroAmount(); + + ScheduleView storage schedule = _schedule(scheduleId); + if (msg.sender != schedule.beneficiary) { + revert UnauthorizedBeneficiary(); + } + if (amount > bondableAmount(scheduleId)) { + revert InsufficientUnbondedAllocation(); + } + + schedule.bondedAmount += amount; + totalBonded += amount; + + TOKEN.safeIncreaseAllowance(address(BONDING_REGISTRY), amount); + BONDING_REGISTRY.bondLicenseFor( + operator, + amount, + address(this), + bytes32(scheduleId) + ); + + emit LockedTokensBonded( + scheduleId, + schedule.beneficiary, + operator, + amount + ); + } + + function onLicenseBondReturned( + address operator, + uint256 amount, + bytes32 sourceId + ) external returns (bytes4 selector) { + if (msg.sender != address(BONDING_REGISTRY)) { + revert UnauthorizedBondingRegistry(); + } + + uint256 scheduleId = uint256(sourceId); + ScheduleView storage schedule = _schedule(scheduleId); + schedule.bondedAmount -= amount; + totalBonded -= amount; + + emit BondedTokensReturned(scheduleId, operator, amount); + return ILicenseBondReceiver.onLicenseBondReturned.selector; + } + + function onLicenseBondSlashed( + address operator, + uint256 amount, + bytes32 sourceId + ) external returns (bytes4 selector) { + if (msg.sender != address(BONDING_REGISTRY)) { + revert UnauthorizedBondingRegistry(); + } + + uint256 scheduleId = uint256(sourceId); + ScheduleView storage schedule = _schedule(scheduleId); + schedule.bondedAmount -= amount; + schedule.slashedAmount += amount; + totalBonded -= amount; + totalSlashed += amount; + + emit BondedTokensSlashed(scheduleId, operator, amount); + return ILicenseBondReceiver.onLicenseBondSlashed.selector; + } + + function vestedAmount( + uint256 scheduleId, + uint64 timestamp + ) public view returns (uint256) { + ScheduleView storage schedule = _schedule(scheduleId); + uint256 effectiveTotal = schedule.totalAmount - schedule.slashedAmount; + uint256 tokenUnlocked = _tokenUnlocked( + schedule, + effectiveTotal, + timestamp + ); + uint256 serviceVested = _serviceVested( + schedule, + effectiveTotal, + timestamp + ); + return tokenUnlocked < serviceVested ? tokenUnlocked : serviceVested; + } + + function claimableAmount(uint256 scheduleId) public view returns (uint256) { + ScheduleView storage schedule = _schedule(scheduleId); + uint256 vested = vestedAmount(scheduleId, uint64(block.timestamp)); + uint256 unavailable = schedule.claimedAmount + schedule.bondedAmount; + if (vested <= unavailable) return 0; + return vested - unavailable; + } + + function bondableAmount(uint256 scheduleId) public view returns (uint256) { + ScheduleView storage schedule = _schedule(scheduleId); + uint256 unavailable = schedule.claimedAmount + + schedule.bondedAmount + + schedule.slashedAmount; + return schedule.totalAmount - unavailable; + } + + function getSchedule( + uint256 scheduleId + ) external view returns (ScheduleView memory) { + return _schedule(scheduleId); + } + + function supportsInterface(bytes4 interfaceId) public pure returns (bool) { + return + interfaceId == type(IInterfoldVestingEscrow).interfaceId || + interfaceId == type(ILicenseBondReceiver).interfaceId || + interfaceId == type(IERC165).interfaceId; + } + + function renounceOwnership() public view override onlyOwner { + revert RenounceOwnershipDisabled(); + } + + function _createSchedule( + ScheduleInput calldata input + ) internal returns (uint256 scheduleId) { + _validateSchedule(input); + + uint256 newTotalScheduled = totalScheduled + input.totalAmount; + if ( + newTotalScheduled > + TOKEN.balanceOf(address(this)) + + totalClaimed + + totalBonded + + totalSlashed + ) { + revert InsufficientEscrowBacking(); + } + + scheduleId = nextScheduleId++; + totalScheduled = newTotalScheduled; + _schedules[scheduleId] = ScheduleView({ + beneficiary: input.beneficiary, + totalAmount: input.totalAmount, + claimedAmount: 0, + bondedAmount: 0, + slashedAmount: 0, + tokenHoldUntil: input.tokenHoldUntil, + tokenUnlockStart: input.tokenUnlockStart, + tokenUnlockEnd: input.tokenUnlockEnd, + serviceStart: input.serviceStart, + serviceCliff: input.serviceCliff, + serviceEnd: input.serviceEnd, + group: input.group + }); + + emit ScheduleCreated( + scheduleId, + input.beneficiary, + input.group, + input.totalAmount, + input.tokenHoldUntil, + input.tokenUnlockStart, + input.tokenUnlockEnd, + input.serviceStart, + input.serviceCliff, + input.serviceEnd + ); + } + + function _validateSchedule(ScheduleInput calldata input) internal pure { + if (input.beneficiary == address(0)) revert ZeroAddress(); + if (input.totalAmount == 0) revert ZeroAmount(); + if (input.tokenUnlockEnd < input.tokenUnlockStart) { + revert InvalidSchedule(); + } + if (input.serviceEnd != 0) { + if (input.serviceEnd <= input.serviceStart) { + revert InvalidSchedule(); + } + if ( + input.serviceCliff < input.serviceStart || + input.serviceCliff > input.serviceEnd + ) { + revert InvalidSchedule(); + } + } else if (input.serviceStart != 0 || input.serviceCliff != 0) { + revert InvalidSchedule(); + } + } + + function _schedule( + uint256 scheduleId + ) internal view returns (ScheduleView storage schedule) { + schedule = _schedules[scheduleId]; + if (schedule.beneficiary == address(0)) revert UnknownSchedule(); + } + + function _tokenUnlocked( + ScheduleView storage schedule, + uint256 total, + uint64 timestamp + ) internal view returns (uint256) { + uint64 unlockStart = schedule.tokenUnlockStart == 0 + ? TGE_TIMESTAMP + : schedule.tokenUnlockStart; + + if (timestamp < schedule.tokenHoldUntil) return 0; + if (schedule.tokenUnlockEnd <= unlockStart) { + return timestamp >= unlockStart ? total : 0; + } + if (timestamp < unlockStart) return 0; + if (timestamp >= schedule.tokenUnlockEnd) return total; + + return + (total * (uint256(timestamp) - uint256(unlockStart))) / + (uint256(schedule.tokenUnlockEnd) - uint256(unlockStart)); + } + + function _serviceVested( + ScheduleView storage schedule, + uint256 total, + uint64 timestamp + ) internal view returns (uint256) { + if (schedule.serviceEnd == 0) return total; + if (timestamp < schedule.serviceCliff) return 0; + if (timestamp >= schedule.serviceEnd) return total; + + return + (total * (uint256(timestamp) - uint256(schedule.serviceStart))) / + (uint256(schedule.serviceEnd) - uint256(schedule.serviceStart)); + } +} diff --git a/packages/enclave-contracts/scripts/deployAndSave/interfoldVestingEscrow.ts b/packages/enclave-contracts/scripts/deployAndSave/interfoldVestingEscrow.ts new file mode 100644 index 0000000000..f7381ab692 --- /dev/null +++ b/packages/enclave-contracts/scripts/deployAndSave/interfoldVestingEscrow.ts @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. +import type { HardhatRuntimeEnvironment } from "hardhat/types/hre"; + +import { + InterfoldVestingEscrow, + InterfoldVestingEscrow__factory as InterfoldVestingEscrowFactory, +} from "../../types"; +import { + getDeploymentChain, + readDeploymentArgs, + storeDeploymentArgs, +} from "../utils"; + +/** + * The arguments for the deployAndSaveInterfoldVestingEscrow function. + */ +export interface InterfoldVestingEscrowArgs { + token?: string; + bondingRegistry?: string; + tgeTimestamp?: string; + owner?: string; + hre: HardhatRuntimeEnvironment; +} + +/** + * Deploys the InterfoldVestingEscrow contract and saves the deployment arguments. + */ +export const deployAndSaveInterfoldVestingEscrow = async ({ + token, + bondingRegistry, + tgeTimestamp, + owner, + hre, +}: InterfoldVestingEscrowArgs): Promise<{ + interfoldVestingEscrow: InterfoldVestingEscrow; +}> => { + const { ethers } = await hre.network.connect(); + const [signer] = await ethers.getSigners(); + const chain = getDeploymentChain(hre); + + const preDeployedArgs = readDeploymentArgs("InterfoldVestingEscrow", chain); + + if ( + !token || + !bondingRegistry || + !tgeTimestamp || + !owner || + (preDeployedArgs?.constructorArgs?.token === token && + preDeployedArgs?.constructorArgs?.bondingRegistry === bondingRegistry && + preDeployedArgs?.constructorArgs?.tgeTimestamp === tgeTimestamp && + preDeployedArgs?.constructorArgs?.owner === owner) + ) { + if (!preDeployedArgs?.address) { + throw new Error( + "InterfoldVestingEscrow address not found, it must be deployed first", + ); + } + const interfoldVestingEscrow = InterfoldVestingEscrowFactory.connect( + preDeployedArgs.address, + signer, + ); + return { interfoldVestingEscrow }; + } + + const interfoldVestingEscrowFactory = await ethers.getContractFactory( + "InterfoldVestingEscrow", + ); + const interfoldVestingEscrow = await interfoldVestingEscrowFactory.deploy( + token, + bondingRegistry, + tgeTimestamp, + owner, + ); + await interfoldVestingEscrow.waitForDeployment(); + + const blockNumber = await ethers.provider.getBlockNumber(); + const interfoldVestingEscrowAddress = + await interfoldVestingEscrow.getAddress(); + + storeDeploymentArgs( + { + constructorArgs: { + token, + bondingRegistry, + tgeTimestamp, + owner, + }, + blockNumber, + address: interfoldVestingEscrowAddress, + }, + "InterfoldVestingEscrow", + chain, + ); + + const interfoldVestingEscrowContract = InterfoldVestingEscrowFactory.connect( + interfoldVestingEscrowAddress, + signer, + ); + + return { interfoldVestingEscrow: interfoldVestingEscrowContract }; +}; diff --git a/packages/enclave-contracts/scripts/deployEnclave.ts b/packages/enclave-contracts/scripts/deployEnclave.ts index 76864f1085..c54cd5b75b 100644 --- a/packages/enclave-contracts/scripts/deployEnclave.ts +++ b/packages/enclave-contracts/scripts/deployEnclave.ts @@ -17,11 +17,13 @@ import { deployAndSaveE3RefundManager } from "./deployAndSave/e3RefundManager"; import { deployAndSaveEnclave } from "./deployAndSave/enclave"; import { deployAndSaveEnclaveTicketToken } from "./deployAndSave/enclaveTicketToken"; import { deployAndSaveEnclaveToken } from "./deployAndSave/enclaveToken"; +import { deployAndSaveInterfoldVestingEscrow } from "./deployAndSave/interfoldVestingEscrow"; import { deployAndSaveMockStableToken } from "./deployAndSave/mockStableToken"; import { deployAndSavePoseidonT3 } from "./deployAndSave/poseidonT3"; import { deployAndSaveSlashingManager } from "./deployAndSave/slashingManager"; import { deployAndSaveAllVerifiers } from "./deployAndSave/verifiers"; import { deployMocks } from "./deployMocks"; +import { isLocalDeploymentChain, readDeploymentArgs } from "./utils"; // BFV parameter presets — hardcoded from crates/fhe-params/src/constants.rs // to avoid a cyclic dependency on @enclave-e3/sdk. @@ -71,6 +73,56 @@ const DEFAULT_TIMEOUT_CONFIG = { decryptionWindow: 3600, }; +function parseRequiredUint64(value: string, label: string): bigint { + if (!/^\d+$/.test(value)) { + throw new Error(`${label} must be a base-10 unix timestamp`); + } + const parsed = BigInt(value); + const maxUint64 = (1n << 64n) - 1n; + if (parsed > maxUint64) { + throw new Error(`${label} must fit in uint64`); + } + return parsed; +} + +function resolveInterfoldTgeTimestamp( + networkName: string, + latestBlockTimestamp: number, +): string { + const configured = process.env.INTERFOLD_TGE_TIMESTAMP; + if (configured?.trim()) { + return parseRequiredUint64( + configured.trim(), + "INTERFOLD_TGE_TIMESTAMP", + ).toString(); + } + + if (!isLocalDeploymentChain(networkName)) { + throw new Error( + "INTERFOLD_TGE_TIMESTAMP must be set for non-local InterfoldVestingEscrow deployment", + ); + } + + const preDeployedTgeTimestamp = readDeploymentArgs( + "InterfoldVestingEscrow", + networkName, + )?.constructorArgs?.tgeTimestamp; + if (typeof preDeployedTgeTimestamp === "string") { + console.warn( + "[WARN] INTERFOLD_TGE_TIMESTAMP not set; reusing saved local InterfoldVestingEscrow TGE timestamp.", + ); + return parseRequiredUint64( + preDeployedTgeTimestamp, + "saved InterfoldVestingEscrow tgeTimestamp", + ).toString(); + } + + console.warn( + "[WARN] INTERFOLD_TGE_TIMESTAMP not set; using latest local block timestamp for InterfoldVestingEscrow.", + ); + return latestBlockTimestamp.toString(); +} + /** Circuit names required for BFV ZK verification in this script */ const DKG_AGGREGATOR_VERIFIER = "DkgAggregatorVerifier"; const DECRYPTION_AGGREGATOR_VERIFIER = "DecryptionAggregatorVerifier"; @@ -91,6 +143,14 @@ export const deployEnclave = async ( const [owner] = await ethers.getSigners(); const ownerAddress = await owner.getAddress(); + const latestBlock = await ethers.provider.getBlock("latest"); + if (!latestBlock) { + throw new Error("Could not read latest block for local TGE timestamp"); + } + const interfoldTgeTimestamp = resolveInterfoldTgeTimestamp( + networkName, + latestBlock.timestamp, + ); const encodedInsecure = encodeBfvParams(BFV_PARAMS.insecure512); const encodedSecure = encodeBfvParams(BFV_PARAMS.secure8192); @@ -206,6 +266,33 @@ export const deployEnclave = async ( const bondingRegistryAddress = await bondingRegistry.getAddress(); console.log("BondingRegistry deployed to:", bondingRegistryAddress); + console.log( + "Deploying InterfoldVestingEscrow with TGE timestamp:", + interfoldTgeTimestamp, + ); + const { interfoldVestingEscrow } = await deployAndSaveInterfoldVestingEscrow({ + token: enclaveTokenAddress, + bondingRegistry: bondingRegistryAddress, + tgeTimestamp: interfoldTgeTimestamp, + owner: ownerAddress, + hre, + }); + const interfoldVestingEscrowAddress = + await interfoldVestingEscrow.getAddress(); + console.log( + "InterfoldVestingEscrow deployed to:", + interfoldVestingEscrowAddress, + ); + + console.log( + "Whitelisting BondingRegistry and InterfoldVestingEscrow in ENCL...", + ); + const whitelistTx = await enclaveToken.whitelistContracts( + bondingRegistryAddress, + interfoldVestingEscrowAddress, + ); + await whitelistTx.wait(); + console.log("Deploying Enclave..."); const { enclave } = await deployAndSaveEnclave({ owner: ownerAddress, @@ -496,6 +583,7 @@ export const deployEnclave = async ( EnclaveTicketToken: ${enclaveTicketTokenAddress} SlashingManager: ${slashingManagerAddress} BondingRegistry: ${bondingRegistryAddress} + InterfoldVestingEscrow: ${interfoldVestingEscrowAddress} CiphernodeRegistry: ${ciphernodeRegistryAddress} E3RefundManager: ${e3RefundManagerAddress} Enclave: ${enclaveAddress} diff --git a/packages/enclave-contracts/scripts/index.ts b/packages/enclave-contracts/scripts/index.ts index f32d255875..6a20e8a6aa 100644 --- a/packages/enclave-contracts/scripts/index.ts +++ b/packages/enclave-contracts/scripts/index.ts @@ -13,6 +13,7 @@ export * from "./deployAndSave/ciphernodeRegistryOwnable"; export * from "./deployAndSave/enclave"; export * from "./deployAndSave/enclaveTicketToken"; export * from "./deployAndSave/enclaveToken"; +export * from "./deployAndSave/interfoldVestingEscrow"; export * from "./deployAndSave/mockStableToken"; export * from "./deployAndSave/slashingManager"; export * from "./deployAndSave/mockComputeProvider"; diff --git a/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts b/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts index b04030363b..24e3610023 100644 --- a/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts +++ b/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts @@ -1179,28 +1179,40 @@ describe("BondingRegistry", function () { * `_takeAssetsFromQueue` during a slash. */ it("H-21: queueAssetsForExit reverts after MAX_ACTIVE_TRANCHES live tranches", async function () { - const { bondingRegistry, licenseToken, operator1 } = - await loadFixture(setup); + const { + bondingRegistry, + licenseToken, + ticketToken, + usdcToken, + operator1, + } = await loadFixture(setup); - // Bond a large enough amount to do many tiny unbonds. - const big = ethers.parseEther("10000"); + // Register and fund tickets so the generic ExitQueueLib ticket path is + // exercised directly. ENCL exits now use source-aware withdrawal logic. + const bondAmount = LICENSE_REQUIRED_BOND; await licenseToken .connect(operator1) - .approve(await bondingRegistry.getAddress(), big); - await bondingRegistry.connect(operator1).bondLicense(big); + .approve(await bondingRegistry.getAddress(), bondAmount); + await bondingRegistry.connect(operator1).bondLicense(bondAmount); await bondingRegistry.connect(operator1).registerOperator(); + const ticketAmount = ethers.parseUnits("10000", 6); + await usdcToken + .connect(operator1) + .approve(await ticketToken.getAddress(), ticketAmount); + await bondingRegistry.connect(operator1).addTicketBalance(ticketAmount); + // Fill the queue with 64 distinct-timestamp tranches. - const step = ethers.parseEther("1"); + const step = ethers.parseUnits("1", 6); for (let i = 0; i < 64; i++) { - await bondingRegistry.connect(operator1).unbondLicense(step); + await bondingRegistry.connect(operator1).removeTicketBalance(step); // Ensure next unlock timestamp differs (no merge). await time.increase(1); } // The 65th must revert. await expect( - bondingRegistry.connect(operator1).unbondLicense(step), + bondingRegistry.connect(operator1).removeTicketBalance(step), ).to.be.revertedWithCustomError(bondingRegistry, "TooManyTranches"); }); diff --git a/packages/enclave-contracts/test/Token/InterfoldVestingEscrow.spec.ts b/packages/enclave-contracts/test/Token/InterfoldVestingEscrow.spec.ts new file mode 100644 index 0000000000..d42fe9c5c3 --- /dev/null +++ b/packages/enclave-contracts/test/Token/InterfoldVestingEscrow.spec.ts @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. +import { expect } from "chai"; + +import { InterfoldVestingEscrow__factory as InterfoldVestingEscrowFactory } from "../../types"; +import { + SEVEN_DAYS, + deployEnclaveSystem, + ethers, + networkHelpers, +} from "../fixtures"; + +const { loadFixture, time } = networkHelpers; + +const DAY = 24n * 60n * 60n; +const YEAR = 365n * DAY; + +const GROUP_PRE_SEED = ethers.encodeBytes32String("PRE_SEED"); +const GROUP_BRIDGE = ethers.encodeBytes32String("BRIDGE"); +const GROUP_TEAM = ethers.encodeBytes32String("GG_TEAM"); + +describe("InterfoldVestingEscrow", function () { + async function setup() { + const signers = await ethers.getSigners(); + const [owner, beneficiary, operator, slasher] = signers; + const ownerAddress = await owner.getAddress(); + const beneficiaryAddress = await beneficiary.getAddress(); + const operatorAddress = await operator.getAddress(); + const slasherAddress = await slasher.getAddress(); + + const sys = await deployEnclaveSystem({ + useMockCiphernodeRegistry: true, + setupOperators: 0, + wireSlashingManager: false, + mintUsdcTo: [], + }); + const { bondingRegistry, licenseToken } = sys; + await bondingRegistry.setSlashingManager(slasherAddress); + + const now = BigInt(await time.latest()); + const tge = now + DAY; + const vestingEscrow = await new InterfoldVestingEscrowFactory(owner).deploy( + await licenseToken.getAddress(), + await bondingRegistry.getAddress(), + tge, + ownerAddress, + ); + await vestingEscrow.waitForDeployment(); + const vestingEscrowAddress = await vestingEscrow.getAddress(); + + await licenseToken.whitelistContracts( + await bondingRegistry.getAddress(), + vestingEscrowAddress, + ); + + await licenseToken.mintAllocation( + vestingEscrowAddress, + ethers.parseEther("1000000"), + "Interfold vesting escrow budget", + ); + + return { + owner, + beneficiary, + operator, + slasher, + beneficiaryAddress, + operatorAddress, + bondingRegistry, + licenseToken, + vestingEscrow, + tge, + }; + } + + async function createSchedule( + overrides: Partial<{ + beneficiary: string; + totalAmount: bigint; + tokenHoldUntil: bigint; + tokenUnlockStart: bigint; + tokenUnlockEnd: bigint; + serviceStart: bigint; + serviceCliff: bigint; + serviceEnd: bigint; + group: string; + }> = {}, + ) { + const fixture = await loadFixture(setup); + const { vestingEscrow, beneficiaryAddress, tge } = fixture; + const scheduleId = await vestingEscrow.nextScheduleId(); + const totalAmount = overrides.totalAmount ?? ethers.parseEther("2400"); + await vestingEscrow.createSchedule({ + beneficiary: overrides.beneficiary ?? beneficiaryAddress, + totalAmount, + tokenHoldUntil: overrides.tokenHoldUntil ?? tge, + tokenUnlockStart: overrides.tokenUnlockStart ?? tge, + tokenUnlockEnd: overrides.tokenUnlockEnd ?? tge + 2n * YEAR, + serviceStart: overrides.serviceStart ?? 0n, + serviceCliff: overrides.serviceCliff ?? 0n, + serviceEnd: overrides.serviceEnd ?? 0n, + group: overrides.group ?? GROUP_PRE_SEED, + }); + return { ...fixture, scheduleId, totalAmount }; + } + + it("releases pre-seed/seed/Legion/GG schedules linearly from TGE", async function () { + const { vestingEscrow, beneficiary, scheduleId, totalAmount, tge } = + await createSchedule(); + + await expect( + vestingEscrow.connect(beneficiary).claim(scheduleId, totalAmount), + ).to.be.revertedWithCustomError(vestingEscrow, "NothingClaimable"); + + await time.increaseTo(tge + YEAR); + + expect(await vestingEscrow.claimableAmount(scheduleId)).to.equal( + totalAmount / 2n, + ); + + await expect( + vestingEscrow.connect(beneficiary).claim(scheduleId, totalAmount / 2n), + ) + .to.emit(vestingEscrow, "TokensClaimed") + .withArgs(scheduleId, await beneficiary.getAddress(), totalAmount / 2n); + }); + + it("accumulates Bridge SAFT linear unlock during the holding period", async function () { + const fixture = await loadFixture(setup); + const { vestingEscrow, beneficiary, beneficiaryAddress, tge } = fixture; + const scheduleId = await vestingEscrow.nextScheduleId(); + const totalAmount = ethers.parseEther("2400"); + + await vestingEscrow.createSchedule({ + beneficiary: beneficiaryAddress, + totalAmount, + tokenHoldUntil: tge + YEAR, + tokenUnlockStart: tge, + tokenUnlockEnd: tge + 2n * YEAR, + serviceStart: 0n, + serviceCliff: 0n, + serviceEnd: 0n, + group: GROUP_BRIDGE, + }); + + await time.increaseTo(tge + YEAR / 2n); + await expect( + vestingEscrow.connect(beneficiary).claim(scheduleId, totalAmount), + ).to.be.revertedWithCustomError(vestingEscrow, "NothingClaimable"); + + await time.increaseTo(tge + YEAR); + expect(await vestingEscrow.claimableAmount(scheduleId)).to.equal( + totalAmount / 2n, + ); + }); + + it("applies GG team service vesting as a second stricter curve", async function () { + const fixture = await loadFixture(setup); + const { vestingEscrow, beneficiary, beneficiaryAddress, tge } = fixture; + const totalAmount = ethers.parseEther("4800"); + const signing = tge - 180n * DAY; + const scheduleId = await vestingEscrow.nextScheduleId(); + + await vestingEscrow.createSchedule({ + beneficiary: beneficiaryAddress, + totalAmount, + tokenHoldUntil: tge, + tokenUnlockStart: tge, + tokenUnlockEnd: tge + 2n * YEAR, + serviceStart: signing, + serviceCliff: signing + YEAR, + serviceEnd: signing + 4n * YEAR, + group: GROUP_TEAM, + }); + + await time.increaseTo(signing + YEAR - DAY); + await expect( + vestingEscrow.connect(beneficiary).claim(scheduleId, totalAmount), + ).to.be.revertedWithCustomError(vestingEscrow, "NothingClaimable"); + + await time.increaseTo(signing + YEAR); + expect(await vestingEscrow.claimableAmount(scheduleId)).to.equal( + totalAmount / 4n, + ); + }); + + it("lets a beneficiary bond locked tokens and reclaim them through the vesting escrow", async function () { + const { + vestingEscrow, + beneficiary, + operator, + operatorAddress, + bondingRegistry, + licenseToken, + scheduleId, + tge, + } = await createSchedule(); + const bondAmount = ethers.parseEther("1000"); + + await expect( + vestingEscrow + .connect(beneficiary) + .bondLockedTokens(scheduleId, operatorAddress, bondAmount), + ) + .to.emit(vestingEscrow, "LockedTokensBonded") + .withArgs( + scheduleId, + await beneficiary.getAddress(), + operatorAddress, + bondAmount, + ); + + expect(await bondingRegistry.getLicenseBond(operatorAddress)).to.equal( + bondAmount, + ); + expect((await vestingEscrow.getSchedule(scheduleId)).bondedAmount).to.equal( + bondAmount, + ); + + await bondingRegistry.connect(operator).unbondLicense(bondAmount); + await time.increase(SEVEN_DAYS + 1); + + await expect(bondingRegistry.connect(operator).claimExits(0, bondAmount)) + .to.emit(vestingEscrow, "BondedTokensReturned") + .withArgs(scheduleId, operatorAddress, bondAmount); + + expect((await vestingEscrow.getSchedule(scheduleId)).bondedAmount).to.equal( + 0n, + ); + + await time.increaseTo(tge + 2n * YEAR); + await vestingEscrow.connect(beneficiary).claim(scheduleId, bondAmount); + expect( + await licenseToken.balanceOf(await beneficiary.getAddress()), + ).to.equal(bondAmount); + }); + + it("slashes license bond sources in LIFO order across direct and locked bonds", async function () { + const { + vestingEscrow, + beneficiary, + operator, + slasher, + operatorAddress, + bondingRegistry, + licenseToken, + scheduleId, + } = await createSchedule(); + + const lockedBond = ethers.parseEther("400"); + const directBond = ethers.parseEther("200"); + await vestingEscrow + .connect(beneficiary) + .bondLockedTokens(scheduleId, operatorAddress, lockedBond); + + await licenseToken.mintAllocation( + operatorAddress, + directBond, + "Operator direct bond", + ); + await licenseToken + .connect(operator) + .approve(await bondingRegistry.getAddress(), directBond); + await bondingRegistry.connect(operator).bondLicense(directBond); + + await expect( + bondingRegistry + .connect(slasher) + .slashLicenseBond( + operatorAddress, + ethers.parseEther("250"), + ethers.encodeBytes32String("TEST_SLASH"), + ), + ) + .to.emit(vestingEscrow, "BondedTokensSlashed") + .withArgs(scheduleId, operatorAddress, ethers.parseEther("50")); + + expect((await vestingEscrow.getSchedule(scheduleId)).bondedAmount).to.equal( + ethers.parseEther("350"), + ); + expect(await bondingRegistry.getLicenseBond(operatorAddress)).to.equal( + ethers.parseEther("350"), + ); + }); +}); From 022a0fde7a785ebb1ffcc1d6f8e401083e578fa1 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 28 May 2026 03:53:36 +0500 Subject: [PATCH 02/24] fix: contract addresses --- examples/CRISP/enclave.config.yaml | 16 ++-- .../crisp-contracts/deployed_contracts.json | 86 +++++++++-------- .../crisp-contracts/hardhat.config.ts | 1 + examples/CRISP/server/.env.example | 4 +- templates/default/deployed_contracts.json | 92 +++++++++---------- templates/default/enclave.config.yaml | 18 ++-- templates/default/hardhat.config.ts | 1 + 7 files changed, 109 insertions(+), 109 deletions(-) diff --git a/examples/CRISP/enclave.config.yaml b/examples/CRISP/enclave.config.yaml index ed52e89d59..58a6ff4221 100644 --- a/examples/CRISP/enclave.config.yaml +++ b/examples/CRISP/enclave.config.yaml @@ -5,23 +5,23 @@ chains: rpc_url: ws://localhost:8545 contracts: e3_program: - address: "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0" - deploy_block: 38 + address: "0xc351628EB244ec633d5f21fBD6621e1a683B1181" + deploy_block: 44 enclave: - address: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" - deploy_block: 14 + address: "0x9A676e781A523b5d0C0e43731313A708CB607508" + deploy_block: 20 ciphernode_registry: address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" - deploy_block: 9 + deploy_block: 13 bonding_registry: address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" - deploy_block: 10 + deploy_block: 14 slashing_manager: address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" - deploy_block: 8 + deploy_block: 12 fee_token: address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" - deploy_block: 4 + deploy_block: 8 - name: "sepolia" enabled: false # Public Sepolia WebSocket endpoint (see repo docs for the recommended default). diff --git a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json index f4bd486ddb..5d2855d05d 100644 --- a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json +++ b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json @@ -148,21 +148,21 @@ }, "localhost": { "PoseidonT3": { - "blockNumber": 3, + "blockNumber": 7, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 4, + "blockNumber": 8, "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, "EnclaveToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 5, + "blockNumber": 9, "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "EnclaveTicketToken": { @@ -171,7 +171,7 @@ "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 7, + "blockNumber": 11, "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, "SlashingManager": { @@ -179,7 +179,7 @@ "initialDelay": "172800", "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 8, + "blockNumber": 12, "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "CiphernodeRegistryOwnable": { @@ -194,7 +194,7 @@ "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" }, - "blockNumber": 9, + "blockNumber": 13, "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, "BondingRegistry": { @@ -216,9 +216,19 @@ "proxyAdminAddress": "0x8aCd85898458400f7Db866d53FCFF6f0D49741FF", "implementationAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, - "blockNumber": 10, + "blockNumber": 14, "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, + "InterfoldVestingEscrow": { + "constructorArgs": { + "token": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "bondingRegistry": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", + "tgeTimestamp": "1779922281", + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + }, + "blockNumber": 17, + "address": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + }, "Enclave": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", @@ -232,66 +242,66 @@ "proxyRecords": { "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c8530000000000000000000000008a791620dd6260079bf849dc5567adc3f2fdc3180000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "proxyAdminAddress": "0x1F708C24a0D3A740cD47cC0444E9480899f3dA7D", - "implementationAddress": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + "proxyAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508", + "proxyAdminAddress": "0x8e80FFe6Dc044F4A766Afd6e5a8732Fe0977A493", + "implementationAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" }, - "blockNumber": 14, - "address": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + "blockNumber": 20, + "address": "0x9A676e781A523b5d0C0e43731313A708CB607508" }, "E3RefundManager": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "enclave": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "enclave": "0x9A676e781A523b5d0C0e43731313A708CB607508", "treasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, "proxyRecords": { - "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a51c1fc2f0d1a1b8494ed1fe312d7c3a78ed91c0000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000009a676e781a523b5d0c0e43731313a708cb607508000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508", - "proxyAdminAddress": "0x8e80FFe6Dc044F4A766Afd6e5a8732Fe0977A493", - "implementationAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + "proxyAddress": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1", + "proxyAdminAddress": "0x24B3c7704709ed1491473F30393FFc93cFB0FC34", + "implementationAddress": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" }, - "blockNumber": 16, - "address": "0x9A676e781A523b5d0C0e43731313A708CB607508" + "blockNumber": 22, + "address": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" }, "MockComputeProvider": { - "blockNumber": 30, - "address": "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00" + "blockNumber": 36, + "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" }, "MockDecryptionVerifier": { - "blockNumber": 31, - "address": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570" + "blockNumber": 37, + "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" }, "MockPkVerifier": { - "blockNumber": 32, - "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" + "blockNumber": 38, + "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" }, "MockE3Program": { - "blockNumber": 33, - "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" + "blockNumber": 39, + "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" }, "MockRISC0Verifier": { - "address": "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90", - "blockNumber": 37 + "address": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", + "blockNumber": 43 }, "HonkVerifier": { - "address": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", - "blockNumber": 38 + "address": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", + "blockNumber": 44 }, "CRISPProgram": { - "address": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", - "blockNumber": 38, + "address": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", + "blockNumber": 44, "constructorArgs": { - "enclave": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "verifierAddress": "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90", - "honkVerifierAddress": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", + "enclave": "0x9A676e781A523b5d0C0e43731313A708CB607508", + "verifierAddress": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", + "honkVerifierAddress": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", "imageId": "0x23734b77b0f76e85623a88d7a82f24c34c94834f2501964ea123b7a2027013a2" } }, "MockVotingToken": { - "address": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", - "blockNumber": 40 + "address": "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc", + "blockNumber": 46 } } } \ No newline at end of file diff --git a/examples/CRISP/packages/crisp-contracts/hardhat.config.ts b/examples/CRISP/packages/crisp-contracts/hardhat.config.ts index f86d42c930..79926563b0 100644 --- a/examples/CRISP/packages/crisp-contracts/hardhat.config.ts +++ b/examples/CRISP/packages/crisp-contracts/hardhat.config.ts @@ -136,6 +136,7 @@ const config: HardhatUserConfig = { '@enclave-e3/contracts/contracts/slashing/SlashingManager.sol', '@enclave-e3/contracts/contracts/token/EnclaveToken.sol', '@enclave-e3/contracts/contracts/token/EnclaveTicketToken.sol', + '@enclave-e3/contracts/contracts/token/InterfoldVestingEscrow.sol', '@enclave-e3/contracts/contracts/test/MockCiphernodeRegistry.sol', '@enclave-e3/contracts/contracts/test/MockComputeProvider.sol', '@enclave-e3/contracts/contracts/test/MockDecryptionVerifier.sol', diff --git a/examples/CRISP/server/.env.example b/examples/CRISP/server/.env.example index fbf89635ab..93231f3e6c 100644 --- a/examples/CRISP/server/.env.example +++ b/examples/CRISP/server/.env.example @@ -15,9 +15,9 @@ CRON_API_KEY=1234567890 # Enclave stack: updated automatically on each `pnpm dev:up` deploy (do not edit by hand unless debugging). # Stale E3_PROGRAM_ADDRESS causes requestE3 to revert with empty data `0x`. -ENCLAVE_ADDRESS=0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0 +ENCLAVE_ADDRESS=0x9A676e781A523b5d0C0e43731313A708CB607508 FEE_TOKEN_ADDRESS=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -E3_PROGRAM_ADDRESS=0x162A433068F51e18b7d13932F27e66a3f99E6890 +E3_PROGRAM_ADDRESS=0xc351628EB244ec633d5f21fBD6621e1a683B1181 CIPHERNODE_REGISTRY_ADDRESS=0xa513E6E4b8f2a923D98304ec87F64353C4D5C853 # CRISP voting eligibility token (MockVotingToken) — NOT the fee token above CRISP_VOTING_TOKEN=0x5081a39b8A5f0E35a8D959395a630b68B74Dd30f diff --git a/templates/default/deployed_contracts.json b/templates/default/deployed_contracts.json index 2bb29ced8a..574aa64964 100644 --- a/templates/default/deployed_contracts.json +++ b/templates/default/deployed_contracts.json @@ -1,21 +1,21 @@ { "localhost": { "PoseidonT3": { - "blockNumber": 17, + "blockNumber": 8, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 18, + "blockNumber": 9, "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, "EnclaveToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 19, + "blockNumber": 10, "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "EnclaveTicketToken": { @@ -24,7 +24,7 @@ "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 21, + "blockNumber": 13, "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, "SlashingManager": { @@ -32,7 +32,7 @@ "initialDelay": "172800", "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 11, + "blockNumber": 14, "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "CiphernodeRegistryOwnable": { @@ -47,7 +47,7 @@ "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" }, - "blockNumber": 12, + "blockNumber": 15, "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, "BondingRegistry": { @@ -69,9 +69,19 @@ "proxyAdminAddress": "0x8aCd85898458400f7Db866d53FCFF6f0D49741FF", "implementationAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, - "blockNumber": 13, + "blockNumber": 16, "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, + "InterfoldVestingEscrow": { + "constructorArgs": { + "token": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "bondingRegistry": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", + "tgeTimestamp": "1779922372", + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + }, + "blockNumber": 19, + "address": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + }, "Enclave": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", @@ -85,72 +95,52 @@ "proxyRecords": { "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c8530000000000000000000000008a791620dd6260079bf849dc5567adc3f2fdc3180000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "proxyAdminAddress": "0x1F708C24a0D3A740cD47cC0444E9480899f3dA7D", - "implementationAddress": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + "proxyAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508", + "proxyAdminAddress": "0x8e80FFe6Dc044F4A766Afd6e5a8732Fe0977A493", + "implementationAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" }, - "blockNumber": 27, - "address": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + "blockNumber": 22, + "address": "0x9A676e781A523b5d0C0e43731313A708CB607508" }, "E3RefundManager": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "enclave": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "enclave": "0x9A676e781A523b5d0C0e43731313A708CB607508", "treasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, "proxyRecords": { - "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a51c1fc2f0d1a1b8494ed1fe312d7c3a78ed91c0000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000009a676e781a523b5d0c0e43731313a708cb607508000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508", - "proxyAdminAddress": "0x8e80FFe6Dc044F4A766Afd6e5a8732Fe0977A493", - "implementationAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + "proxyAddress": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1", + "proxyAdminAddress": "0x24B3c7704709ed1491473F30393FFc93cFB0FC34", + "implementationAddress": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" }, - "blockNumber": 29, - "address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + "blockNumber": 25, + "address": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" }, "MockComputeProvider": { - "blockNumber": 31, - "address": "0x9E545E3C0baAB3E08CdfD552C960A1050f373042" + "blockNumber": 58, + "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" }, "MockDecryptionVerifier": { - "blockNumber": 32, - "address": "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9" + "blockNumber": 59, + "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" }, "MockPkVerifier": { - "blockNumber": 33, - "address": "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8" + "blockNumber": 60, + "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" }, "MockE3Program": { - "blockNumber": 34, - "address": "0x851356ae760d987E095750cCeb3bC6014560891C" - }, - "ZKTranscriptLib": { - "blockNumber": 36, - "address": "0x95401dc811bb5740090279Ba06cfA8fcF6113778" - }, - "DecryptionAggregatorVerifier": { - "blockNumber": 37, - "address": "0x998abeb3E57409262aE5b751f60747921B33613E" - }, - "DkgAggregatorVerifier": { - "blockNumber": 38, - "address": "0x70e0bA845a1A0F2DA3359C97E0285013525FFC49" - }, - "BfvDecryptionVerifier": { - "blockNumber": 39, - "address": "0x4826533B4897376654Bb4d4AD88B7faFD0C98528" - }, - "BfvPkVerifier": { - "blockNumber": 41, - "address": "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF" + "blockNumber": 61, + "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" }, "ImageID": { - "address": "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00", - "blockNumber": 44 + "address": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", + "blockNumber": 67 }, "MyProgram": { - "address": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570", - "blockNumber": 46 + "address": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", + "blockNumber": 69 } } } \ No newline at end of file diff --git a/templates/default/enclave.config.yaml b/templates/default/enclave.config.yaml index 3b92a5ae62..2b8acb61b5 100644 --- a/templates/default/enclave.config.yaml +++ b/templates/default/enclave.config.yaml @@ -3,26 +3,24 @@ chains: rpc_url: "ws://localhost:8545" contracts: e3_program: - address: "0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf" - deploy_block: 32 + address: "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650" + deploy_block: 69 enclave: - address: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" - deploy_block: 16 + address: "0x9A676e781A523b5d0C0e43731313A708CB607508" + deploy_block: 22 ciphernode_registry: address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" - deploy_block: 12 + deploy_block: 15 bonding_registry: address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" - deploy_block: 13 + deploy_block: 16 slashing_manager: address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" deploy_block: 11 fee_token: address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" - deploy_block: 6 - e3_program: - address: "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF" - deploy_block: 51 + deploy_block: 9 + program: dev: true # Default profile: multithread uses (logical CPUs - 1) Rayon workers and the same concurrent job diff --git a/templates/default/hardhat.config.ts b/templates/default/hardhat.config.ts index 666da37a44..ffb5a8311e 100644 --- a/templates/default/hardhat.config.ts +++ b/templates/default/hardhat.config.ts @@ -124,6 +124,7 @@ const config: HardhatUserConfig = { '@enclave-e3/contracts/contracts/E3RefundManager.sol', '@enclave-e3/contracts/contracts/token/EnclaveToken.sol', '@enclave-e3/contracts/contracts/token/EnclaveTicketToken.sol', + '@enclave-e3/contracts/contracts/token/InterfoldVestingEscrow.sol', '@enclave-e3/contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol', '@enclave-e3/contracts/contracts/verifiers/bfv/BfvPkVerifier.sol', '@enclave-e3/contracts/contracts/verifiers/bfv/honk/DkgAggregatorVerifier.sol', From 4e96f001b843fc75faf0e6fe89a7c2c766edcde4 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 28 May 2026 11:09:47 +0500 Subject: [PATCH 03/24] fix: runtime dist in templates --- .../src/circuits/assert-micro-circuits.ts | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/packages/enclave-sdk/src/circuits/assert-micro-circuits.ts b/packages/enclave-sdk/src/circuits/assert-micro-circuits.ts index 051ba9c494..7fb78d500f 100644 --- a/packages/enclave-sdk/src/circuits/assert-micro-circuits.ts +++ b/packages/enclave-sdk/src/circuits/assert-micro-circuits.ts @@ -4,16 +4,43 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -import { readFileSync } from 'node:fs' -import { dirname, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' +import { existsSync, readFileSync } from 'node:fs' +import { dirname, join, resolve } from 'node:path' import { SDKError } from '../utils' /** Matches `IEnclave.CommitteeSize.Micro` and `DEFAULT_E3_CONFIG.committeeSize`. */ export const SDK_CIRCUIT_COMMITTEE = 'micro' -const ACTIVE_PRESET_PATH = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../circuits/bin/.active-preset.json') +const ACTIVE_PRESET_RELATIVE_PATH = 'circuits/bin/.active-preset.json' + +function findActivePresetPath(startDir: string): string | undefined { + let currentDir = resolve(startDir) + + while (true) { + const activePresetPath = join(currentDir, ACTIVE_PRESET_RELATIVE_PATH) + + if (existsSync(activePresetPath)) return activePresetPath + + if (existsSync(join(currentDir, 'pnpm-workspace.yaml')) && existsSync(join(currentDir, 'circuits'))) { + return activePresetPath + } + + const parentDir = dirname(currentDir) + if (parentDir === currentDir) return undefined + + currentDir = parentDir + } +} + +function resolveActivePresetPath(): string { + const currentWorkingDir = typeof process !== 'undefined' && typeof process.cwd === 'function' ? process.cwd() : undefined + + return ( + (currentWorkingDir ? findActivePresetPath(currentWorkingDir) : undefined) ?? + resolve(currentWorkingDir ?? '.', ACTIVE_PRESET_RELATIVE_PATH) + ) +} let checked = false @@ -24,14 +51,15 @@ let checked = false */ export function assertSdkMicroCircuits(): void { if (checked) return - checked = true + + const activePresetPath = resolveActivePresetPath() let raw: string try { - raw = readFileSync(ACTIVE_PRESET_PATH, 'utf-8') + raw = readFileSync(activePresetPath, 'utf-8') } catch { throw new SDKError( - `Missing ${ACTIVE_PRESET_PATH}. Run \`pnpm -C packages/enclave-sdk compile:circuits\` first.`, + `Missing ${activePresetPath}. Run \`pnpm -C packages/enclave-sdk compile:circuits\` first.`, 'SDK_CIRCUIT_STAMP_MISSING', ) } @@ -41,7 +69,7 @@ export function assertSdkMicroCircuits(): void { committee = JSON.parse(raw)?.committee as string | undefined } catch { throw new SDKError( - `Invalid JSON in ${ACTIVE_PRESET_PATH}. Rebuild with \`pnpm -C packages/enclave-sdk compile:circuits\`.`, + `Invalid JSON in ${activePresetPath}. Rebuild with \`pnpm -C packages/enclave-sdk compile:circuits\`.`, 'SDK_CIRCUIT_STAMP_INVALID', ) } @@ -54,4 +82,6 @@ export function assertSdkMicroCircuits(): void { 'SDK_CIRCUIT_COMMITTEE_MISMATCH', ) } + + checked = true } From c73d00cf6a76cd7c28790432e8eb110fabdca53b Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 28 May 2026 17:23:19 +0500 Subject: [PATCH 04/24] fix: sdk test --- .../src/circuits/assert-micro-circuits.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/enclave-sdk/src/circuits/assert-micro-circuits.ts b/packages/enclave-sdk/src/circuits/assert-micro-circuits.ts index 6d39d1f2db..0728fbc729 100644 --- a/packages/enclave-sdk/src/circuits/assert-micro-circuits.ts +++ b/packages/enclave-sdk/src/circuits/assert-micro-circuits.ts @@ -14,25 +14,26 @@ import { SDKError } from '../utils' export const SDK_CIRCUIT_COMMITTEE = 'micro' function findActivePath(): string | null { - // Walk up from the bundle file (depth varies: dist/ vs dist/crypto/ etc.) - // until we find the package root (directory containing package.json). + if (!import.meta.url) return null + let dir = dirname(fileURLToPath(import.meta.url)) + while (true) { if (existsSync(resolve(dir, 'package.json'))) { - // Bundled preset shipped inside the package takes priority (future use). const bundled = resolve(dir, '.active-preset.json') if (existsSync(bundled)) return bundled - // When installed under node_modules the monorepo root is not available; - // skip the check rather than resolving into an unrelated project tree. if (dir.includes('node_modules')) return null return resolve(dir, '../../circuits/bin/.active-preset.json') } + const parent = dirname(dir) if (parent === dir) break + dir = parent } + throw new SDKError('Could not locate SDK package root', 'SDK_CIRCUIT_STAMP_MISSING') } @@ -48,16 +49,17 @@ let checked = false export function assertSdkMicroCircuits(): void { if (checked) return - const activePresetPath = resolveActivePresetPath() - - if (ACTIVE_PRESET_PATH === null) return + if (ACTIVE_PRESET_PATH === null) { + checked = true + return + } let raw: string try { - raw = readFileSync(activePresetPath, 'utf-8') + raw = readFileSync(ACTIVE_PRESET_PATH, 'utf-8') } catch { throw new SDKError( - `Missing ${activePresetPath}. Run \`pnpm -C packages/enclave-sdk compile:circuits\` first.`, + `Missing ${ACTIVE_PRESET_PATH}. Run \`pnpm -C packages/enclave-sdk compile:circuits\` first.`, 'SDK_CIRCUIT_STAMP_MISSING', ) } @@ -67,7 +69,7 @@ export function assertSdkMicroCircuits(): void { committee = JSON.parse(raw)?.committee as string | undefined } catch { throw new SDKError( - `Invalid JSON in ${activePresetPath}. Rebuild with \`pnpm -C packages/enclave-sdk compile:circuits\`.`, + `Invalid JSON in ${ACTIVE_PRESET_PATH}. Rebuild with \`pnpm -C packages/enclave-sdk compile:circuits\`.`, 'SDK_CIRCUIT_STAMP_INVALID', ) } From c2fdaf204b041bbc341c7d7376f816d4588a11fe Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 29 May 2026 19:30:02 +0500 Subject: [PATCH 05/24] feat: token based locking mech --- agent/flow-trace/02_TOKENS_AND_ACTIVATION.md | 46 +- .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 3 + .../06_DEACTIVATION_AND_COMPLETION.md | 8 +- .../crisp-contracts/deployed_contracts.json | 10 - .../crisp-contracts/hardhat.config.ts | 1 - packages/enclave-contracts/README.md | 5 +- .../contracts/Enclave.sol/Enclave.json | 2 +- .../IBondingRegistry.json | 245 +------- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IEnclave.sol/IEnclave.json | 2 +- .../ISlashingManager.json | 2 +- .../CiphernodeRegistryOwnable.json | 2 +- .../EnclaveTicketToken.json | 2 +- .../contracts/interfaces/IBondingRegistry.sol | 108 +--- .../interfaces/IInterfoldVestingEscrow.sol | 109 ---- .../interfaces/ILicenseBondReceiver.sol | 43 -- .../contracts/registry/BondingRegistry.sol | 562 ++---------------- .../contracts/token/EnclaveToken.sol | 507 +++++++++++++++- .../token/InterfoldVestingEscrow.sol | 354 ----------- .../deployAndSave/interfoldVestingEscrow.ts | 105 ---- .../scripts/deployEnclave.ts | 48 +- packages/enclave-contracts/scripts/index.ts | 1 - .../test/Registry/BondingRegistry.spec.ts | 42 +- .../test/Token/EnclaveToken.spec.ts | 440 +++++++++++++- .../test/Token/InterfoldVestingEscrow.spec.ts | 288 --------- templates/default/deployed_contracts.json | 10 - templates/default/hardhat.config.ts | 1 - 27 files changed, 1105 insertions(+), 1843 deletions(-) delete mode 100644 packages/enclave-contracts/contracts/interfaces/IInterfoldVestingEscrow.sol delete mode 100644 packages/enclave-contracts/contracts/interfaces/ILicenseBondReceiver.sol delete mode 100644 packages/enclave-contracts/contracts/token/InterfoldVestingEscrow.sol delete mode 100644 packages/enclave-contracts/scripts/deployAndSave/interfoldVestingEscrow.ts delete mode 100644 packages/enclave-contracts/test/Token/InterfoldVestingEscrow.spec.ts diff --git a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md index b2f1c23708..b38912f85d 100644 --- a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md +++ b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md @@ -19,8 +19,12 @@ Before a node can register, it must stake two types of collateral: │ │ │ MAX_SUPPLY: 1,200,000,000 (1.2B with 18 decimals) │ │ Roles: MINTER_ROLE can mint via mintAllocation() │ +│ LOCK_MANAGER_ROLE manages token-level locks │ │ Transfer restrictions: when transfersRestricted=true, │ │ only whitelisted addresses can transfer │ +│ Lock invariant for normal transfers: │ +│ balanceOf(account) + totalBonded(account) │ +│ >= lockedFloorOf(account) │ │ Used as: LICENSE BOND token │ └───────────────────────────────────────────────────────────┘ @@ -65,34 +69,32 @@ User runs: enclave ciphernode license bond --amount 50000 │ │ │ │ │ │ │ bondLicense(uint256 amount) { │ │ │ │ 1. require(amount > 0) │ -│ │ │ 2. licenseToken.safeTransferFrom( │ +│ │ │ 2. operators[msg.sender].licenseBond += amount │ +│ │ │ → totalBonded(msg.sender) now includes amount │ +│ │ │ 3. licenseToken.safeTransferFrom( │ │ │ │ msg.sender, // from operator │ │ │ │ address(this), // to BondingRegistry │ │ │ │ amount │ │ │ │ ) │ +│ │ │ → ENCL _update can see the pre-recorded bond │ +│ │ │ and enforce locked-floor accounting │ │ │ │ → ENCL tokens move from operator → contract │ -│ │ │ 3. Record LicenseBondSource { │ -│ │ │ amount, withdrawalAddress: msg.sender, │ -│ │ │ sourceId: 0, sequence │ -│ │ │ } │ -│ │ │ 4. operators[msg.sender].licenseBond += amount │ -│ │ │ 5. _updateOperatorStatus(msg.sender) │ +│ │ │ 4. _updateOperatorStatus(msg.sender) │ │ │ │ → May activate if all conditions now met │ -│ │ │ 6. Emit LicenseBondSourceAdded and │ -│ │ │ LicenseBondUpdated(msg.sender, newBond) │ +│ │ │ 5. Emit LicenseBondUpdated(msg.sender, newBond) │ │ │ │ } │ │ │ └──────────────────────────────────────────────────────┘ │ │ └─ OUTPUT: "Transaction hash: 0x..." ``` -### Delegated / locked ENCL bonding +### Locked ENCL bonding -`BondingRegistry.bondLicenseFor(operator, amount, withdrawalAddress, sourceId)` lets a funder supply -ENCL while crediting another operator. `InterfoldVestingEscrow` uses this path so locked allocations -can run nodes without first transferring unrestricted ENCL to the beneficiary. Each ENCL bond source -keeps its own withdrawal address and LIFO sequence. The legacy `bondLicense(amount)` path is -equivalent to `bondLicenseFor(msg.sender, amount, msg.sender, 0)`. +`BondingRegistry.totalBonded(account)` returns active ENCL license bond plus pending ENCL exits that +remain slashable/not returned. `EnclaveToken` uses this view for pooled wallet-level locks, so +locked ENCL can be self-bonded by the same account without becoming transferable. Delegated +source-aware bonding is not part of the pooled-lock model; license bonds are credited to +`msg.sender` through `bondLicense(amount)`. ### Activation check after bonding: @@ -200,16 +202,14 @@ User runs: enclave ciphernode license unbond --amount 10000 │ │ │ 2. require(operators[msg.sender].licenseBond │ │ │ │ >= amount) │ │ │ │ 3. operators[msg.sender].licenseBond -= amount │ -│ │ │ 4. _queueLicenseExitFromSources( │ -│ │ │ msg.sender, amount │ +│ │ │ 4. _exits.queueLicensesForExit( │ +│ │ │ msg.sender, exitDelay, amount │ │ │ │ ) │ -│ │ │ → Pops active LicenseBondSource entries LIFO │ -│ │ │ → Queues PendingLicenseBondSource entries │ -│ │ │ preserving withdrawalAddress + sourceId │ +│ │ │ → Pending ENCL still counts in totalBonded() │ +│ │ │ until claimed or slashed │ │ │ │ 5. _updateOperatorStatus(msg.sender) │ │ │ │ → May DEACTIVATE if bond drops below threshold │ -│ │ │ 6. Emit LicenseBondSourceQueuedForExit and │ -│ │ │ LicenseBondUpdated(msg.sender, newBond) │ +│ │ │ 6. Emit LicenseBondUpdated(msg.sender, newBond) │ │ │ │ } │ │ │ └───────────────────────────────────────────────────────┘ │ @@ -306,6 +306,8 @@ User runs: enclave ciphernode license claim [--max-ticket 50] [--max-license 100 │ │ │ → Each ENCL source pays its withdrawalAddress │ │ │ │ → Receiver callback gets (operator, amount, │ │ │ │ sourceId) when supported │ +│ │ │ → Pending ENCL is removed from totalBonded() │ +│ │ │ as returned ENCL reaches the wallet │ │ │ │ } │ │ │ └───────────────────────────────────────────────────────┘ │ diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index a47069070d..189d1b9b89 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -672,6 +672,9 @@ _executeSlash(proposalId): │ │ │ Slash the newest source first │ │ │ │ → Active slash decrements operators[op].licenseBond│ │ │ │ → Pending slash decrements pending license totals │ +│ │ │ → totalBonded(op) drops immediately; if op has │ +│ │ │ token-level locks, same-wallet ENCL may become │ +│ │ │ encumbered until the locked floor decays/top-up │ │ │ │ → Receiver callback gets (operator, amount, │ │ │ │ sourceId) when supported │ │ │ │ │ diff --git a/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md b/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md index e6094ad3a7..2ebff8eded 100644 --- a/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md +++ b/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md @@ -53,9 +53,9 @@ User runs: enclave ciphernode deactivate --license 20000 │ │ unbondLicense(20000): │ │ │ 1. require(amount != 0, sufficient bonded ENCL) │ │ │ 2. operators[op].licenseBond -= 20000 │ - │ │ 3. _queueLicenseExitFromSources(op, 20000) │ - │ │ → Queues source-aware ENCL exits preserving │ - │ │ withdrawalAddress + sourceId │ + │ │ 3. _exits.queueLicensesForExit(op, exitDelay, 20000)│ + │ │ → Pending ENCL remains in totalBonded(op) for │ + │ │ token-level locked-floor accounting │ │ │ 4. _updateOperatorStatus(operator) │ │ │ → If licenseBond < │ │ │ (licenseRequiredBond * licenseActiveBps / 10000)│ @@ -75,7 +75,7 @@ User runs: enclave ciphernode deactivate --tickets 50 --license 20000 ├─ Calls removeTicketBalance(50) first └─ Then calls unbondLicense(20000) → Tickets are queued in ExitQueueLib - → ENCL is queued in source-aware pending license exits + → ENCL is queued in ExitQueueLib pending license exits and remains counted in totalBonded() ``` --- diff --git a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json index 5d2855d05d..977de9a7f9 100644 --- a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json +++ b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json @@ -219,16 +219,6 @@ "blockNumber": 14, "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, - "InterfoldVestingEscrow": { - "constructorArgs": { - "token": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", - "bondingRegistry": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", - "tgeTimestamp": "1779922281", - "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "blockNumber": 17, - "address": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" - }, "Enclave": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", diff --git a/examples/CRISP/packages/crisp-contracts/hardhat.config.ts b/examples/CRISP/packages/crisp-contracts/hardhat.config.ts index 79926563b0..f86d42c930 100644 --- a/examples/CRISP/packages/crisp-contracts/hardhat.config.ts +++ b/examples/CRISP/packages/crisp-contracts/hardhat.config.ts @@ -136,7 +136,6 @@ const config: HardhatUserConfig = { '@enclave-e3/contracts/contracts/slashing/SlashingManager.sol', '@enclave-e3/contracts/contracts/token/EnclaveToken.sol', '@enclave-e3/contracts/contracts/token/EnclaveTicketToken.sol', - '@enclave-e3/contracts/contracts/token/InterfoldVestingEscrow.sol', '@enclave-e3/contracts/contracts/test/MockCiphernodeRegistry.sol', '@enclave-e3/contracts/contracts/test/MockComputeProvider.sol', '@enclave-e3/contracts/contracts/test/MockDecryptionVerifier.sol', diff --git a/packages/enclave-contracts/README.md b/packages/enclave-contracts/README.md index 09041f4682..d8307402bb 100644 --- a/packages/enclave-contracts/README.md +++ b/packages/enclave-contracts/README.md @@ -57,8 +57,9 @@ Be sure to configure your desired network in `hardhat.config.ts` before deploying. For non-local networks, set `INTERFOLD_TGE_TIMESTAMP` to the agreed ENCL TGE -Unix timestamp before deploying. Local mock deployments default this timestamp -to the latest local block timestamp. +Unix timestamp before deploying. The deployment script configures this on +`EnclaveToken` for token-level lock schedules. Local mock deployments default +this timestamp to the latest local block timestamp. ## Localhost deployment diff --git a/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json b/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json index 843bc79664..52d3e9117e 100644 --- a/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json +++ b/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json @@ -3159,5 +3159,5 @@ }, "immutableReferences": {}, "inputSourceName": "project/contracts/Enclave.sol", - "buildInfoId": "solc-0_8_28-834ec0837fe1c9299862cfab43bdd2eef32c6092" + "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 4bff4350b4..72ca0733ae 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -59,11 +59,6 @@ "name": "MaxAuthorizedDistributors", "type": "error" }, - { - "inputs": [], - "name": "MaxLicenseBondSources", - "type": "error" - }, { "inputs": [], "name": "NoPendingDeregistration", @@ -153,197 +148,6 @@ "name": "ConfigurationUpdated", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "sourceId", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "bytes4", - "name": "selector", - "type": "bytes4" - } - ], - "name": "LicenseBondReceiverCallbackFailed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "funder", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "withdrawalAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "sourceId", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "sequence", - "type": "uint64" - } - ], - "name": "LicenseBondSourceAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "withdrawalAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "sourceId", - "type": "bytes32" - } - ], - "name": "LicenseBondSourceClaimed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "withdrawalAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "unlockTimestamp", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "sourceId", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "sequence", - "type": "uint64" - } - ], - "name": "LicenseBondSourceQueuedForExit", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "withdrawalAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "sourceId", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "sequence", - "type": "uint64" - } - ], - "name": "LicenseBondSourceSlashed", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -610,34 +414,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "withdrawalAddress", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "sourceId", - "type": "bytes32" - } - ], - "name": "bondLicenseFor", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -1248,6 +1024,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "totalBonded", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1286,5 +1081,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-f393c7cf878899096944bbd867df55a6fd9a4724" + "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index 0e99886ec2..6af4162ad7 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1332,5 +1332,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-a6902ac15e994e1c055eb02433db296d1823fdfc" + "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json index 356890b821..1d1a61627f 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json @@ -2427,5 +2427,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IEnclave.sol", - "buildInfoId": "solc-0_8_28-a6902ac15e994e1c055eb02433db296d1823fdfc" + "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index a229906067..ad02e47f47 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1233,5 +1233,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-834ec0837fe1c9299862cfab43bdd2eef32c6092" + "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json b/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json index f58fbbe9a8..c26b5e8230 100644 --- a/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json +++ b/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json @@ -1981,5 +1981,5 @@ }, "immutableReferences": {}, "inputSourceName": "project/contracts/registry/CiphernodeRegistryOwnable.sol", - "buildInfoId": "solc-0_8_28-834ec0837fe1c9299862cfab43bdd2eef32c6092" + "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json b/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json index a085967ec5..8ac0da6735 100644 --- a/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json +++ b/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json @@ -1486,5 +1486,5 @@ ] }, "inputSourceName": "project/contracts/token/EnclaveTicketToken.sol", - "buildInfoId": "solc-0_8_28-4a1ab4524682ea3571c5165bfb08d5f4cb6afec8" + "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" } \ No newline at end of file diff --git a/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol index 0114b66b55..e450f01a97 100644 --- a/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/enclave-contracts/contracts/interfaces/IBondingRegistry.sol @@ -46,9 +46,6 @@ interface IBondingRegistry { /// {MAX_AUTHORIZED_DISTRIBUTORS}. error MaxAuthorizedDistributors(); - /// @notice Thrown when an operator would exceed the live ENCL bond source cap. - error MaxLicenseBondSources(); - /// @notice Thrown when {renounceOwnership} is called. error RenounceOwnershipDisabled(); @@ -84,88 +81,6 @@ interface IBondingRegistry { bytes32 indexed reason ); - /** - * @notice Emitted when a license bond source is recorded for an operator. - * @param operator Address whose operator bond was credited - * @param funder Address that supplied the license tokens - * @param withdrawalAddress Address that receives this source if it exits - * @param amount Amount credited from this source - * @param sourceId Optional external source id for withdrawal receivers - * @param sequence Monotonic source sequence used for LIFO slashing - */ - event LicenseBondSourceAdded( - address indexed operator, - address indexed funder, - address indexed withdrawalAddress, - uint256 amount, - bytes32 sourceId, - uint64 sequence - ); - - /** - * @notice Emitted when a license bond source is queued for delayed exit. - * @param operator Address whose bond source was queued - * @param withdrawalAddress Address that will receive this source after unlock - * @param amount Amount queued from this source - * @param unlockTimestamp Timestamp when this source becomes claimable - * @param sourceId Optional external source id for withdrawal receivers - * @param sequence Original source sequence used for LIFO slashing - */ - event LicenseBondSourceQueuedForExit( - address indexed operator, - address indexed withdrawalAddress, - uint256 amount, - uint64 unlockTimestamp, - bytes32 sourceId, - uint64 sequence - ); - - /** - * @notice Emitted when a queued license bond source is paid to its withdrawal address. - * @param operator Address whose source was claimed - * @param withdrawalAddress Address that received license tokens - * @param amount Amount paid - * @param sourceId Optional external source id for withdrawal receivers - */ - event LicenseBondSourceClaimed( - address indexed operator, - address indexed withdrawalAddress, - uint256 amount, - bytes32 sourceId - ); - - /** - * @notice Emitted when a license bond source is slashed. - * @param operator Address whose source was slashed - * @param withdrawalAddress Withdrawal address attached to the source - * @param amount Amount slashed - * @param sourceId Optional external source id for withdrawal receivers - * @param sequence Original source sequence used for LIFO slashing - */ - event LicenseBondSourceSlashed( - address indexed operator, - address indexed withdrawalAddress, - uint256 amount, - bytes32 sourceId, - uint64 sequence - ); - - /** - * @notice Emitted when a withdrawal receiver callback fails. - * @param receiver Address that rejected or failed the callback - * @param operator Operator associated with the bond source - * @param amount Amount involved in the callback - * @param sourceId Optional external source id for withdrawal receivers - * @param selector Callback selector that failed - */ - event LicenseBondReceiverCallbackFailed( - address indexed receiver, - address indexed operator, - uint256 amount, - bytes32 sourceId, - bytes4 selector - ); - /** * @notice Emitted when operator requests deregistration from the protocol * @param operator Address of the operator @@ -298,6 +213,14 @@ interface IBondingRegistry { */ function getLicenseBond(address operator) external view returns (uint256); + /** + * @notice Get ENCL that still counts toward an account's locked-floor collateral. + * @dev Includes active license bond plus pending ENCL exits that remain slashable/not returned. + * @param account Account/operator whose ENCL bond credit is queried + * @return Active plus pending license-bond amount + */ + function totalBonded(address account) external view returns (uint256); + /** * @notice Get current ticket price * @return Price per ticket in collateral token units @@ -449,21 +372,6 @@ interface IBondingRegistry { */ function bondLicense(uint256 amount) external; - /** - * @notice Bond license tokens supplied by the caller while crediting an operator. - * @param operator Address whose operator bond should be credited - * @param amount Amount of license tokens to pull from the caller - * @param withdrawalAddress Address that receives this source after unbond/decommission - * @param sourceId Optional external id passed back to withdrawal receivers - * @dev Requires caller approval for license token transfer. - */ - function bondLicenseFor( - address operator, - uint256 amount, - address withdrawalAddress, - bytes32 sourceId - ) external; - /** * @notice Unbond license tokens * @param amount Amount of license tokens to unbond diff --git a/packages/enclave-contracts/contracts/interfaces/IInterfoldVestingEscrow.sol b/packages/enclave-contracts/contracts/interfaces/IInterfoldVestingEscrow.sol deleted file mode 100644 index 79d9599922..0000000000 --- a/packages/enclave-contracts/contracts/interfaces/IInterfoldVestingEscrow.sol +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. - -pragma solidity 0.8.28; - -import { - IERC165 -} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -interface IInterfoldVestingEscrow is IERC165 { - struct ScheduleInput { - address beneficiary; - uint256 totalAmount; - uint64 tokenHoldUntil; - uint64 tokenUnlockStart; - uint64 tokenUnlockEnd; - uint64 serviceStart; - uint64 serviceCliff; - uint64 serviceEnd; - bytes32 group; - } - - struct ScheduleView { - address beneficiary; - uint256 totalAmount; - uint256 claimedAmount; - uint256 bondedAmount; - uint256 slashedAmount; - uint64 tokenHoldUntil; - uint64 tokenUnlockStart; - uint64 tokenUnlockEnd; - uint64 serviceStart; - uint64 serviceCliff; - uint64 serviceEnd; - bytes32 group; - } - - event ScheduleCreated( - uint256 indexed scheduleId, - address indexed beneficiary, - bytes32 indexed group, - uint256 totalAmount, - uint64 tokenHoldUntil, - uint64 tokenUnlockStart, - uint64 tokenUnlockEnd, - uint64 serviceStart, - uint64 serviceCliff, - uint64 serviceEnd - ); - - event TokensClaimed( - uint256 indexed scheduleId, - address indexed beneficiary, - uint256 amount - ); - - event LockedTokensBonded( - uint256 indexed scheduleId, - address indexed beneficiary, - address indexed operator, - uint256 amount - ); - - event BondedTokensReturned( - uint256 indexed scheduleId, - address indexed operator, - uint256 amount - ); - - event BondedTokensSlashed( - uint256 indexed scheduleId, - address indexed operator, - uint256 amount - ); - - function createSchedule( - ScheduleInput calldata input - ) external returns (uint256 scheduleId); - - function batchCreateSchedules( - ScheduleInput[] calldata inputs - ) external returns (uint256[] memory scheduleIds); - - function claim(uint256 scheduleId, uint256 maxAmount) external; - - function bondLockedTokens( - uint256 scheduleId, - address operator, - uint256 amount - ) external; - - function vestedAmount( - uint256 scheduleId, - uint64 timestamp - ) external view returns (uint256); - - function claimableAmount( - uint256 scheduleId - ) external view returns (uint256); - - function bondableAmount(uint256 scheduleId) external view returns (uint256); - - function getSchedule( - uint256 scheduleId - ) external view returns (ScheduleView memory); -} diff --git a/packages/enclave-contracts/contracts/interfaces/ILicenseBondReceiver.sol b/packages/enclave-contracts/contracts/interfaces/ILicenseBondReceiver.sol deleted file mode 100644 index c497453d3d..0000000000 --- a/packages/enclave-contracts/contracts/interfaces/ILicenseBondReceiver.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. - -pragma solidity 0.8.28; - -import { - IERC165 -} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/** - * @title ILicenseBondReceiver - * @notice Optional callback surface for contracts that receive ENCL license-bond withdrawals. - */ -interface ILicenseBondReceiver is IERC165 { - /** - * @notice Called by BondingRegistry after a queued ENCL bond source is returned. - * @param operator Operator whose bond source exited - * @param amount Amount returned to the receiver - * @param sourceId External source id supplied at bond time - * @return selector This function's selector on success - */ - function onLicenseBondReturned( - address operator, - uint256 amount, - bytes32 sourceId - ) external returns (bytes4 selector); - - /** - * @notice Called by BondingRegistry after a ENCL bond source is slashed. - * @param operator Operator whose bond source was slashed - * @param amount Amount slashed from the source - * @param sourceId External source id supplied at bond time - * @return selector This function's selector on success - */ - function onLicenseBondSlashed( - address operator, - uint256 amount, - bytes32 sourceId - ) external returns (bytes4 selector); -} diff --git a/packages/enclave-contracts/contracts/registry/BondingRegistry.sol b/packages/enclave-contracts/contracts/registry/BondingRegistry.sol index ae530545d5..cbd296bb71 100644 --- a/packages/enclave-contracts/contracts/registry/BondingRegistry.sol +++ b/packages/enclave-contracts/contracts/registry/BondingRegistry.sol @@ -24,7 +24,6 @@ import { ExitQueueLib } from "../lib/ExitQueueLib.sol"; import { IBondingRegistry } from "../interfaces/IBondingRegistry.sol"; import { ICiphernodeRegistry } from "../interfaces/ICiphernodeRegistry.sol"; -import { ILicenseBondReceiver } from "../interfaces/ILicenseBondReceiver.sol"; import { ISlashingManager } from "../interfaces/ISlashingManager.sol"; import { EnclaveTicketToken } from "../token/EnclaveTicketToken.sol"; @@ -118,10 +117,6 @@ contract BondingRegistry is /// @dev Default 8000 = 80%. Allows operators to unbond up to 20% while remaining active uint256 public licenseActiveBps; - /// @notice Maximum number of live ENCL license bond sources per operator. - /// @dev Bounds LIFO slashing and claim loops for delegated/locked-token bonds. - uint256 public constant MAX_LICENSE_BOND_SOURCES = 64; - /// @notice Number of currently active operators uint256 public numActiveOperators; @@ -139,52 +134,9 @@ contract BondingRegistry is bool active; } - /// @notice Active ENCL bond source credited to an operator. - /// @param amount Remaining active amount from this source - /// @param withdrawalAddress Address that receives this source after exit - /// @param sourceId Optional external id passed to withdrawal receivers - /// @param sequence Monotonic sequence used for LIFO slashing - struct LicenseBondSource { - uint256 amount; - address withdrawalAddress; - bytes32 sourceId; - uint64 sequence; - } - - /// @notice Pending ENCL bond source waiting through the exit delay. - /// @param unlockTimestamp Timestamp when this source becomes claimable - /// @param amount Remaining pending amount from this source - /// @param withdrawalAddress Address that receives this source after exit - /// @param sourceId Optional external id passed to withdrawal receivers - /// @param sequence Original active-source sequence used for LIFO slashing - struct PendingLicenseBondSource { - uint64 unlockTimestamp; - uint256 amount; - address withdrawalAddress; - bytes32 sourceId; - uint64 sequence; - } - /// @notice Maps operator address to their state data mapping(address operator => Operator data) internal operators; - /// @dev Active ENCL bond sources per operator. The tail is the newest source. - mapping(address operator => LicenseBondSource[] sources) - private _licenseSources; - - /// @dev Pending ENCL exit sources per operator. The head is used for claim scans. - mapping(address operator => PendingLicenseBondSource[] sources) - private _pendingLicenseSources; - - /// @dev Claim head for pending ENCL exits. - mapping(address operator => uint256 headIndex) private _pendingLicenseHead; - - /// @dev Aggregate pending ENCL exits per operator. - mapping(address operator => uint256 amount) private _pendingLicenseTotals; - - /// @dev Next ENCL bond source sequence per operator. Starts at 1. - mapping(address operator => uint64 sequence) private _nextLicenseSequence; - /// @notice Total slashed ticket balance available for treasury withdrawal uint256 public slashedTicketBalance; @@ -296,6 +248,12 @@ contract BondingRegistry is return operators[operator].licenseBond; } + /// @inheritdoc IBondingRegistry + function totalBonded(address account) external view returns (uint256) { + (, uint256 pendingLicense) = _exits.getPendingAmounts(account); + return operators[account].licenseBond + pendingLicense; + } + /// @inheritdoc IBondingRegistry function availableTickets( address operator @@ -324,8 +282,7 @@ contract BondingRegistry is function pendingExits( address operator ) external view returns (uint256 ticket, uint256 license) { - (ticket, ) = _exits.getPendingAmounts(operator); - license = _pendingLicenseTotals[operator]; + (ticket, license) = _exits.getPendingAmounts(operator); } /// @notice Preview how much an operator can currently claim @@ -335,8 +292,7 @@ contract BondingRegistry is function previewClaimable( address operator ) external view returns (uint256 ticket, uint256 license) { - (ticket, ) = _exits.previewClaimableAmounts(operator); - license = _previewClaimableLicense(operator); + (ticket, license) = _exits.previewClaimableAmounts(operator); } /// @inheritdoc IBondingRegistry @@ -432,10 +388,12 @@ contract BondingRegistry is } if (ticketOut != 0 || licenseOut != 0) { - _exits.queueAssetsForExit(msg.sender, exitDelay, ticketOut, 0); - if (licenseOut != 0) { - _queueLicenseExitFromSources(msg.sender, licenseOut); - } + _exits.queueAssetsForExit( + msg.sender, + exitDelay, + ticketOut, + licenseOut + ); } // CiphernodeRegistry already emits an event when a ciphernode is removed @@ -492,35 +450,13 @@ contract BondingRegistry is function bondLicense( uint256 amount ) external nonReentrant noExitInProgress(msg.sender) { - _bondLicenseFrom( - msg.sender, - msg.sender, - msg.sender, - amount, - bytes32(0) - ); - } - - /// @inheritdoc IBondingRegistry - function bondLicenseFor( - address operator, - uint256 amount, - address withdrawalAddress, - bytes32 sourceId - ) external nonReentrant noExitInProgress(operator) { - _bondLicenseFrom( - msg.sender, - operator, - withdrawalAddress, - amount, - sourceId - ); + _bondLicense(msg.sender, amount); } /// @inheritdoc IBondingRegistry function unbondLicense( uint256 amount - ) external noExitInProgress(msg.sender) { + ) external nonReentrant noExitInProgress(msg.sender) { require(amount != 0, ZeroAmount()); require( operators[msg.sender].licenseBond >= amount, @@ -528,7 +464,7 @@ contract BondingRegistry is ); operators[msg.sender].licenseBond -= amount; - _queueLicenseExitFromSources(msg.sender, amount); + _exits.queueLicensesForExit(msg.sender, exitDelay, amount); emit LicenseBondUpdated( msg.sender, @@ -549,15 +485,17 @@ contract BondingRegistry is uint256 maxTicketAmount, uint256 maxLicenseAmount ) external nonReentrant { - (uint256 ticketClaim, ) = _exits.claimAssets( + (uint256 ticketClaim, uint256 licenseClaim) = _exits.claimAssets( msg.sender, maxTicketAmount, - 0 + maxLicenseAmount ); - uint256 licenseClaim = _claimLicenseExits(msg.sender, maxLicenseAmount); require(ticketClaim > 0 || licenseClaim > 0, ExitNotReady()); if (ticketClaim > 0) ticketToken.payout(msg.sender, ticketClaim); + if (licenseClaim > 0) { + _safeTransferLicenseWithDeltaCheck(msg.sender, licenseClaim); + } } // ====================== @@ -627,7 +565,7 @@ contract BondingRegistry is require(requestedSlashAmount != 0, ZeroAmount()); Operator storage operatorData = operators[operator]; - uint256 pendingLicenseBalance = _pendingLicenseTotals[operator]; + (, uint256 pendingLicenseBalance) = _exits.getPendingAmounts(operator); uint256 totalAvailableBalance = operatorData.licenseBond + pendingLicenseBalance; uint256 actualSlashAmount = Math.min( @@ -637,7 +575,27 @@ contract BondingRegistry is if (actualSlashAmount == 0) return; - _slashLicenseSourcesLifo(operator, actualSlashAmount); + uint256 activeSlashAmount = Math.min( + actualSlashAmount, + operatorData.licenseBond + ); + if (activeSlashAmount != 0) { + operatorData.licenseBond -= activeSlashAmount; + } + + uint256 remainingSlashAmount = actualSlashAmount - activeSlashAmount; + if (remainingSlashAmount != 0) { + (, uint256 pendingSlashed) = _exits.slashPendingAssets( + operator, + 0, + remainingSlashAmount, + true + ); + require( + pendingSlashed == remainingSlashAmount, + InsufficientBalance() + ); + } slashedLicenseBond += actualSlashAmount; emit LicenseBondUpdated( @@ -861,53 +819,21 @@ contract BondingRegistry is // Internal Functions // ====================== - function _bondLicenseFrom( - address funder, - address operator, - address withdrawalAddress, - uint256 amount, - bytes32 sourceId - ) internal { + function _bondLicense(address operator, uint256 amount) internal { require(operator != address(0), ZeroAddress()); - require(withdrawalAddress != address(0), ZeroAddress()); require(amount != 0, ZeroAmount()); - require( - _liveLicenseSourceCount(operator) < MAX_LICENSE_BOND_SOURCES, - MaxLicenseBondSources() - ); + + operators[operator].licenseBond += amount; uint256 balanceBefore = licenseToken.balanceOf(address(this)); - licenseToken.safeTransferFrom(funder, address(this), amount); + licenseToken.safeTransferFrom(operator, address(this), amount); uint256 actualReceived = licenseToken.balanceOf(address(this)) - balanceBefore; - require(actualReceived != 0, ZeroAmount()); - - uint64 sequence = _nextLicenseSequence[operator]; - if (sequence == 0) sequence = 1; - _nextLicenseSequence[operator] = sequence + 1; - - _licenseSources[operator].push( - LicenseBondSource({ - amount: actualReceived, - withdrawalAddress: withdrawalAddress, - sourceId: sourceId, - sequence: sequence - }) - ); - - operators[operator].licenseBond += actualReceived; + require(actualReceived == amount, InvalidAmount()); - emit LicenseBondSourceAdded( - operator, - funder, - withdrawalAddress, - actualReceived, - sourceId, - sequence - ); emit LicenseBondUpdated( operator, - int256(actualReceived), + int256(amount), operators[operator].licenseBond, REASON_BOND ); @@ -915,392 +841,6 @@ contract BondingRegistry is _updateOperatorStatus(operator); } - function _queueLicenseExitFromSources( - address operator, - uint256 amount - ) internal { - if (amount == 0) return; - - uint64 currentTimestamp = uint64(block.timestamp); - require( - currentTimestamp <= (type(uint64).max - exitDelay), - ExitQueueLib.TimestampOverflow() - ); - uint64 unlockTimestamp = currentTimestamp + exitDelay; - uint256 remaining = amount; - LicenseBondSource[] storage sources = _licenseSources[operator]; - - while (remaining != 0) { - uint256 len = sources.length; - require(len != 0, InsufficientBalance()); - - LicenseBondSource storage source = sources[len - 1]; - uint256 amountToQueue = remaining < source.amount - ? remaining - : source.amount; - - if (amountToQueue != source.amount) { - require( - _liveLicenseSourceCount(operator) < - MAX_LICENSE_BOND_SOURCES, - MaxLicenseBondSources() - ); - } - - source.amount -= amountToQueue; - remaining -= amountToQueue; - - _pendingLicenseSources[operator].push( - PendingLicenseBondSource({ - unlockTimestamp: unlockTimestamp, - amount: amountToQueue, - withdrawalAddress: source.withdrawalAddress, - sourceId: source.sourceId, - sequence: source.sequence - }) - ); - _pendingLicenseTotals[operator] += amountToQueue; - - emit LicenseBondSourceQueuedForExit( - operator, - source.withdrawalAddress, - amountToQueue, - unlockTimestamp, - source.sourceId, - source.sequence - ); - - if (source.amount == 0) sources.pop(); - } - } - - function _claimLicenseExits( - address operator, - uint256 maxLicenseAmount - ) internal returns (uint256 claimedAmount) { - if (maxLicenseAmount == 0) return 0; - - PendingLicenseBondSource[] storage sources = _pendingLicenseSources[ - operator - ]; - uint256 head = _pendingLicenseHead[operator]; - uint256 len = sources.length; - uint256 remaining = maxLicenseAmount; - - for (uint256 i = head; i < len && remaining != 0; i++) { - PendingLicenseBondSource storage source = sources[i]; - if (source.amount == 0) { - if (i == head) head++; - continue; - } - if (block.timestamp < source.unlockTimestamp) continue; - - uint256 amountToClaim = remaining < source.amount - ? remaining - : source.amount; - - source.amount -= amountToClaim; - remaining -= amountToClaim; - claimedAmount += amountToClaim; - _pendingLicenseTotals[operator] -= amountToClaim; - - _safeTransferLicenseWithDeltaCheck( - source.withdrawalAddress, - amountToClaim - ); - emit LicenseBondSourceClaimed( - operator, - source.withdrawalAddress, - amountToClaim, - source.sourceId - ); - _notifyLicenseBondReturned( - source.withdrawalAddress, - operator, - amountToClaim, - source.sourceId - ); - - if (source.amount == 0 && i == head) head++; - } - - _pendingLicenseHead[operator] = head; - } - - function _previewClaimableLicense( - address operator - ) internal view returns (uint256 claimableAmount) { - PendingLicenseBondSource[] storage sources = _pendingLicenseSources[ - operator - ]; - uint256 head = _pendingLicenseHead[operator]; - uint256 len = sources.length; - - for (uint256 i = head; i < len; i++) { - PendingLicenseBondSource storage source = sources[i]; - if (block.timestamp >= source.unlockTimestamp) { - claimableAmount += source.amount; - } - } - } - - function _slashLicenseSourcesLifo( - address operator, - uint256 amount - ) internal { - uint256 remaining = amount; - - while (remaining != 0) { - ( - bool hasPending, - uint256 pendingIndex, - uint64 pendingSequence - ) = _latestPendingLicenseSource(operator); - LicenseBondSource[] storage activeSources = _licenseSources[ - operator - ]; - bool hasActive = activeSources.length != 0; - uint64 activeSequence = hasActive - ? activeSources[activeSources.length - 1].sequence - : 0; - - require(hasPending || hasActive, InsufficientBalance()); - - if ( - hasPending && (!hasActive || pendingSequence >= activeSequence) - ) { - uint256 slashed = _slashPendingLicenseSource( - operator, - pendingIndex, - remaining - ); - remaining -= slashed; - } else { - uint256 slashed = _slashActiveLicenseSource( - operator, - remaining - ); - remaining -= slashed; - } - } - } - - function _slashActiveLicenseSource( - address operator, - uint256 maxAmount - ) internal returns (uint256 slashedAmount) { - LicenseBondSource[] storage sources = _licenseSources[operator]; - uint256 sourceIndex = sources.length - 1; - LicenseBondSource storage source = sources[sourceIndex]; - slashedAmount = maxAmount < source.amount ? maxAmount : source.amount; - - source.amount -= slashedAmount; - operators[operator].licenseBond -= slashedAmount; - - emit LicenseBondSourceSlashed( - operator, - source.withdrawalAddress, - slashedAmount, - source.sourceId, - source.sequence - ); - _notifyLicenseBondSlashed( - source.withdrawalAddress, - operator, - slashedAmount, - source.sourceId - ); - - if (source.amount == 0) sources.pop(); - } - - function _slashPendingLicenseSource( - address operator, - uint256 sourceIndex, - uint256 maxAmount - ) internal returns (uint256 slashedAmount) { - PendingLicenseBondSource storage source = _pendingLicenseSources[ - operator - ][sourceIndex]; - slashedAmount = maxAmount < source.amount ? maxAmount : source.amount; - - source.amount -= slashedAmount; - _pendingLicenseTotals[operator] -= slashedAmount; - - emit LicenseBondSourceSlashed( - operator, - source.withdrawalAddress, - slashedAmount, - source.sourceId, - source.sequence - ); - _notifyLicenseBondSlashed( - source.withdrawalAddress, - operator, - slashedAmount, - source.sourceId - ); - - if ( - source.amount == 0 && sourceIndex == _pendingLicenseHead[operator] - ) { - _advancePendingLicenseHead(operator); - } - } - - function _latestPendingLicenseSource( - address operator - ) internal view returns (bool found, uint256 index, uint64 sequence) { - PendingLicenseBondSource[] storage sources = _pendingLicenseSources[ - operator - ]; - uint256 head = _pendingLicenseHead[operator]; - uint256 len = sources.length; - - for (uint256 i = len; i > head; i--) { - PendingLicenseBondSource storage source = sources[i - 1]; - if (source.amount != 0) { - return (true, i - 1, source.sequence); - } - } - - return (false, 0, 0); - } - - function _advancePendingLicenseHead(address operator) internal { - PendingLicenseBondSource[] storage sources = _pendingLicenseSources[ - operator - ]; - uint256 head = _pendingLicenseHead[operator]; - uint256 len = sources.length; - - while (head < len && sources[head].amount == 0) { - head++; - } - - _pendingLicenseHead[operator] = head; - } - - function _liveLicenseSourceCount( - address operator - ) internal view returns (uint256) { - return - _licenseSources[operator].length + - (_pendingLicenseSources[operator].length - - _pendingLicenseHead[operator]); - } - - function _notifyLicenseBondReturned( - address receiver, - address operator, - uint256 amount, - bytes32 sourceId - ) internal { - _notifyLicenseBondReceiver( - receiver, - operator, - amount, - sourceId, - ILicenseBondReceiver.onLicenseBondReturned.selector - ); - } - - function _notifyLicenseBondSlashed( - address receiver, - address operator, - uint256 amount, - bytes32 sourceId - ) internal { - _notifyLicenseBondReceiver( - receiver, - operator, - amount, - sourceId, - ILicenseBondReceiver.onLicenseBondSlashed.selector - ); - } - - function _notifyLicenseBondReceiver( - address receiver, - address operator, - uint256 amount, - bytes32 sourceId, - bytes4 selector - ) internal { - if (sourceId == bytes32(0) || receiver.code.length == 0) return; - - try - IERC165(receiver).supportsInterface( - type(ILicenseBondReceiver).interfaceId - ) - returns (bool supported) { - if (!supported) return; - } catch { - emit LicenseBondReceiverCallbackFailed( - receiver, - operator, - amount, - sourceId, - selector - ); - return; - } - - if (selector == ILicenseBondReceiver.onLicenseBondReturned.selector) { - try - ILicenseBondReceiver(receiver).onLicenseBondReturned( - operator, - amount, - sourceId - ) - returns (bytes4 returnedSelector) { - if (returnedSelector != selector) { - emit LicenseBondReceiverCallbackFailed( - receiver, - operator, - amount, - sourceId, - selector - ); - } - } catch { - emit LicenseBondReceiverCallbackFailed( - receiver, - operator, - amount, - sourceId, - selector - ); - } - } else { - try - ILicenseBondReceiver(receiver).onLicenseBondSlashed( - operator, - amount, - sourceId - ) - returns (bytes4 returnedSelector) { - if (returnedSelector != selector) { - emit LicenseBondReceiverCallbackFailed( - receiver, - operator, - amount, - sourceId, - selector - ); - } - } catch { - emit LicenseBondReceiverCallbackFailed( - receiver, - operator, - amount, - sourceId, - selector - ); - } - } - } - /// @dev Updates operator's active status based on current conditions /// @dev Operator is active if: registered, has minimum license bond, and has minimum tickets /// @param operator Address of the operator to update @@ -1369,5 +909,5 @@ contract BondingRegistry is /// @dev Reserved storage slots for future upgrades. // solhint-disable-next-line var-name-mixedcase - uint256[45] private __gap; + uint256[50] private __gap; } diff --git a/packages/enclave-contracts/contracts/token/EnclaveToken.sol b/packages/enclave-contracts/contracts/token/EnclaveToken.sol index 1c183ebce4..a93a6ef1ad 100644 --- a/packages/enclave-contracts/contracts/token/EnclaveToken.sol +++ b/packages/enclave-contracts/contracts/token/EnclaveToken.sol @@ -20,6 +20,8 @@ import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; +import { IBondingRegistry } from "../interfaces/IBondingRegistry.sol"; + /** * @title EnclaveToken * @notice The governance and utility token for the Enclave protocol @@ -30,10 +32,17 @@ import { * - MINTER_ROLE can call {mintAllocation} / {batchMintAllocations} up to MAX_SUPPLY. * - WHITELIST_ROLE can manage the transfer whitelist independently from minting so * the same account is not required to control both surfaces. + * - LOCK_MANAGER_ROLE can configure token-level lock schedules, CCA claim sources, + * buyer claim profiles, and the bonding registry used for locked-floor accounting. * * Transfer restrictions are a one-way switch: once {disableTransferRestrictions} is called * they cannot be re-enabled. * + * Token-level locks are pooled per account. For every non-mint/non-burn transfer, the sender + * must satisfy: balanceOf(sender) + BondingRegistry.totalBonded(sender) >= lockedFloorOf(sender). + * This lets locked holders use same-account ENCL as operator bond collateral while preserving + * the explicit product constraint that all ENCL in the same wallet is pooled for locks/slashing. + * * Voting uses {block.timestamp} (EIP-6372 "mode=timestamp") so timepoints align with other * Enclave contracts. */ @@ -65,10 +74,34 @@ contract EnclaveToken is /// @notice Thrown when a transfer is attempted while restrictions are active and neither party is whitelisted error TransferNotAllowed(); + /// @notice Thrown when lock schedule parameters are internally inconsistent. + error InvalidLockSchedule(); + + /// @notice Thrown when an account already has the maximum supported number of lock schedules. + error MaxLockSchedulesExceeded(); + + /// @notice Thrown when a locked account attempts to move below its current locked floor. + error LockedBalanceInvariant( + address account, + uint256 balance, + uint256 bonded, + uint256 lockedFloor + ); + + /// @notice Thrown when a claim source sends locked CCA tokens to an account without an active profile. + error ClaimLockProfileMissing(address account); + + /// @notice Thrown when a schedule requests the default TGE timestamp before it has been configured. + error TgeTimestampUnset(); + /// @notice Maximum supply of the token: 1.2 billion tokens with 18 decimals /// @dev Hard cap on total token supply that cannot be exceeded through minting uint256 public constant MAX_SUPPLY = 1_200_000_000e18; + /// @notice Maximum lock schedules retained for a single account. + /// @dev Bounds the per-transfer loop in {lockedFloorOf}. + uint256 public constant MAX_LOCK_SCHEDULES = 64; + /// @notice Role identifier for accounts authorized to mint new tokens /// @dev Keccak256 hash of "MINTER_ROLE" used in AccessControl bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); @@ -77,9 +110,78 @@ contract EnclaveToken is /// @dev Separated from MINTER_ROLE so mint authority does not also control transferability. bytes32 public constant WHITELIST_ROLE = keccak256("WHITELIST_ROLE"); + /// @notice Role identifier for accounts authorized to manage token-level locks. + bytes32 public constant LOCK_MANAGER_ROLE = keccak256("LOCK_MANAGER_ROLE"); + + /// @notice Token-lock schedule recorded against one account. + /// @param amount Original amount subject to this schedule. + /// @param tokenHoldUntil Absolute timestamp before which no amount is transferable. + /// @param tokenUnlockStart Absolute linear token unlock start timestamp. + /// @param tokenUnlockEnd Absolute linear token unlock end timestamp. + /// @param serviceStart Optional service vesting start timestamp. + /// @param serviceCliff Optional service vesting cliff timestamp. + /// @param serviceEnd Optional service vesting end timestamp. + /// @param group Schedule group marker for indexers and operations. + struct LockSchedule { + uint128 amount; + uint64 tokenHoldUntil; + uint64 tokenUnlockStart; + uint64 tokenUnlockEnd; + uint64 serviceStart; + uint64 serviceCliff; + uint64 serviceEnd; + bytes32 group; + } + + /// @notice Input used to create an absolute lock schedule. + /// @param account Account whose wallet-level locked floor increases. + /// @param amount Amount subject to the schedule. + /// @param tokenHoldUntil Absolute timestamp before which no amount is transferable. + /// @param tokenUnlockStart Absolute linear unlock start. Zero resolves to {tgeTimestamp}. + /// @param tokenUnlockEnd Absolute linear unlock end. + /// @param serviceStart Optional service vesting start timestamp. + /// @param serviceCliff Optional service vesting cliff timestamp. + /// @param serviceEnd Optional service vesting end timestamp. + /// @param group Schedule group marker for indexers and operations. + struct LockScheduleInput { + address account; + uint256 amount; + uint64 tokenHoldUntil; + uint64 tokenUnlockStart; + uint64 tokenUnlockEnd; + uint64 serviceStart; + uint64 serviceCliff; + uint64 serviceEnd; + bytes32 group; + } + + /// @notice Relative lock profile applied to transfers from approved CCA claim sources. + /// @param active Whether the account can receive locked claim-source transfers. + /// @param holdDuration Seconds after claim before any amount is transferable. + /// @param unlockDuration Optional linear unlock duration after the hold ends. Zero is a cliff unlock. + /// @param group Schedule group marker, e.g. REG_S_CCA or REG_D_CCA. + struct ClaimLockProfile { + bool active; + uint64 holdDuration; + uint64 unlockDuration; + bytes32 group; + } + + /// @notice Input used to batch update CCA claim lock profiles. + struct ClaimLockProfileInput { + address account; + ClaimLockProfile profile; + } + /// @notice Tracks the cumulative amount of tokens minted since deployment uint256 public totalMinted; + /// @notice Optional default TGE timestamp used when lock schedule inputs pass tokenUnlockStart = 0. + uint64 public tgeTimestamp; + + /// @notice Registry queried for ENCL that still counts toward an account's locked floor. + IBondingRegistry public bondingRegistry; + /// @notice Mapping of addresses permitted to transfer tokens when restrictions are active /// @dev When transfersRestricted is true, only whitelisted addresses can send or receive tokens mapping(address account => bool allowed) public transferWhitelisted; @@ -88,6 +190,16 @@ contract EnclaveToken is /// @dev When true, only whitelisted addresses can transfer tokens bool public transfersRestricted; + /// @notice Approved CCA/auction claim sources whose outbound ENCL creates wallet-level locks. + mapping(address source => bool approved) public approvedClaimSources; + + /// @notice Relative CCA claim lock profile for each buyer/recipient. + mapping(address account => ClaimLockProfile profile) + public claimLockProfiles; + + /// @dev Lock schedules by account. The array length is bounded by {MAX_LOCK_SCHEDULES}. + mapping(address account => LockSchedule[] schedules) private _lockSchedules; + /// @notice Emitted when tokens are minted as part of a named allocation /// @param recipient Address receiving the minted tokens /// @param amount Number of tokens minted (18 decimals) @@ -107,11 +219,46 @@ contract EnclaveToken is /// @param whitelisted New whitelist status (true = whitelisted, false = not whitelisted) event TransferWhitelistUpdated(address indexed account, bool whitelisted); + /// @notice Emitted when the default TGE timestamp changes. + event TgeTimestampUpdated(uint64 previous, uint64 next); + + /// @notice Emitted when the bonding registry used for locked-floor accounting changes. + event BondingRegistryUpdated( + address indexed previous, + address indexed next + ); + + /// @notice Emitted when a lock schedule is created for an account. + event LockScheduleCreated( + address indexed account, + uint256 indexed scheduleId, + bytes32 indexed group, + uint256 amount, + uint64 tokenHoldUntil, + uint64 tokenUnlockStart, + uint64 tokenUnlockEnd, + uint64 serviceStart, + uint64 serviceCliff, + uint64 serviceEnd + ); + + /// @notice Emitted when a CCA/auction claim source is approved or revoked. + event ClaimSourceUpdated(address indexed source, bool approved); + + /// @notice Emitted when a relative CCA claim lock profile changes. + event ClaimLockProfileUpdated( + address indexed account, + bool active, + uint64 holdDuration, + uint64 unlockDuration, + bytes32 indexed group + ); + /** * @notice Initializes the Enclave token with name "Enclave" and symbol "ENCL" * @dev Sets up the token with voting and permit functionality. Grants admin, minter, and * whitelist roles to the owner; enables transfer restrictions; whitelists the owner. - * @param initialOwner_ Address that will own the contract and receive admin, minter and whitelist roles. + * @param initialOwner_ Address that will own the contract and receive admin, minter, whitelist, and lock roles. */ constructor( address initialOwner_ @@ -120,6 +267,7 @@ contract EnclaveToken is _grantRole(DEFAULT_ADMIN_ROLE, initialOwner_); _grantRole(MINTER_ROLE, initialOwner_); _grantRole(WHITELIST_ROLE, initialOwner_); + _grantRole(LOCK_MANAGER_ROLE, initialOwner_); // Initialise state variables. transfersRestricted = true; @@ -222,22 +370,155 @@ contract EnclaveToken is * @notice Whitelists key protocol contracts to allow them to transfer tokens during restricted periods * @dev Only callable by accounts holding WHITELIST_ROLE. Zero addresses are safely ignored. * @param bondingManager Address of the BondingManager contract (zero address skipped) - * @param vestingEscrow Address of the VestingEscrow contract (zero address skipped) + * @param claimSource Address of a claim source contract (zero address skipped) */ function whitelistContracts( address bondingManager, - address vestingEscrow + address claimSource ) external onlyRole(WHITELIST_ROLE) { if (bondingManager != address(0)) { transferWhitelisted[bondingManager] = true; emit TransferWhitelistUpdated(bondingManager, true); } - if (vestingEscrow != address(0)) { - transferWhitelisted[vestingEscrow] = true; - emit TransferWhitelistUpdated(vestingEscrow, true); + if (claimSource != address(0)) { + transferWhitelisted[claimSource] = true; + emit TransferWhitelistUpdated(claimSource, true); + } + } + + /// @notice Sets the default TGE timestamp used by schedule inputs with tokenUnlockStart = 0. + /// @dev Existing schedules store resolved timestamps and are not changed by this setter. + function setTgeTimestamp( + uint64 newTgeTimestamp + ) external onlyRole(LOCK_MANAGER_ROLE) { + if (newTgeTimestamp == 0) revert InvalidLockSchedule(); + uint64 previous = tgeTimestamp; + tgeTimestamp = newTgeTimestamp; + emit TgeTimestampUpdated(previous, newTgeTimestamp); + } + + /// @notice Sets the bonding registry queried by locked-floor transfer checks. + /// @dev Passing zero disables bonded-credit accounting. Non-zero values must be deployed code. + function setBondingRegistry( + IBondingRegistry newBondingRegistry + ) external onlyRole(LOCK_MANAGER_ROLE) { + address newRegistryAddress = address(newBondingRegistry); + if ( + newRegistryAddress != address(0) && + newRegistryAddress.code.length == 0 + ) revert ZeroAddress(); + + address previous = address(bondingRegistry); + bondingRegistry = newBondingRegistry; + emit BondingRegistryUpdated(previous, newRegistryAddress); + } + + /// @notice Creates a wallet-level lock schedule for an account. + function createLockSchedule( + LockScheduleInput calldata input + ) external onlyRole(LOCK_MANAGER_ROLE) returns (uint256 scheduleId) { + scheduleId = _addLockSchedule(input); + _enforceLockedFloor(input.account); + } + + /// @notice Creates many wallet-level lock schedules. + function batchCreateLockSchedules( + LockScheduleInput[] calldata inputs + ) + external + onlyRole(LOCK_MANAGER_ROLE) + returns (uint256[] memory scheduleIds) + { + uint256 len = inputs.length; + scheduleIds = new uint256[](len); + + for (uint256 i = 0; i < len; i++) { + scheduleIds[i] = _addLockSchedule(inputs[i]); + _enforceLockedFloor(inputs[i].account); + } + } + + /// @notice Approves or revokes a CCA/auction claim source. + function setClaimSource( + address source, + bool approved + ) external onlyRole(LOCK_MANAGER_ROLE) { + if (source == address(0)) revert ZeroAddress(); + approvedClaimSources[source] = approved; + emit ClaimSourceUpdated(source, approved); + } + + /// @notice Sets the relative claim lock profile for a buyer/recipient. + function setClaimLockProfile( + address account, + ClaimLockProfile calldata profile + ) external onlyRole(LOCK_MANAGER_ROLE) { + _setClaimLockProfile(account, profile); + } + + /// @notice Batch sets relative claim lock profiles. + function batchSetClaimLockProfiles( + ClaimLockProfileInput[] calldata inputs + ) external onlyRole(LOCK_MANAGER_ROLE) { + uint256 len = inputs.length; + for (uint256 i = 0; i < len; i++) { + _setClaimLockProfile(inputs[i].account, inputs[i].profile); + } + } + + /// @notice Number of lock schedules recorded for an account. + function lockScheduleCount( + address account + ) external view returns (uint256) { + return _lockSchedules[account].length; + } + + /// @notice Returns a lock schedule by account and index. + function lockScheduleOf( + address account, + uint256 scheduleId + ) external view returns (LockSchedule memory) { + return _lockSchedules[account][scheduleId]; + } + + /// @notice Current amount that must remain controlled by an account's wallet plus bonded ENCL. + function lockedFloorOf(address account) public view returns (uint256) { + return lockedFloorAt(account, uint64(block.timestamp)); + } + + /// @notice Amount that must remain controlled by an account at a given timestamp. + function lockedFloorAt( + address account, + uint64 timestamp + ) public view returns (uint256 lockedFloor) { + LockSchedule[] storage schedules = _lockSchedules[account]; + uint256 len = schedules.length; + for (uint256 i = 0; i < len; i++) { + lockedFloor += _lockedAmount(schedules[i], timestamp); } } + /// @notice ENCL bonded by an account that still counts toward its locked floor. + function totalBondedOf(address account) public view returns (uint256) { + IBondingRegistry registry = bondingRegistry; + if (address(registry) == address(0)) return 0; + return registry.totalBonded(account); + } + + /// @notice Current wallet balance that can be transferred without violating the locked floor. + function transferableBalanceOf( + address account + ) external view returns (uint256) { + uint256 balance = balanceOf(account); + uint256 bonded = totalBondedOf(account); + uint256 lockedFloor = lockedFloorOf(account); + uint256 controlled = balance + bonded; + if (controlled <= lockedFloor) return 0; + + uint256 transferable = controlled - lockedFloor; + return transferable < balance ? transferable : balance; + } + /** * @notice Internal hook that enforces transfer restrictions and updates voting power * @dev Overrides ERC20 and ERC20Votes to add transfer restriction logic. Reverts if transfers @@ -260,6 +541,218 @@ contract EnclaveToken is } } super._update(from, to, value); + + if (from != address(0) && to != address(0)) { + if (approvedClaimSources[from] && value != 0) { + _addClaimLock(to, value); + } + _enforceLockedFloor(from); + } + } + + function _addLockSchedule( + LockScheduleInput calldata input + ) internal returns (uint256 scheduleId) { + if (input.account == address(0)) revert ZeroAddress(); + if (input.amount == 0) revert ZeroAmount(); + if (input.amount > type(uint128).max) revert InvalidLockSchedule(); + + uint64 tokenUnlockStart = _resolveTokenUnlockStart( + input.tokenUnlockStart + ); + _validateLockSchedule( + tokenUnlockStart, + input.tokenUnlockEnd, + input.serviceStart, + input.serviceCliff, + input.serviceEnd + ); + + scheduleId = _pushLockSchedule( + input.account, + LockSchedule({ + amount: uint128(input.amount), + tokenHoldUntil: input.tokenHoldUntil, + tokenUnlockStart: tokenUnlockStart, + tokenUnlockEnd: input.tokenUnlockEnd, + serviceStart: input.serviceStart, + serviceCliff: input.serviceCliff, + serviceEnd: input.serviceEnd, + group: input.group + }) + ); + } + + function _addClaimLock(address account, uint256 amount) internal { + ClaimLockProfile memory profile = claimLockProfiles[account]; + if (!profile.active) revert ClaimLockProfileMissing(account); + if (profile.holdDuration == 0 && profile.unlockDuration == 0) { + revert InvalidLockSchedule(); + } + if (amount > type(uint128).max) revert InvalidLockSchedule(); + + if (block.timestamp > type(uint64).max) revert InvalidLockSchedule(); + uint64 currentTimestamp = uint64(block.timestamp); + + uint256 holdUntil = uint256(currentTimestamp) + profile.holdDuration; + uint256 unlockEnd = holdUntil + profile.unlockDuration; + if (unlockEnd > type(uint64).max) revert InvalidLockSchedule(); + + uint64 tokenHoldUntil = uint64(holdUntil); + uint64 tokenUnlockEnd = uint64(unlockEnd); + + _pushLockSchedule( + account, + LockSchedule({ + amount: uint128(amount), + tokenHoldUntil: tokenHoldUntil, + tokenUnlockStart: tokenHoldUntil, + tokenUnlockEnd: tokenUnlockEnd, + serviceStart: 0, + serviceCliff: 0, + serviceEnd: 0, + group: profile.group + }) + ); + } + + function _pushLockSchedule( + address account, + LockSchedule memory schedule + ) internal returns (uint256 scheduleId) { + LockSchedule[] storage schedules = _lockSchedules[account]; + uint256 len = schedules.length; + if (len >= MAX_LOCK_SCHEDULES) revert MaxLockSchedulesExceeded(); + + schedules.push(schedule); + emit LockScheduleCreated( + account, + len, + schedule.group, + uint256(schedule.amount), + schedule.tokenHoldUntil, + schedule.tokenUnlockStart, + schedule.tokenUnlockEnd, + schedule.serviceStart, + schedule.serviceCliff, + schedule.serviceEnd + ); + return len; + } + + function _setClaimLockProfile( + address account, + ClaimLockProfile calldata profile + ) internal { + if (account == address(0)) revert ZeroAddress(); + if ( + profile.active && + profile.holdDuration == 0 && + profile.unlockDuration == 0 + ) revert InvalidLockSchedule(); + + claimLockProfiles[account] = profile; + emit ClaimLockProfileUpdated( + account, + profile.active, + profile.holdDuration, + profile.unlockDuration, + profile.group + ); + } + + function _resolveTokenUnlockStart( + uint64 tokenUnlockStart + ) internal view returns (uint64) { + if (tokenUnlockStart != 0) return tokenUnlockStart; + + uint64 configuredTgeTimestamp = tgeTimestamp; + if (configuredTgeTimestamp == 0) revert TgeTimestampUnset(); + return configuredTgeTimestamp; + } + + function _validateLockSchedule( + uint64 tokenUnlockStart, + uint64 tokenUnlockEnd, + uint64 serviceStart, + uint64 serviceCliff, + uint64 serviceEnd + ) internal pure { + if (tokenUnlockStart > tokenUnlockEnd) revert InvalidLockSchedule(); + + bool hasServiceCurve = serviceStart != 0 || + serviceCliff != 0 || + serviceEnd != 0; + if (!hasServiceCurve) return; + + if ( + serviceStart == 0 || + serviceEnd <= serviceStart || + serviceCliff < serviceStart || + serviceCliff > serviceEnd + ) revert InvalidLockSchedule(); + } + + function _lockedAmount( + LockSchedule storage schedule, + uint64 timestamp + ) internal view returns (uint256) { + uint256 amount = uint256(schedule.amount); + uint256 tokenUnlocked = _tokenUnlockedAmount(schedule, timestamp); + uint256 serviceVested = _serviceVestedAmount(schedule, timestamp); + uint256 released = tokenUnlocked < serviceVested + ? tokenUnlocked + : serviceVested; + return amount - released; + } + + function _tokenUnlockedAmount( + LockSchedule storage schedule, + uint64 timestamp + ) internal view returns (uint256) { + uint256 amount = uint256(schedule.amount); + if (timestamp < schedule.tokenHoldUntil) return 0; + if (schedule.tokenUnlockStart == schedule.tokenUnlockEnd) return amount; + if (timestamp <= schedule.tokenUnlockStart) return 0; + if (timestamp >= schedule.tokenUnlockEnd) return amount; + + uint256 elapsed = uint256(timestamp - schedule.tokenUnlockStart); + uint256 duration = uint256( + schedule.tokenUnlockEnd - schedule.tokenUnlockStart + ); + return (amount * elapsed) / duration; + } + + function _serviceVestedAmount( + LockSchedule storage schedule, + uint64 timestamp + ) internal view returns (uint256) { + uint256 amount = uint256(schedule.amount); + if (schedule.serviceStart == 0 && schedule.serviceEnd == 0) { + return amount; + } + if (timestamp < schedule.serviceCliff) return 0; + if (timestamp >= schedule.serviceEnd) return amount; + + uint256 elapsed = uint256(timestamp - schedule.serviceStart); + uint256 duration = uint256(schedule.serviceEnd - schedule.serviceStart); + return (amount * elapsed) / duration; + } + + function _enforceLockedFloor(address account) internal view { + uint256 lockedFloor = lockedFloorOf(account); + if (lockedFloor == 0) return; + + uint256 balance = balanceOf(account); + uint256 bonded = totalBondedOf(account); + if (balance + bonded < lockedFloor) { + revert LockedBalanceInvariant( + account, + balance, + bonded, + lockedFloor + ); + } } /** @@ -323,11 +816,13 @@ contract EnclaveToken is _revokeRole(DEFAULT_ADMIN_ROLE, previousOwner); _revokeRole(MINTER_ROLE, previousOwner); _revokeRole(WHITELIST_ROLE, previousOwner); + _revokeRole(LOCK_MANAGER_ROLE, previousOwner); } if (newOwner != address(0)) { _grantRole(DEFAULT_ADMIN_ROLE, newOwner); _grantRole(MINTER_ROLE, newOwner); _grantRole(WHITELIST_ROLE, newOwner); + _grantRole(LOCK_MANAGER_ROLE, newOwner); } } } diff --git a/packages/enclave-contracts/contracts/token/InterfoldVestingEscrow.sol b/packages/enclave-contracts/contracts/token/InterfoldVestingEscrow.sol deleted file mode 100644 index 75f3bd5235..0000000000 --- a/packages/enclave-contracts/contracts/token/InterfoldVestingEscrow.sol +++ /dev/null @@ -1,354 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. - -pragma solidity 0.8.28; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { - SafeERC20 -} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol"; -import { - ReentrancyGuard -} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import { - IERC165 -} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -import { IBondingRegistry } from "../interfaces/IBondingRegistry.sol"; -import { - IInterfoldVestingEscrow -} from "../interfaces/IInterfoldVestingEscrow.sol"; -import { ILicenseBondReceiver } from "../interfaces/ILicenseBondReceiver.sol"; - -/** - * @title InterfoldVestingEscrow - * @notice Multi-schedule protocol token vesting escrow for Interfold TGE allocations. - * @dev Schedules combine a token transfer lock and an optional service vesting curve. The - * releasable amount is the stricter of the two curves, less claimed and bonded amounts. - */ -contract InterfoldVestingEscrow is - IInterfoldVestingEscrow, - ILicenseBondReceiver, - Ownable2Step, - ReentrancyGuard -{ - using SafeERC20 for IERC20; - - error ZeroAddress(); - error ZeroAmount(); - error InvalidSchedule(); - error UnknownSchedule(); - error UnauthorizedBeneficiary(); - error NothingClaimable(); - error InsufficientUnbondedAllocation(); - error InsufficientEscrowBacking(); - error UnauthorizedBondingRegistry(); - error RenounceOwnershipDisabled(); - - IERC20 public immutable TOKEN; - IBondingRegistry public immutable BONDING_REGISTRY; - uint64 public immutable TGE_TIMESTAMP; - - uint256 public nextScheduleId; - uint256 public totalScheduled; - uint256 public totalClaimed; - uint256 public totalBonded; - uint256 public totalSlashed; - - mapping(uint256 scheduleId => ScheduleView schedule) private _schedules; - - constructor( - IERC20 token_, - IBondingRegistry bondingRegistry_, - uint64 tgeTimestamp_, - address initialOwner_ - ) Ownable(initialOwner_) { - if ( - address(token_) == address(0) || - address(bondingRegistry_) == address(0) || - initialOwner_ == address(0) - ) { - revert ZeroAddress(); - } - TOKEN = token_; - BONDING_REGISTRY = bondingRegistry_; - TGE_TIMESTAMP = tgeTimestamp_; - nextScheduleId = 1; - } - - function createSchedule( - ScheduleInput calldata input - ) external onlyOwner returns (uint256 scheduleId) { - scheduleId = _createSchedule(input); - } - - function batchCreateSchedules( - ScheduleInput[] calldata inputs - ) external onlyOwner returns (uint256[] memory scheduleIds) { - uint256 len = inputs.length; - scheduleIds = new uint256[](len); - for (uint256 i = 0; i < len; i++) { - scheduleIds[i] = _createSchedule(inputs[i]); - } - } - - function claim( - uint256 scheduleId, - uint256 maxAmount - ) external nonReentrant { - ScheduleView storage schedule = _schedule(scheduleId); - if (msg.sender != schedule.beneficiary) { - revert UnauthorizedBeneficiary(); - } - - uint256 amount = claimableAmount(scheduleId); - if (maxAmount < amount) amount = maxAmount; - if (amount == 0) revert NothingClaimable(); - - schedule.claimedAmount += amount; - totalClaimed += amount; - - TOKEN.safeTransfer(schedule.beneficiary, amount); - emit TokensClaimed(scheduleId, schedule.beneficiary, amount); - } - - function bondLockedTokens( - uint256 scheduleId, - address operator, - uint256 amount - ) external nonReentrant { - if (operator == address(0)) revert ZeroAddress(); - if (amount == 0) revert ZeroAmount(); - - ScheduleView storage schedule = _schedule(scheduleId); - if (msg.sender != schedule.beneficiary) { - revert UnauthorizedBeneficiary(); - } - if (amount > bondableAmount(scheduleId)) { - revert InsufficientUnbondedAllocation(); - } - - schedule.bondedAmount += amount; - totalBonded += amount; - - TOKEN.safeIncreaseAllowance(address(BONDING_REGISTRY), amount); - BONDING_REGISTRY.bondLicenseFor( - operator, - amount, - address(this), - bytes32(scheduleId) - ); - - emit LockedTokensBonded( - scheduleId, - schedule.beneficiary, - operator, - amount - ); - } - - function onLicenseBondReturned( - address operator, - uint256 amount, - bytes32 sourceId - ) external returns (bytes4 selector) { - if (msg.sender != address(BONDING_REGISTRY)) { - revert UnauthorizedBondingRegistry(); - } - - uint256 scheduleId = uint256(sourceId); - ScheduleView storage schedule = _schedule(scheduleId); - schedule.bondedAmount -= amount; - totalBonded -= amount; - - emit BondedTokensReturned(scheduleId, operator, amount); - return ILicenseBondReceiver.onLicenseBondReturned.selector; - } - - function onLicenseBondSlashed( - address operator, - uint256 amount, - bytes32 sourceId - ) external returns (bytes4 selector) { - if (msg.sender != address(BONDING_REGISTRY)) { - revert UnauthorizedBondingRegistry(); - } - - uint256 scheduleId = uint256(sourceId); - ScheduleView storage schedule = _schedule(scheduleId); - schedule.bondedAmount -= amount; - schedule.slashedAmount += amount; - totalBonded -= amount; - totalSlashed += amount; - - emit BondedTokensSlashed(scheduleId, operator, amount); - return ILicenseBondReceiver.onLicenseBondSlashed.selector; - } - - function vestedAmount( - uint256 scheduleId, - uint64 timestamp - ) public view returns (uint256) { - ScheduleView storage schedule = _schedule(scheduleId); - uint256 effectiveTotal = schedule.totalAmount - schedule.slashedAmount; - uint256 tokenUnlocked = _tokenUnlocked( - schedule, - effectiveTotal, - timestamp - ); - uint256 serviceVested = _serviceVested( - schedule, - effectiveTotal, - timestamp - ); - return tokenUnlocked < serviceVested ? tokenUnlocked : serviceVested; - } - - function claimableAmount(uint256 scheduleId) public view returns (uint256) { - ScheduleView storage schedule = _schedule(scheduleId); - uint256 vested = vestedAmount(scheduleId, uint64(block.timestamp)); - uint256 unavailable = schedule.claimedAmount + schedule.bondedAmount; - if (vested <= unavailable) return 0; - return vested - unavailable; - } - - function bondableAmount(uint256 scheduleId) public view returns (uint256) { - ScheduleView storage schedule = _schedule(scheduleId); - uint256 unavailable = schedule.claimedAmount + - schedule.bondedAmount + - schedule.slashedAmount; - return schedule.totalAmount - unavailable; - } - - function getSchedule( - uint256 scheduleId - ) external view returns (ScheduleView memory) { - return _schedule(scheduleId); - } - - function supportsInterface(bytes4 interfaceId) public pure returns (bool) { - return - interfaceId == type(IInterfoldVestingEscrow).interfaceId || - interfaceId == type(ILicenseBondReceiver).interfaceId || - interfaceId == type(IERC165).interfaceId; - } - - function renounceOwnership() public view override onlyOwner { - revert RenounceOwnershipDisabled(); - } - - function _createSchedule( - ScheduleInput calldata input - ) internal returns (uint256 scheduleId) { - _validateSchedule(input); - - uint256 newTotalScheduled = totalScheduled + input.totalAmount; - if ( - newTotalScheduled > - TOKEN.balanceOf(address(this)) + - totalClaimed + - totalBonded + - totalSlashed - ) { - revert InsufficientEscrowBacking(); - } - - scheduleId = nextScheduleId++; - totalScheduled = newTotalScheduled; - _schedules[scheduleId] = ScheduleView({ - beneficiary: input.beneficiary, - totalAmount: input.totalAmount, - claimedAmount: 0, - bondedAmount: 0, - slashedAmount: 0, - tokenHoldUntil: input.tokenHoldUntil, - tokenUnlockStart: input.tokenUnlockStart, - tokenUnlockEnd: input.tokenUnlockEnd, - serviceStart: input.serviceStart, - serviceCliff: input.serviceCliff, - serviceEnd: input.serviceEnd, - group: input.group - }); - - emit ScheduleCreated( - scheduleId, - input.beneficiary, - input.group, - input.totalAmount, - input.tokenHoldUntil, - input.tokenUnlockStart, - input.tokenUnlockEnd, - input.serviceStart, - input.serviceCliff, - input.serviceEnd - ); - } - - function _validateSchedule(ScheduleInput calldata input) internal pure { - if (input.beneficiary == address(0)) revert ZeroAddress(); - if (input.totalAmount == 0) revert ZeroAmount(); - if (input.tokenUnlockEnd < input.tokenUnlockStart) { - revert InvalidSchedule(); - } - if (input.serviceEnd != 0) { - if (input.serviceEnd <= input.serviceStart) { - revert InvalidSchedule(); - } - if ( - input.serviceCliff < input.serviceStart || - input.serviceCliff > input.serviceEnd - ) { - revert InvalidSchedule(); - } - } else if (input.serviceStart != 0 || input.serviceCliff != 0) { - revert InvalidSchedule(); - } - } - - function _schedule( - uint256 scheduleId - ) internal view returns (ScheduleView storage schedule) { - schedule = _schedules[scheduleId]; - if (schedule.beneficiary == address(0)) revert UnknownSchedule(); - } - - function _tokenUnlocked( - ScheduleView storage schedule, - uint256 total, - uint64 timestamp - ) internal view returns (uint256) { - uint64 unlockStart = schedule.tokenUnlockStart == 0 - ? TGE_TIMESTAMP - : schedule.tokenUnlockStart; - - if (timestamp < schedule.tokenHoldUntil) return 0; - if (schedule.tokenUnlockEnd <= unlockStart) { - return timestamp >= unlockStart ? total : 0; - } - if (timestamp < unlockStart) return 0; - if (timestamp >= schedule.tokenUnlockEnd) return total; - - return - (total * (uint256(timestamp) - uint256(unlockStart))) / - (uint256(schedule.tokenUnlockEnd) - uint256(unlockStart)); - } - - function _serviceVested( - ScheduleView storage schedule, - uint256 total, - uint64 timestamp - ) internal view returns (uint256) { - if (schedule.serviceEnd == 0) return total; - if (timestamp < schedule.serviceCliff) return 0; - if (timestamp >= schedule.serviceEnd) return total; - - return - (total * (uint256(timestamp) - uint256(schedule.serviceStart))) / - (uint256(schedule.serviceEnd) - uint256(schedule.serviceStart)); - } -} diff --git a/packages/enclave-contracts/scripts/deployAndSave/interfoldVestingEscrow.ts b/packages/enclave-contracts/scripts/deployAndSave/interfoldVestingEscrow.ts deleted file mode 100644 index f7381ab692..0000000000 --- a/packages/enclave-contracts/scripts/deployAndSave/interfoldVestingEscrow.ts +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. -import type { HardhatRuntimeEnvironment } from "hardhat/types/hre"; - -import { - InterfoldVestingEscrow, - InterfoldVestingEscrow__factory as InterfoldVestingEscrowFactory, -} from "../../types"; -import { - getDeploymentChain, - readDeploymentArgs, - storeDeploymentArgs, -} from "../utils"; - -/** - * The arguments for the deployAndSaveInterfoldVestingEscrow function. - */ -export interface InterfoldVestingEscrowArgs { - token?: string; - bondingRegistry?: string; - tgeTimestamp?: string; - owner?: string; - hre: HardhatRuntimeEnvironment; -} - -/** - * Deploys the InterfoldVestingEscrow contract and saves the deployment arguments. - */ -export const deployAndSaveInterfoldVestingEscrow = async ({ - token, - bondingRegistry, - tgeTimestamp, - owner, - hre, -}: InterfoldVestingEscrowArgs): Promise<{ - interfoldVestingEscrow: InterfoldVestingEscrow; -}> => { - const { ethers } = await hre.network.connect(); - const [signer] = await ethers.getSigners(); - const chain = getDeploymentChain(hre); - - const preDeployedArgs = readDeploymentArgs("InterfoldVestingEscrow", chain); - - if ( - !token || - !bondingRegistry || - !tgeTimestamp || - !owner || - (preDeployedArgs?.constructorArgs?.token === token && - preDeployedArgs?.constructorArgs?.bondingRegistry === bondingRegistry && - preDeployedArgs?.constructorArgs?.tgeTimestamp === tgeTimestamp && - preDeployedArgs?.constructorArgs?.owner === owner) - ) { - if (!preDeployedArgs?.address) { - throw new Error( - "InterfoldVestingEscrow address not found, it must be deployed first", - ); - } - const interfoldVestingEscrow = InterfoldVestingEscrowFactory.connect( - preDeployedArgs.address, - signer, - ); - return { interfoldVestingEscrow }; - } - - const interfoldVestingEscrowFactory = await ethers.getContractFactory( - "InterfoldVestingEscrow", - ); - const interfoldVestingEscrow = await interfoldVestingEscrowFactory.deploy( - token, - bondingRegistry, - tgeTimestamp, - owner, - ); - await interfoldVestingEscrow.waitForDeployment(); - - const blockNumber = await ethers.provider.getBlockNumber(); - const interfoldVestingEscrowAddress = - await interfoldVestingEscrow.getAddress(); - - storeDeploymentArgs( - { - constructorArgs: { - token, - bondingRegistry, - tgeTimestamp, - owner, - }, - blockNumber, - address: interfoldVestingEscrowAddress, - }, - "InterfoldVestingEscrow", - chain, - ); - - const interfoldVestingEscrowContract = InterfoldVestingEscrowFactory.connect( - interfoldVestingEscrowAddress, - signer, - ); - - return { interfoldVestingEscrow: interfoldVestingEscrowContract }; -}; diff --git a/packages/enclave-contracts/scripts/deployEnclave.ts b/packages/enclave-contracts/scripts/deployEnclave.ts index c54cd5b75b..e203a9a67a 100644 --- a/packages/enclave-contracts/scripts/deployEnclave.ts +++ b/packages/enclave-contracts/scripts/deployEnclave.ts @@ -17,13 +17,12 @@ import { deployAndSaveE3RefundManager } from "./deployAndSave/e3RefundManager"; import { deployAndSaveEnclave } from "./deployAndSave/enclave"; import { deployAndSaveEnclaveTicketToken } from "./deployAndSave/enclaveTicketToken"; import { deployAndSaveEnclaveToken } from "./deployAndSave/enclaveToken"; -import { deployAndSaveInterfoldVestingEscrow } from "./deployAndSave/interfoldVestingEscrow"; import { deployAndSaveMockStableToken } from "./deployAndSave/mockStableToken"; import { deployAndSavePoseidonT3 } from "./deployAndSave/poseidonT3"; import { deployAndSaveSlashingManager } from "./deployAndSave/slashingManager"; import { deployAndSaveAllVerifiers } from "./deployAndSave/verifiers"; import { deployMocks } from "./deployMocks"; -import { isLocalDeploymentChain, readDeploymentArgs } from "./utils"; +import { isLocalDeploymentChain } from "./utils"; // BFV parameter presets — hardcoded from crates/fhe-params/src/constants.rs // to avoid a cyclic dependency on @enclave-e3/sdk. @@ -99,26 +98,12 @@ function resolveInterfoldTgeTimestamp( if (!isLocalDeploymentChain(networkName)) { throw new Error( - "INTERFOLD_TGE_TIMESTAMP must be set for non-local InterfoldVestingEscrow deployment", + "INTERFOLD_TGE_TIMESTAMP must be set for non-local token-lock deployment", ); } - const preDeployedTgeTimestamp = readDeploymentArgs( - "InterfoldVestingEscrow", - networkName, - )?.constructorArgs?.tgeTimestamp; - if (typeof preDeployedTgeTimestamp === "string") { - console.warn( - "[WARN] INTERFOLD_TGE_TIMESTAMP not set; reusing saved local InterfoldVestingEscrow TGE timestamp.", - ); - return parseRequiredUint64( - preDeployedTgeTimestamp, - "saved InterfoldVestingEscrow tgeTimestamp", - ).toString(); - } - console.warn( - "[WARN] INTERFOLD_TGE_TIMESTAMP not set; using latest local block timestamp for InterfoldVestingEscrow.", + "[WARN] INTERFOLD_TGE_TIMESTAMP not set; using latest local block timestamp for ENCL token locks.", ); return latestBlockTimestamp.toString(); } @@ -266,30 +251,14 @@ export const deployEnclave = async ( const bondingRegistryAddress = await bondingRegistry.getAddress(); console.log("BondingRegistry deployed to:", bondingRegistryAddress); - console.log( - "Deploying InterfoldVestingEscrow with TGE timestamp:", - interfoldTgeTimestamp, - ); - const { interfoldVestingEscrow } = await deployAndSaveInterfoldVestingEscrow({ - token: enclaveTokenAddress, - bondingRegistry: bondingRegistryAddress, - tgeTimestamp: interfoldTgeTimestamp, - owner: ownerAddress, - hre, - }); - const interfoldVestingEscrowAddress = - await interfoldVestingEscrow.getAddress(); - console.log( - "InterfoldVestingEscrow deployed to:", - interfoldVestingEscrowAddress, - ); + console.log("Configuring ENCL pooled lock accounting..."); + await (await enclaveToken.setTgeTimestamp(interfoldTgeTimestamp)).wait(); + await (await enclaveToken.setBondingRegistry(bondingRegistryAddress)).wait(); - console.log( - "Whitelisting BondingRegistry and InterfoldVestingEscrow in ENCL...", - ); + console.log("Whitelisting BondingRegistry in ENCL..."); const whitelistTx = await enclaveToken.whitelistContracts( bondingRegistryAddress, - interfoldVestingEscrowAddress, + ethersLib.ZeroAddress, ); await whitelistTx.wait(); @@ -583,7 +552,6 @@ export const deployEnclave = async ( EnclaveTicketToken: ${enclaveTicketTokenAddress} SlashingManager: ${slashingManagerAddress} BondingRegistry: ${bondingRegistryAddress} - InterfoldVestingEscrow: ${interfoldVestingEscrowAddress} CiphernodeRegistry: ${ciphernodeRegistryAddress} E3RefundManager: ${e3RefundManagerAddress} Enclave: ${enclaveAddress} diff --git a/packages/enclave-contracts/scripts/index.ts b/packages/enclave-contracts/scripts/index.ts index 6a20e8a6aa..f32d255875 100644 --- a/packages/enclave-contracts/scripts/index.ts +++ b/packages/enclave-contracts/scripts/index.ts @@ -13,7 +13,6 @@ export * from "./deployAndSave/ciphernodeRegistryOwnable"; export * from "./deployAndSave/enclave"; export * from "./deployAndSave/enclaveTicketToken"; export * from "./deployAndSave/enclaveToken"; -export * from "./deployAndSave/interfoldVestingEscrow"; export * from "./deployAndSave/mockStableToken"; export * from "./deployAndSave/slashingManager"; export * from "./deployAndSave/mockComputeProvider"; diff --git a/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts b/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts index 24e3610023..da721a5589 100644 --- a/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts +++ b/packages/enclave-contracts/test/Registry/BondingRegistry.spec.ts @@ -129,6 +129,9 @@ describe("BondingRegistry", function () { expect( await bondingRegistry.getLicenseBond(await operator1.getAddress()), ).to.equal(bondAmount); + expect( + await bondingRegistry.totalBonded(await operator1.getAddress()), + ).to.equal(bondAmount); }); it("reverts if amount is zero", async function () { @@ -249,6 +252,43 @@ describe("BondingRegistry", function () { await operator1.getAddress(), ); expect(licensePending).to.equal(unbondAmount); + expect( + await bondingRegistry.totalBonded(await operator1.getAddress()), + ).to.equal(bondAmount); + }); + + it("slashes active and pending license bond from totalBonded", async function () { + const { bondingRegistry, licenseToken, operator1, notTheOwner } = + await loadFixture(setup); + const operatorAddress = await operator1.getAddress(); + const slashReason = ethers.encodeBytes32String("TEST_SLASH"); + + const bondAmount = ethers.parseEther("1000"); + const unbondAmount = ethers.parseEther("300"); + const slashAmount = ethers.parseEther("800"); + + await bondingRegistry.setSlashingManager(await notTheOwner.getAddress()); + await licenseToken + .connect(operator1) + .approve(await bondingRegistry.getAddress(), bondAmount); + await bondingRegistry.connect(operator1).bondLicense(bondAmount); + await bondingRegistry.connect(operator1).unbondLicense(unbondAmount); + + await expect( + bondingRegistry + .connect(notTheOwner) + .slashLicenseBond(operatorAddress, slashAmount, slashReason), + ) + .to.emit(bondingRegistry, "LicenseBondUpdated") + .withArgs(operatorAddress, -slashAmount, 0, slashReason); + + const [, pendingLicense] = + await bondingRegistry.pendingExits(operatorAddress); + expect(pendingLicense).to.equal(bondAmount - slashAmount); + expect(await bondingRegistry.totalBonded(operatorAddress)).to.equal( + bondAmount - slashAmount, + ); + expect(await bondingRegistry.slashedLicenseBond()).to.equal(slashAmount); }); }); @@ -1188,7 +1228,7 @@ describe("BondingRegistry", function () { } = await loadFixture(setup); // Register and fund tickets so the generic ExitQueueLib ticket path is - // exercised directly. ENCL exits now use source-aware withdrawal logic. + // exercised directly alongside ENCL exits. const bondAmount = LICENSE_REQUIRED_BOND; await licenseToken .connect(operator1) diff --git a/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts b/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts index 57bf9d4f37..b65b8d2346 100644 --- a/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts +++ b/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts @@ -4,21 +4,87 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. import { expect } from "chai"; -import { network } from "hardhat"; import { EnclaveToken__factory as EnclaveTokenFactory } from "../../types"; +import { + SEVEN_DAYS, + deployEnclaveSystem, + ethers, + networkHelpers, +} from "../fixtures"; -const { ethers, networkHelpers } = await network.connect(); const { loadFixture, time } = networkHelpers; +const DAY = 24n * 60n * 60n; +const YEAR = 365n * DAY; + +const GROUP_PRE_SEED = ethers.encodeBytes32String("PRE_SEED"); +const GROUP_BRIDGE = ethers.encodeBytes32String("BRIDGE"); +const GROUP_TEAM = ethers.encodeBytes32String("GG_TEAM"); +const GROUP_CCA_REG_S = ethers.encodeBytes32String("CCA_REG_S"); + describe("EnclaveToken", function () { async function deploy() { - const [deployer, admin, minter, whitelister, alice, bob] = + const [deployer, admin, minter, whitelister, alice, bob, claimSource] = await ethers.getSigners(); const token = await new EnclaveTokenFactory(deployer).deploy( await admin.getAddress(), ); - return { deployer, admin, minter, whitelister, alice, bob, token }; + return { + deployer, + admin, + minter, + whitelister, + alice, + bob, + claimSource, + token, + }; + } + + async function deployWithUnlockedTransfers() { + const fixture = await deploy(); + await fixture.token.connect(fixture.admin).disableTransferRestrictions(); + return fixture; + } + + async function createLinearLock( + overrides: Partial<{ + totalAmount: bigint; + tokenHoldUntil: bigint; + tokenUnlockStart: bigint; + tokenUnlockEnd: bigint; + serviceStart: bigint; + serviceCliff: bigint; + serviceEnd: bigint; + group: string; + }> = {}, + ) { + const fixture = await loadFixture(deployWithUnlockedTransfers); + const { token, admin, alice } = fixture; + const aliceAddress = await alice.getAddress(); + const now = BigInt(await time.latest()); + const tge = now + DAY; + const totalAmount = overrides.totalAmount ?? ethers.parseEther("2400"); + + await token.connect(admin).setTgeTimestamp(tge); + await token + .connect(admin) + .mintAllocation(aliceAddress, totalAmount, "Locked allocation"); + + await token.connect(admin).createLockSchedule({ + account: aliceAddress, + amount: totalAmount, + tokenHoldUntil: overrides.tokenHoldUntil ?? tge, + tokenUnlockStart: overrides.tokenUnlockStart ?? 0n, + tokenUnlockEnd: overrides.tokenUnlockEnd ?? tge + 2n * YEAR, + serviceStart: overrides.serviceStart ?? 0n, + serviceCliff: overrides.serviceCliff ?? 0n, + serviceEnd: overrides.serviceEnd ?? 0n, + group: overrides.group ?? GROUP_PRE_SEED, + }); + + return { ...fixture, aliceAddress, tge, totalAmount }; } // ── H-15 ────────────────────────────────────────────────────────────────── @@ -96,4 +162,370 @@ describe("EnclaveToken", function () { expect(await token.CLOCK_MODE()).to.equal("mode=timestamp"); }); }); + + describe("pooled token-level locks", function () { + it("requires LOCK_MANAGER_ROLE for schedule creation", async function () { + const { token, alice } = await loadFixture(deployWithUnlockedTransfers); + await expect( + token.connect(alice).createLockSchedule({ + account: await alice.getAddress(), + amount: ethers.parseEther("1"), + tokenHoldUntil: 1n, + tokenUnlockStart: 1n, + tokenUnlockEnd: 1n, + serviceStart: 0n, + serviceCliff: 0n, + serviceEnd: 0n, + group: GROUP_PRE_SEED, + }), + ).to.be.revertedWithCustomError( + token, + "AccessControlUnauthorizedAccount", + ); + }); + + it("blocks transfers that would drop a wallet below its locked floor", async function () { + const { token, alice, bob, totalAmount, tge } = await createLinearLock(); + const bobAddress = await bob.getAddress(); + + expect(await token.lockedFloorOf(await alice.getAddress())).to.equal( + totalAmount, + ); + + await expect( + token.connect(alice).transfer(bobAddress, 1n), + ).to.be.revertedWithCustomError(token, "LockedBalanceInvariant"); + + await time.increaseTo(tge + YEAR); + expect(await token.lockedFloorOf(await alice.getAddress())).to.equal( + totalAmount / 2n, + ); + expect( + await token.transferableBalanceOf(await alice.getAddress()), + ).to.equal(totalAmount / 2n); + + await token.connect(alice).transfer(bobAddress, totalAmount / 2n); + await expect( + token.connect(alice).transfer(bobAddress, totalAmount / 2n), + ).to.be.revertedWithCustomError(token, "LockedBalanceInvariant"); + + await time.increaseTo(tge + 2n * YEAR); + await token.connect(alice).transfer(bobAddress, totalAmount / 2n); + expect(await token.balanceOf(await alice.getAddress())).to.equal(0n); + }); + + it("keeps launch whitelist separate from locked-floor enforcement", async function () { + const { token, admin, alice, bob } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + const bobAddress = await bob.getAddress(); + const now = BigInt(await time.latest()); + const tge = now + DAY; + const totalAmount = ethers.parseEther("100"); + + await token.connect(admin).setTgeTimestamp(tge); + await token + .connect(admin) + .mintAllocation(aliceAddress, totalAmount, "Locked allocation"); + await token.connect(admin).toggleTransferWhitelist(aliceAddress); + await token.connect(admin).createLockSchedule({ + account: aliceAddress, + amount: totalAmount, + tokenHoldUntil: tge, + tokenUnlockStart: 0n, + tokenUnlockEnd: tge + YEAR, + serviceStart: 0n, + serviceCliff: 0n, + serviceEnd: 0n, + group: GROUP_PRE_SEED, + }); + + await expect( + token.connect(alice).transfer(bobAddress, 1n), + ).to.be.revertedWithCustomError(token, "LockedBalanceInvariant"); + }); + + it("accumulates Bridge SAFT linear unlock while the hold is still active", async function () { + const fixture = await loadFixture(deployWithUnlockedTransfers); + const { token, admin, alice } = fixture; + const aliceAddress = await alice.getAddress(); + const now = BigInt(await time.latest()); + const tge = now + DAY; + const totalAmount = ethers.parseEther("2400"); + + await token.connect(admin).setTgeTimestamp(tge); + await token + .connect(admin) + .mintAllocation(aliceAddress, totalAmount, "Bridge allocation"); + await token.connect(admin).createLockSchedule({ + account: aliceAddress, + amount: totalAmount, + tokenHoldUntil: tge + YEAR, + tokenUnlockStart: 0n, + tokenUnlockEnd: tge + 2n * YEAR, + serviceStart: 0n, + serviceCliff: 0n, + serviceEnd: 0n, + group: GROUP_BRIDGE, + }); + + await time.increaseTo(tge + YEAR / 2n); + expect(await token.lockedFloorOf(aliceAddress)).to.equal(totalAmount); + + await time.increaseTo(tge + YEAR); + expect(await token.lockedFloorOf(aliceAddress)).to.equal( + totalAmount / 2n, + ); + }); + + it("applies team service vesting as the stricter curve", async function () { + const fixture = await loadFixture(deployWithUnlockedTransfers); + const { token, admin, alice } = fixture; + const aliceAddress = await alice.getAddress(); + const now = BigInt(await time.latest()); + const tge = now + DAY; + const signing = tge - 180n * DAY; + const totalAmount = ethers.parseEther("4800"); + + await token.connect(admin).setTgeTimestamp(tge); + await token + .connect(admin) + .mintAllocation(aliceAddress, totalAmount, "Team allocation"); + await token.connect(admin).createLockSchedule({ + account: aliceAddress, + amount: totalAmount, + tokenHoldUntil: tge, + tokenUnlockStart: 0n, + tokenUnlockEnd: tge + 2n * YEAR, + serviceStart: signing, + serviceCliff: signing + YEAR, + serviceEnd: signing + 4n * YEAR, + group: GROUP_TEAM, + }); + + await time.increaseTo(signing + YEAR - DAY); + expect(await token.lockedFloorOf(aliceAddress)).to.equal(totalAmount); + + await time.increaseTo(signing + YEAR); + expect(await token.lockedFloorOf(aliceAddress)).to.equal( + totalAmount - totalAmount / 4n, + ); + }); + + it("creates lock schedules on transfers from approved CCA claim sources", async function () { + const { token, admin, alice, bob, claimSource } = await loadFixture( + deployWithUnlockedTransfers, + ); + const aliceAddress = await alice.getAddress(); + const bobAddress = await bob.getAddress(); + const claimSourceAddress = await claimSource.getAddress(); + const claimAmount = ethers.parseEther("500"); + + await token.connect(admin).setClaimSource(claimSourceAddress, true); + await token.connect(admin).setClaimLockProfile(aliceAddress, { + active: true, + holdDuration: 40n * DAY, + unlockDuration: 0n, + group: GROUP_CCA_REG_S, + }); + await token + .connect(admin) + .mintAllocation(claimSourceAddress, claimAmount, "CCA claim source"); + + await expect( + token.connect(claimSource).transfer(aliceAddress, claimAmount), + ).to.emit(token, "LockScheduleCreated"); + + expect(await token.lockedFloorOf(aliceAddress)).to.equal(claimAmount); + await expect( + token.connect(alice).transfer(bobAddress, 1n), + ).to.be.revertedWithCustomError(token, "LockedBalanceInvariant"); + + await time.increase(40n * DAY + 1n); + expect(await token.lockedFloorOf(aliceAddress)).to.equal(0n); + await token.connect(alice).transfer(bobAddress, claimAmount); + }); + + it("rejects claim-source transfers when the recipient has no active profile", async function () { + const { token, admin, alice, claimSource } = await loadFixture( + deployWithUnlockedTransfers, + ); + const claimSourceAddress = await claimSource.getAddress(); + const claimAmount = ethers.parseEther("1"); + + await token.connect(admin).setClaimSource(claimSourceAddress, true); + await token + .connect(admin) + .mintAllocation(claimSourceAddress, claimAmount, "CCA claim source"); + + await expect( + token + .connect(claimSource) + .transfer(await alice.getAddress(), claimAmount), + ) + .to.be.revertedWithCustomError(token, "ClaimLockProfileMissing") + .withArgs(await alice.getAddress()); + }); + + it("does not let admins create schedules beyond current controlled balance", async function () { + const { token, admin, alice } = await loadFixture( + deployWithUnlockedTransfers, + ); + const now = BigInt(await time.latest()); + const tge = now + DAY; + + await token.connect(admin).setTgeTimestamp(tge); + await expect( + token.connect(admin).createLockSchedule({ + account: await alice.getAddress(), + amount: ethers.parseEther("1"), + tokenHoldUntil: tge, + tokenUnlockStart: 0n, + tokenUnlockEnd: tge + YEAR, + serviceStart: 0n, + serviceCliff: 0n, + serviceEnd: 0n, + group: GROUP_PRE_SEED, + }), + ).to.be.revertedWithCustomError(token, "LockedBalanceInvariant"); + }); + + it("counts self-bonded and pending-exit ENCL toward the locked floor", async function () { + const signers = await ethers.getSigners(); + const [, beneficiary, slasher] = signers; + const beneficiaryAddress = await beneficiary.getAddress(); + const slasherAddress = await slasher.getAddress(); + const sys = await deployEnclaveSystem({ + useMockCiphernodeRegistry: true, + setupOperators: 0, + wireSlashingManager: false, + mintUsdcTo: [], + }); + const { bondingRegistry, licenseToken, owner } = sys; + const bondingRegistryAddress = await bondingRegistry.getAddress(); + const now = BigInt(await time.latest()); + const tge = now + DAY; + const totalAmount = ethers.parseEther("1000"); + const bondAmount = ethers.parseEther("800"); + const unbondAmount = ethers.parseEther("300"); + + await bondingRegistry.setSlashingManager(slasherAddress); + await licenseToken.setTgeTimestamp(tge); + await licenseToken.setBondingRegistry(bondingRegistryAddress); + await licenseToken.mintAllocation( + beneficiaryAddress, + totalAmount, + "Locked allocation", + ); + await licenseToken.createLockSchedule({ + account: beneficiaryAddress, + amount: totalAmount, + tokenHoldUntil: tge, + tokenUnlockStart: 0n, + tokenUnlockEnd: tge + YEAR, + serviceStart: 0n, + serviceCliff: 0n, + serviceEnd: 0n, + group: GROUP_PRE_SEED, + }); + + await licenseToken + .connect(beneficiary) + .approve(bondingRegistryAddress, bondAmount); + await bondingRegistry.connect(beneficiary).bondLicense(bondAmount); + + expect(await licenseToken.balanceOf(beneficiaryAddress)).to.equal( + totalAmount - bondAmount, + ); + expect(await licenseToken.totalBondedOf(beneficiaryAddress)).to.equal( + bondAmount, + ); + expect( + await licenseToken.transferableBalanceOf(beneficiaryAddress), + ).to.equal(0n); + + await bondingRegistry.connect(beneficiary).unbondLicense(unbondAmount); + expect(await licenseToken.totalBondedOf(beneficiaryAddress)).to.equal( + bondAmount, + ); + + await time.increase(SEVEN_DAYS + 1); + await bondingRegistry.connect(beneficiary).claimExits(0, unbondAmount); + expect(await licenseToken.totalBondedOf(beneficiaryAddress)).to.equal( + bondAmount - unbondAmount, + ); + expect(await licenseToken.balanceOf(beneficiaryAddress)).to.equal( + totalAmount - bondAmount + unbondAmount, + ); + + await expect( + licenseToken + .connect(beneficiary) + .transfer(await owner.getAddress(), ethers.parseEther("100")), + ).to.be.revertedWithCustomError(licenseToken, "LockedBalanceInvariant"); + }); + + it("encumbers later same-wallet tokens after a slash deficit", async function () { + const signers = await ethers.getSigners(); + const [, beneficiary, slasher, recipient] = signers; + const beneficiaryAddress = await beneficiary.getAddress(); + const recipientAddress = await recipient.getAddress(); + const slasherAddress = await slasher.getAddress(); + const sys = await deployEnclaveSystem({ + useMockCiphernodeRegistry: true, + setupOperators: 0, + wireSlashingManager: false, + mintUsdcTo: [], + }); + const { bondingRegistry, licenseToken } = sys; + const bondingRegistryAddress = await bondingRegistry.getAddress(); + const now = BigInt(await time.latest()); + const tge = now + DAY; + const totalAmount = ethers.parseEther("1000"); + const slashAmount = ethers.parseEther("400"); + + await bondingRegistry.setSlashingManager(slasherAddress); + await licenseToken.setTgeTimestamp(tge); + await licenseToken.setBondingRegistry(bondingRegistryAddress); + await licenseToken.mintAllocation( + beneficiaryAddress, + totalAmount, + "Locked allocation", + ); + await licenseToken.createLockSchedule({ + account: beneficiaryAddress, + amount: totalAmount, + tokenHoldUntil: tge, + tokenUnlockStart: 0n, + tokenUnlockEnd: tge + YEAR, + serviceStart: 0n, + serviceCliff: 0n, + serviceEnd: 0n, + group: GROUP_PRE_SEED, + }); + + await licenseToken + .connect(beneficiary) + .approve(bondingRegistryAddress, totalAmount); + await bondingRegistry.connect(beneficiary).bondLicense(totalAmount); + await bondingRegistry + .connect(slasher) + .slashLicenseBond( + beneficiaryAddress, + slashAmount, + ethers.encodeBytes32String("TEST_SLASH"), + ); + + await licenseToken.mintAllocation( + beneficiaryAddress, + slashAmount, + "Later unlocked top-up", + ); + await expect( + licenseToken.connect(beneficiary).transfer(recipientAddress, 1n), + ).to.be.revertedWithCustomError(licenseToken, "LockedBalanceInvariant"); + + await time.increaseTo(tge + YEAR); + await licenseToken.connect(beneficiary).transfer(recipientAddress, 1n); + }); + }); }); diff --git a/packages/enclave-contracts/test/Token/InterfoldVestingEscrow.spec.ts b/packages/enclave-contracts/test/Token/InterfoldVestingEscrow.spec.ts deleted file mode 100644 index d42fe9c5c3..0000000000 --- a/packages/enclave-contracts/test/Token/InterfoldVestingEscrow.spec.ts +++ /dev/null @@ -1,288 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. -import { expect } from "chai"; - -import { InterfoldVestingEscrow__factory as InterfoldVestingEscrowFactory } from "../../types"; -import { - SEVEN_DAYS, - deployEnclaveSystem, - ethers, - networkHelpers, -} from "../fixtures"; - -const { loadFixture, time } = networkHelpers; - -const DAY = 24n * 60n * 60n; -const YEAR = 365n * DAY; - -const GROUP_PRE_SEED = ethers.encodeBytes32String("PRE_SEED"); -const GROUP_BRIDGE = ethers.encodeBytes32String("BRIDGE"); -const GROUP_TEAM = ethers.encodeBytes32String("GG_TEAM"); - -describe("InterfoldVestingEscrow", function () { - async function setup() { - const signers = await ethers.getSigners(); - const [owner, beneficiary, operator, slasher] = signers; - const ownerAddress = await owner.getAddress(); - const beneficiaryAddress = await beneficiary.getAddress(); - const operatorAddress = await operator.getAddress(); - const slasherAddress = await slasher.getAddress(); - - const sys = await deployEnclaveSystem({ - useMockCiphernodeRegistry: true, - setupOperators: 0, - wireSlashingManager: false, - mintUsdcTo: [], - }); - const { bondingRegistry, licenseToken } = sys; - await bondingRegistry.setSlashingManager(slasherAddress); - - const now = BigInt(await time.latest()); - const tge = now + DAY; - const vestingEscrow = await new InterfoldVestingEscrowFactory(owner).deploy( - await licenseToken.getAddress(), - await bondingRegistry.getAddress(), - tge, - ownerAddress, - ); - await vestingEscrow.waitForDeployment(); - const vestingEscrowAddress = await vestingEscrow.getAddress(); - - await licenseToken.whitelistContracts( - await bondingRegistry.getAddress(), - vestingEscrowAddress, - ); - - await licenseToken.mintAllocation( - vestingEscrowAddress, - ethers.parseEther("1000000"), - "Interfold vesting escrow budget", - ); - - return { - owner, - beneficiary, - operator, - slasher, - beneficiaryAddress, - operatorAddress, - bondingRegistry, - licenseToken, - vestingEscrow, - tge, - }; - } - - async function createSchedule( - overrides: Partial<{ - beneficiary: string; - totalAmount: bigint; - tokenHoldUntil: bigint; - tokenUnlockStart: bigint; - tokenUnlockEnd: bigint; - serviceStart: bigint; - serviceCliff: bigint; - serviceEnd: bigint; - group: string; - }> = {}, - ) { - const fixture = await loadFixture(setup); - const { vestingEscrow, beneficiaryAddress, tge } = fixture; - const scheduleId = await vestingEscrow.nextScheduleId(); - const totalAmount = overrides.totalAmount ?? ethers.parseEther("2400"); - await vestingEscrow.createSchedule({ - beneficiary: overrides.beneficiary ?? beneficiaryAddress, - totalAmount, - tokenHoldUntil: overrides.tokenHoldUntil ?? tge, - tokenUnlockStart: overrides.tokenUnlockStart ?? tge, - tokenUnlockEnd: overrides.tokenUnlockEnd ?? tge + 2n * YEAR, - serviceStart: overrides.serviceStart ?? 0n, - serviceCliff: overrides.serviceCliff ?? 0n, - serviceEnd: overrides.serviceEnd ?? 0n, - group: overrides.group ?? GROUP_PRE_SEED, - }); - return { ...fixture, scheduleId, totalAmount }; - } - - it("releases pre-seed/seed/Legion/GG schedules linearly from TGE", async function () { - const { vestingEscrow, beneficiary, scheduleId, totalAmount, tge } = - await createSchedule(); - - await expect( - vestingEscrow.connect(beneficiary).claim(scheduleId, totalAmount), - ).to.be.revertedWithCustomError(vestingEscrow, "NothingClaimable"); - - await time.increaseTo(tge + YEAR); - - expect(await vestingEscrow.claimableAmount(scheduleId)).to.equal( - totalAmount / 2n, - ); - - await expect( - vestingEscrow.connect(beneficiary).claim(scheduleId, totalAmount / 2n), - ) - .to.emit(vestingEscrow, "TokensClaimed") - .withArgs(scheduleId, await beneficiary.getAddress(), totalAmount / 2n); - }); - - it("accumulates Bridge SAFT linear unlock during the holding period", async function () { - const fixture = await loadFixture(setup); - const { vestingEscrow, beneficiary, beneficiaryAddress, tge } = fixture; - const scheduleId = await vestingEscrow.nextScheduleId(); - const totalAmount = ethers.parseEther("2400"); - - await vestingEscrow.createSchedule({ - beneficiary: beneficiaryAddress, - totalAmount, - tokenHoldUntil: tge + YEAR, - tokenUnlockStart: tge, - tokenUnlockEnd: tge + 2n * YEAR, - serviceStart: 0n, - serviceCliff: 0n, - serviceEnd: 0n, - group: GROUP_BRIDGE, - }); - - await time.increaseTo(tge + YEAR / 2n); - await expect( - vestingEscrow.connect(beneficiary).claim(scheduleId, totalAmount), - ).to.be.revertedWithCustomError(vestingEscrow, "NothingClaimable"); - - await time.increaseTo(tge + YEAR); - expect(await vestingEscrow.claimableAmount(scheduleId)).to.equal( - totalAmount / 2n, - ); - }); - - it("applies GG team service vesting as a second stricter curve", async function () { - const fixture = await loadFixture(setup); - const { vestingEscrow, beneficiary, beneficiaryAddress, tge } = fixture; - const totalAmount = ethers.parseEther("4800"); - const signing = tge - 180n * DAY; - const scheduleId = await vestingEscrow.nextScheduleId(); - - await vestingEscrow.createSchedule({ - beneficiary: beneficiaryAddress, - totalAmount, - tokenHoldUntil: tge, - tokenUnlockStart: tge, - tokenUnlockEnd: tge + 2n * YEAR, - serviceStart: signing, - serviceCliff: signing + YEAR, - serviceEnd: signing + 4n * YEAR, - group: GROUP_TEAM, - }); - - await time.increaseTo(signing + YEAR - DAY); - await expect( - vestingEscrow.connect(beneficiary).claim(scheduleId, totalAmount), - ).to.be.revertedWithCustomError(vestingEscrow, "NothingClaimable"); - - await time.increaseTo(signing + YEAR); - expect(await vestingEscrow.claimableAmount(scheduleId)).to.equal( - totalAmount / 4n, - ); - }); - - it("lets a beneficiary bond locked tokens and reclaim them through the vesting escrow", async function () { - const { - vestingEscrow, - beneficiary, - operator, - operatorAddress, - bondingRegistry, - licenseToken, - scheduleId, - tge, - } = await createSchedule(); - const bondAmount = ethers.parseEther("1000"); - - await expect( - vestingEscrow - .connect(beneficiary) - .bondLockedTokens(scheduleId, operatorAddress, bondAmount), - ) - .to.emit(vestingEscrow, "LockedTokensBonded") - .withArgs( - scheduleId, - await beneficiary.getAddress(), - operatorAddress, - bondAmount, - ); - - expect(await bondingRegistry.getLicenseBond(operatorAddress)).to.equal( - bondAmount, - ); - expect((await vestingEscrow.getSchedule(scheduleId)).bondedAmount).to.equal( - bondAmount, - ); - - await bondingRegistry.connect(operator).unbondLicense(bondAmount); - await time.increase(SEVEN_DAYS + 1); - - await expect(bondingRegistry.connect(operator).claimExits(0, bondAmount)) - .to.emit(vestingEscrow, "BondedTokensReturned") - .withArgs(scheduleId, operatorAddress, bondAmount); - - expect((await vestingEscrow.getSchedule(scheduleId)).bondedAmount).to.equal( - 0n, - ); - - await time.increaseTo(tge + 2n * YEAR); - await vestingEscrow.connect(beneficiary).claim(scheduleId, bondAmount); - expect( - await licenseToken.balanceOf(await beneficiary.getAddress()), - ).to.equal(bondAmount); - }); - - it("slashes license bond sources in LIFO order across direct and locked bonds", async function () { - const { - vestingEscrow, - beneficiary, - operator, - slasher, - operatorAddress, - bondingRegistry, - licenseToken, - scheduleId, - } = await createSchedule(); - - const lockedBond = ethers.parseEther("400"); - const directBond = ethers.parseEther("200"); - await vestingEscrow - .connect(beneficiary) - .bondLockedTokens(scheduleId, operatorAddress, lockedBond); - - await licenseToken.mintAllocation( - operatorAddress, - directBond, - "Operator direct bond", - ); - await licenseToken - .connect(operator) - .approve(await bondingRegistry.getAddress(), directBond); - await bondingRegistry.connect(operator).bondLicense(directBond); - - await expect( - bondingRegistry - .connect(slasher) - .slashLicenseBond( - operatorAddress, - ethers.parseEther("250"), - ethers.encodeBytes32String("TEST_SLASH"), - ), - ) - .to.emit(vestingEscrow, "BondedTokensSlashed") - .withArgs(scheduleId, operatorAddress, ethers.parseEther("50")); - - expect((await vestingEscrow.getSchedule(scheduleId)).bondedAmount).to.equal( - ethers.parseEther("350"), - ); - expect(await bondingRegistry.getLicenseBond(operatorAddress)).to.equal( - ethers.parseEther("350"), - ); - }); -}); diff --git a/templates/default/deployed_contracts.json b/templates/default/deployed_contracts.json index 6598c0ea10..031c0e58dd 100644 --- a/templates/default/deployed_contracts.json +++ b/templates/default/deployed_contracts.json @@ -72,16 +72,6 @@ "blockNumber": 12, "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, - "InterfoldVestingEscrow": { - "constructorArgs": { - "token": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", - "bondingRegistry": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", - "tgeTimestamp": "1779922372", - "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "blockNumber": 19, - "address": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" - }, "Enclave": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", diff --git a/templates/default/hardhat.config.ts b/templates/default/hardhat.config.ts index ffb5a8311e..666da37a44 100644 --- a/templates/default/hardhat.config.ts +++ b/templates/default/hardhat.config.ts @@ -124,7 +124,6 @@ const config: HardhatUserConfig = { '@enclave-e3/contracts/contracts/E3RefundManager.sol', '@enclave-e3/contracts/contracts/token/EnclaveToken.sol', '@enclave-e3/contracts/contracts/token/EnclaveTicketToken.sol', - '@enclave-e3/contracts/contracts/token/InterfoldVestingEscrow.sol', '@enclave-e3/contracts/contracts/verifiers/bfv/BfvDecryptionVerifier.sol', '@enclave-e3/contracts/contracts/verifiers/bfv/BfvPkVerifier.sol', '@enclave-e3/contracts/contracts/verifiers/bfv/honk/DkgAggregatorVerifier.sol', From d7e0aef3debd231ef021875f3fb3b224798ae766 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 29 May 2026 19:33:20 +0500 Subject: [PATCH 06/24] fix: contract addresses --- examples/CRISP/enclave.config.yaml | 16 ++-- .../crisp-contracts/deployed_contracts.json | 76 +++++++++---------- examples/CRISP/server/.env.example | 4 +- templates/default/deployed_contracts.json | 62 +++++++-------- templates/default/enclave.config.yaml | 14 ++-- 5 files changed, 86 insertions(+), 86 deletions(-) diff --git a/examples/CRISP/enclave.config.yaml b/examples/CRISP/enclave.config.yaml index 58a6ff4221..3eeab69d7f 100644 --- a/examples/CRISP/enclave.config.yaml +++ b/examples/CRISP/enclave.config.yaml @@ -5,23 +5,23 @@ chains: rpc_url: ws://localhost:8545 contracts: e3_program: - address: "0xc351628EB244ec633d5f21fBD6621e1a683B1181" - deploy_block: 44 + address: "0xFD471836031dc5108809D173A067e8486B9047A3" + deploy_block: 43 enclave: - address: "0x9A676e781A523b5d0C0e43731313A708CB607508" - deploy_block: 20 + address: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" + deploy_block: 19 ciphernode_registry: address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" - deploy_block: 13 + deploy_block: 11 bonding_registry: address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" - deploy_block: 14 + deploy_block: 12 slashing_manager: address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" - deploy_block: 12 + deploy_block: 10 fee_token: address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" - deploy_block: 8 + deploy_block: 6 - name: "sepolia" enabled: false # Public Sepolia WebSocket endpoint (see repo docs for the recommended default). diff --git a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json index 977de9a7f9..199229ec1f 100644 --- a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json +++ b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json @@ -148,21 +148,21 @@ }, "localhost": { "PoseidonT3": { - "blockNumber": 7, + "blockNumber": 5, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 8, + "blockNumber": 6, "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, "EnclaveToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 9, + "blockNumber": 7, "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "EnclaveTicketToken": { @@ -171,7 +171,7 @@ "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 11, + "blockNumber": 9, "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, "SlashingManager": { @@ -179,7 +179,7 @@ "initialDelay": "172800", "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 12, + "blockNumber": 10, "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "CiphernodeRegistryOwnable": { @@ -194,7 +194,7 @@ "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" }, - "blockNumber": 13, + "blockNumber": 11, "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, "BondingRegistry": { @@ -216,7 +216,7 @@ "proxyAdminAddress": "0x8aCd85898458400f7Db866d53FCFF6f0D49741FF", "implementationAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, - "blockNumber": 14, + "blockNumber": 12, "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, "Enclave": { @@ -232,66 +232,66 @@ "proxyRecords": { "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c8530000000000000000000000008a791620dd6260079bf849dc5567adc3f2fdc3180000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508", - "proxyAdminAddress": "0x8e80FFe6Dc044F4A766Afd6e5a8732Fe0977A493", - "implementationAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + "proxyAddress": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "proxyAdminAddress": "0x524F04724632eED237cbA3c37272e018b3A7967e", + "implementationAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508" }, - "blockNumber": 20, - "address": "0x9A676e781A523b5d0C0e43731313A708CB607508" + "blockNumber": 19, + "address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" }, "E3RefundManager": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "enclave": "0x9A676e781A523b5d0C0e43731313A708CB607508", + "enclave": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", "treasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, "proxyRecords": { - "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000009a676e781a523b5d0c0e43731313a708cb607508000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000b306bf915c4d645ff596e518faf3f9669b97016000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1", - "proxyAdminAddress": "0x24B3c7704709ed1491473F30393FFc93cFB0FC34", - "implementationAddress": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" + "proxyAddress": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE", + "proxyAdminAddress": "0x2b961E3959b79326A8e7F64Ef0d2d825707669b5", + "implementationAddress": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" }, - "blockNumber": 22, - "address": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + "blockNumber": 21, + "address": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" }, "MockComputeProvider": { - "blockNumber": 36, - "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" + "blockNumber": 35, + "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" }, "MockDecryptionVerifier": { - "blockNumber": 37, - "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" + "blockNumber": 36, + "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" }, "MockPkVerifier": { - "blockNumber": 38, - "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" + "blockNumber": 37, + "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" }, "MockE3Program": { - "blockNumber": 39, - "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" + "blockNumber": 38, + "address": "0xb7278A61aa25c888815aFC32Ad3cC52fF24fE575" }, "MockRISC0Verifier": { - "address": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", - "blockNumber": 43 + "address": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", + "blockNumber": 42 }, "HonkVerifier": { - "address": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", - "blockNumber": 44 + "address": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", + "blockNumber": 43 }, "CRISPProgram": { - "address": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", - "blockNumber": 44, + "address": "0xFD471836031dc5108809D173A067e8486B9047A3", + "blockNumber": 43, "constructorArgs": { - "enclave": "0x9A676e781A523b5d0C0e43731313A708CB607508", - "verifierAddress": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", - "honkVerifierAddress": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", + "enclave": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "verifierAddress": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", + "honkVerifierAddress": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", "imageId": "0x23734b77b0f76e85623a88d7a82f24c34c94834f2501964ea123b7a2027013a2" } }, "MockVotingToken": { - "address": "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc", - "blockNumber": 46 + "address": "0x1429859428C0aBc9C2C47C8Ee9FBaf82cFA0F20f", + "blockNumber": 45 } } } \ No newline at end of file diff --git a/examples/CRISP/server/.env.example b/examples/CRISP/server/.env.example index 93231f3e6c..237d673f94 100644 --- a/examples/CRISP/server/.env.example +++ b/examples/CRISP/server/.env.example @@ -15,9 +15,9 @@ CRON_API_KEY=1234567890 # Enclave stack: updated automatically on each `pnpm dev:up` deploy (do not edit by hand unless debugging). # Stale E3_PROGRAM_ADDRESS causes requestE3 to revert with empty data `0x`. -ENCLAVE_ADDRESS=0x9A676e781A523b5d0C0e43731313A708CB607508 +ENCLAVE_ADDRESS=0x0B306BF915C4d645ff596e518fAf3F9669b97016 FEE_TOKEN_ADDRESS=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -E3_PROGRAM_ADDRESS=0xc351628EB244ec633d5f21fBD6621e1a683B1181 +E3_PROGRAM_ADDRESS=0xFD471836031dc5108809D173A067e8486B9047A3 CIPHERNODE_REGISTRY_ADDRESS=0xa513E6E4b8f2a923D98304ec87F64353C4D5C853 # CRISP voting eligibility token (MockVotingToken) — NOT the fee token above CRISP_VOTING_TOKEN=0x5081a39b8A5f0E35a8D959395a630b68B74Dd30f diff --git a/templates/default/deployed_contracts.json b/templates/default/deployed_contracts.json index 031c0e58dd..0390e54eb5 100644 --- a/templates/default/deployed_contracts.json +++ b/templates/default/deployed_contracts.json @@ -1,21 +1,21 @@ { "localhost": { "PoseidonT3": { - "blockNumber": 4, + "blockNumber": 6, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 5, + "blockNumber": 7, "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, "EnclaveToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 6, + "blockNumber": 8, "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "EnclaveTicketToken": { @@ -24,7 +24,7 @@ "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 8, + "blockNumber": 10, "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, "SlashingManager": { @@ -32,7 +32,7 @@ "initialDelay": "172800", "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 9, + "blockNumber": 11, "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "CiphernodeRegistryOwnable": { @@ -47,7 +47,7 @@ "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" }, - "blockNumber": 11, + "blockNumber": 12, "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, "BondingRegistry": { @@ -69,7 +69,7 @@ "proxyAdminAddress": "0x8aCd85898458400f7Db866d53FCFF6f0D49741FF", "implementationAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, - "blockNumber": 12, + "blockNumber": 14, "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, "Enclave": { @@ -85,52 +85,52 @@ "proxyRecords": { "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c8530000000000000000000000008a791620dd6260079bf849dc5567adc3f2fdc3180000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508", - "proxyAdminAddress": "0x8e80FFe6Dc044F4A766Afd6e5a8732Fe0977A493", - "implementationAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + "proxyAddress": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "proxyAdminAddress": "0x524F04724632eED237cbA3c37272e018b3A7967e", + "implementationAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508" }, - "blockNumber": 16, - "address": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + "blockNumber": 21, + "address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" }, "E3RefundManager": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "enclave": "0x9A676e781A523b5d0C0e43731313A708CB607508", + "enclave": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", "treasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, "proxyRecords": { - "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000009a676e781a523b5d0c0e43731313a708cb607508000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000b306bf915c4d645ff596e518faf3f9669b97016000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1", - "proxyAdminAddress": "0x24B3c7704709ed1491473F30393FFc93cFB0FC34", - "implementationAddress": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" + "proxyAddress": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE", + "proxyAdminAddress": "0x2b961E3959b79326A8e7F64Ef0d2d825707669b5", + "implementationAddress": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" }, - "blockNumber": 18, - "address": "0x9A676e781A523b5d0C0e43731313A708CB607508" + "blockNumber": 23, + "address": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" }, "MockComputeProvider": { - "blockNumber": 51, - "address": "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00" + "blockNumber": 56, + "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" }, "MockDecryptionVerifier": { - "blockNumber": 53, - "address": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570" + "blockNumber": 58, + "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" }, "MockPkVerifier": { - "blockNumber": 54, - "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" + "blockNumber": 59, + "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" }, "MockE3Program": { - "blockNumber": 55, - "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" + "blockNumber": 60, + "address": "0xb7278A61aa25c888815aFC32Ad3cC52fF24fE575" }, "ImageID": { - "address": "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1", - "blockNumber": 60 + "address": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", + "blockNumber": 65 }, "MyProgram": { - "address": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", - "blockNumber": 62 + "address": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", + "blockNumber": 67 } } } \ No newline at end of file diff --git a/templates/default/enclave.config.yaml b/templates/default/enclave.config.yaml index 2b8acb61b5..486c6208d5 100644 --- a/templates/default/enclave.config.yaml +++ b/templates/default/enclave.config.yaml @@ -3,23 +3,23 @@ chains: rpc_url: "ws://localhost:8545" contracts: e3_program: - address: "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650" - deploy_block: 69 + address: "0xc351628EB244ec633d5f21fBD6621e1a683B1181" + deploy_block: 67 enclave: - address: "0x9A676e781A523b5d0C0e43731313A708CB607508" - deploy_block: 22 + address: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" + deploy_block: 21 ciphernode_registry: address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" - deploy_block: 15 + deploy_block: 12 bonding_registry: address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" - deploy_block: 16 + deploy_block: 14 slashing_manager: address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" deploy_block: 11 fee_token: address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" - deploy_block: 9 + deploy_block: 7 program: dev: true From 0232cf39b94bf7247f83f8c2ce495e99e762351e Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sat, 30 May 2026 02:08:31 +0500 Subject: [PATCH 07/24] fix: review comments --- .../contracts/token/EnclaveToken.sol | 38 +++++-- .../test/Token/EnclaveToken.spec.ts | 104 ++++++++++++++++++ 2 files changed, 135 insertions(+), 7 deletions(-) diff --git a/packages/enclave-contracts/contracts/token/EnclaveToken.sol b/packages/enclave-contracts/contracts/token/EnclaveToken.sol index a93a6ef1ad..76023b12ef 100644 --- a/packages/enclave-contracts/contracts/token/EnclaveToken.sol +++ b/packages/enclave-contracts/contracts/token/EnclaveToken.sol @@ -543,7 +543,11 @@ contract EnclaveToken is super._update(from, to, value); if (from != address(0) && to != address(0)) { - if (approvedClaimSources[from] && value != 0) { + if ( + approvedClaimSources[from] && + value != 0 && + from != address(bondingRegistry) + ) { _addClaimLock(to, value); } _enforceLockedFloor(from); @@ -591,8 +595,7 @@ contract EnclaveToken is } if (amount > type(uint128).max) revert InvalidLockSchedule(); - if (block.timestamp > type(uint64).max) revert InvalidLockSchedule(); - uint64 currentTimestamp = uint64(block.timestamp); + uint64 currentTimestamp = _currentTimestamp(); uint256 holdUntil = uint256(currentTimestamp) + profile.holdDuration; uint256 unlockEnd = holdUntil + profile.unlockDuration; @@ -622,12 +625,17 @@ contract EnclaveToken is ) internal returns (uint256 scheduleId) { LockSchedule[] storage schedules = _lockSchedules[account]; uint256 len = schedules.length; - if (len >= MAX_LOCK_SCHEDULES) revert MaxLockSchedulesExceeded(); + if (len < MAX_LOCK_SCHEDULES) { + schedules.push(schedule); + scheduleId = len; + } else { + scheduleId = _reclaimUnlockedScheduleSlot(schedules); + schedules[scheduleId] = schedule; + } - schedules.push(schedule); emit LockScheduleCreated( account, - len, + scheduleId, schedule.group, uint256(schedule.amount), schedule.tokenHoldUntil, @@ -637,7 +645,18 @@ contract EnclaveToken is schedule.serviceCliff, schedule.serviceEnd ); - return len; + } + + function _reclaimUnlockedScheduleSlot( + LockSchedule[] storage schedules + ) internal view returns (uint256 scheduleId) { + uint64 currentTimestamp = _currentTimestamp(); + uint256 len = schedules.length; + for (uint256 i = 0; i < len; i++) { + if (_lockedAmount(schedules[i], currentTimestamp) == 0) return i; + } + + revert MaxLockSchedulesExceeded(); } function _setClaimLockProfile( @@ -671,6 +690,11 @@ contract EnclaveToken is return configuredTgeTimestamp; } + function _currentTimestamp() internal view returns (uint64) { + if (block.timestamp > type(uint64).max) revert InvalidLockSchedule(); + return uint64(block.timestamp); + } + function _validateLockSchedule( uint64 tokenUnlockStart, uint64 tokenUnlockEnd, diff --git a/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts b/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts index b65b8d2346..ce5cb7275e 100644 --- a/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts +++ b/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts @@ -345,6 +345,71 @@ describe("EnclaveToken", function () { await token.connect(alice).transfer(bobAddress, claimAmount); }); + it("reuses fully unlocked schedule slots at the schedule cap", async function () { + const { token, admin, alice } = await loadFixture( + deployWithUnlockedTransfers, + ); + const aliceAddress = await alice.getAddress(); + const now = BigInt(await time.latest()); + const unlockedAmount = ethers.parseEther("1"); + const activeAmount = ethers.parseEther("2"); + + await token + .connect(admin) + .mintAllocation( + aliceAddress, + unlockedAmount * 64n + activeAmount, + "Schedule capacity", + ); + + for (let i = 0; i < 64; i++) { + await token.connect(admin).createLockSchedule({ + account: aliceAddress, + amount: unlockedAmount, + tokenHoldUntil: now, + tokenUnlockStart: now, + tokenUnlockEnd: now, + serviceStart: 0n, + serviceCliff: 0n, + serviceEnd: 0n, + group: GROUP_PRE_SEED, + }); + } + + expect(await token.lockScheduleCount(aliceAddress)).to.equal(64n); + expect(await token.lockedFloorOf(aliceAddress)).to.equal(0n); + + await expect( + token.connect(admin).createLockSchedule({ + account: aliceAddress, + amount: activeAmount, + tokenHoldUntil: now + DAY, + tokenUnlockStart: now + DAY, + tokenUnlockEnd: now + 2n * DAY, + serviceStart: 0n, + serviceCliff: 0n, + serviceEnd: 0n, + group: GROUP_PRE_SEED, + }), + ) + .to.emit(token, "LockScheduleCreated") + .withArgs( + aliceAddress, + 0n, + GROUP_PRE_SEED, + activeAmount, + now + DAY, + now + DAY, + now + 2n * DAY, + 0n, + 0n, + 0n, + ); + + expect(await token.lockScheduleCount(aliceAddress)).to.equal(64n); + expect(await token.lockedFloorOf(aliceAddress)).to.equal(activeAmount); + }); + it("rejects claim-source transfers when the recipient has no active profile", async function () { const { token, admin, alice, claimSource } = await loadFixture( deployWithUnlockedTransfers, @@ -366,6 +431,45 @@ describe("EnclaveToken", function () { .withArgs(await alice.getAddress()); }); + it("does not create claim locks for BondingRegistry exit payouts", async function () { + const signers = await ethers.getSigners(); + const [, beneficiary] = signers; + const beneficiaryAddress = await beneficiary.getAddress(); + const sys = await deployEnclaveSystem({ + useMockCiphernodeRegistry: true, + setupOperators: 0, + mintUsdcTo: [], + }); + const { bondingRegistry, licenseToken } = sys; + const bondingRegistryAddress = await bondingRegistry.getAddress(); + const bondAmount = ethers.parseEther("100"); + const unbondAmount = ethers.parseEther("25"); + + await licenseToken.setBondingRegistry(bondingRegistryAddress); + await licenseToken.setClaimSource(bondingRegistryAddress, true); + await licenseToken.mintAllocation( + beneficiaryAddress, + bondAmount, + "License bond", + ); + await licenseToken + .connect(beneficiary) + .approve(bondingRegistryAddress, bondAmount); + + await bondingRegistry.connect(beneficiary).bondLicense(bondAmount); + await bondingRegistry.connect(beneficiary).unbondLicense(unbondAmount); + + await time.increase(SEVEN_DAYS + 1); + await bondingRegistry.connect(beneficiary).claimExits(0, unbondAmount); + + expect(await licenseToken.lockScheduleCount(beneficiaryAddress)).to.equal( + 0n, + ); + expect(await licenseToken.balanceOf(beneficiaryAddress)).to.equal( + unbondAmount, + ); + }); + it("does not let admins create schedules beyond current controlled balance", async function () { const { token, admin, alice } = await loadFixture( deployWithUnlockedTransfers, From 42a30ca332579e4f4056222dd83f2addd9ba13aa Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sat, 30 May 2026 02:12:25 +0500 Subject: [PATCH 08/24] fix: conflicts --- examples/CRISP/enclave.config.yaml | 12 +- .../crisp-contracts/deployed_contracts.json | 251 ++---------------- templates/default/deployed_contracts.json | 24 +- templates/default/enclave.config.yaml | 2 +- 4 files changed, 26 insertions(+), 263 deletions(-) diff --git a/examples/CRISP/enclave.config.yaml b/examples/CRISP/enclave.config.yaml index 095416c39b..5f3cb9254b 100644 --- a/examples/CRISP/enclave.config.yaml +++ b/examples/CRISP/enclave.config.yaml @@ -6,22 +6,22 @@ chains: contracts: e3_program: address: "0xFD471836031dc5108809D173A067e8486B9047A3" - deploy_block: 43 + deploy_block: 42 enclave: address: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" - deploy_block: 19 + deploy_block: 18 ciphernode_registry: address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" - deploy_block: 11 + deploy_block: 10 bonding_registry: address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" - deploy_block: 12 + deploy_block: 11 slashing_manager: address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" - deploy_block: 10 + deploy_block: 9 fee_token: address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" - deploy_block: 4 + deploy_block: 5 program: dev: true # risc0: diff --git a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json index 9c59973288..5af0879d71 100644 --- a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json +++ b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json @@ -1,170 +1,21 @@ { - "sepolia": { - "PoseidonT3": { - "blockNumber": 10939899, - "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" - }, - "MockUSDC": { - "constructorArgs": { - "initialSupply": "1000000" - }, - "blockNumber": 10939865, - "address": "0x2721Cdf281d40744aD567cBf3e7100F60bcbAE79" - }, - "EnclaveToken": { - "constructorArgs": { - "owner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B" - }, - "blockNumber": 10939866, - "address": "0xbAb3220FD04a193a192F07879673597Cd695Cb03" - }, - "EnclaveTicketToken": { - "constructorArgs": { - "baseToken": "0x2721Cdf281d40744aD567cBf3e7100F60bcbAE79", - "registry": "0x0000000000000000000000000000000000000001", - "owner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B" - }, - "blockNumber": 10939867, - "address": "0x2446f2AC9632f17af96053e48dEDff44b50711Ea" - }, - "SlashingManager": { - "constructorArgs": { - "initialDelay": "172800", - "admin": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B" - }, - "blockNumber": 10939868, - "address": "0x0553387EE0992Fe339579728B6c777164fD1de40" - }, - "CiphernodeRegistryOwnable": { - "constructorArgs": { - "owner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "submissionWindow": "10" - }, - "proxyRecords": { - "initData": "0xcd6dc6870000000000000000000000008837e47c4bb520ade83aab761c3b60679443af1b000000000000000000000000000000000000000000000000000000000000000a", - "initialOwner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "proxyAddress": "0x4D707127F72a216EA116AF0B4262dD7382F84259", - "proxyAdminAddress": "0x080DD900b6471f5850105E9023AEcbF5857B6bCB", - "implementationAddress": "0xE5E77dE44e71a535D8376dB650447ab9AB3ADE92" - }, - "blockNumber": 10939869, - "address": "0x4D707127F72a216EA116AF0B4262dD7382F84259" - }, - "BondingRegistry": { - "constructorArgs": { - "owner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "ticketToken": "0x2446f2AC9632f17af96053e48dEDff44b50711Ea", - "licenseToken": "0xbAb3220FD04a193a192F07879673597Cd695Cb03", - "registry": "0x4D707127F72a216EA116AF0B4262dD7382F84259", - "slashedFundsTreasury": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "ticketPrice": "10000000", - "licenseRequiredBond": "100000000000000000000", - "minTicketBalance": "1", - "exitDelay": "604800" - }, - "proxyRecords": { - "initData": "0x7333fa820000000000000000000000008837e47c4bb520ade83aab761c3b60679443af1b0000000000000000000000002446f2ac9632f17af96053e48dedff44b50711ea000000000000000000000000bab3220fd04a193a192f07879673597cd695cb030000000000000000000000004d707127f72a216ea116af0b4262dd7382f842590000000000000000000000008837e47c4bb520ade83aab761c3b60679443af1b00000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", - "initialOwner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "proxyAddress": "0xb9b64c5e0a30f38ed33760f299613087aAe87283", - "proxyAdminAddress": "0x2db58f52cA92516d079042Bc14D3C55c9839794e", - "implementationAddress": "0x871E4390995301A0918b1f559b0f97782063D682" - }, - "blockNumber": 10939870, - "address": "0xb9b64c5e0a30f38ed33760f299613087aAe87283" - }, - "Enclave": { - "constructorArgs": { - "owner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "registry": "0x4D707127F72a216EA116AF0B4262dD7382F84259", - "bondingRegistry": "0xb9b64c5e0a30f38ed33760f299613087aAe87283", - "e3RefundManager": "0x0000000000000000000000000000000000000001", -<<<<<<< HEAD - "feeToken": "0x9B1820D75bb09433D17C674A289fc6dD53e9c389", - "maxDuration": "2592000", - "timeoutConfig": "{\"committeeFormationWindow\":3600,\"dkgWindow\":7200,\"computeWindow\":86400,\"decryptionWindow\":3600}" - }, - "proxyRecords": { - "initData": "0x4d600e5d0000000000000000000000008837e47c4bb520ade83aab761c3b60679443af1b000000000000000000000000497feea9abb72229aab1584c22b5416ff128926b000000000000000000000000788046999530304dde121e19ed456180aca6b7c100000000000000000000000000000000000000000000000000000000000000010000000000000000000000009b1820d75bb09433d17c674a289fc6dd53e9c3890000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", - "initialOwner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "proxyAddress": "0xB47B267876B60a06138Bc9dfCee7aa3E26907CCB", - "proxyAdminAddress": "0x24f01F6a6AbD1e011D585bd0C8bAde8A74788b66", - "implementationAddress": "0x5BcC26D32b6E6F0ACcb87ECDE43d7E8F74C55025" - }, - "blockNumber": 10697349, - "address": "0xB47B267876B60a06138Bc9dfCee7aa3E26907CCB" - }, - "E3RefundManager": { - "constructorArgs": { - "owner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "enclave": "0xB47B267876B60a06138Bc9dfCee7aa3E26907CCB", - "treasury": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B" - }, - "proxyRecords": { - "initData": "0xc0c53b8b0000000000000000000000008837e47c4bb520ade83aab761c3b60679443af1b000000000000000000000000b47b267876b60a06138bc9dfcee7aa3e26907ccb0000000000000000000000008837e47c4bb520ade83aab761c3b60679443af1b", - "initialOwner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "proxyAddress": "0x91eebD89bb00CBE9E5Eabf2a01a22704f88AD098", - "proxyAdminAddress": "0x8a549f0Aa95Cf10576aD6c364183A0A2316AC5F3", - "implementationAddress": "0xaFB096E53C6c5eF18A96def5d842c2c2c82Fe28b" - }, - "blockNumber": 10697351, - "address": "0x91eebD89bb00CBE9E5Eabf2a01a22704f88AD098" - }, - "MockComputeProvider": { - "blockNumber": 10697354, - "address": "0xa0BB5B87A8cfbf59057F30E988010A08D984faA4" - }, - "MockDecryptionVerifier": { - "blockNumber": 10697355, - "address": "0x5875FaCfb96D4d60d0212fa1c84B65a36c1250DB" - }, - "MockPkVerifier": { - "blockNumber": 10697356, - "address": "0x18C81901c6Fd139e67647054Ebe593756e535185" - }, - "MockE3Program": { - "blockNumber": 10697357, - "address": "0x9D55635A78B96A72ee540bdE376722350e9B8B70" - }, - "MockRISC0Verifier": { - "address": "0x8aE4111B4B06fa5c02Ce6982E53507041c6e5ff8", - "blockNumber": 10697381 - }, - "HonkVerifier": { - "address": "0x4c60BA20E3321EB23E1b222B1fb0ad54c48972Dc", - "blockNumber": 10697382 - }, - "CRISPProgram": { - "address": "0xba3B07aBFd0B8cad68aa1E946CC7AF5C1B1c8B5D", - "blockNumber": 10697382, - "constructorArgs": { - "enclave": "0xB47B267876B60a06138Bc9dfCee7aa3E26907CCB", - "verifierAddress": "0x8aE4111B4B06fa5c02Ce6982E53507041c6e5ff8", - "honkVerifierAddress": "0x4c60BA20E3321EB23E1b222B1fb0ad54c48972Dc", - "imageId": "0x23734b77b0f76e85623a88d7a82f24c34c94834f2501964ea123b7a2027013a2" - } - }, - "MockVotingToken": { - "address": "0xA2Ced5053E66BA1E42583ffC996038A24110fEc7", - "blockNumber": 10697384 - } - }, "localhost": { "PoseidonT3": { - "blockNumber": 5, + "blockNumber": 4, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 6, + "blockNumber": 5, "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, "EnclaveToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 7, + "blockNumber": 6, "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "EnclaveTicketToken": { @@ -173,7 +24,7 @@ "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 9, + "blockNumber": 8, "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, "SlashingManager": { @@ -181,7 +32,7 @@ "initialDelay": "172800", "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 10, + "blockNumber": 9, "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "CiphernodeRegistryOwnable": { @@ -196,7 +47,7 @@ "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" }, - "blockNumber": 11, + "blockNumber": 10, "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, "BondingRegistry": { @@ -218,7 +69,7 @@ "proxyAdminAddress": "0x8aCd85898458400f7Db866d53FCFF6f0D49741FF", "implementationAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, - "blockNumber": 12, + "blockNumber": 11, "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, "Enclave": { @@ -228,21 +79,17 @@ "bondingRegistry": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", "e3RefundManager": "0x0000000000000000000000000000000000000001", "feeToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", -======= - "feeToken": "0x2721Cdf281d40744aD567cBf3e7100F60bcbAE79", ->>>>>>> main "maxDuration": "2592000", "timeoutConfig": "{\"dkgWindow\":7200,\"computeWindow\":86400,\"decryptionWindow\":3600}" }, "proxyRecords": { -<<<<<<< HEAD "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c8530000000000000000000000008a791620dd6260079bf849dc5567adc3f2fdc3180000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "proxyAddress": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", "proxyAdminAddress": "0x524F04724632eED237cbA3c37272e018b3A7967e", "implementationAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508" }, - "blockNumber": 19, + "blockNumber": 18, "address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" }, "E3RefundManager": { @@ -258,110 +105,46 @@ "proxyAdminAddress": "0x2b961E3959b79326A8e7F64Ef0d2d825707669b5", "implementationAddress": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" }, - "blockNumber": 21, + "blockNumber": 20, "address": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" }, "MockComputeProvider": { - "blockNumber": 35, + "blockNumber": 34, "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" -======= - "initData": "0x4d600e5d0000000000000000000000008837e47c4bb520ade83aab761c3b60679443af1b0000000000000000000000004d707127f72a216ea116af0b4262dd7382f84259000000000000000000000000b9b64c5e0a30f38ed33760f299613087aae8728300000000000000000000000000000000000000000000000000000000000000010000000000000000000000002721cdf281d40744ad567cbf3e7100f60bcbae790000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", - "initialOwner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "proxyAddress": "0x670eFE043d1D340148037b4b76c4F9dfED294309", - "proxyAdminAddress": "0xA2C502EaDeEa534A6a772656a339570381fCB9b5", - "implementationAddress": "0x2cb325A86A543A39752405588c9D59c23c0ea7B8" - }, - "blockNumber": 10939874, - "address": "0x670eFE043d1D340148037b4b76c4F9dfED294309" - }, - "E3RefundManager": { - "constructorArgs": { - "owner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "enclave": "0x670eFE043d1D340148037b4b76c4F9dfED294309", - "treasury": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B" - }, - "proxyRecords": { - "initData": "0xc0c53b8b0000000000000000000000008837e47c4bb520ade83aab761c3b60679443af1b000000000000000000000000670efe043d1d340148037b4b76c4f9dfed2943090000000000000000000000008837e47c4bb520ade83aab761c3b60679443af1b", - "initialOwner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "proxyAddress": "0xD327ec50C7a8909f90DE15fe53872e88B4948Ee0", - "proxyAdminAddress": "0x4CfC7BCA035bFA28be27fB5aeBc87bDA86E7e240", - "implementationAddress": "0xcfD77dB6899Df39969Db2B3f69eA755cFefAf68d" - }, - "blockNumber": 10939876, - "address": "0xD327ec50C7a8909f90DE15fe53872e88B4948Ee0" - }, - "MockComputeProvider": { - "blockNumber": 10939891, - "address": "0x1A5BFB6b2d26E2EBeA6fd92B29Ab392Ab961b04F" }, "MockDecryptionVerifier": { - "blockNumber": 10939892, - "address": "0x52E2e2d903d68d9611599d05bA9E856805bA563F" - }, - "MockPkVerifier": { - "blockNumber": 10939893, - "address": "0x3d9979A65A36B1A339f213f0Ae545555AebD16DD" - }, - "MockE3Program": { - "blockNumber": 10939894, - "address": "0xB37D0DFc2967423786466489A5E44fe6b3b0c116" ->>>>>>> main - }, - "MockDecryptionVerifier": { - "blockNumber": 36, + "blockNumber": 35, "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" }, "MockPkVerifier": { - "blockNumber": 37, + "blockNumber": 36, "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" }, "MockE3Program": { - "blockNumber": 38, + "blockNumber": 37, "address": "0xb7278A61aa25c888815aFC32Ad3cC52fF24fE575" }, "MockRISC0Verifier": { -<<<<<<< HEAD "address": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", - "blockNumber": 42 + "blockNumber": 41 }, "HonkVerifier": { "address": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", - "blockNumber": 43 + "blockNumber": 42 }, "CRISPProgram": { "address": "0xFD471836031dc5108809D173A067e8486B9047A3", - "blockNumber": 43, + "blockNumber": 42, "constructorArgs": { "enclave": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", "verifierAddress": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", "honkVerifierAddress": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", -======= - "address": "0x05c415E39AF18B2cc118fFC98258afe38636EAb0", - "blockNumber": 10939931 - }, - "HonkVerifier": { - "address": "0x70AA319AA8a305Eb65a78b0791b2526BE7193Cfa", - "blockNumber": 10939932 - }, - "CRISPProgram": { - "address": "0xbCc418F4dd1266Cc6070b1e2AC728ef56De946e7", - "blockNumber": 10939932, - "constructorArgs": { - "enclave": "0x670eFE043d1D340148037b4b76c4F9dfED294309", - "verifierAddress": "0x05c415E39AF18B2cc118fFC98258afe38636EAb0", - "honkVerifierAddress": "0x70AA319AA8a305Eb65a78b0791b2526BE7193Cfa", ->>>>>>> main "imageId": "0x23734b77b0f76e85623a88d7a82f24c34c94834f2501964ea123b7a2027013a2" } }, "MockVotingToken": { -<<<<<<< HEAD "address": "0x1429859428C0aBc9C2C47C8Ee9FBaf82cFA0F20f", - "blockNumber": 45 -======= - "address": "0x4Ef697B1bE877384b0f205205a8EDA49E09b63ae", - "blockNumber": 10939934 ->>>>>>> main + "blockNumber": 44 } } } \ No newline at end of file diff --git a/templates/default/deployed_contracts.json b/templates/default/deployed_contracts.json index 3b0059e8bc..6382f22184 100644 --- a/templates/default/deployed_contracts.json +++ b/templates/default/deployed_contracts.json @@ -47,11 +47,7 @@ "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" }, -<<<<<<< HEAD "blockNumber": 12, -======= - "blockNumber": 10, ->>>>>>> main "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, "BondingRegistry": { @@ -73,7 +69,7 @@ "proxyAdminAddress": "0x8aCd85898458400f7Db866d53FCFF6f0D49741FF", "implementationAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, - "blockNumber": 14, + "blockNumber": 13, "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, "Enclave": { @@ -113,27 +109,11 @@ "address": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" }, "MockComputeProvider": { -<<<<<<< HEAD "blockNumber": 56, -======= - "blockNumber": 51, - "address": "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00" - }, - "MockDecryptionVerifier": { - "blockNumber": 52, - "address": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570" - }, - "MockPkVerifier": { - "blockNumber": 54, - "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" - }, - "MockE3Program": { - "blockNumber": 55, ->>>>>>> main "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" }, "MockDecryptionVerifier": { - "blockNumber": 58, + "blockNumber": 57, "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" }, "MockPkVerifier": { diff --git a/templates/default/enclave.config.yaml b/templates/default/enclave.config.yaml index 486c6208d5..25c3b5409b 100644 --- a/templates/default/enclave.config.yaml +++ b/templates/default/enclave.config.yaml @@ -13,7 +13,7 @@ chains: deploy_block: 12 bonding_registry: address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" - deploy_block: 14 + deploy_block: 13 slashing_manager: address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" deploy_block: 11 From 7345cd02a4ac7266e8f09d3d6acb7d3fa25439e9 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 3 Jun 2026 20:44:56 +0500 Subject: [PATCH 09/24] feat: at token mode --- .../contracts/Enclave.sol/Enclave.json | 6 +- .../IBondingRegistry.json | 6 +- .../ICiphernodeRegistry.json | 6 +- .../interfaces/IEnclave.sol/IEnclave.json | 6 +- .../ISlashingManager.json | 6 +- .../CiphernodeRegistryOwnable.json | 6 +- .../EnclaveTicketToken.json | 6 +- .../contracts/token/EnclaveToken.sol | 111 ++++++++++++++++-- 8 files changed, 111 insertions(+), 42 deletions(-) diff --git a/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json b/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json index faf6b66b58..c38ef24e26 100644 --- a/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json +++ b/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json @@ -3159,9 +3159,5 @@ }, "immutableReferences": {}, "inputSourceName": "project/contracts/Enclave.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" -======= - "buildInfoId": "solc-0_8_28-58b894a0ac77a2d784be77b69288a062b8f6f518" ->>>>>>> main + "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index c8dd598bf7..7492007ff0 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1081,9 +1081,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" -======= - "buildInfoId": "solc-0_8_28-58b894a0ac77a2d784be77b69288a062b8f6f518" ->>>>>>> main + "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index 81d70d36da..d0fbc1948a 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1332,9 +1332,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" -======= - "buildInfoId": "solc-0_8_28-58b894a0ac77a2d784be77b69288a062b8f6f518" ->>>>>>> main + "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json index 67fb1f19ff..cf9097dc1d 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json @@ -2427,9 +2427,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IEnclave.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" -======= - "buildInfoId": "solc-0_8_28-58b894a0ac77a2d784be77b69288a062b8f6f518" ->>>>>>> main + "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index 8ac9312c6c..aaa518636a 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1233,9 +1233,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" -======= - "buildInfoId": "solc-0_8_28-58b894a0ac77a2d784be77b69288a062b8f6f518" ->>>>>>> main + "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json b/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json index b8218a9e5c..3cfe0f6af8 100644 --- a/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json +++ b/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json @@ -1981,9 +1981,5 @@ }, "immutableReferences": {}, "inputSourceName": "project/contracts/registry/CiphernodeRegistryOwnable.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" -======= - "buildInfoId": "solc-0_8_28-58b894a0ac77a2d784be77b69288a062b8f6f518" ->>>>>>> main + "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json b/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json index 5d93094fc2..49b7b8bf45 100644 --- a/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json +++ b/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json @@ -1486,9 +1486,5 @@ ] }, "inputSourceName": "project/contracts/token/EnclaveTicketToken.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-ba3c388c5eb55d5d111498c7ff48b24b6af6cd6c" -======= - "buildInfoId": "solc-0_8_28-58b894a0ac77a2d784be77b69288a062b8f6f518" ->>>>>>> main + "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" } \ No newline at end of file diff --git a/packages/enclave-contracts/contracts/token/EnclaveToken.sol b/packages/enclave-contracts/contracts/token/EnclaveToken.sol index 76023b12ef..55101c5cf1 100644 --- a/packages/enclave-contracts/contracts/token/EnclaveToken.sol +++ b/packages/enclave-contracts/contracts/token/EnclaveToken.sol @@ -27,16 +27,25 @@ import { IBondingRegistry } from "../interfaces/IBondingRegistry.sol"; * @notice The governance and utility token for the Enclave protocol * @dev ERC20 token with voting capabilities, permit functionality, and controlled minting. * + * Lifecycle — Virtual → Live (TGE): + * The token starts in {TokenMode.Virtual}. Once the configured {tgeEarliest} + * timestamp has passed, LOCK_MANAGER_ROLE calls {tge} to transition to + * {TokenMode.Live}. At that point {tgeTimestamp} is set to block.timestamp and + * all TGE-anchored lock schedules (those with tokenUnlockStart = 0) can be + * created and resolve. + * * Roles: - * - DEFAULT_ADMIN_ROLE manages role assignments and can {disableTransferRestrictions}. + * - DEFAULT_ADMIN_ROLE manages role assignments and can {disableTransferRestrictions} + * (only after TGE — requires {TokenMode.Live}). * - MINTER_ROLE can call {mintAllocation} / {batchMintAllocations} up to MAX_SUPPLY. * - WHITELIST_ROLE can manage the transfer whitelist independently from minting so * the same account is not required to control both surfaces. * - LOCK_MANAGER_ROLE can configure token-level lock schedules, CCA claim sources, - * buyer claim profiles, and the bonding registry used for locked-floor accounting. + * buyer claim profiles, the bonding registry, and the TGE transition. * * Transfer restrictions are a one-way switch: once {disableTransferRestrictions} is called - * they cannot be re-enabled. + * they cannot be re-enabled. The TGE transition ({TokenMode.Virtual} → {TokenMode.Live}) + * is also one-way. * * Token-level locks are pooled per account. For every non-mint/non-burn transfer, the sender * must satisfy: balanceOf(sender) + BondingRegistry.totalBonded(sender) >= lockedFloorOf(sender). @@ -94,6 +103,23 @@ contract EnclaveToken is /// @notice Thrown when a schedule requests the default TGE timestamp before it has been configured. error TgeTimestampUnset(); + /// @notice Thrown when {tge} is called before the earliest allowed timestamp. + error TgeTooEarly(uint64 current, uint64 earliest); + + /// @notice Thrown when {tge} is called but the token is already live. + error TgeAlreadyLive(); + + /// @notice Thrown when an operation requires the token to be in Live mode. + error TokenNotLive(); + + /// @notice Token lifecycle mode. + /// @dev Virtual: pre-TGE phase. Live: TGE has occurred, {tgeTimestamp} is set, + /// and TGE-anchored lock schedules can be created and resolved. + enum TokenMode { + Virtual, + Live + } + /// @notice Maximum supply of the token: 1.2 billion tokens with 18 decimals /// @dev Hard cap on total token supply that cannot be exceeded through minting uint256 public constant MAX_SUPPLY = 1_200_000_000e18; @@ -179,6 +205,12 @@ contract EnclaveToken is /// @notice Optional default TGE timestamp used when lock schedule inputs pass tokenUnlockStart = 0. uint64 public tgeTimestamp; + /// @notice Current token lifecycle mode. Starts as {TokenMode.Virtual}. + TokenMode public mode; + + /// @notice Earliest timestamp at which {tge} may be called. + uint64 public tgeEarliest; + /// @notice Registry queried for ENCL that still counts toward an account's locked floor. IBondingRegistry public bondingRegistry; @@ -197,6 +229,11 @@ contract EnclaveToken is mapping(address account => ClaimLockProfile profile) public claimLockProfiles; + /// @notice Recipients that do NOT receive a claim-lock schedule when they + /// receive tokens from an approved claim source. Used for CCA sweeps, + /// treasury returns, and other system recipients that are not buyers. + mapping(address account => bool exempt) public claimLockExemptRecipients; + /// @dev Lock schedules by account. The array length is bounded by {MAX_LOCK_SCHEDULES}. mapping(address account => LockSchedule[] schedules) private _lockSchedules; @@ -254,6 +291,15 @@ contract EnclaveToken is bytes32 indexed group ); + /// @notice Emitted when the token transitions from Virtual to Live mode. + event TgeTriggered(uint64 timestamp); + + /// @notice Emitted when the earliest TGE timestamp is updated. + event TgeEarliestUpdated(uint64 previous, uint64 next); + + /// @notice Emitted when a claim-lock exemption is set or cleared. + event ClaimLockExemptionUpdated(address indexed account, bool exempt); + /** * @notice Initializes the Enclave token with name "Enclave" and symbol "ENCL" * @dev Sets up the token with voting and permit functionality. Grants admin, minter, and @@ -270,6 +316,7 @@ contract EnclaveToken is _grantRole(LOCK_MANAGER_ROLE, initialOwner_); // Initialise state variables. + mode = TokenMode.Virtual; transfersRestricted = true; transferWhitelisted[initialOwner_] = true; @@ -339,13 +386,14 @@ contract EnclaveToken is /** * @notice Permanently disables transfer restrictions. * @dev Once disabled, restrictions cannot be re-enabled (one-way switch). - * Only callable by DEFAULT_ADMIN_ROLE. Idempotent: a no-op when already disabled - * so deployment/setup scripts can call it unconditionally. + * Only callable by DEFAULT_ADMIN_ROLE, and only after the token is Live + * ({tge} has been called). Idempotent: a no-op when already disabled. */ function disableTransferRestrictions() external onlyRole(DEFAULT_ADMIN_ROLE) { + if (mode != TokenMode.Live) revert TokenNotLive(); if (!transfersRestricted) return; transfersRestricted = false; emit TransferRestrictionUpdated(false); @@ -387,10 +435,13 @@ contract EnclaveToken is } /// @notice Sets the default TGE timestamp used by schedule inputs with tokenUnlockStart = 0. - /// @dev Existing schedules store resolved timestamps and are not changed by this setter. + /// @dev Only callable in Live mode. Before TGE, {tge} sets the timestamp atomically with the + /// mode transition. This setter exists for administrative correction after TGE. + /// Existing schedules store resolved timestamps and are not changed by this setter. function setTgeTimestamp( uint64 newTgeTimestamp ) external onlyRole(LOCK_MANAGER_ROLE) { + if (mode != TokenMode.Live) revert TokenNotLive(); if (newTgeTimestamp == 0) revert InvalidLockSchedule(); uint64 previous = tgeTimestamp; tgeTimestamp = newTgeTimestamp; @@ -413,6 +464,40 @@ contract EnclaveToken is emit BondingRegistryUpdated(previous, newRegistryAddress); } + /// @notice Sets the earliest timestamp at which {tge} may be called. + /// @dev Must be set before {tge} is called. Value is a Unix timestamp in seconds. + function setTgeEarliest( + uint64 newTgeEarliest + ) external onlyRole(LOCK_MANAGER_ROLE) { + if (newTgeEarliest == 0) revert InvalidLockSchedule(); + if (mode == TokenMode.Live) revert TgeAlreadyLive(); + uint64 previous = tgeEarliest; + tgeEarliest = newTgeEarliest; + emit TgeEarliestUpdated(previous, newTgeEarliest); + } + + /// @notice Transitions the token from Virtual to Live mode. + /// @dev One-way switch. Requires {tgeEarliest} to be set and the current + /// block timestamp to be >= {tgeEarliest}. After TGE, the pooled-token + /// lock enforcement (balance + bonded >= lockedFloor) becomes active + /// and ALL schedules that reference the default TGE timestamp resolve. + function tge() external onlyRole(LOCK_MANAGER_ROLE) { + if (mode == TokenMode.Live) revert TgeAlreadyLive(); + uint64 earliest = tgeEarliest; + if (earliest == 0) revert TgeTimestampUnset(); + uint64 current = _currentTimestamp(); + if (current < earliest) revert TgeTooEarly(current, earliest); + + mode = TokenMode.Live; + tgeTimestamp = current; + emit TgeTriggered(current); + } + + /// @notice Returns true when the token is in Live (post-TGE) mode. + function isLive() external view returns (bool) { + return mode == TokenMode.Live; + } + /// @notice Creates a wallet-level lock schedule for an account. function createLockSchedule( LockScheduleInput calldata input @@ -466,6 +551,18 @@ contract EnclaveToken is } } + /// @notice Marks an address as exempt from automatic claim-lock schedule creation. + /// @dev Used for CCA sweep/treasury recipients that receive tokens from an approved + /// claim source but are not buyers. Exempt recipients skip {_addClaimLock}. + function setClaimLockExemption( + address account, + bool exempt + ) external onlyRole(LOCK_MANAGER_ROLE) { + if (account == address(0)) revert ZeroAddress(); + claimLockExemptRecipients[account] = exempt; + emit ClaimLockExemptionUpdated(account, exempt); + } + /// @notice Number of lock schedules recorded for an account. function lockScheduleCount( address account @@ -546,7 +643,7 @@ contract EnclaveToken is if ( approvedClaimSources[from] && value != 0 && - from != address(bondingRegistry) + !claimLockExemptRecipients[to] ) { _addClaimLock(to, value); } From caa3b69542f534efc67dc38638d15165a4d81c94 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 4 Jun 2026 12:35:36 +0500 Subject: [PATCH 10/24] fix: tests --- .../contracts/Enclave.sol/Enclave.json | 2 +- .../IBondingRegistry.sol/IBondingRegistry.json | 2 +- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IEnclave.sol/IEnclave.json | 2 +- .../ISlashingManager.sol/ISlashingManager.json | 2 +- .../CiphernodeRegistryOwnable.json | 2 +- .../EnclaveTicketToken.json | 2 +- .../contracts/token/EnclaveToken.sol | 17 ++++++++++------- .../scripts/deployAndSave/enclaveToken.ts | 5 +++++ packages/enclave-contracts/tasks/ciphernode.ts | 5 +++++ .../test/E3Lifecycle/E3Integration.spec.ts | 1 + .../test/Token/EnclaveToken.spec.ts | 6 ++++++ .../enclave-contracts/test/fixtures/system.ts | 2 ++ 13 files changed, 36 insertions(+), 14 deletions(-) diff --git a/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json b/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json index c38ef24e26..7d0772735c 100644 --- a/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json +++ b/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json @@ -3159,5 +3159,5 @@ }, "immutableReferences": {}, "inputSourceName": "project/contracts/Enclave.sol", - "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" + "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 7492007ff0..69a2971a80 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1081,5 +1081,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" + "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index d0fbc1948a..9a2bfbed28 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1332,5 +1332,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" + "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json index cf9097dc1d..cc7dc4561d 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json @@ -2427,5 +2427,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IEnclave.sol", - "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" + "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index aaa518636a..c5818fbdfc 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1233,5 +1233,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" + "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json b/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json index 3cfe0f6af8..6d84a5ce7f 100644 --- a/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json +++ b/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json @@ -1981,5 +1981,5 @@ }, "immutableReferences": {}, "inputSourceName": "project/contracts/registry/CiphernodeRegistryOwnable.sol", - "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" + "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json b/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json index 49b7b8bf45..56b95b1940 100644 --- a/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json +++ b/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json @@ -1486,5 +1486,5 @@ ] }, "inputSourceName": "project/contracts/token/EnclaveTicketToken.sol", - "buildInfoId": "solc-0_8_28-9fd399eef134d64177ea2204432fda87672507cf" + "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" } \ No newline at end of file diff --git a/packages/enclave-contracts/contracts/token/EnclaveToken.sol b/packages/enclave-contracts/contracts/token/EnclaveToken.sol index 55101c5cf1..31d3f53530 100644 --- a/packages/enclave-contracts/contracts/token/EnclaveToken.sol +++ b/packages/enclave-contracts/contracts/token/EnclaveToken.sol @@ -435,13 +435,13 @@ contract EnclaveToken is } /// @notice Sets the default TGE timestamp used by schedule inputs with tokenUnlockStart = 0. - /// @dev Only callable in Live mode. Before TGE, {tge} sets the timestamp atomically with the - /// mode transition. This setter exists for administrative correction after TGE. + /// @dev Callable in both Virtual and Live mode. Before TGE this allows pre-configuring + /// the timestamp that schedules will resolve against. After TGE, {tge} sets the + /// timestamp to block.timestamp; this setter exists for administrative correction. /// Existing schedules store resolved timestamps and are not changed by this setter. function setTgeTimestamp( uint64 newTgeTimestamp ) external onlyRole(LOCK_MANAGER_ROLE) { - if (mode != TokenMode.Live) revert TokenNotLive(); if (newTgeTimestamp == 0) revert InvalidLockSchedule(); uint64 previous = tgeTimestamp; tgeTimestamp = newTgeTimestamp; @@ -478,9 +478,9 @@ contract EnclaveToken is /// @notice Transitions the token from Virtual to Live mode. /// @dev One-way switch. Requires {tgeEarliest} to be set and the current - /// block timestamp to be >= {tgeEarliest}. After TGE, the pooled-token - /// lock enforcement (balance + bonded >= lockedFloor) becomes active - /// and ALL schedules that reference the default TGE timestamp resolve. + /// block timestamp to be >= {tgeEarliest}. Sets {tgeTimestamp} to + /// block.timestamp unless a future timestamp was pre-configured via + /// {setTgeTimestamp} during Virtual mode. function tge() external onlyRole(LOCK_MANAGER_ROLE) { if (mode == TokenMode.Live) revert TgeAlreadyLive(); uint64 earliest = tgeEarliest; @@ -489,7 +489,9 @@ contract EnclaveToken is if (current < earliest) revert TgeTooEarly(current, earliest); mode = TokenMode.Live; - tgeTimestamp = current; + if (tgeTimestamp == 0) { + tgeTimestamp = current; + } emit TgeTriggered(current); } @@ -643,6 +645,7 @@ contract EnclaveToken is if ( approvedClaimSources[from] && value != 0 && + from != address(bondingRegistry) && !claimLockExemptRecipients[to] ) { _addClaimLock(to, value); diff --git a/packages/enclave-contracts/scripts/deployAndSave/enclaveToken.ts b/packages/enclave-contracts/scripts/deployAndSave/enclaveToken.ts index 1db61357a4..ed34c5a0ee 100644 --- a/packages/enclave-contracts/scripts/deployAndSave/enclaveToken.ts +++ b/packages/enclave-contracts/scripts/deployAndSave/enclaveToken.ts @@ -39,6 +39,11 @@ async function disableTransferRestrictionsForLocal( try { const isRestricted = await contract.transfersRestricted(); if (isRestricted) { + const isLive = await contract.isLive(); + if (!isLive) { + await (await contract.setTgeEarliest(1)).wait(); + await (await contract.tge()).wait(); + } const tx = await contract.disableTransferRestrictions(); await tx.wait(); console.log("Transfer restrictions disabled for local development"); diff --git a/packages/enclave-contracts/tasks/ciphernode.ts b/packages/enclave-contracts/tasks/ciphernode.ts index c013ef97a8..f85cb587e3 100644 --- a/packages/enclave-contracts/tasks/ciphernode.ts +++ b/packages/enclave-contracts/tasks/ciphernode.ts @@ -304,6 +304,11 @@ export const ciphernodeMintTokens = task( await enclaveTokenContract.transfersRestricted(); if (transfersRestricted) { console.log("Allowing EnclaveToken to be transferrable..."); + const isLive = await enclaveTokenContract.isLive(); + if (!isLive) { + await (await enclaveTokenContract.setTgeEarliest(1)).wait(); + await (await enclaveTokenContract.tge()).wait(); + } const transferEnabledTx = await enclaveTokenContract.disableTransferRestrictions(); await transferEnabledTx.wait(); diff --git a/packages/enclave-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/enclave-contracts/test/E3Lifecycle/E3Integration.spec.ts index 476afa0f89..8ff49ec17c 100644 --- a/packages/enclave-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/enclave-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -146,6 +146,7 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const ticketTokenAddress = await bondingRegistry.ticketToken(); const ticketAmount = ethers.parseUnits("100", 6); + // Token is already Live from deployEnclaveSystem fixture await enclToken.disableTransferRestrictions(); await enclToken.mintAllocation( operatorAddress, diff --git a/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts b/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts index ce5cb7275e..7fd3def316 100644 --- a/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts +++ b/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts @@ -44,6 +44,8 @@ describe("EnclaveToken", function () { async function deployWithUnlockedTransfers() { const fixture = await deploy(); + await fixture.token.connect(fixture.admin).setTgeEarliest(1); + await fixture.token.connect(fixture.admin).tge(); await fixture.token.connect(fixture.admin).disableTransferRestrictions(); return fixture; } @@ -142,6 +144,8 @@ describe("EnclaveToken", function () { it("disableTransferRestrictions is one-way (idempotent no-op on second call)", async function () { const { token, admin } = await loadFixture(deploy); + await token.connect(admin).setTgeEarliest(1); + await token.connect(admin).tge(); await expect(token.connect(admin).disableTransferRestrictions()) .to.emit(token, "TransferRestrictionUpdated") .withArgs(false); @@ -222,6 +226,8 @@ describe("EnclaveToken", function () { const tge = now + DAY; const totalAmount = ethers.parseEther("100"); + await token.connect(admin).setTgeEarliest(1); + await token.connect(admin).tge(); await token.connect(admin).setTgeTimestamp(tge); await token .connect(admin) diff --git a/packages/enclave-contracts/test/fixtures/system.ts b/packages/enclave-contracts/test/fixtures/system.ts index 65bdc60f19..2e4e013957 100644 --- a/packages/enclave-contracts/test/fixtures/system.ts +++ b/packages/enclave-contracts/test/fixtures/system.ts @@ -466,6 +466,8 @@ export async function deployEnclaveSystem( } // ── Operators ───────────────────────────────────────────────────────────── + await licenseToken.setTgeEarliest(1); + await licenseToken.tge(); await licenseToken.disableTransferRestrictions(); if (operators.length > 0) { for (const operator of operators) { From cdd893d8384206e1396ea5c01b689f360c064fd4 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 4 Jun 2026 18:25:47 +0500 Subject: [PATCH 11/24] fix: review --- .../contracts/Enclave.sol/Enclave.json | 2 +- .../IBondingRegistry.json | 2 +- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IEnclave.sol/IEnclave.json | 2 +- .../ISlashingManager.json | 2 +- .../CiphernodeRegistryOwnable.json | 2 +- .../EnclaveTicketToken.json | 2 +- .../contracts/token/EnclaveToken.sol | 22 +++++++++++-------- .../test/Token/EnclaveToken.spec.ts | 2 ++ 9 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json b/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json index 7d0772735c..cca1c82180 100644 --- a/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json +++ b/packages/enclave-contracts/artifacts/contracts/Enclave.sol/Enclave.json @@ -3159,5 +3159,5 @@ }, "immutableReferences": {}, "inputSourceName": "project/contracts/Enclave.sol", - "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" + "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 69a2971a80..c72b29db60 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1081,5 +1081,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" + "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index 9a2bfbed28..9852b0063e 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1332,5 +1332,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" + "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json index cc7dc4561d..fef91a137a 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json @@ -2427,5 +2427,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IEnclave.sol", - "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" + "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index c5818fbdfc..22e781110e 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1233,5 +1233,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" + "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json b/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json index 6d84a5ce7f..644fb6d99c 100644 --- a/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json +++ b/packages/enclave-contracts/artifacts/contracts/registry/CiphernodeRegistryOwnable.sol/CiphernodeRegistryOwnable.json @@ -1981,5 +1981,5 @@ }, "immutableReferences": {}, "inputSourceName": "project/contracts/registry/CiphernodeRegistryOwnable.sol", - "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" + "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json b/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json index 56b95b1940..a71009b58e 100644 --- a/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json +++ b/packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json @@ -1486,5 +1486,5 @@ ] }, "inputSourceName": "project/contracts/token/EnclaveTicketToken.sol", - "buildInfoId": "solc-0_8_28-321b7f4335c74c6b1b757029cf93cbba5bb38258" + "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" } \ No newline at end of file diff --git a/packages/enclave-contracts/contracts/token/EnclaveToken.sol b/packages/enclave-contracts/contracts/token/EnclaveToken.sol index 31d3f53530..fea6efe10b 100644 --- a/packages/enclave-contracts/contracts/token/EnclaveToken.sol +++ b/packages/enclave-contracts/contracts/token/EnclaveToken.sol @@ -183,11 +183,15 @@ contract EnclaveToken is /// @notice Relative lock profile applied to transfers from approved CCA claim sources. /// @param active Whether the account can receive locked claim-source transfers. - /// @param holdDuration Seconds after claim before any amount is transferable. + /// @param lockStart Absolute timestamp (e.g. CCA auction end) that anchors the + /// hold and unlock durations. All buyers in the same route share the same + /// lock start regardless of when they claim. + /// @param holdDuration Seconds after {lockStart} before any amount is transferable. /// @param unlockDuration Optional linear unlock duration after the hold ends. Zero is a cliff unlock. /// @param group Schedule group marker, e.g. REG_S_CCA or REG_D_CCA. struct ClaimLockProfile { bool active; + uint64 lockStart; uint64 holdDuration; uint64 unlockDuration; bytes32 group; @@ -286,6 +290,7 @@ contract EnclaveToken is event ClaimLockProfileUpdated( address indexed account, bool active, + uint64 lockStart, uint64 holdDuration, uint64 unlockDuration, bytes32 indexed group @@ -695,9 +700,7 @@ contract EnclaveToken is } if (amount > type(uint128).max) revert InvalidLockSchedule(); - uint64 currentTimestamp = _currentTimestamp(); - - uint256 holdUntil = uint256(currentTimestamp) + profile.holdDuration; + uint256 holdUntil = uint256(profile.lockStart) + profile.holdDuration; uint256 unlockEnd = holdUntil + profile.unlockDuration; if (unlockEnd > type(uint64).max) revert InvalidLockSchedule(); @@ -764,16 +767,17 @@ contract EnclaveToken is ClaimLockProfile calldata profile ) internal { if (account == address(0)) revert ZeroAddress(); - if ( - profile.active && - profile.holdDuration == 0 && - profile.unlockDuration == 0 - ) revert InvalidLockSchedule(); + if (profile.active) { + if (profile.lockStart == 0) revert InvalidLockSchedule(); + if (profile.holdDuration == 0 && profile.unlockDuration == 0) + revert InvalidLockSchedule(); + } claimLockProfiles[account] = profile; emit ClaimLockProfileUpdated( account, profile.active, + profile.lockStart, profile.holdDuration, profile.unlockDuration, profile.group diff --git a/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts b/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts index 7fd3def316..a839820ecd 100644 --- a/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts +++ b/packages/enclave-contracts/test/Token/EnclaveToken.spec.ts @@ -325,10 +325,12 @@ describe("EnclaveToken", function () { const bobAddress = await bob.getAddress(); const claimSourceAddress = await claimSource.getAddress(); const claimAmount = ethers.parseEther("500"); + const now = BigInt(await time.latest()); await token.connect(admin).setClaimSource(claimSourceAddress, true); await token.connect(admin).setClaimLockProfile(aliceAddress, { active: true, + lockStart: now, holdDuration: 40n * DAY, unlockDuration: 0n, group: GROUP_CCA_REG_S, From 08e520c7ef888f1e49a8e7d9dbaf088f054e168e Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 11 Jun 2026 16:33:07 +0500 Subject: [PATCH 12/24] fix: resolve conflicts --- agent/flow-trace/02_TOKENS_AND_ACTIVATION.md | 10 +- .../flow-trace/05_FAILURE_REFUND_SLASHING.md | 4 +- .../06_DEACTIVATION_AND_COMPLETION.md | 4 +- circuits/benchmarks/README.md | 6 +- docs/pages/noir-circuits.mdx | 4 +- .../crisp-contracts/deployed_contracts.json | 12 --- packages/interfold-contracts/README.md | 4 +- .../IBondingRegistry.json | 6 +- .../ICiphernodeRegistry.json | 6 +- .../interfaces/IInterfold.sol/IInterfold.json | 7 +- .../ISlashingManager.json | 6 +- .../InterfoldTicketToken.json | 7 +- .../contracts/interfaces/IBondingRegistry.sol | 6 +- .../contracts/token/InterfoldToken.sol | 12 +-- .../scripts/deployInterfold.ts | 2 +- .../test/Registry/BondingRegistry.spec.ts | 2 +- .../test/Token/InterfoldToken.spec.ts | 12 +-- templates/default/deployed_contracts.json | 94 +++++++++---------- templates/default/interfold.config.yaml | 18 ++-- 19 files changed, 94 insertions(+), 128 deletions(-) diff --git a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md index f602e0808a..d153f42f1e 100644 --- a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md +++ b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md @@ -88,11 +88,11 @@ User runs: interfold ciphernode license bond --amount 50000 └─ OUTPUT: "Transaction hash: 0x..." ``` -### Locked ENCL bonding +### Locked INTF bonding -`BondingRegistry.totalBonded(account)` returns active ENCL license bond plus pending ENCL exits that -remain slashable/not returned. `EnclaveToken` uses this view for pooled wallet-level locks, so -locked ENCL can be self-bonded by the same account without becoming transferable. Delegated +`BondingRegistry.totalBonded(account)` returns active INTF license bond plus pending INTF exits that +remain slashable/not returned. `InterfoldToken` uses this view for pooled wallet-level locks, so +locked INTF can be self-bonded by the same account without becoming transferable. Delegated source-aware bonding is not part of the pooled-lock model; license bonds are credited to `msg.sender` through `bondLicense(amount)`. @@ -205,7 +205,7 @@ User runs: interfold ciphernode license unbond --amount 10000 │ │ │ 4. _exits.queueLicensesForExit( │ │ │ │ msg.sender, exitDelay, amount │ │ │ │ ) │ -│ │ │ → Pending ENCL still counts in totalBonded() │ +│ │ │ → Pending INTF still counts in totalBonded() │ │ │ │ until claimed or slashed │ │ │ │ 5. _updateOperatorStatus(msg.sender) │ │ │ │ → May DEACTIVATE if bond drops below threshold │ diff --git a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md index e4ec49f4ab..394ac40995 100644 --- a/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md +++ b/agent/flow-trace/05_FAILURE_REFUND_SLASHING.md @@ -661,7 +661,7 @@ _executeSlash(proposalId): │ │ │ │ ┌─── BondingRegistry.slashLicenseBond() ───────────────┐ │ │ │ │ -│ │ │ 1. Compute active + pending ENCL source total │ +│ │ │ 1. Compute active + pending INTF source total │ │ │ │ │ │ │ │ 2. _slashLicenseSourcesLifo(operator, amount): │ │ │ │ Compare newest active source sequence with │ @@ -670,7 +670,7 @@ _executeSlash(proposalId): │ │ │ → Active slash decrements operators[op].licenseBond│ │ │ │ → Pending slash decrements pending license totals │ │ │ │ → totalBonded(op) drops immediately; if op has │ -│ │ │ token-level locks, same-wallet ENCL may become │ +│ │ │ token-level locks, same-wallet INTF may become │ │ │ │ encumbered until the locked floor decays/top-up │ │ │ │ → Receiver callback gets (operator, amount, │ │ │ │ sourceId) when supported │ diff --git a/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md b/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md index 10a8b17453..066ecf78f4 100644 --- a/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md +++ b/agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md @@ -54,7 +54,7 @@ User runs: interfold ciphernode deactivate --license 20000 │ │ 1. require(amount != 0, sufficient bonded INTF) │ │ │ 2. operators[op].licenseBond -= 20000 │ │ │ 3. _exits.queueLicensesForExit(op, exitDelay, 20000)│ - │ │ → Pending ENCL remains in totalBonded(op) for │ + │ │ → Pending INTF remains in totalBonded(op) for │ │ │ token-level locked-floor accounting │ │ │ 4. _updateOperatorStatus(operator) │ │ │ → If licenseBond < │ @@ -75,7 +75,7 @@ User runs: interfold ciphernode deactivate --tickets 50 --license 20000 ├─ Calls removeTicketBalance(50) first └─ Then calls unbondLicense(20000) → Tickets are queued in ExitQueueLib - → ENCL is queued in ExitQueueLib pending license exits and remains counted in totalBonded() + → INTF is queued in ExitQueueLib pending license exits and remains counted in totalBonded() ``` --- diff --git a/circuits/benchmarks/README.md b/circuits/benchmarks/README.md index 024109a2ef..f45fdb4393 100644 --- a/circuits/benchmarks/README.md +++ b/circuits/benchmarks/README.md @@ -21,7 +21,7 @@ From this directory: | Flag / env | Effect | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `--committee micro\|small\|medium\|large` | Benchmark a committee without rebuilding the Noir tree first (runs `pnpm build:circuits --committee` when needed). Overrides on-disk `active.nr` for output dir naming and integration-test env. | -| `ENCLAVE_COMMITTEE_SIZE=` | Same committee for the Rust integration test (`test_trbfv_actor`); set automatically when using `--committee`. | +| `INTERFOLD_COMMITTEE_SIZE=` | Same committee for the Rust integration test (`test_trbfv_actor`); set automatically when using `--committee`. | Options and secure-only **config** circuit behavior are documented in the script and `config.json`. @@ -51,9 +51,9 @@ pnpm build:circuits --preset insecure-512 --committee medium pnpm check:committee # → ✓ check:committee: medium (H=8, T=4) consistent across active.nr, utils.ts, .active-preset.json -# 3. Run the benchmark. ENCLAVE_COMMITTEE_SIZE makes the Rust test pick the same committee +# 3. Run the benchmark. INTERFOLD_COMMITTEE_SIZE makes the Rust test pick the same committee # and panic up-front if it disagrees with the stamp. -ENCLAVE_COMMITTEE_SIZE=medium ./circuits/benchmarks/run_benchmarks.sh --mode insecure +INTERFOLD_COMMITTEE_SIZE=medium ./circuits/benchmarks/run_benchmarks.sh --mode insecure # 4. To go back to micro, run step 1 again with --committee micro. pnpm build:circuits --preset insecure-512 --committee micro diff --git a/docs/pages/noir-circuits.mdx b/docs/pages/noir-circuits.mdx index 976f397887..ed372a89c8 100644 --- a/docs/pages/noir-circuits.mdx +++ b/docs/pages/noir-circuits.mdx @@ -90,9 +90,9 @@ pnpm check:committee # verify active.nr, utils.ts, p **Preset** (`insecure-512` | `secure-8192`) selects the BFV parameter set; **committee** (`micro` | `small` | `medium` | `large`) selects `(N, T, H)` for secret sharing. Always switch committee via `pnpm build:circuits --committee ` — see -[`scripts/README.md`](https://github.com/gnosisguild/enclave/blob/main/scripts/README.md#circuit-builder) +[`scripts/README.md`](https://github.com/gnosisguild/interfold/blob/main/scripts/README.md#circuit-builder) and -[`circuits/benchmarks/README.md`](https://github.com/gnosisguild/enclave/blob/main/circuits/benchmarks/README.md). +[`circuits/benchmarks/README.md`](https://github.com/gnosisguild/interfold/blob/main/circuits/benchmarks/README.md). Generate per-circuit witness/config artifacts with `zk_cli` (must pass matching `--committee`): diff --git a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json index a46ee52223..289257c553 100644 --- a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json +++ b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json @@ -94,15 +94,9 @@ }, "E3RefundManager": { "constructorArgs": { -<<<<<<< HEAD - "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "enclave": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "treasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" -======= "owner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", "interfold": "0x670eFE043d1D340148037b4b76c4F9dfED294309", "treasury": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B" ->>>>>>> main }, "proxyRecords": { "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000b306bf915c4d645ff596e518faf3f9669b97016000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", @@ -142,15 +136,9 @@ "address": "0xFD471836031dc5108809D173A067e8486B9047A3", "blockNumber": 42, "constructorArgs": { -<<<<<<< HEAD - "enclave": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "verifierAddress": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", - "honkVerifierAddress": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", -======= "interfold": "0x670eFE043d1D340148037b4b76c4F9dfED294309", "verifierAddress": "0x05c415E39AF18B2cc118fFC98258afe38636EAb0", "honkVerifierAddress": "0x70AA319AA8a305Eb65a78b0791b2526BE7193Cfa", ->>>>>>> main "imageId": "0x23734b77b0f76e85623a88d7a82f24c34c94834f2501964ea123b7a2027013a2" } }, diff --git a/packages/interfold-contracts/README.md b/packages/interfold-contracts/README.md index 034ccb3c24..e3e6b36114 100644 --- a/packages/interfold-contracts/README.md +++ b/packages/interfold-contracts/README.md @@ -56,9 +56,9 @@ directory, as well as to the `deployed_contracts.json` file. Be sure to configure your desired network in `hardhat.config.ts` before deploying. -For non-local networks, set `INTERFOLD_TGE_TIMESTAMP` to the agreed ENCL TGE +For non-local networks, set `INTERFOLD_TGE_TIMESTAMP` to the agreed INTF TGE Unix timestamp before deploying. The deployment script configures this on -`EnclaveToken` for token-level lock schedules. Local mock deployments default +`InterfoldToken` for token-level lock schedules. Local mock deployments default this timestamp to the latest local block timestamp. ## Localhost deployment diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index f0fc298cf1..0893cd291c 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1081,9 +1081,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", -<<<<<<< HEAD:packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json - "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" -======= - "buildInfoId": "solc-0_8_28-8c071345fedd32f4664163cf658339e6750c8f10" ->>>>>>> main:packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json + "buildInfoId": "solc-0_8_28-b09c5d8313e36da17310e2043045396de2e05713" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index 1a900a47b4..7e7de77430 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1332,9 +1332,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", -<<<<<<< HEAD:packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json - "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" -======= - "buildInfoId": "solc-0_8_28-8c071345fedd32f4664163cf658339e6750c8f10" ->>>>>>> main:packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json + "buildInfoId": "solc-0_8_28-b09c5d8313e36da17310e2043045396de2e05713" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index c201ed3104..ec5a06b811 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -2426,11 +2426,6 @@ "linkReferences": {}, "deployedLinkReferences": {}, "immutableReferences": {}, -<<<<<<< HEAD:packages/enclave-contracts/artifacts/contracts/interfaces/IEnclave.sol/IEnclave.json - "inputSourceName": "project/contracts/interfaces/IEnclave.sol", - "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" -======= "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-8c071345fedd32f4664163cf658339e6750c8f10" ->>>>>>> main:packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json + "buildInfoId": "solc-0_8_28-b09c5d8313e36da17310e2043045396de2e05713" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index 0551f2ea0a..3f82d929f2 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1233,9 +1233,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", -<<<<<<< HEAD:packages/enclave-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json - "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" -======= - "buildInfoId": "solc-0_8_28-8c071345fedd32f4664163cf658339e6750c8f10" ->>>>>>> main:packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json + "buildInfoId": "solc-0_8_28-b09c5d8313e36da17310e2043045396de2e05713" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json index 2123404386..cde8ddfa3c 100644 --- a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json +++ b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json @@ -1485,11 +1485,6 @@ } ] }, -<<<<<<< HEAD:packages/enclave-contracts/artifacts/contracts/token/EnclaveTicketToken.sol/EnclaveTicketToken.json - "inputSourceName": "project/contracts/token/EnclaveTicketToken.sol", - "buildInfoId": "solc-0_8_28-99196e36a88d1f8615dcb8a76e750e1cbdb201c4" -======= "inputSourceName": "project/contracts/token/InterfoldTicketToken.sol", - "buildInfoId": "solc-0_8_28-8c071345fedd32f4664163cf658339e6750c8f10" ->>>>>>> main:packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json + "buildInfoId": "solc-0_8_28-d96b1451d9af86253d4103fca7abd8920706ac94" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol index 678b7e9e34..0b9cbec71e 100644 --- a/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol +++ b/packages/interfold-contracts/contracts/interfaces/IBondingRegistry.sol @@ -214,9 +214,9 @@ interface IBondingRegistry { function getLicenseBond(address operator) external view returns (uint256); /** - * @notice Get ENCL that still counts toward an account's locked-floor collateral. - * @dev Includes active license bond plus pending ENCL exits that remain slashable/not returned. - * @param account Account/operator whose ENCL bond credit is queried + * @notice Get INTF that still counts toward an account's locked-floor collateral. + * @dev Includes active license bond plus pending INTF exits that remain slashable/not returned. + * @param account Account/operator whose INTF bond credit is queried * @return Active plus pending license-bond amount */ function totalBonded(address account) external view returns (uint256); diff --git a/packages/interfold-contracts/contracts/token/InterfoldToken.sol b/packages/interfold-contracts/contracts/token/InterfoldToken.sol index 320a9e77cf..25c6583e59 100644 --- a/packages/interfold-contracts/contracts/token/InterfoldToken.sol +++ b/packages/interfold-contracts/contracts/token/InterfoldToken.sol @@ -49,8 +49,8 @@ import { IBondingRegistry } from "../interfaces/IBondingRegistry.sol"; * * Token-level locks are pooled per account. For every non-mint/non-burn transfer, the sender * must satisfy: balanceOf(sender) + BondingRegistry.totalBonded(sender) >= lockedFloorOf(sender). - * This lets locked holders use same-account ENCL as operator bond collateral while preserving - * the explicit product constraint that all ENCL in the same wallet is pooled for locks/slashing. + * This lets locked holders use same-account INTF as operator bond collateral while preserving + * the explicit product constraint that all INTF in the same wallet is pooled for locks/slashing. * * Voting uses {block.timestamp} (EIP-6372 "mode=timestamp") so timepoints align with other * Interfold contracts. @@ -215,7 +215,7 @@ contract InterfoldToken is /// @notice Earliest timestamp at which {tge} may be called. uint64 public tgeEarliest; - /// @notice Registry queried for ENCL that still counts toward an account's locked floor. + /// @notice Registry queried for INTF that still counts toward an account's locked floor. IBondingRegistry public bondingRegistry; /// @notice Mapping of addresses permitted to transfer tokens when restrictions are active @@ -226,7 +226,7 @@ contract InterfoldToken is /// @dev When true, only whitelisted addresses can transfer tokens bool public transfersRestricted; - /// @notice Approved CCA/auction claim sources whose outbound ENCL creates wallet-level locks. + /// @notice Approved CCA/auction claim sources whose outbound INTF creates wallet-level locks. mapping(address source => bool approved) public approvedClaimSources; /// @notice Relative CCA claim lock profile for each buyer/recipient. @@ -589,7 +589,7 @@ contract InterfoldToken is return _lockSchedules[account][scheduleId]; } - /// @notice Current amount that must remain controlled by an account's wallet plus bonded ENCL. + /// @notice Current amount that must remain controlled by an account's wallet plus bonded INTF. function lockedFloorOf(address account) public view returns (uint256) { return lockedFloorAt(account, uint64(block.timestamp)); } @@ -606,7 +606,7 @@ contract InterfoldToken is } } - /// @notice ENCL bonded by an account that still counts toward its locked floor. + /// @notice INTF bonded by an account that still counts toward its locked floor. function totalBondedOf(address account) public view returns (uint256) { IBondingRegistry registry = bondingRegistry; if (address(registry) == address(0)) return 0; diff --git a/packages/interfold-contracts/scripts/deployInterfold.ts b/packages/interfold-contracts/scripts/deployInterfold.ts index fb6e5db09a..265f456dcd 100644 --- a/packages/interfold-contracts/scripts/deployInterfold.ts +++ b/packages/interfold-contracts/scripts/deployInterfold.ts @@ -103,7 +103,7 @@ function resolveInterfoldTgeTimestamp( } console.warn( - "[WARN] INTERFOLD_TGE_TIMESTAMP not set; using latest local block timestamp for ENCL token locks.", + "[WARN] INTERFOLD_TGE_TIMESTAMP not set; using latest local block timestamp for INTF token locks.", ); return latestBlockTimestamp.toString(); } diff --git a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts index 581793dc72..63bda915d8 100644 --- a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts +++ b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts @@ -1228,7 +1228,7 @@ describe("BondingRegistry", function () { } = await loadFixture(setup); // Register and fund tickets so the generic ExitQueueLib ticket path is - // exercised directly alongside ENCL exits. + // exercised directly alongside INTF exits. const bondAmount = LICENSE_REQUIRED_BOND; await licenseToken .connect(operator1) diff --git a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts index a4f46cb59f..b128461768 100644 --- a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts +++ b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts @@ -4,15 +4,15 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. import { expect } from "chai"; + +import { InterfoldToken__factory as InterfoldTokenFactory } from "../../types"; import { SEVEN_DAYS, - deployEnclaveSystem, + deployInterfoldSystem, ethers, networkHelpers, } from "../fixtures"; -import { InterfoldToken__factory as InterfoldTokenFactory } from "../../types"; -const { ethers, networkHelpers } = await network.connect(); const { loadFixture, time } = networkHelpers; const GROUP_PRE_SEED = ethers.encodeBytes32String("PRE_SEED"); @@ -443,7 +443,7 @@ describe("InterfoldToken", function () { const signers = await ethers.getSigners(); const [, beneficiary] = signers; const beneficiaryAddress = await beneficiary.getAddress(); - const sys = await deployEnclaveSystem({ + const sys = await deployInterfoldSystem({ useMockCiphernodeRegistry: true, setupOperators: 0, mintUsdcTo: [], @@ -506,7 +506,7 @@ describe("InterfoldToken", function () { const [, beneficiary, slasher] = signers; const beneficiaryAddress = await beneficiary.getAddress(); const slasherAddress = await slasher.getAddress(); - const sys = await deployEnclaveSystem({ + const sys = await deployInterfoldSystem({ useMockCiphernodeRegistry: true, setupOperators: 0, wireSlashingManager: false, @@ -582,7 +582,7 @@ describe("InterfoldToken", function () { const beneficiaryAddress = await beneficiary.getAddress(); const recipientAddress = await recipient.getAddress(); const slasherAddress = await slasher.getAddress(); - const sys = await deployEnclaveSystem({ + const sys = await deployInterfoldSystem({ useMockCiphernodeRegistry: true, setupOperators: 0, wireSlashingManager: false, diff --git a/templates/default/deployed_contracts.json b/templates/default/deployed_contracts.json index 7dbdeaf139..3d993352b5 100644 --- a/templates/default/deployed_contracts.json +++ b/templates/default/deployed_contracts.json @@ -1,21 +1,21 @@ { "localhost": { "PoseidonT3": { - "blockNumber": 4, + "blockNumber": 14, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 5, + "blockNumber": 15, "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, "InterfoldToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 6, + "blockNumber": 16, "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "InterfoldTicketToken": { @@ -24,16 +24,16 @@ "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 8, - "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" + "blockNumber": 17, + "address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" }, "SlashingManager": { "constructorArgs": { "initialDelay": "172800", "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 9, - "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + "blockNumber": 18, + "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, "CiphernodeRegistryOwnable": { "constructorArgs": { @@ -43,19 +43,19 @@ "proxyRecords": { "initData": "0xcd6dc687000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000000a", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", - "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", - "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" + "proxyAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F", + "proxyAdminAddress": "0x3B02fF1e626Ed7a8fd6eC5299e2C54e1421B626B", + "implementationAddress": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, - "blockNumber": 10, - "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + "blockNumber": 19, + "address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" }, "BondingRegistry": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "ticketToken": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", + "ticketToken": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", "licenseToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", - "registry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", + "registry": "0x0165878A594ca255338adfa4d48449f69242Eb8F", "slashedFundsTreasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "ticketPrice": "10000000", "licenseRequiredBond": "100000000000000000000", @@ -63,74 +63,74 @@ "exitDelay": "604800" }, "proxyRecords": { - "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000dc64a140aa3e981100a9beca4e685f962f0cf6c90000000000000000000000009fe46736679d2d9a65f0992f2272de9f3c7fa6e0000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c853000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", + "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000cf7ed3acca5a467e9e704c703e8d87f634fb0fc90000000000000000000000009fe46736679d2d9a65f0992f2272de9f3c7fa6e00000000000000000000000000165878a594ca255338adfa4d48449f69242eb8f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", - "proxyAdminAddress": "0x8aCd85898458400f7Db866d53FCFF6f0D49741FF", - "implementationAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" + "proxyAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", + "proxyAdminAddress": "0x94099942864EA81cCF197E9D71ac53310b1468D8", + "implementationAddress": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, - "blockNumber": 12, - "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" + "blockNumber": 20, + "address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, "Interfold": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "registry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", - "bondingRegistry": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", + "registry": "0x0165878A594ca255338adfa4d48449f69242Eb8F", + "bondingRegistry": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", "e3RefundManager": "0x0000000000000000000000000000000000000001", "feeToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", "maxDuration": "2592000", "timeoutConfig": "{\"dkgWindow\":7200,\"computeWindow\":86400,\"decryptionWindow\":3600}" }, "proxyRecords": { - "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c8530000000000000000000000008a791620dd6260079bf849dc5567adc3f2fdc3180000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", + "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000165878a594ca255338adfa4d48449f69242eb8f0000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe60000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "proxyAdminAddress": "0x524F04724632eED237cbA3c37272e018b3A7967e", - "implementationAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508" + "proxyAddress": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", + "proxyAdminAddress": "0x8dAF17A20c9DBA35f005b6324F493785D239719d", + "implementationAddress": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" }, - "blockNumber": 16, - "address": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + "blockNumber": 24, + "address": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" }, "E3RefundManager": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "enclave": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "interfold": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", "treasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, "proxyRecords": { - "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000b306bf915c4d645ff596e518faf3f9669b97016000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000b7f8bc63bbcad18155201308c8f3540b07f84f5e000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE", - "proxyAdminAddress": "0x2b961E3959b79326A8e7F64Ef0d2d825707669b5", - "implementationAddress": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + "proxyAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "proxyAdminAddress": "0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08", + "implementationAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" }, - "blockNumber": 18, - "address": "0x9A676e781A523b5d0C0e43731313A708CB607508" + "blockNumber": 26, + "address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" }, "MockComputeProvider": { - "blockNumber": 51, - "address": "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00" + "blockNumber": 40, + "address": "0x9d4454B023096f34B160D6B654540c56A1F81688" }, "MockDecryptionVerifier": { - "blockNumber": 52, - "address": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570" + "blockNumber": 41, + "address": "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00" }, "MockPkVerifier": { - "blockNumber": 54, - "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" + "blockNumber": 42, + "address": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570" }, "MockE3Program": { - "blockNumber": 55, - "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" + "blockNumber": 43, + "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" }, "ImageID": { - "address": "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1", - "blockNumber": 60 + "address": "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90", + "blockNumber": 48 }, "MyProgram": { - "address": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", - "blockNumber": 62 + "address": "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1", + "blockNumber": 50 } } } \ No newline at end of file diff --git a/templates/default/interfold.config.yaml b/templates/default/interfold.config.yaml index c7dae4e578..22f3290045 100644 --- a/templates/default/interfold.config.yaml +++ b/templates/default/interfold.config.yaml @@ -3,23 +3,23 @@ chains: rpc_url: 'ws://localhost:8545' contracts: e3_program: - address: "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3" - deploy_block: 51 + address: "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1" + deploy_block: 50 interfold: - address: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" - deploy_block: 23 + address: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + deploy_block: 24 ciphernode_registry: - address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" - deploy_block: 18 - bonding_registry: - address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" + address: "0x0165878A594ca255338adfa4d48449f69242Eb8F" deploy_block: 19 + bonding_registry: + address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" + deploy_block: 20 slashing_manager: address: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' deploy_block: 11 fee_token: address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" - deploy_block: 10 + deploy_block: 15 program: dev: true # risc0: From 401553ae9d855ecc05282f1227d05680a5788488 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 11 Jun 2026 19:34:27 +0500 Subject: [PATCH 13/24] feat: token contract --- agent/flow-trace/02_TOKENS_AND_ACTIVATION.md | 2 +- .../contracts/test/MockBondingRegistry.sol | 17 + .../contracts/token/InterfoldToken.sol | 1274 ++++++------- .../ignition/modules/interfoldToken.ts | 12 +- .../scripts/deployAndSave/interfoldToken.ts | 23 +- .../scripts/deployInterfold.ts | 18 +- .../interfold-contracts/tasks/ciphernode.ts | 31 +- .../test/E3Lifecycle/E3Integration.spec.ts | 5 +- .../test/E3Lifecycle/Sortition.spec.ts | 4 +- .../test/Registry/BondingRegistry.spec.ts | 4 +- .../test/Slashing/CommitteeExpulsion.spec.ts | 4 +- .../test/Slashing/SlashingLanes.spec.ts | 4 +- .../test/Slashing/SlashingManager.spec.ts | 4 +- .../test/Token/InterfoldToken.spec.ts | 1569 ++++++++++++----- .../test/fixtures/operators.ts | 4 +- .../test/fixtures/system.ts | 42 +- 16 files changed, 1777 insertions(+), 1240 deletions(-) create mode 100644 packages/interfold-contracts/contracts/test/MockBondingRegistry.sol diff --git a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md index d153f42f1e..fbd9078645 100644 --- a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md +++ b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md @@ -356,7 +356,7 @@ active = registered ─────────── After exitDelay seconds: INTF → returned to source withdrawal address - USDC → paid out from ETK.payableBalance + USDC → paid out from ITK.payableBalance ``` --- diff --git a/packages/interfold-contracts/contracts/test/MockBondingRegistry.sol b/packages/interfold-contracts/contracts/test/MockBondingRegistry.sol new file mode 100644 index 0000000000..c491804d5d --- /dev/null +++ b/packages/interfold-contracts/contracts/test/MockBondingRegistry.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. +pragma solidity 0.8.28; + +/// @notice Minimal mock BondingRegistry for standalone InterfoldToken tests. +/// Returns 0 for totalBonded so locked-balance enforcement works +/// without a full system deployment. +contract MockBondingRegistry { + function totalBonded( + address /* account */ + ) external pure returns (uint256) { + return 0; + } +} diff --git a/packages/interfold-contracts/contracts/token/InterfoldToken.sol b/packages/interfold-contracts/contracts/token/InterfoldToken.sol index 25c6583e59..66f84ce3d6 100644 --- a/packages/interfold-contracts/contracts/token/InterfoldToken.sol +++ b/packages/interfold-contracts/contracts/token/InterfoldToken.sol @@ -24,36 +24,10 @@ import { IBondingRegistry } from "../interfaces/IBondingRegistry.sol"; /** * @title InterfoldToken - * @notice The governance and utility token for the Interfold protocol - * @dev ERC20 token with voting capabilities, permit functionality, and controlled minting. + * @notice The governance and utility token for the Interfold protocol, with + * wallet-level lock enforcement designed around the Uniswap CCA + * distribution flow. * - * Lifecycle — Virtual → Live (TGE): - * The token starts in {TokenMode.Virtual}. Once the configured {tgeEarliest} - * timestamp has passed, LOCK_MANAGER_ROLE calls {tge} to transition to - * {TokenMode.Live}. At that point {tgeTimestamp} is set to block.timestamp and - * all TGE-anchored lock schedules (those with tokenUnlockStart = 0) can be - * created and resolve. - * - * Roles: - * - DEFAULT_ADMIN_ROLE manages role assignments and can {disableTransferRestrictions} - * (only after TGE — requires {TokenMode.Live}). - * - MINTER_ROLE can call {mintAllocation} / {batchMintAllocations} up to MAX_SUPPLY. - * - WHITELIST_ROLE can manage the transfer whitelist independently from minting so - * the same account is not required to control both surfaces. - * - LOCK_MANAGER_ROLE can configure token-level lock schedules, CCA claim sources, - * buyer claim profiles, the bonding registry, and the TGE transition. - * - * Transfer restrictions are a one-way switch: once {disableTransferRestrictions} is called - * they cannot be re-enabled. The TGE transition ({TokenMode.Virtual} → {TokenMode.Live}) - * is also one-way. - * - * Token-level locks are pooled per account. For every non-mint/non-burn transfer, the sender - * must satisfy: balanceOf(sender) + BondingRegistry.totalBonded(sender) >= lockedFloorOf(sender). - * This lets locked holders use same-account INTF as operator bond collateral while preserving - * the explicit product constraint that all INTF in the same wallet is pooled for locks/slashing. - * - * Voting uses {block.timestamp} (EIP-6372 "mode=timestamp") so timepoints align with other - * Interfold contracts. */ contract InterfoldToken is ERC20, @@ -62,859 +36,732 @@ contract InterfoldToken is Ownable2Step, AccessControl { - /// @notice Thrown when {renounceOwnership} is called. Ownership is - /// critical for protocol governance; renouncing would permanently - /// freeze admin functions and is disallowed. - error RenounceOwnershipDisabled(); - // Custom errors + // ───────────────────────────────────────────────────────────────────────── + // Types + // ───────────────────────────────────────────────────────────────────────── + + /// @notice Global token lifecycle phase, derived from the immutable CCA + /// window and the TGE: Virtual (pre-sale), PublicSale (CCA + /// bidding window), Cooldown (sale ended, TGE not yet fired), + /// Live (TGE fired). + enum Phase { + Virtual, + PublicSale, + Cooldown, + Live + } + + enum Anchor { + Absolute, + Tge + } - /// @notice Thrown when a zero address is provided where a valid address is required + /// @param anchor How the curve's start resolves (Absolute or Tge). + /// @param start Anchor timestamp when {anchor} is Absolute; must be zero + /// when {anchor} is Tge. + /// @param cliffDuration Seconds after the anchor before anything releases. + /// @param vestDuration Total linear release duration measured from the + /// anchor; zero means everything releases at the cliff. + struct Curve { + Anchor anchor; + uint64 start; + uint64 cliffDuration; + uint64 vestDuration; + } + + /// @param holdUntil Optional absolute timestamp before which nothing is + /// transferable, whatever the unlock curve has accrued; + /// @param unlock Unlock curve. + struct LockPolicy { + uint64 holdUntil; + Curve unlock; + } + + struct Lock { + bytes32 policyId; + uint256 amount; + } + + struct MintAllocation { + address recipient; + uint256 amount; + bytes32 policyId; + bytes32 label; + } + + // ───────────────────────────────────────────────────────────────────────── + // Errors + // ───────────────────────────────────────────────────────────────────────── + + /// @notice A zero address was provided where a valid address is required. error ZeroAddress(); - /// @notice Thrown when attempting to mint zero tokens + /// @notice A zero amount or zero timestamp was provided where a non-zero + /// value is required. error ZeroAmount(); - /// @notice Thrown when minting would exceed the maximum token supply - error ExceedsTotalSupply(); + /// @notice Minting would exceed {MAX_SUPPLY}. + error MaxSupplyExceeded(); - /// @notice Thrown when array parameters have mismatched lengths - error ArrayLengthMismatch(); + /// @notice The transfer is not one of the movements allowed pre-TGE: + /// bonding (the registry on either side) or a CCA distribution + /// ({CLAIM_SOURCE} sending, any phase). + error TransferRestricted(address from, address to); - /// @notice Thrown when a transfer is attempted while restrictions are active and neither party is whitelisted - error TransferNotAllowed(); + /// @notice {mint} or {mintAllocations} was called after the Virtual phase; the + /// full supply is distributed before {CCA_START}. + error MintingClosed(); - /// @notice Thrown when lock schedule parameters are internally inconsistent. - error InvalidLockSchedule(); + /// @notice {tge} was called but the token is already live. + error AlreadyLive(); - /// @notice Thrown when an account already has the maximum supported number of lock schedules. - error MaxLockSchedulesExceeded(); + /// @notice {tge} was called before {CCA_END} + {TGE_COOLDOWN}. + error TgeTooEarly(uint64 current, uint64 notBefore); - /// @notice Thrown when a locked account attempts to move below its current locked floor. - error LockedBalanceInvariant( - address account, - uint256 balance, - uint256 bonded, - uint256 lockedFloor - ); + /// @notice The CCA window is empty, inverted, or does not start in the + /// future. + error InvalidCcaWindow(uint64 ccaStart, uint64 ccaEnd); - /// @notice Thrown when a claim source sends locked CCA tokens to an account without an active profile. - error ClaimLockProfileMissing(address account); + /// @notice Policy parameters are internally inconsistent. + error InvalidPolicy(); - /// @notice Thrown when a schedule requests the default TGE timestamp before it has been configured. - error TgeTimestampUnset(); + /// @notice The policy id is already defined; policies are write-once. + error PolicyAlreadyDefined(bytes32 policyId); - /// @notice Thrown when {tge} is called before the earliest allowed timestamp. - error TgeTooEarly(uint64 current, uint64 earliest); + /// @notice The referenced policy id has not been defined. + error PolicyNotDefined(bytes32 policyId); - /// @notice Thrown when {tge} is called but the token is already live. - error TgeAlreadyLive(); + /// @notice A transfer of `value` exceeds the sender's spendable balance + /// (balance + bonded − locked balance). + error InsufficientUnlockedBalance( + address account, + uint256 spendable, + uint256 value + ); - /// @notice Thrown when an operation requires the token to be in Live mode. - error TokenNotLive(); + /// @notice The bonding registry address has no deployed code. + error InvalidBondingRegistry(address registry); - /// @notice Token lifecycle mode. - /// @dev Virtual: pre-TGE phase. Live: TGE has occurred, {tgeTimestamp} is set, - /// and TGE-anchored lock schedules can be created and resolved. - enum TokenMode { - Virtual, - Live - } + /// @notice Thrown when {renounceOwnership} is called. Ownership is + /// critical for protocol governance; renouncing would permanently + /// freeze admin functions and is disallowed. + error RenounceOwnershipDisabled(); - /// @notice Maximum supply of the token: 1.2 billion tokens with 18 decimals - /// @dev Hard cap on total token supply that cannot be exceeded through minting - uint256 public constant MAX_SUPPLY = 1_200_000_000e18; + // ───────────────────────────────────────────────────────────────────────── + // Constants and immutables + // ───────────────────────────────────────────────────────────────────────── - /// @notice Maximum lock schedules retained for a single account. - /// @dev Bounds the per-transfer loop in {lockedFloorOf}. - uint256 public constant MAX_LOCK_SCHEDULES = 64; + uint256 public constant MAX_SUPPLY = 1_200_000_000e18; - /// @notice Role identifier for accounts authorized to mint new tokens - /// @dev Keccak256 hash of "MINTER_ROLE" used in AccessControl + /// @notice Role authorized to mint allocations, while Virtual. bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - /// @notice Role identifier for accounts authorized to manage the transfer whitelist. - /// @dev Separated from MINTER_ROLE so mint authority does not also control transferability. + /// @notice Role authorized to manage the pre-TGE transfer whitelist. bytes32 public constant WHITELIST_ROLE = keccak256("WHITELIST_ROLE"); - /// @notice Role identifier for accounts authorized to manage token-level locks. + /// @notice Role authorized to create lock policies, manage the lock whitelist bytes32 public constant LOCK_MANAGER_ROLE = keccak256("LOCK_MANAGER_ROLE"); - /// @notice Token-lock schedule recorded against one account. - /// @param amount Original amount subject to this schedule. - /// @param tokenHoldUntil Absolute timestamp before which no amount is transferable. - /// @param tokenUnlockStart Absolute linear token unlock start timestamp. - /// @param tokenUnlockEnd Absolute linear token unlock end timestamp. - /// @param serviceStart Optional service vesting start timestamp. - /// @param serviceCliff Optional service vesting cliff timestamp. - /// @param serviceEnd Optional service vesting end timestamp. - /// @param group Schedule group marker for indexers and operations. - struct LockSchedule { - uint128 amount; - uint64 tokenHoldUntil; - uint64 tokenUnlockStart; - uint64 tokenUnlockEnd; - uint64 serviceStart; - uint64 serviceCliff; - uint64 serviceEnd; - bytes32 group; - } - - /// @notice Input used to create an absolute lock schedule. - /// @param account Account whose wallet-level locked floor increases. - /// @param amount Amount subject to the schedule. - /// @param tokenHoldUntil Absolute timestamp before which no amount is transferable. - /// @param tokenUnlockStart Absolute linear unlock start. Zero resolves to {tgeTimestamp}. - /// @param tokenUnlockEnd Absolute linear unlock end. - /// @param serviceStart Optional service vesting start timestamp. - /// @param serviceCliff Optional service vesting cliff timestamp. - /// @param serviceEnd Optional service vesting end timestamp. - /// @param group Schedule group marker for indexers and operations. - struct LockScheduleInput { - address account; - uint256 amount; - uint64 tokenHoldUntil; - uint64 tokenUnlockStart; - uint64 tokenUnlockEnd; - uint64 serviceStart; - uint64 serviceCliff; - uint64 serviceEnd; - bytes32 group; - } - - /// @notice Relative lock profile applied to transfers from approved CCA claim sources. - /// @param active Whether the account can receive locked claim-source transfers. - /// @param lockStart Absolute timestamp (e.g. CCA auction end) that anchors the - /// hold and unlock durations. All buyers in the same route share the same - /// lock start regardless of when they claim. - /// @param holdDuration Seconds after {lockStart} before any amount is transferable. - /// @param unlockDuration Optional linear unlock duration after the hold ends. Zero is a cliff unlock. - /// @param group Schedule group marker, e.g. REG_S_CCA or REG_D_CCA. - struct ClaimLockProfile { - bool active; - uint64 lockStart; - uint64 holdDuration; - uint64 unlockDuration; - bytes32 group; - } - - /// @notice Input used to batch update CCA claim lock profiles. - struct ClaimLockProfileInput { - address account; - ClaimLockProfile profile; - } - - /// @notice Tracks the cumulative amount of tokens minted since deployment - uint256 public totalMinted; - - /// @notice Optional default TGE timestamp used when lock schedule inputs pass tokenUnlockStart = 0. - uint64 public tgeTimestamp; + /// @notice Minimum time between {CCA_END} and {tge}. + uint64 public constant TGE_COOLDOWN = 45 days; + + bytes32 public constant PENDING_LOCK_POLICY_ID = "PENDING"; - /// @notice Current token lifecycle mode. Starts as {TokenMode.Virtual}. - TokenMode public mode; + /// @notice Start of the CCA auction window, fixed at deployment. + uint64 public immutable CCA_START; - /// @notice Earliest timestamp at which {tge} may be called. - uint64 public tgeEarliest; + /// @notice End of the CCA auction window, fixed at deployment + uint64 public immutable CCA_END; - /// @notice Registry queried for INTF that still counts toward an account's locked floor. - IBondingRegistry public bondingRegistry; + /// @notice The CCA auction contract + address public immutable CLAIM_SOURCE; - /// @notice Mapping of addresses permitted to transfer tokens when restrictions are active - /// @dev When transfersRestricted is true, only whitelisted addresses can send or receive tokens - mapping(address account => bool allowed) public transferWhitelisted; + /// @notice Registry whose bonded INTF counts toward locked balances. + IBondingRegistry public immutable BONDING_REGISTRY; - /// @notice Indicates whether token transfers are currently restricted - /// @dev When true, only whitelisted addresses can transfer tokens - bool public transfersRestricted; + // ───────────────────────────────────────────────────────────────────────── + // Storage + // ───────────────────────────────────────────────────────────────────────── + + /// @notice TGE timestamp; zero until {tge} is called, then immutable. + uint64 public tgeTimestamp; - /// @notice Approved CCA/auction claim sources whose outbound INTF creates wallet-level locks. - mapping(address source => bool approved) public approvedClaimSources; + /// @notice Addresses allowed to transfer before TGE. + mapping(address account => bool whitelisted) public transferWhitelist; - /// @notice Relative CCA claim lock profile for each buyer/recipient. - mapping(address account => ClaimLockProfile profile) - public claimLockProfiles; + /// @notice Addresses exempt from automatic claim-source lock creation. + mapping(address account => bool whitelisted) public lockWhitelist; - /// @notice Recipients that do NOT receive a claim-lock schedule when they - /// receive tokens from an approved claim source. Used for CCA sweeps, - /// treasury returns, and other system recipients that are not buyers. - mapping(address account => bool exempt) public claimLockExemptRecipients; + /// @notice Write-once lock policies by id. + mapping(bytes32 policyId => LockPolicy policy) internal lockPolicies; - /// @dev Lock schedules by account. The array length is bounded by {MAX_LOCK_SCHEDULES}. - mapping(address account => LockSchedule[] schedules) private _lockSchedules; + /// @notice Active locks by account. + mapping(address account => Lock[] entries) public locks; - /// @notice Emitted when tokens are minted as part of a named allocation - /// @param recipient Address receiving the minted tokens - /// @param amount Number of tokens minted (18 decimals) - /// @param allocation Description of the allocation for tracking purposes + /// @notice Policy buckets for links that arrived before enough claim + /// balance existed to classify them. + mapping(address account => Lock[] entries) public queuedLocks; + + // ───────────────────────────────────────────────────────────────────────── + // Events + // ───────────────────────────────────────────────────────────────────────── + + /// @notice Emitted for every mint instruction. event AllocationMinted( address indexed recipient, uint256 amount, - string allocation + bytes32 indexed policyId, + bytes32 indexed label ); - /// @notice Emitted when the transfer restriction setting is changed - /// @param restricted New state of transfer restrictions (true = restricted, false = unrestricted) - event TransferRestrictionUpdated(bool restricted); + /// @notice Emitted when a lock policy is defined (write-once). + event PolicyDefined(bytes32 indexed policyId, LockPolicy policy); - /// @notice Emitted when an address is added to or removed from the transfer whitelist - /// @param account Address whose whitelist status changed - /// @param whitelisted New whitelist status (true = whitelisted, false = not whitelisted) + /// @notice Emitted when an account's transfer whitelist status changes. event TransferWhitelistUpdated(address indexed account, bool whitelisted); - /// @notice Emitted when the default TGE timestamp changes. - event TgeTimestampUpdated(uint64 previous, uint64 next); - - /// @notice Emitted when the bonding registry used for locked-floor accounting changes. - event BondingRegistryUpdated( - address indexed previous, - address indexed next - ); - - /// @notice Emitted when a lock schedule is created for an account. - event LockScheduleCreated( - address indexed account, - uint256 indexed scheduleId, - bytes32 indexed group, - uint256 amount, - uint64 tokenHoldUntil, - uint64 tokenUnlockStart, - uint64 tokenUnlockEnd, - uint64 serviceStart, - uint64 serviceCliff, - uint64 serviceEnd - ); - - /// @notice Emitted when a CCA/auction claim source is approved or revoked. - event ClaimSourceUpdated(address indexed source, bool approved); + /// @notice Emitted when an account's lock whitelist status changes. + event LockWhitelistUpdated(address indexed account, bool whitelisted); - /// @notice Emitted when a relative CCA claim lock profile changes. - event ClaimLockProfileUpdated( + /// @notice Emitted whenever the amount `account` holds locked under + /// `policyId` changes; `amount` is the new total. + event LockUpdated( address indexed account, - bool active, - uint64 lockStart, - uint64 holdDuration, - uint64 unlockDuration, - bytes32 indexed group + bytes32 indexed policyId, + uint256 amount ); - /// @notice Emitted when the token transitions from Virtual to Live mode. + /// @notice Emitted once, when {tge} fires. event TgeTriggered(uint64 timestamp); - /// @notice Emitted when the earliest TGE timestamp is updated. - event TgeEarliestUpdated(uint64 previous, uint64 next); - - /// @notice Emitted when a claim-lock exemption is set or cleared. - event ClaimLockExemptionUpdated(address indexed account, bool exempt); + // ───────────────────────────────────────────────────────────────────────── + // Constructor + // ───────────────────────────────────────────────────────────────────────── /** - * @notice Initializes the Interfold token with name "Interfold" and symbol "INTF" - * @dev Sets up the token with voting and permit functionality. Grants admin, minter, and - * whitelist roles to the owner; enables transfer restrictions; whitelists the owner. - * @param initialOwner_ Address that will own the contract and receive admin, minter, whitelist, and lock roles. + * @notice Deploys INTF with no TGE set. + * @dev The initial owner receives every role via the {_transferOwnership} + * sync. Operational roles can additionally be granted to dedicated + * keys post-deployment. + * @param initialOwner_ Initial owner; receives all roles. + * @param ccaStart_ CCA auction window start; + * @param ccaEnd_ CCA auction window end; after `ccaStart_`. + * @param claimSource_ The CCA auction contract + * @param bondingRegistry_ Registry whose bonded INTF */ constructor( - address initialOwner_ + address initialOwner_, + uint64 ccaStart_, + uint64 ccaEnd_, + address claimSource_, + IBondingRegistry bondingRegistry_ ) ERC20("Interfold", "INTF") ERC20Permit("Interfold") Ownable(initialOwner_) { - // Grant the deployer all admin roles. - _grantRole(DEFAULT_ADMIN_ROLE, initialOwner_); - _grantRole(MINTER_ROLE, initialOwner_); - _grantRole(WHITELIST_ROLE, initialOwner_); - _grantRole(LOCK_MANAGER_ROLE, initialOwner_); - - // Initialise state variables. - mode = TokenMode.Virtual; - transfersRestricted = true; - transferWhitelisted[initialOwner_] = true; - - emit TransferRestrictionUpdated(true); - emit TransferWhitelistUpdated(initialOwner_, true); + if (ccaStart_ <= block.timestamp) { + revert InvalidCcaWindow(ccaStart_, ccaEnd_); + } + if (ccaEnd_ <= ccaStart_) revert InvalidCcaWindow(ccaStart_, ccaEnd_); + if (claimSource_ == address(0)) revert ZeroAddress(); + address registry = address(bondingRegistry_); + if (registry == address(0)) revert ZeroAddress(); + if (registry.code.length == 0) { + revert InvalidBondingRegistry(registry); + } + CCA_START = ccaStart_; + CCA_END = ccaEnd_; + CLAIM_SOURCE = claimSource_; + BONDING_REGISTRY = bondingRegistry_; } - /** - * @notice Mints a named allocation of tokens to a specified recipient - * @dev Only callable by accounts with MINTER_ROLE. Reverts if recipient is zero address, - * amount is zero, or minting would exceed MAX_SUPPLY. - * @param recipient Address to receive the minted tokens (cannot be zero address) - * @param amount Number of tokens to mint in wei (18 decimals, must be greater than zero) - * @param allocation Human-readable description of this allocation for tracking and auditing purposes - */ - function mintAllocation( + // ───────────────────────────────────────────────────────────────────────── + // Minting + // ───────────────────────────────────────────────────────────────────────── + + /// @notice Plain vanilla admin mint: INTF with no lock attached. Only + /// allowed during the Virtual phase. + function mint( address recipient, uint256 amount, - string memory allocation - ) external onlyRole(MINTER_ROLE) { - if (recipient == address(0)) revert ZeroAddress(); - if (amount == 0) revert ZeroAmount(); - // Ensure we do not exceed the total supply. - if (totalMinted + amount > MAX_SUPPLY) revert ExceedsTotalSupply(); - - _mint(recipient, amount); - totalMinted += amount; - emit AllocationMinted(recipient, amount, allocation); - } - - /** - * @notice Mints multiple named allocations to different recipients in a single transaction - * @dev Only callable by accounts with MINTER_ROLE. All arrays must have the same length. - * Reverts if any amount is zero, or if cumulative minting would exceed MAX_SUPPLY. - * @param recipients Array of addresses to receive minted tokens - * @param amounts Array of token amounts to mint (18 decimals, must match recipients length) - * @param allocations Array of allocation descriptions (must match recipients length) - */ - function batchMintAllocations( - address[] calldata recipients, - uint256[] calldata amounts, - string[] calldata allocations + bytes32 label + ) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (phase() != Phase.Virtual) revert MintingClosed(); + _mintTokens(recipient, amount); + emit AllocationMinted(recipient, amount, bytes32(0), label); + } + + /// @notice Mints allocations locked under their policies; the path the + /// minter role uses to distribute vested supply. Only allowed + /// during the Virtual phase. + function mintAllocations( + MintAllocation[] calldata allocations ) external onlyRole(MINTER_ROLE) { - uint256 len = recipients.length; - if (amounts.length != len || allocations.length != len) { - revert ArrayLengthMismatch(); - } - - uint256 minted = totalMinted; - + if (phase() != Phase.Virtual) revert MintingClosed(); + uint256 len = allocations.length; for (uint256 i = 0; i < len; i++) { - address recipient = recipients[i]; - uint256 amount = amounts[i]; - if (recipient == address(0)) revert ZeroAddress(); - if (amount == 0) revert ZeroAmount(); - - if (amount > MAX_SUPPLY - minted) revert ExceedsTotalSupply(); - minted += amount; - - _mint(recipient, amount); - emit AllocationMinted(recipient, amount, allocations[i]); + _mintAllocation(allocations[i]); } - - totalMinted = minted; } - /** - * @notice Permanently disables transfer restrictions. - * @dev Once disabled, restrictions cannot be re-enabled (one-way switch). - * Only callable by DEFAULT_ADMIN_ROLE, and only after the token is Live - * ({tge} has been called). Idempotent: a no-op when already disabled. - */ - function disableTransferRestrictions() - external - onlyRole(DEFAULT_ADMIN_ROLE) - { - if (mode != TokenMode.Live) revert TokenNotLive(); - if (!transfersRestricted) return; - transfersRestricted = false; - emit TransferRestrictionUpdated(false); - } + // ───────────────────────────────────────────────────────────────────────── + // Launch lifecycle + // ───────────────────────────────────────────────────────────────────────── /** - * @notice Toggles an account's transfer whitelist status between enabled and disabled - * @dev Only callable by accounts holding WHITELIST_ROLE. Flips the current whitelist - * state for the given account. Whitelisted accounts can send and receive tokens even - * when transfer restrictions are active. - * @param account Address whose whitelist status will be toggled + * @notice Sets {tgeTimestamp} to the current block timestamp, exactly once. + * @dev Permissionless: anyone may trigger the TGE once {CCA_END} + + * {TGE_COOLDOWN} has passed, so launch cannot be stalled by an idle + * operator. */ - function toggleTransferWhitelist( - address account - ) external onlyRole(WHITELIST_ROLE) { - bool newStatus = !transferWhitelisted[account]; - transferWhitelisted[account] = newStatus; - emit TransferWhitelistUpdated(account, newStatus); - } - - /** - * @notice Whitelists key protocol contracts to allow them to transfer tokens during restricted periods - * @dev Only callable by accounts holding WHITELIST_ROLE. Zero addresses are safely ignored. - * @param bondingManager Address of the BondingManager contract (zero address skipped) - * @param claimSource Address of a claim source contract (zero address skipped) - */ - function whitelistContracts( - address bondingManager, - address claimSource - ) external onlyRole(WHITELIST_ROLE) { - if (bondingManager != address(0)) { - transferWhitelisted[bondingManager] = true; - emit TransferWhitelistUpdated(bondingManager, true); - } - if (claimSource != address(0)) { - transferWhitelisted[claimSource] = true; - emit TransferWhitelistUpdated(claimSource, true); - } - } - - /// @notice Sets the default TGE timestamp used by schedule inputs with tokenUnlockStart = 0. - /// @dev Callable in both Virtual and Live mode. Before TGE this allows pre-configuring - /// the timestamp that schedules will resolve against. After TGE, {tge} sets the - /// timestamp to block.timestamp; this setter exists for administrative correction. - /// Existing schedules store resolved timestamps and are not changed by this setter. - function setTgeTimestamp( - uint64 newTgeTimestamp - ) external onlyRole(LOCK_MANAGER_ROLE) { - if (newTgeTimestamp == 0) revert InvalidLockSchedule(); - uint64 previous = tgeTimestamp; - tgeTimestamp = newTgeTimestamp; - emit TgeTimestampUpdated(previous, newTgeTimestamp); - } - - /// @notice Sets the bonding registry queried by locked-floor transfer checks. - /// @dev Passing zero disables bonded-credit accounting. Non-zero values must be deployed code. - function setBondingRegistry( - IBondingRegistry newBondingRegistry - ) external onlyRole(LOCK_MANAGER_ROLE) { - address newRegistryAddress = address(newBondingRegistry); - if ( - newRegistryAddress != address(0) && - newRegistryAddress.code.length == 0 - ) revert ZeroAddress(); - - address previous = address(bondingRegistry); - bondingRegistry = newBondingRegistry; - emit BondingRegistryUpdated(previous, newRegistryAddress); - } - - /// @notice Sets the earliest timestamp at which {tge} may be called. - /// @dev Must be set before {tge} is called. Value is a Unix timestamp in seconds. - function setTgeEarliest( - uint64 newTgeEarliest - ) external onlyRole(LOCK_MANAGER_ROLE) { - if (newTgeEarliest == 0) revert InvalidLockSchedule(); - if (mode == TokenMode.Live) revert TgeAlreadyLive(); - uint64 previous = tgeEarliest; - tgeEarliest = newTgeEarliest; - emit TgeEarliestUpdated(previous, newTgeEarliest); - } - - /// @notice Transitions the token from Virtual to Live mode. - /// @dev One-way switch. Requires {tgeEarliest} to be set and the current - /// block timestamp to be >= {tgeEarliest}. Sets {tgeTimestamp} to - /// block.timestamp unless a future timestamp was pre-configured via - /// {setTgeTimestamp} during Virtual mode. - function tge() external onlyRole(LOCK_MANAGER_ROLE) { - if (mode == TokenMode.Live) revert TgeAlreadyLive(); - uint64 earliest = tgeEarliest; - if (earliest == 0) revert TgeTimestampUnset(); - uint64 current = _currentTimestamp(); + function tge() external { + if (tgeTimestamp != 0) revert AlreadyLive(); + uint64 current = uint64(block.timestamp); + uint64 earliest = CCA_END + TGE_COOLDOWN; if (current < earliest) revert TgeTooEarly(current, earliest); - - mode = TokenMode.Live; - if (tgeTimestamp == 0) { - tgeTimestamp = current; - } + tgeTimestamp = current; emit TgeTriggered(current); } - /// @notice Returns true when the token is in Live (post-TGE) mode. - function isLive() external view returns (bool) { - return mode == TokenMode.Live; + /// @notice Current lifecycle phase. Live is event-driven ({tge}); the + /// earlier phases derive from the immutable CCA window. + function phase() public view returns (Phase) { + if (tgeTimestamp != 0) return Phase.Live; + uint64 current = uint64(block.timestamp); + if (current < CCA_START) return Phase.Virtual; + if (current < CCA_END) return Phase.PublicSale; + return Phase.Cooldown; } - /// @notice Creates a wallet-level lock schedule for an account. - function createLockSchedule( - LockScheduleInput calldata input - ) external onlyRole(LOCK_MANAGER_ROLE) returns (uint256 scheduleId) { - scheduleId = _addLockSchedule(input); - _enforceLockedFloor(input.account); - } - - /// @notice Creates many wallet-level lock schedules. - function batchCreateLockSchedules( - LockScheduleInput[] calldata inputs - ) - external - onlyRole(LOCK_MANAGER_ROLE) - returns (uint256[] memory scheduleIds) - { - uint256 len = inputs.length; - scheduleIds = new uint256[](len); - - for (uint256 i = 0; i < len; i++) { - scheduleIds[i] = _addLockSchedule(inputs[i]); - _enforceLockedFloor(inputs[i].account); - } - } + // ───────────────────────────────────────────────────────────────────────── + // Whitelistig + // ───────────────────────────────────────────────────────────────────────── - /// @notice Approves or revokes a CCA/auction claim source. - function setClaimSource( - address source, - bool approved - ) external onlyRole(LOCK_MANAGER_ROLE) { - if (source == address(0)) revert ZeroAddress(); - approvedClaimSources[source] = approved; - emit ClaimSourceUpdated(source, approved); + function setTransferWhitelisted( + address account, + bool whitelisted + ) external onlyRole(WHITELIST_ROLE) { + if (account == address(0)) revert ZeroAddress(); + transferWhitelist[account] = whitelisted; + emit TransferWhitelistUpdated(account, whitelisted); } - /// @notice Sets the relative claim lock profile for a buyer/recipient. - function setClaimLockProfile( + function setLockWhitelisted( address account, - ClaimLockProfile calldata profile + bool whitelisted ) external onlyRole(LOCK_MANAGER_ROLE) { - _setClaimLockProfile(account, profile); + if (account == address(0)) revert ZeroAddress(); + lockWhitelist[account] = whitelisted; + emit LockWhitelistUpdated(account, whitelisted); } - /// @notice Batch sets relative claim lock profiles. - function batchSetClaimLockProfiles( - ClaimLockProfileInput[] calldata inputs + // ───────────────────────────────────────────────────────────────────────── + // Lock configuration + // ───────────────────────────────────────────────────────────────────────── + + /** + * @notice Creates a lock policy. Policies are write-once: once created, + * the terms backing existing locks can never be changed, by anyone. + * @param policyId Non-zero identifier, e.g. bytes32("CCA_REG_S"). + * @param policy Lock terms. The unlock curve is required, must lock + * something, and must be consistent with its anchor mode; + * `holdUntil` is optional (zero = none). + */ + function createLockPolicy( + bytes32 policyId, + LockPolicy calldata policy ) external onlyRole(LOCK_MANAGER_ROLE) { - uint256 len = inputs.length; - for (uint256 i = 0; i < len; i++) { - _setClaimLockProfile(inputs[i].account, inputs[i].profile); + if (policyId == bytes32(0) || policyId == PENDING_LOCK_POLICY_ID) { + revert InvalidPolicy(); + } + + if (_policyDefined(policyId)) { + revert PolicyAlreadyDefined(policyId); } + _validateCurve(policy.unlock); + + lockPolicies[policyId] = policy; + emit PolicyDefined(policyId, policy); } - /// @notice Marks an address as exempt from automatic claim-lock schedule creation. - /// @dev Used for CCA sweep/treasury recipients that receive tokens from an approved - /// claim source but are not buyers. Exempt recipients skip {_addClaimLock}. - function setClaimLockExemption( + /** + * @notice Links `amount` of `account`'s claims to `policyId` — the + * Predicate/KYC bucket import. + * + * Claims from the CCA can come before or after the operator's + * linkClaim. {_claim} and {_linkClaim} manipulate {locks} and + * {queuedLocks} to link balances to policies in a resilient + * way: it doesn't matter who calls what first. + */ + function linkClaim( address account, - bool exempt + uint256 amount, + bytes32 policyId ) external onlyRole(LOCK_MANAGER_ROLE) { if (account == address(0)) revert ZeroAddress(); - claimLockExemptRecipients[account] = exempt; - emit ClaimLockExemptionUpdated(account, exempt); - } + if (amount == 0) revert ZeroAmount(); + if (!_policyDefined(policyId)) revert PolicyNotDefined(policyId); - /// @notice Number of lock schedules recorded for an account. - function lockScheduleCount( - address account - ) external view returns (uint256) { - return _lockSchedules[account].length; + _linkClaim(account, amount, policyId); } - /// @notice Returns a lock schedule by account and index. - function lockScheduleOf( - address account, - uint256 scheduleId - ) external view returns (LockSchedule memory) { - return _lockSchedules[account][scheduleId]; - } + // ───────────────────────────────────────────────────────────────────────── + // Lock views + // ───────────────────────────────────────────────────────────────────────── - /// @notice Current amount that must remain controlled by an account's wallet plus bonded INTF. - function lockedFloorOf(address account) public view returns (uint256) { - return lockedFloorAt(account, uint64(block.timestamp)); + /// @notice Current locked balance of `account`: the amount that must remain + /// controlled (wallet balance + bonded) by the account. + function lockedBalanceOf(address account) public view returns (uint256) { + return lockedBalanceAt(account, uint64(block.timestamp)); } - /// @notice Amount that must remain controlled by an account at a given timestamp. - function lockedFloorAt( + /// @notice Locked balance of `account` at `timestamp`, evaluated against the + /// current configuration (an unset TGE keeps {Anchor.Tge} policies + /// fully locked for any timestamp). + function lockedBalanceAt( address account, uint64 timestamp - ) public view returns (uint256 lockedFloor) { - LockSchedule[] storage schedules = _lockSchedules[account]; - uint256 len = schedules.length; - for (uint256 i = 0; i < len; i++) { - lockedFloor += _lockedAmount(schedules[i], timestamp); + ) public view returns (uint256 lockedBalance) { + Lock[] storage accountLocks = locks[account]; + for (uint256 i = 0; i < accountLocks.length; i++) { + Lock storage accountLock = accountLocks[i]; + bytes32 policyId = accountLock.policyId; + uint256 amount = accountLock.amount; + if (policyId == PENDING_LOCK_POLICY_ID) { + // Unclassified claims are fully locked, immune to time. + lockedBalance += amount; + } else { + lockedBalance += _lockedAmount( + lockPolicies[policyId], + amount, + timestamp + ); + } } } - /// @notice INTF bonded by an account that still counts toward its locked floor. - function totalBondedOf(address account) public view returns (uint256) { - IBondingRegistry registry = bondingRegistry; - if (address(registry) == address(0)) return 0; - return registry.totalBonded(account); - } - - /// @notice Current wallet balance that can be transferred without violating the locked floor. + /// @notice Wallet balance `account` can transfer right now: the wallet + /// must retain whatever part of its locked balance its bond does + /// not already cover. + /// @dev Never consults the registry for accounts with nothing locked. function transferableBalanceOf( address account - ) external view returns (uint256) { + ) public view returns (uint256) { uint256 balance = balanceOf(account); - uint256 bonded = totalBondedOf(account); - uint256 lockedFloor = lockedFloorOf(account); - uint256 controlled = balance + bonded; - if (controlled <= lockedFloor) return 0; + uint256 lockedBalance = lockedBalanceOf(account); + if (lockedBalance == 0) return balance; - uint256 transferable = controlled - lockedFloor; - return transferable < balance ? transferable : balance; + uint256 bondedBalance = BONDING_REGISTRY.totalBonded(account); + uint256 mustRetain = lockedBalance > bondedBalance + ? lockedBalance - bondedBalance + : 0; + return balance > mustRetain ? balance - mustRetain : 0; } + // ───────────────────────────────────────────────────────────────────────── + // Transfer hook + // ───────────────────────────────────────────────────────────────────────── + /** - * @notice Internal hook that enforces transfer restrictions and updates voting power - * @dev Overrides ERC20 and ERC20Votes to add transfer restriction logic. Reverts if transfers - * are restricted and neither sender nor receiver is whitelisted. Minting (from == 0) and - * burning (to == 0) are always allowed regardless of restrictions. - * - * @param from Address sending tokens (zero address for minting) - * @param to Address receiving tokens (zero address for burning) - * @param value Amount of tokens being transferred + * @dev Applies, in order: + * 1. The pre-TGE transfer gate (_isTransferRestricted) + * 2. The lock check, the sender can move at most its transferable + * balance + * 3. The transfer itself, via parent contract + * 4. Claim-lock creation, unless the recipient is in lockWhitelist. */ function _update( address from, address to, uint256 value ) internal override(ERC20, ERC20Votes) { - // When transfers are restricted, only whitelisted addresses can send or receive. - if (from != address(0) && to != address(0) && transfersRestricted) { - if (!transferWhitelisted[from] && !transferWhitelisted[to]) { - revert TransferNotAllowed(); + bool isMint = from == address(0); + bool isBurn = to == address(0); + + if (_isTransferRestricted(from, to)) { + revert TransferRestricted(from, to); + } + + if (!isMint && !transferWhitelist[to] && !transferWhitelist[from]) { + uint256 transferable = transferableBalanceOf(from); + if (value > transferable) { + revert InsufficientUnlockedBalance(from, transferable, value); } } + super._update(from, to, value); - if (from != address(0) && to != address(0)) { - if ( - approvedClaimSources[from] && - value != 0 && - from != address(bondingRegistry) && - !claimLockExemptRecipients[to] - ) { - _addClaimLock(to, value); - } - _enforceLockedFloor(from); + // from == CLAIM_SOURCE implies neither mint nor an unset claim source. + if ( + !isBurn && value != 0 && from == CLAIM_SOURCE && !lockWhitelist[to] + ) { + _claim(to, value); } } - function _addLockSchedule( - LockScheduleInput calldata input - ) internal returns (uint256 scheduleId) { - if (input.account == address(0)) revert ZeroAddress(); - if (input.amount == 0) revert ZeroAmount(); - if (input.amount > type(uint128).max) revert InvalidLockSchedule(); + /// @dev Whether a transfer from `from` to `to` is blocked by the + /// pre-TGE gate. Always false once {tge} fires; mints and burns + /// are never gated. The locked-balance check + /// ({transferableBalanceOf}) applies independently. + function _isTransferRestricted( + address from, + address to + ) internal view returns (bool) { + if (tgeTimestamp != 0) return false; + if (from == address(0) || to == address(0)) return false; + + address registry = address(BONDING_REGISTRY); + bool isBonding = from == registry || to == registry; + // The claim source is trusted in any phase: the CCA enforces its own + // (block-based) claim timing. Every distribution still lands in a + // lock via {_claim}. + bool isCcaDistribution = from == CLAIM_SOURCE; + bool isWhitelisted = transferWhitelist[from] || transferWhitelist[to]; + return !isBonding && !isCcaDistribution && !isWhitelisted; + } + + // ───────────────────────────────────────────────────────────────────────── + // Internals + // ───────────────────────────────────────────────────────────────────────── + + function _mintAllocation(MintAllocation calldata allocation) internal { + // Every batch allocation is locked under a policy; unlocked supply + // goes through {mint}. + if (allocation.policyId == bytes32(0)) { + revert InvalidPolicy(); + } + + if (!_policyDefined(allocation.policyId)) { + revert PolicyNotDefined(allocation.policyId); + } - uint64 tokenUnlockStart = _resolveTokenUnlockStart( - input.tokenUnlockStart + _mintTokens(allocation.recipient, allocation.amount); + _addOrIncrementLock( + allocation.recipient, + locks[allocation.recipient], + allocation.policyId, + allocation.amount ); - _validateLockSchedule( - tokenUnlockStart, - input.tokenUnlockEnd, - input.serviceStart, - input.serviceCliff, - input.serviceEnd - ); - - scheduleId = _pushLockSchedule( - input.account, - LockSchedule({ - amount: uint128(input.amount), - tokenHoldUntil: input.tokenHoldUntil, - tokenUnlockStart: tokenUnlockStart, - tokenUnlockEnd: input.tokenUnlockEnd, - serviceStart: input.serviceStart, - serviceCliff: input.serviceCliff, - serviceEnd: input.serviceEnd, - group: input.group - }) + emit AllocationMinted( + allocation.recipient, + allocation.amount, + allocation.policyId, + allocation.label ); } - function _addClaimLock(address account, uint256 amount) internal { - ClaimLockProfile memory profile = claimLockProfiles[account]; - if (!profile.active) revert ClaimLockProfileMissing(account); - if (profile.holdDuration == 0 && profile.unlockDuration == 0) { - revert InvalidLockSchedule(); + /// @dev Shared mint path: amount and supply-cap validation plus the + /// ERC20 mint itself. + function _mintTokens(address recipient, uint256 amount) internal { + if (amount == 0) revert ZeroAmount(); + if (totalSupply() + amount > MAX_SUPPLY) { + revert MaxSupplyExceeded(); } - if (amount > type(uint128).max) revert InvalidLockSchedule(); + _mint(recipient, amount); + } - uint256 holdUntil = uint256(profile.lockStart) + profile.holdDuration; - uint256 unlockEnd = holdUntil + profile.unlockDuration; - if (unlockEnd > type(uint64).max) revert InvalidLockSchedule(); + /// @dev Claim behavior: + /// + /// - When a claim arrives, greedily fill queued policy buckets until + /// the claim amount is exhausted. + /// - Each consumed portion is moved into active locks under the + /// bucket's policyId. A claim may fully consume a bucket or + /// partially consume the current bucket. + /// - Any claim amount left after all queued buckets are consumed + /// lands as { PENDING_LOCK_POLICY_ID }. + /// + /// Note: no order guarantees on claims and links. + function _claim(address account, uint256 amount) private { + uint256 remaining = amount; + while (remaining != 0) { + (uint256 consumed, bytes32 policyId) = _consumeLock( + account, + queuedLocks[account], + bytes32(0), + remaining + ); - uint64 tokenHoldUntil = uint64(holdUntil); - uint64 tokenUnlockEnd = uint64(unlockEnd); + if (consumed == 0) { + policyId = PENDING_LOCK_POLICY_ID; + consumed = remaining; + } - _pushLockSchedule( - account, - LockSchedule({ - amount: uint128(amount), - tokenHoldUntil: tokenHoldUntil, - tokenUnlockStart: tokenHoldUntil, - tokenUnlockEnd: tokenUnlockEnd, - serviceStart: 0, - serviceCliff: 0, - serviceEnd: 0, - group: profile.group - }) - ); + _addOrIncrementLock(account, locks[account], policyId, consumed); + remaining -= consumed; + } } - function _pushLockSchedule( + /// @dev Link behavior: + /// + /// - When linking a claim amount to a real policyId, consume as much + /// active pending balance as possible. + /// - The consumed portion moves from {PENDING_LOCK_POLICY_ID} into + /// an active lock under {policyId}. + /// - Any unfilled remainder increments that policy's queued bucket + /// for future claims to consume. + /// + /// Note: no order guarantees on claims and links. + function _linkClaim( address account, - LockSchedule memory schedule - ) internal returns (uint256 scheduleId) { - LockSchedule[] storage schedules = _lockSchedules[account]; - uint256 len = schedules.length; - if (len < MAX_LOCK_SCHEDULES) { - schedules.push(schedule); - scheduleId = len; - } else { - scheduleId = _reclaimUnlockedScheduleSlot(schedules); - schedules[scheduleId] = schedule; - } - - emit LockScheduleCreated( + uint256 amount, + bytes32 policyId + ) private { + (uint256 consumed, ) = _consumeLock( account, - scheduleId, - schedule.group, - uint256(schedule.amount), - schedule.tokenHoldUntil, - schedule.tokenUnlockStart, - schedule.tokenUnlockEnd, - schedule.serviceStart, - schedule.serviceCliff, - schedule.serviceEnd + locks[account], + PENDING_LOCK_POLICY_ID, + amount ); - } + uint256 remaining = amount - consumed; - function _reclaimUnlockedScheduleSlot( - LockSchedule[] storage schedules - ) internal view returns (uint256 scheduleId) { - uint64 currentTimestamp = _currentTimestamp(); - uint256 len = schedules.length; - for (uint256 i = 0; i < len; i++) { - if (_lockedAmount(schedules[i], currentTimestamp) == 0) return i; + // if we consumed from PENDING policy, add the real thing + if (consumed != 0) { + _addOrIncrementLock(account, locks[account], policyId, consumed); } - revert MaxLockSchedulesExceeded(); + // Whatever is left queues under the target policy. + if (remaining != 0) { + _addOrIncrementLock( + account, + queuedLocks[account], + policyId, + remaining + ); + } } - function _setClaimLockProfile( + function _consumeLock( address account, - ClaimLockProfile calldata profile - ) internal { - if (account == address(0)) revert ZeroAddress(); - if (profile.active) { - if (profile.lockStart == 0) revert InvalidLockSchedule(); - if (profile.holdDuration == 0 && profile.unlockDuration == 0) - revert InvalidLockSchedule(); + Lock[] storage entries, + bytes32 filterPolicyId, + uint256 amount + ) internal returns (uint256 consumed, bytes32 consumedPolicyId) { + uint256 len = entries.length; + uint256 i; + + if (filterPolicyId != bytes32(0)) { + for (; i < len; i++) { + if (entries[i].policyId == filterPolicyId) { + break; + } + } + if (i == len) return (0, bytes32(0)); + } else if (len == 0) { + return (0, bytes32(0)); } - claimLockProfiles[account] = profile; - emit ClaimLockProfileUpdated( - account, - profile.active, - profile.lockStart, - profile.holdDuration, - profile.unlockDuration, - profile.group - ); - } + consumedPolicyId = entries[i].policyId; + consumed = entries[i].amount; + assert(consumed > 0); + if (consumed > amount) { + consumed = amount; + } - function _resolveTokenUnlockStart( - uint64 tokenUnlockStart - ) internal view returns (uint64) { - if (tokenUnlockStart != 0) return tokenUnlockStart; + uint256 remaining = entries[i].amount - consumed; + if (remaining == 0) { + _removeLockAt(entries, i); + } else { + entries[i].amount = remaining; + } - uint64 configuredTgeTimestamp = tgeTimestamp; - if (configuredTgeTimestamp == 0) revert TgeTimestampUnset(); - return configuredTgeTimestamp; + emit LockUpdated(account, consumedPolicyId, remaining); } - function _currentTimestamp() internal view returns (uint64) { - if (block.timestamp > type(uint64).max) revert InvalidLockSchedule(); - return uint64(block.timestamp); + function _addOrIncrementLock( + address account, + Lock[] storage entries, + bytes32 policyId, + uint256 amount + ) internal returns (uint256 newAmount) { + uint256 len = entries.length; + for (uint256 i = 0; i < len; i++) { + if (entries[i].policyId == policyId) { + entries[i].amount += amount; + newAmount = entries[i].amount; + emit LockUpdated(account, policyId, newAmount); + return newAmount; + } + } + entries.push(Lock(policyId, amount)); + emit LockUpdated(account, policyId, amount); + return amount; } - function _validateLockSchedule( - uint64 tokenUnlockStart, - uint64 tokenUnlockEnd, - uint64 serviceStart, - uint64 serviceCliff, - uint64 serviceEnd - ) internal pure { - if (tokenUnlockStart > tokenUnlockEnd) revert InvalidLockSchedule(); + function _removeLockAt(Lock[] storage entries, uint256 index) internal { + entries[index] = entries[entries.length - 1]; + entries.pop(); + } - bool hasServiceCurve = serviceStart != 0 || - serviceCliff != 0 || - serviceEnd != 0; - if (!hasServiceCurve) return; + function _policyDefined(bytes32 policyId) internal view returns (bool) { + Curve storage unlock = lockPolicies[policyId].unlock; + return unlock.cliffDuration != 0 || unlock.vestDuration != 0; + } + /// @dev Validates the unlock curve: it must lock something and be + /// consistent with its anchor mode. + function _validateCurve(Curve calldata curve) internal pure { + if (curve.cliffDuration == 0 && curve.vestDuration == 0) { + revert InvalidPolicy(); + } + if (curve.anchor == Anchor.Absolute && curve.start == 0) { + revert InvalidPolicy(); + } + if (curve.anchor == Anchor.Tge && curve.start != 0) { + revert InvalidPolicy(); + } + // A cliff past the vest end would be a disguised step function. if ( - serviceStart == 0 || - serviceEnd <= serviceStart || - serviceCliff < serviceStart || - serviceCliff > serviceEnd - ) revert InvalidLockSchedule(); + curve.vestDuration != 0 && curve.cliffDuration > curve.vestDuration + ) { + revert InvalidPolicy(); + } } + /// @dev Still-locked amount under `policy` at `timestamp`: everything + /// before {LockPolicy.holdUntil}; the unlock curve's remainder + /// after (the curve accrues through the hold, so the accrued + /// portion catches up the moment the hold lapses). Fails closed: + /// A TGE-anchored curve releases nothing while TGE is unset. function _lockedAmount( - LockSchedule storage schedule, + LockPolicy storage policy, + uint256 amount, uint64 timestamp ) internal view returns (uint256) { - uint256 amount = uint256(schedule.amount); - uint256 tokenUnlocked = _tokenUnlockedAmount(schedule, timestamp); - uint256 serviceVested = _serviceVestedAmount(schedule, timestamp); - uint256 released = tokenUnlocked < serviceVested - ? tokenUnlocked - : serviceVested; - return amount - released; - } + if (timestamp < policy.holdUntil) return amount; - function _tokenUnlockedAmount( - LockSchedule storage schedule, - uint64 timestamp - ) internal view returns (uint256) { - uint256 amount = uint256(schedule.amount); - if (timestamp < schedule.tokenHoldUntil) return 0; - if (schedule.tokenUnlockStart == schedule.tokenUnlockEnd) return amount; - if (timestamp <= schedule.tokenUnlockStart) return 0; - if (timestamp >= schedule.tokenUnlockEnd) return amount; - - uint256 elapsed = uint256(timestamp - schedule.tokenUnlockStart); - uint256 duration = uint256( - schedule.tokenUnlockEnd - schedule.tokenUnlockStart - ); - return (amount * elapsed) / duration; - } + Curve storage curve = policy.unlock; + uint256 anchor; + if (curve.anchor == Anchor.Tge) { + anchor = tgeTimestamp; + } else { + anchor = curve.start; + } - function _serviceVestedAmount( - LockSchedule storage schedule, - uint64 timestamp - ) internal view returns (uint256) { - uint256 amount = uint256(schedule.amount); - if (schedule.serviceStart == 0 && schedule.serviceEnd == 0) { + if (anchor == 0 || timestamp < anchor + curve.cliffDuration) { return amount; } - if (timestamp < schedule.serviceCliff) return 0; - if (timestamp >= schedule.serviceEnd) return amount; - uint256 elapsed = uint256(timestamp - schedule.serviceStart); - uint256 duration = uint256(schedule.serviceEnd - schedule.serviceStart); - return (amount * elapsed) / duration; - } - - function _enforceLockedFloor(address account) internal view { - uint256 lockedFloor = lockedFloorOf(account); - if (lockedFloor == 0) return; - - uint256 balance = balanceOf(account); - uint256 bonded = totalBondedOf(account); - if (balance + bonded < lockedFloor) { - revert LockedBalanceInvariant( - account, - balance, - bonded, - lockedFloor - ); + uint256 vestDuration = curve.vestDuration; + if (vestDuration == 0 || timestamp >= anchor + vestDuration) { + return 0; } + return amount - (amount * (timestamp - anchor)) / vestDuration; } - /** - * @notice Checks if this contract implements a given interface - * @dev Implements ERC165 interface detection via AccessControl - * @param interfaceId The interface identifier to check, as specified in ERC-165 - * @return bool True if the contract implements the interface, false otherwise - */ - function supportsInterface( - bytes4 interfaceId - ) public view override(AccessControl) returns (bool) { - return super.supportsInterface(interfaceId); - } + // ───────────────────────────────────────────────────────────────────────── + // Required overrides + // ───────────────────────────────────────────────────────────────────────── - /** - * @notice Returns the current nonce for an address, used for permit signatures - * @dev Resolves the override conflict between ERC20Permit and Nonces by calling the parent - * implementation. Nonces are incremented with each permit to prevent replay attacks. - * @param owner Address to query the nonce for - * @return uint256 The current nonce value for the given address - */ + /// @inheritdoc ERC20Permit function nonces( address owner ) public view override(ERC20Permit, Nonces) returns (uint256) { return super.nonces(owner); } - // ── EIP-6372 clock (timestamp mode) ─────────────────────────────────────── - - /// @notice EIP-6372 clock — uses {block.timestamp}. + /// @notice EIP-6372 clock — block.timestamp, aligned with other Interfold + /// contracts. function clock() public view override returns (uint48) { return uint48(block.timestamp); } @@ -933,13 +780,6 @@ contract InterfoldToken is /** * @notice Synchronises AccessControl roles whenever Ownable2Step completes a * transfer (i.e. when {acceptOwnership} is called by the pending owner). - * @dev Without this override, the new `owner()` would have no roles: the previous - * owner would silently retain DEFAULT_ADMIN_ROLE, MINTER_ROLE, and WHITELIST_ROLE. - * Called internally by {Ownable._transferOwnership}; never call directly. - * - * Roles are also granted during construction (previousOwner == address(0)), - * but the constructor body already calls `_grantRole` explicitly, so the - * grant here is idempotent for the deployment case and adds no overhead. */ function _transferOwnership(address newOwner) internal override { address previousOwner = owner(); diff --git a/packages/interfold-contracts/ignition/modules/interfoldToken.ts b/packages/interfold-contracts/ignition/modules/interfoldToken.ts index 0c17e6353a..1e8e6d8989 100644 --- a/packages/interfold-contracts/ignition/modules/interfoldToken.ts +++ b/packages/interfold-contracts/ignition/modules/interfoldToken.ts @@ -7,8 +7,18 @@ import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; export default buildModule("InterfoldToken", (m) => { const owner = m.getParameter("owner"); + const ccaStart = m.getParameter("ccaStart"); + const ccaEnd = m.getParameter("ccaEnd"); + const claimSource = m.getParameter("claimSource"); + const bondingRegistry = m.getParameter("bondingRegistry"); - const interfoldToken = m.contract("InterfoldToken", [owner]); + const interfoldToken = m.contract("InterfoldToken", [ + owner, + ccaStart, + ccaEnd, + claimSource, + bondingRegistry, + ]); return { interfoldToken }; }) as any; diff --git a/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts b/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts index 08fe746a1a..d75cf11d61 100644 --- a/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts +++ b/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts @@ -37,19 +37,18 @@ async function disableTransferRestrictionsForLocal( console.log("Contract address", await contract.getAddress()); try { - const isRestricted = await contract.transfersRestricted(); - if (isRestricted) { - const isLive = await contract.isLive(); - if (!isLive) { - await (await contract.setTgeEarliest(1)).wait(); - await (await contract.tge()).wait(); - } - const tx = await contract.disableTransferRestrictions(); - await tx.wait(); - console.log("Transfer restrictions disabled for local development"); + const tgeTs = await contract.tgeTimestamp(); + if (tgeTs === 0n) { + // Attempt TGE — will succeed only if CCA_END + 45 days has passed. + // On local chains the deployer should advance time manually first. + await (await contract.tge()).wait(); + console.log("TGE fired for local development"); } - } catch (error) { - console.warn("Failed to disable transfer restrictions:", error); + } catch (error: any) { + console.warn( + "TGE not yet available (CCA cooldown may not have passed):", + error.reason ?? error.message ?? error, + ); } } diff --git a/packages/interfold-contracts/scripts/deployInterfold.ts b/packages/interfold-contracts/scripts/deployInterfold.ts index 265f456dcd..80eaf80601 100644 --- a/packages/interfold-contracts/scripts/deployInterfold.ts +++ b/packages/interfold-contracts/scripts/deployInterfold.ts @@ -132,10 +132,6 @@ export const deployInterfold = async ( if (!latestBlock) { throw new Error("Could not read latest block for local TGE timestamp"); } - const interfoldTgeTimestamp = resolveInterfoldTgeTimestamp( - networkName, - latestBlock.timestamp, - ); const encodedInsecure = encodeBfvParams(BFV_PARAMS.insecure512); const encodedSecure = encodeBfvParams(BFV_PARAMS.secure8192); @@ -251,19 +247,13 @@ export const deployInterfold = async ( const bondingRegistryAddress = await bondingRegistry.getAddress(); console.log("BondingRegistry deployed to:", bondingRegistryAddress); - console.log("Configuring INTF pooled lock accounting..."); - await (await interfoldToken.setTgeTimestamp(interfoldTgeTimestamp)).wait(); + // BONDING_REGISTRY is immutable (set at construction). + // Bonding transfers are always allowed pre-TGE per the phase-based gate. + console.log("Whitelisting BondingRegistry in INTF..."); await ( - await interfoldToken.setBondingRegistry(bondingRegistryAddress) + await interfoldToken.setTransferWhitelisted(bondingRegistryAddress, true) ).wait(); - console.log("Whitelisting BondingRegistry in INTF..."); - const whitelistTx = await interfoldToken.whitelistContracts( - bondingRegistryAddress, - ethersLib.ZeroAddress, - ); - await whitelistTx.wait(); - console.log("Deploying Interfold..."); const { interfold } = await deployAndSaveInterfold({ owner: ownerAddress, diff --git a/packages/interfold-contracts/tasks/ciphernode.ts b/packages/interfold-contracts/tasks/ciphernode.ts index d02bc60e81..0ed448247f 100644 --- a/packages/interfold-contracts/tasks/ciphernode.ts +++ b/packages/interfold-contracts/tasks/ciphernode.ts @@ -276,10 +276,10 @@ export const ciphernodeMintTokens = task( console.log(`Minting tokens for: ${ciphernodeAddress}`); console.log(`Minting ${intfAmount} INTF...`); - const intfTx = await interfoldTokenContract.mintAllocation( + const intfTx = await interfoldTokenContract.mint( ciphernodeAddress, ethers.parseEther(intfAmount), - "Ciphernode allocation", + ethers.encodeBytes32String("Ciphernode allocation"), ); await intfTx.wait(); console.log(`${intfAmount} INTF minted`); @@ -300,19 +300,18 @@ export const ciphernodeMintTokens = task( console.log(`INTF: ${ethers.formatEther(intfBalance)}`); console.log(`USDC: ${ethers.formatUnits(usdcBalance, 6)}`); - const transfersRestricted = - await interfoldTokenContract.transfersRestricted(); - if (transfersRestricted) { - console.log("Allowing InterfoldToken to be transferrable..."); - const isLive = await interfoldTokenContract.isLive(); - if (!isLive) { - await (await interfoldTokenContract.setTgeEarliest(1)).wait(); + const tgeTs = await interfoldTokenContract.tgeTimestamp(); + if (tgeTs === 0n) { + console.log("Firing TGE to enable transfers..."); + try { await (await interfoldTokenContract.tge()).wait(); + console.log("InterfoldToken TGE fired, transfers enabled"); + } catch (e: any) { + console.warn( + "TGE not yet available (CCA cooldown may not have passed):", + e.reason ?? e.message ?? e, + ); } - const transferEnabledTx = - await interfoldTokenContract.disableTransferRestrictions(); - await transferEnabledTx.wait(); - console.log("InterfoldToken transfers are now enabled"); } } catch (error) { console.error("Token minting failed:", error); @@ -420,10 +419,12 @@ export const ciphernodeAdminAdd = task( console.log("Step 1: Minting and transferring INTF to ciphernode..."); - const intfTx = await interfoldTokenConnected.mintAllocation( + const intfTx = await interfoldTokenConnected.mint( adminWallet.address, licenseBondWei, - "Admin allocation for ciphernode registration", + ethers.encodeBytes32String( + "Admin allocation for ciphernode registration", + ), ); await intfTx.wait(); diff --git a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts index 31177c1920..19a50dfd5b 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/E3Integration.spec.ts @@ -146,11 +146,10 @@ describe("E3 Integration - Refund/Timeout Mechanism", function () { const ticketTokenAddress = await bondingRegistry.ticketToken(); const ticketAmount = ethers.parseUnits("100", 6); - await intfToken.disableTransferRestrictions(); - await intfToken.mintAllocation( + await intfToken.mint( operatorAddress, ethers.parseEther("10000"), - "Test allocation", + ethers.encodeBytes32String("Test allocation"), ); await usdcToken.mint(operatorAddress, ethers.parseUnits("100000", 6)); diff --git a/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts b/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts index b6db2b6ab3..bd4775f3b3 100644 --- a/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts +++ b/packages/interfold-contracts/test/E3Lifecycle/Sortition.spec.ts @@ -33,10 +33,10 @@ async function fundOperator( ticketAmount: bigint, ) { const operatorAddress = await operator.getAddress(); - await licenseToken.mintAllocation( + await licenseToken.mint( operatorAddress, ethers.parseEther("10000"), - "Test allocation", + ethers.encodeBytes32String("Test allocation"), ); await feeToken.mint(operatorAddress, ethers.parseUnits("1000000", 6)); await licenseToken diff --git a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts index 63bda915d8..fb1e2b0833 100644 --- a/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts +++ b/packages/interfold-contracts/test/Registry/BondingRegistry.spec.ts @@ -56,10 +56,10 @@ describe("BondingRegistry", function () { for (const address of [ownerAddress, operator1Address, operator2Address]) { await usdcToken.mint(address, USDC_AMOUNT); - await licenseToken.mintAllocation( + await licenseToken.mint( address, LICENSE_AMOUNT, - "Test allocation", + ethers.encodeBytes32String("Test allocation"), ); } diff --git a/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts b/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts index 429e9a544c..80cb7bf9ec 100644 --- a/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts +++ b/packages/interfold-contracts/test/Slashing/CommitteeExpulsion.spec.ts @@ -105,10 +105,10 @@ describe("Committee Expulsion & Fault Tolerance", function () { async function setupOperator(operator: Signer) { const operatorAddress = await operator.getAddress(); - await intfToken.mintAllocation( + await intfToken.mint( operatorAddress, ethers.parseEther("10000"), - "Test allocation", + ethers.encodeBytes32String("Test allocation"), ); await usdcToken.mint(operatorAddress, ethers.parseUnits("100000", 6)); diff --git a/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts b/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts index 4d289a06ae..650dd7d60a 100644 --- a/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts +++ b/packages/interfold-contracts/test/Slashing/SlashingLanes.spec.ts @@ -111,10 +111,10 @@ describe("SlashingManager — lanes, roles, EIP-712 & admin handover", function } = sys; const mockCiphernodeRegistry = mockCiphernodeRegistryOpt!; - await interfoldToken.mintAllocation( + await interfoldToken.mint( operatorAddress, ethers.parseEther("2000"), - "Test allocation", + ethers.encodeBytes32String("Test allocation"), ); await slashingManager.addSlasher(await slasher.getAddress()); await slashingManager.setCiphernodeRegistry( diff --git a/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts b/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts index ad9fd97227..145e861a07 100644 --- a/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts +++ b/packages/interfold-contracts/test/Slashing/SlashingManager.spec.ts @@ -139,10 +139,10 @@ describe("SlashingManager", function () { const mockCiphernodeRegistryAddress = await mockCiphernodeRegistry.getAddress(); - await interfoldToken.mintAllocation( + await interfoldToken.mint( operatorAddress, ethers.parseEther("2000"), - "Test allocation", + ethers.encodeBytes32String("Test allocation"), ); await slashingManager.addSlasher(await slasher.getAddress()); await slashingManager.setCiphernodeRegistry(mockCiphernodeRegistryAddress); diff --git a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts index b128461768..e01adb1103 100644 --- a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts +++ b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts @@ -5,503 +5,1148 @@ // or FITNESS FOR A PARTICULAR PURPOSE. import { expect } from "chai"; -import { InterfoldToken__factory as InterfoldTokenFactory } from "../../types"; import { - SEVEN_DAYS, - deployInterfoldSystem, - ethers, - networkHelpers, -} from "../fixtures"; + InterfoldToken__factory as InterfoldTokenFactory, + MockBondingRegistry__factory as MockBondingRegistryFactory, +} from "../../types"; +import { deployInterfoldSystem, ethers, networkHelpers } from "../fixtures"; const { loadFixture, time } = networkHelpers; -const GROUP_PRE_SEED = ethers.encodeBytes32String("PRE_SEED"); -const GROUP_BRIDGE = ethers.encodeBytes32String("BRIDGE"); -const GROUP_TEAM = ethers.encodeBytes32String("GG_TEAM"); -const GROUP_CCA_REG_S = ethers.encodeBytes32String("CCA_REG_S"); - const DAY = 24n * 60n * 60n; const YEAR = 365n * DAY; describe("InterfoldToken", function () { + // ── Helpers ───────────────────────────────────────────────────────────── + + /// Deploy a minimal MockBondingRegistry + InterfoldToken for standalone tests. + /// CCA window starts far in the future so tests control the phase via + /// `time.increaseTo` / `time.increase`. async function deploy() { - const [deployer, admin, minter, whitelister, alice, bob, claimSource] = - await ethers.getSigners(); + const [ + deployer, + admin, + minter, + whitelister, + lockManager, + alice, + bob, + claimSource, + ] = await ethers.getSigners(); + + // Deploy a minimal mock BondingRegistry that returns 0 for totalBonded. + const mockRegistry = await new MockBondingRegistryFactory( + deployer, + ).deploy(); + await mockRegistry.waitForDeployment(); + + const now = BigInt(await time.latest()); + const ccaStart = now + 10n * DAY; // far future — Virtual phase + const ccaEnd = ccaStart + 7n * DAY; + const token = await new InterfoldTokenFactory(deployer).deploy( await admin.getAddress(), + ccaStart, + ccaEnd, + await claimSource.getAddress(), + await mockRegistry.getAddress(), ); + return { deployer, admin, minter, whitelister, + lockManager, alice, bob, claimSource, token, + mockRegistry, + ccaStart, + ccaEnd, }; } - async function deployWithUnlockedTransfers() { - const fixture = await deploy(); - await fixture.token.connect(fixture.admin).setTgeEarliest(1); - await fixture.token.connect(fixture.admin).tge(); - await fixture.token.connect(fixture.admin).disableTransferRestrictions(); - return fixture; + /// Deploy, create a policy, mint locked tokens, THEN fire TGE. + /// Returns everything needed for transfer-enforcement tests. + async function deployWithLockAndTge( + opts: { + policyName?: string; + mintAmount?: bigint; + vestDuration?: bigint; + holdUntil?: bigint; + recipient?: "alice" | "claimSource"; + } = {}, + ) { + const fixture = await loadFixture(deploy); + const { token, admin, alice, claimSource, ccaEnd } = fixture; + const recipient = opts.recipient === "claimSource" ? claimSource : alice; + const recipientAddress = await recipient.getAddress(); + const policyId = await createLinearPolicy( + token, + admin, + opts.policyName ?? "TEST_LOCK", + { + vestDuration: opts.vestDuration ?? 2n * YEAR, + holdUntil: opts.holdUntil, + }, + ); + const amount = opts.mintAmount ?? ethers.parseEther("1000"); + + // Mint during Virtual phase. + await token.connect(admin).mintAllocations([ + { + recipient: recipientAddress, + amount, + policyId, + label: ethers.encodeBytes32String("test"), + }, + ]); + + // Fire TGE. + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + const tgeTx = await token.tge(); + const receipt = await tgeTx.wait(); + const tgeBlock = await ethers.provider.getBlock(receipt!.blockNumber); + const tgeTimestamp = BigInt(tgeBlock!.timestamp); + + return { ...fixture, policyId, amount, tgeTimestamp, recipientAddress }; } - async function createLinearLock( - overrides: Partial<{ - totalAmount: bigint; - tokenHoldUntil: bigint; - tokenUnlockStart: bigint; - tokenUnlockEnd: bigint; - serviceStart: bigint; - serviceCliff: bigint; - serviceEnd: bigint; - group: string; - }> = {}, - ) { - const fixture = await loadFixture(deployWithUnlockedTransfers); - const { token, admin, alice } = fixture; - const aliceAddress = await alice.getAddress(); - const now = BigInt(await time.latest()); - const tge = now + DAY; - const totalAmount = overrides.totalAmount ?? ethers.parseEther("2400"); + /// Deploy, mint unlocked tokens to alice, THEN fire TGE. + async function deployWithUnlockedAndTge(mintAmount?: bigint) { + const fixture = await loadFixture(deploy); + const { token, admin, alice, ccaEnd } = fixture; + const amount = mintAmount ?? ethers.parseEther("500"); - await token.connect(admin).setTgeTimestamp(tge); await token .connect(admin) - .mintAllocation(aliceAddress, totalAmount, "Locked allocation"); + .mint(await alice.getAddress(), amount, ethers.ZeroHash); - await token.connect(admin).createLockSchedule({ - account: aliceAddress, - amount: totalAmount, - tokenHoldUntil: overrides.tokenHoldUntil ?? tge, - tokenUnlockStart: overrides.tokenUnlockStart ?? 0n, - tokenUnlockEnd: overrides.tokenUnlockEnd ?? tge + 2n * YEAR, - serviceStart: overrides.serviceStart ?? 0n, - serviceCliff: overrides.serviceCliff ?? 0n, - serviceEnd: overrides.serviceEnd ?? 0n, - group: overrides.group ?? GROUP_PRE_SEED, - }); + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + await token.tge(); + + return { ...fixture, amount }; + } - return { ...fixture, aliceAddress, tge, totalAmount }; + // ── Helpers for lock policies ─────────────────────────────────────────── + + /// Create a standard linear lock policy and return its id. + async function createLinearPolicy( + token: Awaited>["token"], + admin: Awaited>["admin"], + policyId: string, + opts: { + anchor?: number; // 0 = Absolute, 1 = Tge + start?: bigint; + cliffDuration?: bigint; + vestDuration?: bigint; + holdUntil?: bigint; + } = {}, + ) { + const id = ethers.encodeBytes32String(policyId); + const anchor = opts.anchor ?? 1; // default Tge-anchored + const start = opts.start ?? 0n; + const cliffDuration = opts.cliffDuration ?? 0n; + const vestDuration = opts.vestDuration ?? 2n * YEAR; + await token.connect(admin).createLockPolicy(id, { + holdUntil: opts.holdUntil ?? 0n, + unlock: { anchor, start, cliffDuration, vestDuration }, + }); + return id; } - // ── H-15 ────────────────────────────────────────────────────────────────── - describe("H-15 — WHITELIST_ROLE separation + one-way disable", function () { - it("admin starts with DEFAULT_ADMIN_ROLE, MINTER_ROLE, and WHITELIST_ROLE", async function () { + // ═════════════════════════════════════════════════════════════════════════ + // Deployment & Constructor + // ═════════════════════════════════════════════════════════════════════════ + + describe("constructor", function () { + it("reverts when claimSource is zero address", async function () { + const [deployer] = await ethers.getSigners(); + const mockRegistry = await new MockBondingRegistryFactory( + deployer, + ).deploy(); + await mockRegistry.waitForDeployment(); + const now = BigInt(await time.latest()); + const ccaStart = now + DAY; + const ccaEnd = ccaStart + 7n * DAY; + + await expect( + new InterfoldTokenFactory(deployer).deploy( + await deployer.getAddress(), + ccaStart, + ccaEnd, + ethers.ZeroAddress, + await mockRegistry.getAddress(), + ), + ).to.be.revertedWithCustomError( + { interface: InterfoldTokenFactory.createInterface() }, + "ZeroAddress", + ); + }); + + it("reverts when bondingRegistry is zero address", async function () { + const [deployer] = await ethers.getSigners(); + const now = BigInt(await time.latest()); + const ccaStart = now + DAY; + const ccaEnd = ccaStart + 7n * DAY; + + await expect( + new InterfoldTokenFactory(deployer).deploy( + await deployer.getAddress(), + ccaStart, + ccaEnd, + await deployer.getAddress(), + ethers.ZeroAddress, + ), + ).to.be.revertedWithCustomError( + { interface: InterfoldTokenFactory.createInterface() }, + "ZeroAddress", + ); + }); + + it("reverts when bondingRegistry has no code (EOA)", async function () { + const [deployer, admin] = await ethers.getSigners(); + const now = BigInt(await time.latest()); + const ccaStart = now + DAY; + const ccaEnd = ccaStart + 7n * DAY; + + await expect( + new InterfoldTokenFactory(deployer).deploy( + await admin.getAddress(), + ccaStart, + ccaEnd, + await deployer.getAddress(), + await admin.getAddress(), // EOA, not a contract + ), + ).to.be.revertedWithCustomError( + { interface: InterfoldTokenFactory.createInterface() }, + "InvalidBondingRegistry", + ); + }); + + it("reverts when CCA start is in the past", async function () { + const [deployer] = await ethers.getSigners(); + const mockRegistry = await new MockBondingRegistryFactory( + deployer, + ).deploy(); + await mockRegistry.waitForDeployment(); + const now = BigInt(await time.latest()); + + await expect( + new InterfoldTokenFactory(deployer).deploy( + await deployer.getAddress(), + now, // in the past (or now) + now + 7n * DAY, + await deployer.getAddress(), + await mockRegistry.getAddress(), + ), + ).to.be.revertedWithCustomError( + { interface: InterfoldTokenFactory.createInterface() }, + "InvalidCcaWindow", + ); + }); + + it("reverts when CCA end is not after start", async function () { + const [deployer] = await ethers.getSigners(); + const mockRegistry = await new MockBondingRegistryFactory( + deployer, + ).deploy(); + await mockRegistry.waitForDeployment(); + const now = BigInt(await time.latest()); + const ccaStart = now + DAY; + const ccaEnd = ccaStart; // equal, not greater + + await expect( + new InterfoldTokenFactory(deployer).deploy( + await deployer.getAddress(), + ccaStart, + ccaEnd, + await deployer.getAddress(), + await mockRegistry.getAddress(), + ), + ).to.be.revertedWithCustomError( + { interface: InterfoldTokenFactory.createInterface() }, + "InvalidCcaWindow", + ); + }); + + it("initial owner receives all roles", async function () { const { token, admin } = await loadFixture(deploy); - const DEFAULT_ADMIN_ROLE = await token.DEFAULT_ADMIN_ROLE(); - const MINTER_ROLE = await token.MINTER_ROLE(); - const WHITELIST_ROLE = await token.WHITELIST_ROLE(); + const adminAddress = await admin.getAddress(); expect( - await token.hasRole(DEFAULT_ADMIN_ROLE, await admin.getAddress()), - ).to.equal(true); - expect( - await token.hasRole(MINTER_ROLE, await admin.getAddress()), - ).to.equal(true); - expect( - await token.hasRole(WHITELIST_ROLE, await admin.getAddress()), - ).to.equal(true); + await token.hasRole(await token.DEFAULT_ADMIN_ROLE(), adminAddress), + ).to.be.true; + expect(await token.hasRole(await token.MINTER_ROLE(), adminAddress)).to.be + .true; + expect(await token.hasRole(await token.WHITELIST_ROLE(), adminAddress)).to + .be.true; + expect(await token.hasRole(await token.LOCK_MANAGER_ROLE(), adminAddress)) + .to.be.true; + }); + }); + + // ═════════════════════════════════════════════════════════════════════════ + // Phase lifecycle + // ═════════════════════════════════════════════════════════════════════════ + + describe("phase()", function () { + it("starts in Virtual phase", async function () { + const { token } = await loadFixture(deploy); + expect(await token.phase()).to.equal(0); // Phase.Virtual }); - it("non-WHITELIST_ROLE cannot call toggleTransferWhitelist", async function () { - const { token, alice } = await loadFixture(deploy); + it("enters PublicSale during CCA window", async function () { + const { token, ccaStart } = await loadFixture(deploy); + await time.increaseTo(ccaStart); + expect(await token.phase()).to.equal(1); // Phase.PublicSale + }); + + it("enters Cooldown after CCA_END before TGE", async function () { + const { token, ccaEnd } = await loadFixture(deploy); + await time.increaseTo(ccaEnd); + expect(await token.phase()).to.equal(2); // Phase.Cooldown + }); + + it("enters Live phase after TGE", async function () { + const { token, ccaEnd } = await loadFixture(deploy); + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + await token.tge(); + expect(await token.phase()).to.equal(3); // Phase.Live + }); + }); + + // ═════════════════════════════════════════════════════════════════════════ + // Minting + // ═════════════════════════════════════════════════════════════════════════ + + describe("mint", function () { + it("DEFAULT_ADMIN_ROLE can mint unlocked tokens during Virtual", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const amount = ethers.parseEther("100"); await expect( - token.connect(alice).toggleTransferWhitelist(await alice.getAddress()), - ).to.be.revertedWithCustomError( + token + .connect(admin) + .mint( + await alice.getAddress(), + amount, + ethers.encodeBytes32String("test"), + ), + ) + .to.emit(token, "AllocationMinted") + .withArgs( + await alice.getAddress(), + amount, + ethers.ZeroHash, + ethers.encodeBytes32String("test"), + ); + expect(await token.balanceOf(await alice.getAddress())).to.equal(amount); + }); + + it("mint reverts after Virtual phase", async function () { + const { token, admin, alice, ccaStart } = await loadFixture(deploy); + await time.increaseTo(ccaStart); + await expect( + token + .connect(admin) + .mint( + await alice.getAddress(), + ethers.parseEther("1"), + ethers.encodeBytes32String("test"), + ), + ).to.be.revertedWithCustomError(token, "MintingClosed"); + }); + + it("reverts with zero amount", async function () { + const { token, admin, alice } = await loadFixture(deploy); + await expect( + token + .connect(admin) + .mint( + await alice.getAddress(), + 0n, + ethers.encodeBytes32String("test"), + ), + ).to.be.revertedWithCustomError(token, "ZeroAmount"); + }); + + it("reverts when MAX_SUPPLY would be exceeded", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const maxSupply = await token.MAX_SUPPLY(); + await expect( + token + .connect(admin) + .mint(await alice.getAddress(), maxSupply + 1n, ethers.ZeroHash), + ).to.be.revertedWithCustomError(token, "MaxSupplyExceeded"); + }); + }); + + describe("mintAllocations", function () { + it("MINTER_ROLE can mint locked allocations during Virtual", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const policyId = await createLinearPolicy(token, admin, "TEST_POLICY"); + const amount = ethers.parseEther("1000"); + + await expect( + token.connect(admin).mintAllocations([ + { + recipient: await alice.getAddress(), + amount, + policyId, + label: ethers.encodeBytes32String("test"), + }, + ]), + ) + .to.emit(token, "AllocationMinted") + .withArgs( + await alice.getAddress(), + amount, + policyId, + ethers.encodeBytes32String("test"), + ); + + // Tokens are locked — lockedBalanceOf should be > 0. + expect(await token.lockedBalanceOf(await alice.getAddress())).to.equal( + amount, + ); + expect(await token.balanceOf(await alice.getAddress())).to.equal(amount); + }); + + it("reverts with zero policyId", async function () { + const { token, admin, alice } = await loadFixture(deploy); + await expect( + token.connect(admin).mintAllocations([ + { + recipient: await alice.getAddress(), + amount: ethers.parseEther("1"), + policyId: ethers.ZeroHash, + label: ethers.encodeBytes32String("test"), + }, + ]), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("reverts with undefined policy", async function () { + const { token, admin, alice } = await loadFixture(deploy); + await expect( + token.connect(admin).mintAllocations([ + { + recipient: await alice.getAddress(), + amount: ethers.parseEther("1"), + policyId: ethers.encodeBytes32String("UNDEFINED"), + label: ethers.encodeBytes32String("test"), + }, + ]), + ).to.be.revertedWithCustomError(token, "PolicyNotDefined"); + }); + + it("reverts after Virtual phase", async function () { + const { token, admin, alice, ccaStart } = await loadFixture(deploy); + const policyId = await createLinearPolicy(token, admin, "TEST_POLICY"); + await time.increaseTo(ccaStart); + await expect( + token.connect(admin).mintAllocations([ + { + recipient: await alice.getAddress(), + amount: ethers.parseEther("1"), + policyId, + label: ethers.ZeroHash, + }, + ]), + ).to.be.revertedWithCustomError(token, "MintingClosed"); + }); + }); + + // ═════════════════════════════════════════════════════════════════════════ + // TGE + // ═════════════════════════════════════════════════════════════════════════ + + describe("tge()", function () { + it("reverts before CCA_END + TGE_COOLDOWN", async function () { + const { token, ccaEnd } = await loadFixture(deploy); + await time.increaseTo(ccaEnd); // Cooldown phase but not enough + await expect(token.tge()).to.be.revertedWithCustomError( token, - "AccessControlUnauthorizedAccount", + "TgeTooEarly", ); }); - it("WHITELIST_ROLE without MINTER_ROLE can whitelist", async function () { - const { token, admin, whitelister, alice } = await loadFixture(deploy); - const WHITELIST_ROLE = await token.WHITELIST_ROLE(); - await token - .connect(admin) - .grantRole(WHITELIST_ROLE, await whitelister.getAddress()); + it("anyone can trigger TGE after cooldown", async function () { + const { token, ccaEnd, alice } = await loadFixture(deploy); + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + await expect(token.connect(alice).tge()).to.emit(token, "TgeTriggered"); + expect(await token.tgeTimestamp()).to.be.gt(0); + expect(await token.phase()).to.equal(3); // Live + }); + + it("reverts if already live", async function () { + const { token, ccaEnd } = await loadFixture(deploy); + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + await token.tge(); + await expect(token.tge()).to.be.revertedWithCustomError( + token, + "AlreadyLive", + ); + }); + }); + + // ═════════════════════════════════════════════════════════════════════════ + // Whitelisting + // ═════════════════════════════════════════════════════════════════════════ + + describe("setTransferWhitelisted", function () { + it("WHITELIST_ROLE can whitelist an address", async function () { + const { token, admin, alice } = await loadFixture(deploy); await expect( token - .connect(whitelister) - .toggleTransferWhitelist(await alice.getAddress()), + .connect(admin) + .setTransferWhitelisted(await alice.getAddress(), true), ) .to.emit(token, "TransferWhitelistUpdated") .withArgs(await alice.getAddress(), true); + expect(await token.transferWhitelist(await alice.getAddress())).to.be + .true; }); - it("non-admin cannot disableTransferRestrictions", async function () { + it("non-WHITELIST_ROLE cannot whitelist", async function () { const { token, alice } = await loadFixture(deploy); await expect( - token.connect(alice).disableTransferRestrictions(), + token + .connect(alice) + .setTransferWhitelisted(await alice.getAddress(), true), ).to.be.revertedWithCustomError( token, "AccessControlUnauthorizedAccount", ); }); - it("disableTransferRestrictions is one-way (idempotent no-op on second call)", async function () { + it("reverts with zero address", async function () { const { token, admin } = await loadFixture(deploy); - await token.connect(admin).setTgeEarliest(1); - await token.connect(admin).tge(); - await expect(token.connect(admin).disableTransferRestrictions()) - .to.emit(token, "TransferRestrictionUpdated") - .withArgs(false); - expect(await token.transfersRestricted()).to.equal(false); - // Second call: idempotent no-op (does not revert, does not emit). - await expect( - token.connect(admin).disableTransferRestrictions(), - ).to.not.emit(token, "TransferRestrictionUpdated"); - expect(await token.transfersRestricted()).to.equal(false); + await expect( + token.connect(admin).setTransferWhitelisted(ethers.ZeroAddress, true), + ).to.be.revertedWithCustomError(token, "ZeroAddress"); }); }); - // ── M-29 ────────────────────────────────────────────────────────────────── - describe("M-29 — EIP-6372 timestamp clock", function () { - it("clock() returns block.timestamp and CLOCK_MODE() is mode=timestamp", async function () { - const { token } = await loadFixture(deploy); - expect(await token.clock()).to.equal(await time.latest()); - expect(await token.CLOCK_MODE()).to.equal("mode=timestamp"); + describe("setLockWhitelisted", function () { + it("LOCK_MANAGER_ROLE can manage lock whitelist", async function () { + const { token, admin, alice } = await loadFixture(deploy); + await expect( + token.connect(admin).setLockWhitelisted(await alice.getAddress(), true), + ) + .to.emit(token, "LockWhitelistUpdated") + .withArgs(await alice.getAddress(), true); + }); + + it("non-LOCK_MANAGER_ROLE cannot manage lock whitelist", async function () { + const { token, alice } = await loadFixture(deploy); + await expect( + token.connect(alice).setLockWhitelisted(await alice.getAddress(), true), + ).to.be.revertedWithCustomError( + token, + "AccessControlUnauthorizedAccount", + ); }); }); - describe("pooled token-level locks", function () { - it("requires LOCK_MANAGER_ROLE for schedule creation", async function () { - const { token, alice } = await loadFixture(deployWithUnlockedTransfers); - await expect( - token.connect(alice).createLockSchedule({ - account: await alice.getAddress(), - amount: ethers.parseEther("1"), - tokenHoldUntil: 1n, - tokenUnlockStart: 1n, - tokenUnlockEnd: 1n, - serviceStart: 0n, - serviceCliff: 0n, - serviceEnd: 0n, - group: GROUP_PRE_SEED, + // ═════════════════════════════════════════════════════════════════════════ + // Lock Policies + // ═════════════════════════════════════════════════════════════════════════ + + describe("createLockPolicy", function () { + it("LOCK_MANAGER_ROLE can create a policy", async function () { + const { token, admin } = await loadFixture(deploy); + const policyId = ethers.encodeBytes32String("MY_POLICY"); + await expect( + token.connect(admin).createLockPolicy(policyId, { + holdUntil: 0n, + unlock: { + anchor: 1, // Tge + start: 0n, + cliffDuration: 0n, + vestDuration: 2n * YEAR, + }, + }), + ).to.emit(token, "PolicyDefined"); + }); + + it("reverts on duplicate policy id (write-once)", async function () { + const { token, admin } = await loadFixture(deploy); + const policyId = ethers.encodeBytes32String("MY_POLICY"); + await token.connect(admin).createLockPolicy(policyId, { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: YEAR, + }, + }); + await expect( + token.connect(admin).createLockPolicy(policyId, { + holdUntil: 0n, + unlock: { + anchor: 0, + start: 1n, + cliffDuration: 1n, + vestDuration: 0n, + }, + }), + ).to.be.revertedWithCustomError(token, "PolicyAlreadyDefined"); + }); + + it("reverts with zero policyId", async function () { + const { token, admin } = await loadFixture(deploy); + await expect( + token.connect(admin).createLockPolicy(ethers.ZeroHash, { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: YEAR, + }, }), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("reverts with PENDING policyId", async function () { + const { token, admin } = await loadFixture(deploy); + await expect( + token + .connect(admin) + .createLockPolicy(ethers.encodeBytes32String("PENDING"), { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: YEAR, + }, + }), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("reverts when both cliff and vest are zero", async function () { + const { token, admin } = await loadFixture(deploy); + await expect( + token + .connect(admin) + .createLockPolicy(ethers.encodeBytes32String("BAD"), { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: 0n, + }, + }), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("reverts when Absolute anchor has zero start", async function () { + const { token, admin } = await loadFixture(deploy); + await expect( + token + .connect(admin) + .createLockPolicy(ethers.encodeBytes32String("BAD"), { + holdUntil: 0n, + unlock: { + anchor: 0, + start: 0n, + cliffDuration: 1n, + vestDuration: 0n, + }, + }), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("reverts when Tge anchor has non-zero start", async function () { + const { token, admin } = await loadFixture(deploy); + await expect( + token + .connect(admin) + .createLockPolicy(ethers.encodeBytes32String("BAD"), { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 1n, + cliffDuration: 1n, + vestDuration: 0n, + }, + }), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("reverts when cliff exceeds vest duration", async function () { + const { token, admin } = await loadFixture(deploy); + await expect( + token + .connect(admin) + .createLockPolicy(ethers.encodeBytes32String("BAD"), { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 2n * YEAR, + vestDuration: YEAR, + }, + }), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("non-LOCK_MANAGER_ROLE cannot create a policy", async function () { + const { token, alice } = await loadFixture(deploy); + await expect( + token + .connect(alice) + .createLockPolicy(ethers.encodeBytes32String("MY_POLICY"), { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: YEAR, + }, + }), ).to.be.revertedWithCustomError( token, "AccessControlUnauthorizedAccount", ); }); + }); - it("blocks transfers that would drop a wallet below its locked floor", async function () { - const { token, alice, bob, totalAmount, tge } = await createLinearLock(); - const bobAddress = await bob.getAddress(); + // ═════════════════════════════════════════════════════════════════════════ + // Lock enforcement + // ═════════════════════════════════════════════════════════════════════════ - expect(await token.lockedFloorOf(await alice.getAddress())).to.equal( - totalAmount, + describe("lockedBalanceOf / lockedBalanceAt / transferableBalanceOf", function () { + it("lockedBalanceOf returns 0 for accounts with no locks", async function () { + const { token, alice } = await loadFixture(deploy); + expect(await token.lockedBalanceOf(await alice.getAddress())).to.equal( + 0n, ); + }); - await expect( - token.connect(alice).transfer(bobAddress, 1n), - ).to.be.revertedWithCustomError(token, "LockedBalanceInvariant"); + it("mintAllocation creates a lock tracked by lockedBalanceOf", async function () { + const { token, alice, amount } = await deployWithLockAndTge({ + mintAmount: ethers.parseEther("2400"), + }); + expect(await token.lockedBalanceOf(await alice.getAddress())).to.equal( + amount, + ); + }); + + it("TGE-anchored policy releases nothing before TGE timestamp", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const policyId = await createLinearPolicy(token, admin, "TEST_POLICY", { + vestDuration: 2n * YEAR, + }); + const amount = ethers.parseEther("2400"); + + await token.connect(admin).mintAllocations([ + { + recipient: await alice.getAddress(), + amount, + policyId, + label: ethers.encodeBytes32String("test"), + }, + ]); + + // TGE not fired yet, Tge-anchored curve should keep everything locked. + expect(await token.lockedBalanceOf(await alice.getAddress())).to.equal( + amount, + ); + }); + + it("linear unlock over time after TGE", async function () { + const { token, admin, alice, ccaEnd } = await loadFixture(deploy); + const policyId = await createLinearPolicy(token, admin, "TEST_POLICY", { + vestDuration: 2n * YEAR, + }); + const amount = ethers.parseEther("2400"); + + await token.connect(admin).mintAllocations([ + { + recipient: await alice.getAddress(), + amount, + policyId, + label: ethers.encodeBytes32String("test"), + }, + ]); + + // Fire TGE. + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + const tgeTx = await token.tge(); + const receipt = await tgeTx.wait(); + const tgeBlock = await ethers.provider.getBlock(receipt!.blockNumber); + const tgeTimestamp = BigInt(tgeBlock!.timestamp); + + // Right at TGE: everything still locked (cliffDuration = 0 so it starts + // vesting immediately — but at timestamp == anchor, nothing has accrued). + expect(await token.lockedBalanceOf(await alice.getAddress())).to.equal( + amount, + ); + + // Halfway through vesting: half unlocked. + await time.increaseTo(tgeTimestamp + YEAR); + expect(await token.lockedBalanceOf(await alice.getAddress())).to.equal( + amount / 2n, + ); + + // Past vest end: fully unlocked. + await time.increaseTo(tgeTimestamp + 2n * YEAR); + expect(await token.lockedBalanceOf(await alice.getAddress())).to.equal( + 0n, + ); + }); + + it("holdUntil keeps everything locked regardless of curve", async function () { + // Use deployWithLockAndTge which creates a Tge-anchored lock with holdUntil=0. + // Then verify lockedBalanceAt at various timestamps. + const { token, alice, amount, tgeTimestamp } = await deployWithLockAndTge( + { mintAmount: ethers.parseEther("1000") }, + ); + const aliceAddress = await alice.getAddress(); + + // At tgeTimestamp, lock is fully locked (no time elapsed). + expect(await token.lockedBalanceAt(aliceAddress, tgeTimestamp)).to.equal( + amount, + ); + + // At tgeTimestamp + YEAR, half is unlocked (linear vest over 2Y). + expect( + await token.lockedBalanceAt(aliceAddress, tgeTimestamp + YEAR), + ).to.equal(amount / 2n); - await time.increaseTo(tge + YEAR); - expect(await token.lockedFloorOf(await alice.getAddress())).to.equal( - totalAmount / 2n, + // At tgeTimestamp + 2*YEAR, fully unlocked. + expect( + await token.lockedBalanceAt(aliceAddress, tgeTimestamp + 2n * YEAR), + ).to.equal(0n); + }); + + it("transferableBalanceOf returns full balance when nothing locked", async function () { + const { token, alice, amount } = await deployWithUnlockedAndTge( + ethers.parseEther("100"), ); expect( await token.transferableBalanceOf(await alice.getAddress()), - ).to.equal(totalAmount / 2n); + ).to.equal(amount); + }); + + it("transferableBalanceOf = 0 when fully locked and no bond", async function () { + const { token, alice, amount } = await deployWithLockAndTge({ + mintAmount: ethers.parseEther("1000"), + }); + expect( + await token.transferableBalanceOf(await alice.getAddress()), + ).to.equal(0n); + }); + }); - await token.connect(alice).transfer(bobAddress, totalAmount / 2n); + // ═════════════════════════════════════════════════════════════════════════ + // Transfer enforcement + // ═════════════════════════════════════════════════════════════════════════ + + describe("transfer enforcement", function () { + it("blocks transfer that would drop below locked balance", async function () { + const { token, alice, bob, amount } = await deployWithLockAndTge({ + mintAmount: ethers.parseEther("1000"), + }); + // After TGE, a tiny fraction may have unlocked (1-2 seconds of vesting). + // transferableBalance should be far less than the full amount. + const transferable = await token.transferableBalanceOf( + await alice.getAddress(), + ); + expect(transferable).to.be.lt(amount / 2n); + // Attempting to transfer the full amount should revert. await expect( - token.connect(alice).transfer(bobAddress, totalAmount / 2n), - ).to.be.revertedWithCustomError(token, "LockedBalanceInvariant"); + token.connect(alice).transfer(await bob.getAddress(), amount), + ).to.be.revertedWithCustomError(token, "InsufficientUnlockedBalance"); + }); + + it("allows transfer of unlocked portion", async function () { + const { token, alice, bob, amount, tgeTimestamp } = + await deployWithLockAndTge({ + mintAmount: ethers.parseEther("1000"), + vestDuration: 2n * YEAR, + }); + + await time.increaseTo(tgeTimestamp + YEAR); - await time.increaseTo(tge + 2n * YEAR); - await token.connect(alice).transfer(bobAddress, totalAmount / 2n); - expect(await token.balanceOf(await alice.getAddress())).to.equal(0n); + // Half unlocked. + const half = amount / 2n; + expect( + await token.transferableBalanceOf(await alice.getAddress()), + ).to.equal(half); + + await token.connect(alice).transfer(await bob.getAddress(), half); }); - it("keeps launch whitelist separate from locked-floor enforcement", async function () { - const { token, admin, alice, bob } = await loadFixture(deploy); - const aliceAddress = await alice.getAddress(); - const bobAddress = await bob.getAddress(); - const now = BigInt(await time.latest()); - const tge = now + DAY; - const totalAmount = ethers.parseEther("100"); + it("pre-TGE: bonding registry transfers are allowed", async function () { + const { token, admin, alice, mockRegistry } = await loadFixture(deploy); + const amount = ethers.parseEther("100"); + const registryAddress = await mockRegistry.getAddress(); - await token.connect(admin).setTgeEarliest(1); - await token.connect(admin).tge(); - await token.connect(admin).setTgeTimestamp(tge); await token .connect(admin) - .mintAllocation(aliceAddress, totalAmount, "Locked allocation"); - await token.connect(admin).toggleTransferWhitelist(aliceAddress); - await token.connect(admin).createLockSchedule({ - account: aliceAddress, - amount: totalAmount, - tokenHoldUntil: tge, - tokenUnlockStart: 0n, - tokenUnlockEnd: tge + YEAR, - serviceStart: 0n, - serviceCliff: 0n, - serviceEnd: 0n, - group: GROUP_PRE_SEED, - }); + .mint(await alice.getAddress(), amount, ethers.ZeroHash); - await expect( - token.connect(alice).transfer(bobAddress, 1n), - ).to.be.revertedWithCustomError(token, "LockedBalanceInvariant"); + // Transfer TO bonding registry — should work pre-TGE. + await token.connect(alice).transfer(registryAddress, amount); }); - it("accumulates Bridge SAFT linear unlock while the hold is still active", async function () { - const fixture = await loadFixture(deployWithUnlockedTransfers); - const { token, admin, alice } = fixture; - const aliceAddress = await alice.getAddress(); - const now = BigInt(await time.latest()); - const tge = now + DAY; - const totalAmount = ethers.parseEther("2400"); + it("pre-TGE: whitelisted addresses can transfer", async function () { + const { token, admin, alice, bob } = await loadFixture(deploy); + const amount = ethers.parseEther("100"); - await token.connect(admin).setTgeTimestamp(tge); await token .connect(admin) - .mintAllocation(aliceAddress, totalAmount, "Bridge allocation"); - await token.connect(admin).createLockSchedule({ - account: aliceAddress, - amount: totalAmount, - tokenHoldUntil: tge + YEAR, - tokenUnlockStart: 0n, - tokenUnlockEnd: tge + 2n * YEAR, - serviceStart: 0n, - serviceCliff: 0n, - serviceEnd: 0n, - group: GROUP_BRIDGE, - }); + .mint(await alice.getAddress(), amount, ethers.ZeroHash); + await token + .connect(admin) + .setTransferWhitelisted(await alice.getAddress(), true); + + await token.connect(alice).transfer(await bob.getAddress(), amount); + }); - await time.increaseTo(tge + YEAR / 2n); - expect(await token.lockedFloorOf(aliceAddress)).to.equal(totalAmount); + it("pre-TGE: claim source transfers are allowed", async function () { + const { token, admin, alice, claimSource } = await loadFixture(deploy); + const amount = ethers.parseEther("100"); - await time.increaseTo(tge + YEAR); - expect(await token.lockedFloorOf(aliceAddress)).to.equal( - totalAmount / 2n, - ); + await token + .connect(admin) + .mint(await claimSource.getAddress(), amount, ethers.ZeroHash); + + await token + .connect(claimSource) + .transfer(await alice.getAddress(), amount); }); - it("applies team service vesting as the stricter curve", async function () { - const fixture = await loadFixture(deployWithUnlockedTransfers); - const { token, admin, alice } = fixture; - const aliceAddress = await alice.getAddress(); - const now = BigInt(await time.latest()); - const tge = now + DAY; - const signing = tge - 180n * DAY; - const totalAmount = ethers.parseEther("4800"); + it("pre-TGE: regular transfers are blocked", async function () { + const { token, admin, alice, bob } = await loadFixture(deploy); + const amount = ethers.parseEther("100"); - await token.connect(admin).setTgeTimestamp(tge); await token .connect(admin) - .mintAllocation(aliceAddress, totalAmount, "Team allocation"); - await token.connect(admin).createLockSchedule({ - account: aliceAddress, - amount: totalAmount, - tokenHoldUntil: tge, - tokenUnlockStart: 0n, - tokenUnlockEnd: tge + 2n * YEAR, - serviceStart: signing, - serviceCliff: signing + YEAR, - serviceEnd: signing + 4n * YEAR, - group: GROUP_TEAM, - }); + .mint(await alice.getAddress(), amount, ethers.ZeroHash); + + await expect( + token.connect(alice).transfer(await bob.getAddress(), amount), + ).to.be.revertedWithCustomError(token, "TransferRestricted"); + }); + }); + + // ═════════════════════════════════════════════════════════════════════════ + // Claim-source auto-lock & linkClaim + // ═════════════════════════════════════════════════════════════════════════ + + describe("claim-source auto-lock & linkClaim", function () { + it("CLAIM_SOURCE transfers create PENDING locks", async function () { + const { token, alice, claimSource, amount } = + await deployWithUnlockedAndTge(ethers.parseEther("500")); - await time.increaseTo(signing + YEAR - DAY); - expect(await token.lockedFloorOf(aliceAddress)).to.equal(totalAmount); + // Transfer from alice to claimSource first so claimSource has tokens. + await token + .connect(alice) + .transfer(await claimSource.getAddress(), amount); + + await token + .connect(claimSource) + .transfer(await alice.getAddress(), amount); - await time.increaseTo(signing + YEAR); - expect(await token.lockedFloorOf(aliceAddress)).to.equal( - totalAmount - totalAmount / 4n, + // Pending lock should be created. + expect(await token.lockedBalanceOf(await alice.getAddress())).to.equal( + amount, ); }); - it("creates lock schedules on transfers from approved CCA claim sources", async function () { - const { token, admin, alice, bob, claimSource } = await loadFixture( - deployWithUnlockedTransfers, - ); - const aliceAddress = await alice.getAddress(); - const bobAddress = await bob.getAddress(); - const claimSourceAddress = await claimSource.getAddress(); - const claimAmount = ethers.parseEther("500"); - const now = BigInt(await time.latest()); + it("lockWhitelist exempts from auto-lock on claim-source transfer", async function () { + const { token, admin, alice, claimSource, amount } = + await deployWithUnlockedAndTge(ethers.parseEther("500")); - await token.connect(admin).setClaimSource(claimSourceAddress, true); - await token.connect(admin).setClaimLockProfile(aliceAddress, { - active: true, - lockStart: now, - holdDuration: 40n * DAY, - unlockDuration: 0n, - group: GROUP_CCA_REG_S, - }); await token .connect(admin) - .mintAllocation(claimSourceAddress, claimAmount, "CCA claim source"); + .setLockWhitelisted(await alice.getAddress(), true); - await expect( - token.connect(claimSource).transfer(aliceAddress, claimAmount), - ).to.emit(token, "LockScheduleCreated"); + // Transfer tokens from alice to claimSource so claimSource can send. + await token + .connect(alice) + .transfer(await claimSource.getAddress(), amount); - expect(await token.lockedFloorOf(aliceAddress)).to.equal(claimAmount); - await expect( - token.connect(alice).transfer(bobAddress, 1n), - ).to.be.revertedWithCustomError(token, "LockedBalanceInvariant"); + await token + .connect(claimSource) + .transfer(await alice.getAddress(), amount); - await time.increase(40n * DAY + 1n); - expect(await token.lockedFloorOf(aliceAddress)).to.equal(0n); - await token.connect(alice).transfer(bobAddress, claimAmount); + // No lock created because recipient is lock-whitelisted. + expect(await token.lockedBalanceOf(await alice.getAddress())).to.equal( + 0n, + ); + expect(await token.balanceOf(await alice.getAddress())).to.equal(amount); }); - it("reuses fully unlocked schedule slots at the schedule cap", async function () { - const { token, admin, alice } = await loadFixture( - deployWithUnlockedTransfers, - ); - const aliceAddress = await alice.getAddress(); - const now = BigInt(await time.latest()); - const unlockedAmount = ethers.parseEther("1"); - const activeAmount = ethers.parseEther("2"); + it("linkClaim moves PENDING to a real policy", async function () { + const { token, admin, alice, claimSource, amount } = + await deployWithUnlockedAndTge(ethers.parseEther("500")); + const policyId = await createLinearPolicy(token, admin, "REAL_POLICY", { + vestDuration: 2n * YEAR, + }); + // Transfer from alice to claimSource so claimSource can send. await token - .connect(admin) - .mintAllocation( - aliceAddress, - unlockedAmount * 64n + activeAmount, - "Schedule capacity", - ); + .connect(alice) + .transfer(await claimSource.getAddress(), amount); + await token + .connect(claimSource) + .transfer(await alice.getAddress(), amount); - for (let i = 0; i < 64; i++) { - await token.connect(admin).createLockSchedule({ - account: aliceAddress, - amount: unlockedAmount, - tokenHoldUntil: now, - tokenUnlockStart: now, - tokenUnlockEnd: now, - serviceStart: 0n, - serviceCliff: 0n, - serviceEnd: 0n, - group: GROUP_PRE_SEED, - }); - } - - expect(await token.lockScheduleCount(aliceAddress)).to.equal(64n); - expect(await token.lockedFloorOf(aliceAddress)).to.equal(0n); - - await expect( - token.connect(admin).createLockSchedule({ - account: aliceAddress, - amount: activeAmount, - tokenHoldUntil: now + DAY, - tokenUnlockStart: now + DAY, - tokenUnlockEnd: now + 2n * DAY, - serviceStart: 0n, - serviceCliff: 0n, - serviceEnd: 0n, - group: GROUP_PRE_SEED, - }), - ) - .to.emit(token, "LockScheduleCreated") - .withArgs( - aliceAddress, - 0n, - GROUP_PRE_SEED, - activeAmount, - now + DAY, - now + DAY, - now + 2n * DAY, - 0n, - 0n, - 0n, - ); + // Now link the claim to the real policy. + await token + .connect(admin) + .linkClaim(await alice.getAddress(), amount, policyId); - expect(await token.lockScheduleCount(aliceAddress)).to.equal(64n); - expect(await token.lockedFloorOf(aliceAddress)).to.equal(activeAmount); + // Lock should still exist but now under the real policy (allow tiny + // rounding from vesting elapsed seconds). + const lb = await token.lockedBalanceOf(await alice.getAddress()); + expect(lb).to.be.closeTo(amount, ethers.parseEther("0.01")); }); - it("rejects claim-source transfers when the recipient has no active profile", async function () { - const { token, admin, alice, claimSource } = await loadFixture( - deployWithUnlockedTransfers, + it("linkClaim queues unfilled amounts for future claims", async function () { + const fixture = await loadFixture(deploy); + const { token, admin, alice, claimSource, ccaEnd } = fixture; + const policyId = await createLinearPolicy(token, admin, "FUTURE_POLICY", { + vestDuration: 2n * YEAR, + }); + const linkAmount = ethers.parseEther("500"); + + // Link before any claim arrives — should queue. + await token + .connect(admin) + .linkClaim(await alice.getAddress(), linkAmount, policyId); + + // No balance yet so no active lock. + expect(await token.lockedBalanceOf(await alice.getAddress())).to.equal( + 0n, ); - const claimSourceAddress = await claimSource.getAddress(); - const claimAmount = ethers.parseEther("1"); - await token.connect(admin).setClaimSource(claimSourceAddress, true); + // Mint tokens to claimSource during Virtual phase. await token .connect(admin) - .mintAllocation(claimSourceAddress, claimAmount, "CCA claim source"); + .mint(await claimSource.getAddress(), linkAmount, ethers.ZeroHash); + + // Fire TGE so transfers are unrestricted. + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + await token.tge(); + + // Now send a claim — it should consume the queued lock. + await token + .connect(claimSource) + .transfer(await alice.getAddress(), linkAmount); + + // Queued lock should be consumed and active lock created (allow tiny + // rounding from vesting elapsed seconds). + const lb2 = await token.lockedBalanceOf(await alice.getAddress()); + expect(lb2).to.be.closeTo(linkAmount, ethers.parseEther("0.01")); + }); + it("linkClaim reverts with undefined policy", async function () { + const { token, admin, alice } = await loadFixture(deploy); await expect( token - .connect(claimSource) - .transfer(await alice.getAddress(), claimAmount), - ) - .to.be.revertedWithCustomError(token, "ClaimLockProfileMissing") - .withArgs(await alice.getAddress()); + .connect(admin) + .linkClaim( + await alice.getAddress(), + ethers.parseEther("1"), + ethers.encodeBytes32String("UNDEFINED"), + ), + ).to.be.revertedWithCustomError(token, "PolicyNotDefined"); }); - it("does not create claim locks for BondingRegistry exit payouts", async function () { - const signers = await ethers.getSigners(); - const [, beneficiary] = signers; - const beneficiaryAddress = await beneficiary.getAddress(); - const sys = await deployInterfoldSystem({ - useMockCiphernodeRegistry: true, - setupOperators: 0, - mintUsdcTo: [], - }); - const { bondingRegistry, licenseToken } = sys; - const bondingRegistryAddress = await bondingRegistry.getAddress(); - const bondAmount = ethers.parseEther("100"); - const unbondAmount = ethers.parseEther("25"); + it("linkClaim reverts with zero amount", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const policyId = await createLinearPolicy(token, admin, "REAL_POLICY"); + await expect( + token.connect(admin).linkClaim(await alice.getAddress(), 0n, policyId), + ).to.be.revertedWithCustomError(token, "ZeroAmount"); + }); - await licenseToken.setBondingRegistry(bondingRegistryAddress); - await licenseToken.setClaimSource(bondingRegistryAddress, true); - await licenseToken.mintAllocation( - beneficiaryAddress, - bondAmount, - "License bond", + it("non-LOCK_MANAGER_ROLE cannot linkClaim", async function () { + const { token, alice } = await loadFixture(deploy); + await expect( + token + .connect(alice) + .linkClaim( + await alice.getAddress(), + ethers.parseEther("1"), + ethers.encodeBytes32String("ANY"), + ), + ).to.be.revertedWithCustomError( + token, + "AccessControlUnauthorizedAccount", ); - await licenseToken - .connect(beneficiary) - .approve(bondingRegistryAddress, bondAmount); + }); - await bondingRegistry.connect(beneficiary).bondLicense(bondAmount); - await bondingRegistry.connect(beneficiary).unbondLicense(unbondAmount); + it("queued locks survive multiple partial claims", async function () { + const fixture = await loadFixture(deploy); + const { token, admin, alice, claimSource, ccaEnd } = fixture; + const policyId = await createLinearPolicy(token, admin, "PARTIAL", { + vestDuration: 2n * YEAR, + }); + const linkAmount = ethers.parseEther("1000"); - await time.increase(SEVEN_DAYS + 1); - await bondingRegistry.connect(beneficiary).claimExits(0, unbondAmount); + // Queue a large amount. + await token + .connect(admin) + .linkClaim(await alice.getAddress(), linkAmount, policyId); - expect(await licenseToken.lockScheduleCount(beneficiaryAddress)).to.equal( - 0n, - ); - expect(await licenseToken.balanceOf(beneficiaryAddress)).to.equal( - unbondAmount, - ); - }); + // Mint all claim tokens during Virtual phase. + const totalClaim = ethers.parseEther("700"); + await token + .connect(admin) + .mint(await claimSource.getAddress(), totalClaim, ethers.ZeroHash); + + // Fire TGE. + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + await token.tge(); + + // Send a partial claim. + const partialAmount = ethers.parseEther("400"); + await token + .connect(claimSource) + .transfer(await alice.getAddress(), partialAmount); - it("does not let admins create schedules beyond current controlled balance", async function () { - const { token, admin, alice } = await loadFixture( - deployWithUnlockedTransfers, + let lb3 = await token.lockedBalanceOf(await alice.getAddress()); + expect(lb3).to.be.closeTo(partialAmount, ethers.parseEther("0.01")); + + // Send another claim. + const anotherAmount = ethers.parseEther("300"); + await token + .connect(claimSource) + .transfer(await alice.getAddress(), anotherAmount); + + lb3 = await token.lockedBalanceOf(await alice.getAddress()); + expect(lb3).to.be.closeTo( + partialAmount + anotherAmount, + ethers.parseEther("0.01"), ); - const now = BigInt(await time.latest()); - const tge = now + DAY; - - await token.connect(admin).setTgeTimestamp(tge); - await expect( - token.connect(admin).createLockSchedule({ - account: await alice.getAddress(), - amount: ethers.parseEther("1"), - tokenHoldUntil: tge, - tokenUnlockStart: 0n, - tokenUnlockEnd: tge + YEAR, - serviceStart: 0n, - serviceCliff: 0n, - serviceEnd: 0n, - group: GROUP_PRE_SEED, - }), - ).to.be.revertedWithCustomError(token, "LockedBalanceInvariant"); }); + }); - it("counts self-bonded and pending-exit ENCL toward the locked floor", async function () { + // ═════════════════════════════════════════════════════════════════════════ + // BondingRegistry integration (uses deployInterfoldSystem) + // ═════════════════════════════════════════════════════════════════════════ + + describe("BondingRegistry integration", function () { + it("transferableBalanceOf counts bonded INTF toward the locked floor", async function () { const signers = await ethers.getSigners(); const [, beneficiary, slasher] = signers; const beneficiaryAddress = await beneficiary.getAddress(); @@ -512,132 +1157,150 @@ describe("InterfoldToken", function () { wireSlashingManager: false, mintUsdcTo: [], }); - const { bondingRegistry, licenseToken, owner } = sys; + const { bondingRegistry, licenseToken } = sys; const bondingRegistryAddress = await bondingRegistry.getAddress(); - const now = BigInt(await time.latest()); - const tge = now + DAY; const totalAmount = ethers.parseEther("1000"); const bondAmount = ethers.parseEther("800"); - const unbondAmount = ethers.parseEther("300"); await bondingRegistry.setSlashingManager(slasherAddress); - await licenseToken.setTgeTimestamp(tge); - await licenseToken.setBondingRegistry(bondingRegistryAddress); - await licenseToken.mintAllocation( + + // Mint unlocked tokens and bond some. + await licenseToken.mint( beneficiaryAddress, totalAmount, - "Locked allocation", + ethers.encodeBytes32String("test"), ); - await licenseToken.createLockSchedule({ - account: beneficiaryAddress, - amount: totalAmount, - tokenHoldUntil: tge, - tokenUnlockStart: 0n, - tokenUnlockEnd: tge + YEAR, - serviceStart: 0n, - serviceCliff: 0n, - serviceEnd: 0n, - group: GROUP_PRE_SEED, - }); - await licenseToken .connect(beneficiary) .approve(bondingRegistryAddress, bondAmount); await bondingRegistry.connect(beneficiary).bondLicense(bondAmount); + // Wallet balance is totalAmount - bondAmount, bonded = bondAmount. + // No locks so everything is transferable. expect(await licenseToken.balanceOf(beneficiaryAddress)).to.equal( totalAmount - bondAmount, ); - expect(await licenseToken.totalBondedOf(beneficiaryAddress)).to.equal( - bondAmount, - ); expect( await licenseToken.transferableBalanceOf(beneficiaryAddress), - ).to.equal(0n); - - await bondingRegistry.connect(beneficiary).unbondLicense(unbondAmount); - expect(await licenseToken.totalBondedOf(beneficiaryAddress)).to.equal( - bondAmount, - ); - - await time.increase(SEVEN_DAYS + 1); - await bondingRegistry.connect(beneficiary).claimExits(0, unbondAmount); - expect(await licenseToken.totalBondedOf(beneficiaryAddress)).to.equal( - bondAmount - unbondAmount, + ).to.equal(totalAmount - bondAmount); + + // Now create a lock policy and mint a locked allocation. + const policyId = ethers.encodeBytes32String("BOND_TEST"); + await licenseToken.createLockPolicy(policyId, { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: 2n * YEAR, + }, + }); + const lockAmount = ethers.parseEther("400"); + // Mint extra unlocked tokens to fund the lock. + await licenseToken.mint( + beneficiaryAddress, + lockAmount, + ethers.encodeBytes32String("extra"), ); - expect(await licenseToken.balanceOf(beneficiaryAddress)).to.equal( - totalAmount - bondAmount + unbondAmount, + await licenseToken.mintAllocations([ + { + recipient: beneficiaryAddress, + amount: lockAmount, + policyId, + label: ethers.encodeBytes32String("locked"), + }, + ]); + + // Locked balance ≈ lockAmount (400) — Tge-anchored with tiny vesting. + // Bonded balance = bondAmount (800). + // Since bonded > locked, the bond covers all locks. + // Wallet = totalAmount - bondAmount + lockAmount + lockAmount + // = 1000 - 800 + 400 + 400 = 1000. + // transferable = balance - max(0, locked - bonded) ≈ 1000 - 0 = 1000. + const tb = await licenseToken.transferableBalanceOf(beneficiaryAddress); + expect(tb).to.be.closeTo( + totalAmount - bondAmount + lockAmount + lockAmount, + ethers.parseEther("0.01"), ); - - await expect( - licenseToken - .connect(beneficiary) - .transfer(await owner.getAddress(), ethers.parseEther("100")), - ).to.be.revertedWithCustomError(licenseToken, "LockedBalanceInvariant"); }); - it("encumbers later same-wallet tokens after a slash deficit", async function () { - const signers = await ethers.getSigners(); - const [, beneficiary, slasher, recipient] = signers; - const beneficiaryAddress = await beneficiary.getAddress(); - const recipientAddress = await recipient.getAddress(); - const slasherAddress = await slasher.getAddress(); + it("bonding registry transfers are allowed pre-TGE", async function () { const sys = await deployInterfoldSystem({ useMockCiphernodeRegistry: true, setupOperators: 0, - wireSlashingManager: false, mintUsdcTo: [], }); - const { bondingRegistry, licenseToken } = sys; + const { bondingRegistry, licenseToken, owner } = sys; const bondingRegistryAddress = await bondingRegistry.getAddress(); - const now = BigInt(await time.latest()); - const tge = now + DAY; - const totalAmount = ethers.parseEther("1000"); - const slashAmount = ethers.parseEther("400"); + const bondAmount = ethers.parseEther("100"); - await bondingRegistry.setSlashingManager(slasherAddress); - await licenseToken.setTgeTimestamp(tge); - await licenseToken.setBondingRegistry(bondingRegistryAddress); - await licenseToken.mintAllocation( - beneficiaryAddress, - totalAmount, - "Locked allocation", + await licenseToken.mint( + await owner.getAddress(), + bondAmount, + ethers.encodeBytes32String("test"), ); - await licenseToken.createLockSchedule({ - account: beneficiaryAddress, - amount: totalAmount, - tokenHoldUntil: tge, - tokenUnlockStart: 0n, - tokenUnlockEnd: tge + YEAR, - serviceStart: 0n, - serviceCliff: 0n, - serviceEnd: 0n, - group: GROUP_PRE_SEED, - }); - await licenseToken - .connect(beneficiary) - .approve(bondingRegistryAddress, totalAmount); - await bondingRegistry.connect(beneficiary).bondLicense(totalAmount); - await bondingRegistry - .connect(slasher) - .slashLicenseBond( - beneficiaryAddress, - slashAmount, - ethers.encodeBytes32String("TEST_SLASH"), - ); + .connect(owner) + .approve(bondingRegistryAddress, bondAmount); + // Bonding transfer should succeed. + await bondingRegistry.connect(owner).bondLicense(bondAmount); + }); + }); - await licenseToken.mintAllocation( - beneficiaryAddress, - slashAmount, - "Later unlocked top-up", - ); + // ═════════════════════════════════════════════════════════════════════════ + // Ownership + // ═════════════════════════════════════════════════════════════════════════ + + describe("ownership", function () { + it("renounceOwnership is disabled", async function () { + const { token, admin } = await loadFixture(deploy); await expect( - licenseToken.connect(beneficiary).transfer(recipientAddress, 1n), - ).to.be.revertedWithCustomError(licenseToken, "LockedBalanceInvariant"); + token.connect(admin).renounceOwnership(), + ).to.be.revertedWithCustomError(token, "RenounceOwnershipDisabled"); + }); - await time.increaseTo(tge + YEAR); - await licenseToken.connect(beneficiary).transfer(recipientAddress, 1n); + it("ownership transfer syncs AccessControl roles", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const adminAddress = await admin.getAddress(); + const aliceAddress = await alice.getAddress(); + + // Transfer ownership to alice via 2-step. + await token.connect(admin).transferOwnership(aliceAddress); + await token.connect(alice).acceptOwnership(); + + // Old owner loses all roles. + expect( + await token.hasRole(await token.DEFAULT_ADMIN_ROLE(), adminAddress), + ).to.be.false; + expect(await token.hasRole(await token.MINTER_ROLE(), adminAddress)).to.be + .false; + expect(await token.hasRole(await token.WHITELIST_ROLE(), adminAddress)).to + .be.false; + expect(await token.hasRole(await token.LOCK_MANAGER_ROLE(), adminAddress)) + .to.be.false; + + // New owner gains all roles. + expect( + await token.hasRole(await token.DEFAULT_ADMIN_ROLE(), aliceAddress), + ).to.be.true; + expect(await token.hasRole(await token.MINTER_ROLE(), aliceAddress)).to.be + .true; + expect(await token.hasRole(await token.WHITELIST_ROLE(), aliceAddress)).to + .be.true; + expect(await token.hasRole(await token.LOCK_MANAGER_ROLE(), aliceAddress)) + .to.be.true; + }); + }); + + // ═════════════════════════════════════════════════════════════════════════ + // EIP-6372 + // ═════════════════════════════════════════════════════════════════════════ + + describe("EIP-6372", function () { + it("clock() returns block.timestamp and CLOCK_MODE() is mode=timestamp", async function () { + const { token } = await loadFixture(deploy); + expect(await token.clock()).to.equal(await time.latest()); + expect(await token.CLOCK_MODE()).to.equal("mode=timestamp"); }); }); }); diff --git a/packages/interfold-contracts/test/fixtures/operators.ts b/packages/interfold-contracts/test/fixtures/operators.ts index f3e31b6fd4..3ed612d325 100644 --- a/packages/interfold-contracts/test/fixtures/operators.ts +++ b/packages/interfold-contracts/test/fixtures/operators.ts @@ -22,10 +22,10 @@ export async function setupOperatorForSortition( ): Promise { const operatorAddress = await operator.getAddress(); - await licenseToken.mintAllocation( + await licenseToken.mint( operatorAddress, ethers.parseEther("10000"), - "Test allocation", + ethers.encodeBytes32String("Test allocation"), ); await usdcToken.mint(operatorAddress, ethers.parseUnits("100000", 6)); diff --git a/packages/interfold-contracts/test/fixtures/system.ts b/packages/interfold-contracts/test/fixtures/system.ts index 7a14928c39..91149f6b2e 100644 --- a/packages/interfold-contracts/test/fixtures/system.ts +++ b/packages/interfold-contracts/test/fixtures/system.ts @@ -253,13 +253,8 @@ export async function deployInterfoldSystem( usdcToken = MockUSDCFactory.connect(await mockUSDC.getAddress(), owner); } - const { interfoldToken } = await ignition.deploy(InterfoldTokenModule, { - parameters: { InterfoldToken: { owner: ownerAddress } }, - }); - const licenseToken = InterfoldTokenFactory.connect( - await interfoldToken.getAddress(), - owner, - ); + // Deferred: InterfoldToken is deployed after BondingRegistry so the + // immutable BONDING_REGISTRY reference can be set. See below. const { interfoldTicketToken } = await ignition.deploy( InterfoldTicketTokenModule, @@ -322,6 +317,7 @@ export async function deployInterfoldSystem( effectiveRegistryAddress = mockRegAddress; } + // ── BondingRegistry (deployed before token; uses ADDRESS_ONE placeholder) ── const { bondingRegistry: _bondingRegistry } = await ignition.deploy( BondingRegistryModule, { @@ -329,7 +325,7 @@ export async function deployInterfoldSystem( BondingRegistry: { owner: ownerAddress, ticketToken: await ticketToken.getAddress(), - licenseToken: await licenseToken.getAddress(), + licenseToken: ADDRESS_ONE, // placeholder — fixed below registry: effectiveRegistryAddress, slashedFundsTreasury: slashedFundsTreasuryAddress, ticketPrice: TICKET_PRICE, @@ -344,6 +340,31 @@ export async function deployInterfoldSystem( await _bondingRegistry.getAddress(), owner, ); + const bondingRegistryAddress = await bondingRegistry.getAddress(); + + // ── InterfoldToken (deployed after BondingRegistry for immutable ref) ── + const deployTime = BigInt(await time.latest()); + const ccaStart = deployTime + 1000n; // keep Virtual phase during setup + const ccaEnd = ccaStart + 7n * 24n * 60n * 60n; // 7-day CCA window + const claimSource = ownerAddress; // owner as placeholder claim source + const { interfoldToken } = await ignition.deploy(InterfoldTokenModule, { + parameters: { + InterfoldToken: { + owner: ownerAddress, + ccaStart, + ccaEnd, + claimSource, + bondingRegistry: bondingRegistryAddress, + }, + }, + }); + const licenseToken = InterfoldTokenFactory.connect( + await interfoldToken.getAddress(), + owner, + ); + + // Fix the BondingRegistry licenseToken placeholder. + await bondingRegistry.setLicenseToken(await licenseToken.getAddress()); // ── Interfold ──────────────────────────────────────────────────────────────── const { interfold: _interfold } = await ignition.deploy(InterfoldModule, { @@ -465,10 +486,7 @@ export async function deployInterfoldSystem( await interfold.setCommitteeThresholds(size, [min, max]); } - // ── Operators ───────────────────────────────────────────────────────────── - await licenseToken.setTgeEarliest(1); - await licenseToken.tge(); - await licenseToken.disableTransferRestrictions(); + // ── Operators (token stays in Virtual phase — bonding allowed pre-TGE) ──── if (operators.length > 0) { for (const operator of operators) { await setupOperatorForSortition( From 58a5bef58d8d3f0c8ad536047e670b8a24a1a04d Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 11 Jun 2026 20:46:29 +0500 Subject: [PATCH 14/24] fix: update flow trace --- agent/flow-trace/02_TOKENS_AND_ACTIVATION.md | 97 +++++++++++++++----- 1 file changed, 76 insertions(+), 21 deletions(-) diff --git a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md index fbd9078645..c10e4d27fa 100644 --- a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md +++ b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md @@ -16,15 +16,53 @@ Before a node can register, it must stake two types of collateral: ┌───────────────────────────────────────────────────────────┐ │ InterfoldToken (INTF) │ │ ERC20 + ERC20Permit + ERC20Votes + AccessControl │ +│ + Ownable2Step │ │ │ │ MAX_SUPPLY: 1,200,000,000 (1.2B with 18 decimals) │ -│ Roles: MINTER_ROLE can mint via mintAllocation() │ -│ LOCK_MANAGER_ROLE manages token-level locks │ -│ Transfer restrictions: when transfersRestricted=true, │ -│ only whitelisted addresses can transfer │ -│ Lock invariant for normal transfers: │ -│ balanceOf(account) + totalBonded(account) │ -│ >= lockedFloorOf(account) │ +│ Immutables: CCA_START, CCA_END, CLAIM_SOURCE, │ +│ BONDING_REGISTRY (set at construction) │ +│ │ +│ Lifecycle phases (derived from CCA window + TGE): │ +│ Virtual → PublicSale → Cooldown → Live │ +│ - Virtual: mint() + mintAllocations() allowed │ +│ - PublicSale: CCA bidding window │ +│ - Cooldown: CCA ended, TGE not yet called │ +│ - Live: TGE fired (permissionless after cooldown) │ +│ │ +│ Minting (Virtual phase only): │ +│ - mint(recipient, amount, label) │ +│ DEFAULT_ADMIN_ROLE — unlocked tokens │ +│ - mintAllocations(MintAllocation[]) │ +│ MINTER_ROLE — tokens locked under a policy │ +│ │ +│ Pre-TGE transfer gate (phase-based, automatic): │ +│ Allowed: bonding registry, claim source, whitelisted │ +│ Blocked: all other transfers │ +│ Once TGE fires, all transfers unrestricted │ +│ │ +│ Lock system (wallet-level pooled enforcement): │ +│ - createLockPolicy(id, LockPolicy) → write-once │ +│ LOCK_MANAGER_ROLE │ +│ - linkClaim(account, amount, policyId) │ +│ LOCK_MANAGER_ROLE │ +│ - LockPolicy: { holdUntil, Curve { anchor, start, │ +│ cliffDuration, vestDuration } } │ +│ - Anchor: Absolute (fixed start) | Tge (tgeTimestamp) │ +│ - PENDING_LOCK_POLICY_ID for unclassified claims │ +│ - Queued locks consumed by later claims (linkClaim) │ +│ │ +│ Lock invariant for transfers: │ +│ transferable = balance - max(0, lockedBalance - │ +│ BONDING_REGISTRY.totalBonded(account)) │ +│ Transfer reverts with InsufficientUnlockedBalance │ +│ if value > transferable │ +│ │ +│ Whitelisting: │ +│ - setTransferWhitelisted(addr, bool) │ +│ WHITELIST_ROLE — pre-TGE transfer gate │ +│ - setLockWhitelisted(addr, bool) │ +│ LOCK_MANAGER_ROLE — exempt from claim-source locks │ +│ │ │ Used as: LICENSE BOND token │ └───────────────────────────────────────────────────────────┘ @@ -389,20 +427,37 @@ The token contracts were hardened against the following audit findings. All chan - **M-29 — EIP-6372 timestamp clock.** `clock() = uint48(block.timestamp)`, `CLOCK_MODE() = "mode=timestamp"`. -### InterfoldToken (INTF) - -- **H-15 — WHITELIST_ROLE separation + one-way disable.** New `WHITELIST_ROLE` gates - `toggleTransferWhitelist` and `whitelistContracts`, decoupling whitelist edits from `MINTER_ROLE`. - `disableTransferRestrictions` is `DEFAULT_ADMIN_ROLE` only and idempotent (silent no-op when - already disabled) so deployment/setup scripts can call it unconditionally. -- **M-21 — per-epoch mint cap.** New rolling cap configured via - `setMintCap(epochLength, capPerEpoch)` (`ZeroEpochLength` on zero length). Both `mintAllocation` - and `batchMintAllocations` route through `_accountForMintAgainstCap`, which rolls the epoch - (`MintEpochRolled(newStart)`) and reverts `ExceedsMintCap` on overflow. Constructor defaults to a - 30-day epoch with `cap = MAX_SUPPLY` so bootstrap deployments keep working; governance is expected - to tighten this before broad distribution. -- **M-29 — EIP-6372 timestamp clock.** Same timestamp clock as ITK, aligning INTF voting checkpoints - with timepoints used elsewhere. +### InterfoldToken (INTF) — Complete Rewrite + +The INTF token was rewritten to implement a CCA-auction-aligned lifecycle with wallet-level lock +enforcement based on immutable policy curves. Key changes: + +- **Phase-based lifecycle.** The token derives its phase from immutable `CCA_START` / `CCA_END` and + the one-way `tge()` call: Virtual → PublicSale → Cooldown → Live. Minting is gated to Virtual + phase only; TGE is permissionless after `CCA_END + TGE_COOLDOWN` (45 days). The pre-TGE transfer + gate automatically lifts at TGE — no `disableTransferRestrictions` / `transfersRestricted` flag. +- **Pre-TGE transfer gate.** Before TGE, only bonding-registry transfers, claim-source + distributions, and whitelisted addresses can transfer. Bonding is always allowed so operators can + stake during Virtual phase. +- **Immutable constructor parameters.** `CCA_START`, `CCA_END`, `CLAIM_SOURCE`, and + `BONDING_REGISTRY` are set at construction and cannot change. The BondingRegistry must be deployed + first (or a placeholder used and fixed via `setLicenseToken`). +- **Lock policy system.** `createLockPolicy(id, LockPolicy)` creates write-once policies with + `Curve { anchor (Absolute|Tge), start, cliffDuration, vestDuration }` and optional `holdUntil`. + `linkClaim(account, amount, policyId)` classifies pending claim-source tokens under a real policy. + `PENDING_LOCK_POLICY_ID` holds unclassified claim tokens until linked. +- **Pooled wallet enforcement.** `lockedBalanceOf(account)` sums active locks (including PENDING). + `transferableBalanceOf(account) = balance - max(0, locked - BONDING_REGISTRY.totalBonded(account))`. + Transfers that exceed the transferable balance revert with `InsufficientUnlockedBalance`. +- **Claim-source auto-lock.** Tokens arriving from `CLAIM_SOURCE` are automatically locked as + PENDING unless the recipient is in `lockWhitelist`. `linkClaim` moves PENDING to a real policy and + queues unfilled amounts for future claims. +- **EIP-6372 timestamp clock.** `clock()` returns `block.timestamp`, `CLOCK_MODE()` is + `"mode=timestamp"`. +- **Minting.** `mint(recipient, amount, label)` (DEFAULT_ADMIN_ROLE, unlocked) and + `mintAllocations(MintAllocation[])` (MINTER_ROLE, locked to a policy) are both Virtual-only. +- **Ownership.** `renounceOwnership()` is disabled. Two-step ownership transfer via Ownable2Step + syncs all AccessControl roles atomically. ### Registry coordination From b259376393d6b3daeb0e0c03d7111225197f2ee6 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 12 Jun 2026 12:57:21 +0500 Subject: [PATCH 15/24] fix: typo --- .../contracts/token/InterfoldToken.sol | 48 ++++++++++++++----- .../test/Token/InterfoldToken.spec.ts | 18 +++---- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/packages/interfold-contracts/contracts/token/InterfoldToken.sol b/packages/interfold-contracts/contracts/token/InterfoldToken.sol index 66f84ce3d6..5dd56f7d6b 100644 --- a/packages/interfold-contracts/contracts/token/InterfoldToken.sol +++ b/packages/interfold-contracts/contracts/token/InterfoldToken.sol @@ -159,7 +159,8 @@ contract InterfoldToken is /// @notice Role authorized to manage the pre-TGE transfer whitelist. bytes32 public constant WHITELIST_ROLE = keccak256("WHITELIST_ROLE"); - /// @notice Role authorized to create lock policies, manage the lock whitelist + /// @notice Role authorized to create lock policies and manage claim-lock + /// exemptions. bytes32 public constant LOCK_MANAGER_ROLE = keccak256("LOCK_MANAGER_ROLE"); /// @notice Minimum time between {CCA_END} and {tge}. @@ -190,7 +191,7 @@ contract InterfoldToken is mapping(address account => bool whitelisted) public transferWhitelist; /// @notice Addresses exempt from automatic claim-source lock creation. - mapping(address account => bool whitelisted) public lockWhitelist; + mapping(address account => bool exempt) public claimLockExempt; /// @notice Write-once lock policies by id. mapping(bytes32 policyId => LockPolicy policy) internal lockPolicies; @@ -220,8 +221,8 @@ contract InterfoldToken is /// @notice Emitted when an account's transfer whitelist status changes. event TransferWhitelistUpdated(address indexed account, bool whitelisted); - /// @notice Emitted when an account's lock whitelist status changes. - event LockWhitelistUpdated(address indexed account, bool whitelisted); + /// @notice Emitted when an account's claim-lock exemption status changes. + event ClaimLockExemptUpdated(address indexed account, bool exempt); /// @notice Emitted whenever the amount `account` holds locked under /// `policyId` changes; `amount` is the new total. @@ -335,7 +336,7 @@ contract InterfoldToken is } // ───────────────────────────────────────────────────────────────────────── - // Whitelistig + // Whitelisting // ───────────────────────────────────────────────────────────────────────── function setTransferWhitelisted( @@ -347,13 +348,15 @@ contract InterfoldToken is emit TransferWhitelistUpdated(account, whitelisted); } - function setLockWhitelisted( + /// @notice Sets whether `account` is exempt from automatic claim-lock + /// creation when receiving tokens from {CLAIM_SOURCE}. + function setClaimLockExempt( address account, - bool whitelisted + bool exempt ) external onlyRole(LOCK_MANAGER_ROLE) { if (account == address(0)) revert ZeroAddress(); - lockWhitelist[account] = whitelisted; - emit LockWhitelistUpdated(account, whitelisted); + claimLockExempt[account] = exempt; + emit ClaimLockExemptUpdated(account, exempt); } // ───────────────────────────────────────────────────────────────────────── @@ -459,6 +462,24 @@ contract InterfoldToken is return balance > mustRetain ? balance - mustRetain : 0; } + /// @notice Returns the full lock policy for `policyId`, or an empty + /// struct if the policy has not been defined. + function lockPolicyOf( + bytes32 policyId + ) external view returns (LockPolicy memory) { + return lockPolicies[policyId]; + } + + /// @notice Number of distinct lock policy entries for `account`. + function lockCount(address account) external view returns (uint256) { + return locks[account].length; + } + + /// @notice Number of distinct queued lock policy entries for `account`. + function queuedLockCount(address account) external view returns (uint256) { + return queuedLocks[account].length; + } + // ───────────────────────────────────────────────────────────────────────── // Transfer hook // ───────────────────────────────────────────────────────────────────────── @@ -469,7 +490,7 @@ contract InterfoldToken is * 2. The lock check, the sender can move at most its transferable * balance * 3. The transfer itself, via parent contract - * 4. Claim-lock creation, unless the recipient is in lockWhitelist. + * 4. Claim-lock creation, unless the recipient is claim-lock exempt. */ function _update( address from, @@ -483,7 +504,7 @@ contract InterfoldToken is revert TransferRestricted(from, to); } - if (!isMint && !transferWhitelist[to] && !transferWhitelist[from]) { + if (!isMint) { uint256 transferable = transferableBalanceOf(from); if (value > transferable) { revert InsufficientUnlockedBalance(from, transferable, value); @@ -494,7 +515,10 @@ contract InterfoldToken is // from == CLAIM_SOURCE implies neither mint nor an unset claim source. if ( - !isBurn && value != 0 && from == CLAIM_SOURCE && !lockWhitelist[to] + !isBurn && + value != 0 && + from == CLAIM_SOURCE && + !claimLockExempt[to] ) { _claim(to, value); } diff --git a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts index e01adb1103..086c87977b 100644 --- a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts +++ b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts @@ -534,20 +534,20 @@ describe("InterfoldToken", function () { }); }); - describe("setLockWhitelisted", function () { - it("LOCK_MANAGER_ROLE can manage lock whitelist", async function () { + describe("setClaimLockExempt", function () { + it("LOCK_MANAGER_ROLE can manage claim-lock exemption", async function () { const { token, admin, alice } = await loadFixture(deploy); await expect( - token.connect(admin).setLockWhitelisted(await alice.getAddress(), true), + token.connect(admin).setClaimLockExempt(await alice.getAddress(), true), ) - .to.emit(token, "LockWhitelistUpdated") + .to.emit(token, "ClaimLockExemptUpdated") .withArgs(await alice.getAddress(), true); }); - it("non-LOCK_MANAGER_ROLE cannot manage lock whitelist", async function () { + it("non-LOCK_MANAGER_ROLE cannot manage claim-lock exemption", async function () { const { token, alice } = await loadFixture(deploy); await expect( - token.connect(alice).setLockWhitelisted(await alice.getAddress(), true), + token.connect(alice).setClaimLockExempt(await alice.getAddress(), true), ).to.be.revertedWithCustomError( token, "AccessControlUnauthorizedAccount", @@ -968,13 +968,13 @@ describe("InterfoldToken", function () { ); }); - it("lockWhitelist exempts from auto-lock on claim-source transfer", async function () { + it("claimLockExempt exempts from auto-lock on claim-source transfer", async function () { const { token, admin, alice, claimSource, amount } = await deployWithUnlockedAndTge(ethers.parseEther("500")); await token .connect(admin) - .setLockWhitelisted(await alice.getAddress(), true); + .setClaimLockExempt(await alice.getAddress(), true); // Transfer tokens from alice to claimSource so claimSource can send. await token @@ -985,7 +985,7 @@ describe("InterfoldToken", function () { .connect(claimSource) .transfer(await alice.getAddress(), amount); - // No lock created because recipient is lock-whitelisted. + // No lock created because recipient is claim-lock exempt. expect(await token.lockedBalanceOf(await alice.getAddress())).to.equal( 0n, ); From 964555d53b1cd0d3fcd325d37a0fc5b5d43a18c5 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 12 Jun 2026 20:21:25 +0500 Subject: [PATCH 16/24] fix: split queue events --- .../IBondingRegistry.json | 6 +- .../ICiphernodeRegistry.json | 6 +- .../interfaces/IInterfold.sol/IInterfold.json | 6 +- .../ISlashingManager.json | 6 +- .../InterfoldTicketToken.json | 6 +- .../contracts/token/InterfoldToken.sol | 66 ++++- .../test/Token/InterfoldToken.spec.ts | 268 ++++++++++++++++++ 7 files changed, 325 insertions(+), 39 deletions(-) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index c1568883ec..98ea8d874f 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1081,9 +1081,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-b09c5d8313e36da17310e2043045396de2e05713" -======= - "buildInfoId": "solc-0_8_28-bc3ca5094033f5b4b0cf838691e05da99cc1561b" ->>>>>>> main + "buildInfoId": "solc-0_8_28-79cef1a89bb3d7a1a91353c32084bda8128e6af4" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index 806cf4e47b..f68df375c3 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1332,9 +1332,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-b09c5d8313e36da17310e2043045396de2e05713" -======= - "buildInfoId": "solc-0_8_28-bc3ca5094033f5b4b0cf838691e05da99cc1561b" ->>>>>>> main + "buildInfoId": "solc-0_8_28-79cef1a89bb3d7a1a91353c32084bda8128e6af4" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index d8b59f6137..711cec80e1 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -2427,9 +2427,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-b09c5d8313e36da17310e2043045396de2e05713" -======= - "buildInfoId": "solc-0_8_28-bc3ca5094033f5b4b0cf838691e05da99cc1561b" ->>>>>>> main + "buildInfoId": "solc-0_8_28-79cef1a89bb3d7a1a91353c32084bda8128e6af4" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index cd48040ea9..c431e6075d 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1233,9 +1233,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-b09c5d8313e36da17310e2043045396de2e05713" -======= - "buildInfoId": "solc-0_8_28-bc3ca5094033f5b4b0cf838691e05da99cc1561b" ->>>>>>> main + "buildInfoId": "solc-0_8_28-79cef1a89bb3d7a1a91353c32084bda8128e6af4" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json index 7a64e898cd..fa5545b179 100644 --- a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json +++ b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json @@ -1486,9 +1486,5 @@ ] }, "inputSourceName": "project/contracts/token/InterfoldTicketToken.sol", -<<<<<<< HEAD - "buildInfoId": "solc-0_8_28-d96b1451d9af86253d4103fca7abd8920706ac94" -======= - "buildInfoId": "solc-0_8_28-bc3ca5094033f5b4b0cf838691e05da99cc1561b" ->>>>>>> main + "buildInfoId": "solc-0_8_28-79cef1a89bb3d7a1a91353c32084bda8128e6af4" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/token/InterfoldToken.sol b/packages/interfold-contracts/contracts/token/InterfoldToken.sol index 5dd56f7d6b..5aebf65d4c 100644 --- a/packages/interfold-contracts/contracts/token/InterfoldToken.sol +++ b/packages/interfold-contracts/contracts/token/InterfoldToken.sol @@ -224,9 +224,17 @@ contract InterfoldToken is /// @notice Emitted when an account's claim-lock exemption status changes. event ClaimLockExemptUpdated(address indexed account, bool exempt); - /// @notice Emitted whenever the amount `account` holds locked under - /// `policyId` changes; `amount` is the new total. - event LockUpdated( + /// @notice Emitted whenever an active lock amount changes; `amount` is + /// the new total under `policyId`. + event ActiveLockUpdated( + address indexed account, + bytes32 indexed policyId, + uint256 amount + ); + + /// @notice Emitted whenever a queued lock amount changes; `amount` is + /// the new remaining queued total under `policyId`. + event QueuedLockUpdated( address indexed account, bytes32 indexed policyId, uint256 amount @@ -565,7 +573,8 @@ contract InterfoldToken is allocation.recipient, locks[allocation.recipient], allocation.policyId, - allocation.amount + allocation.amount, + true ); emit AllocationMinted( allocation.recipient, @@ -603,7 +612,8 @@ contract InterfoldToken is account, queuedLocks[account], bytes32(0), - remaining + remaining, + false ); if (consumed == 0) { @@ -611,7 +621,13 @@ contract InterfoldToken is consumed = remaining; } - _addOrIncrementLock(account, locks[account], policyId, consumed); + _addOrIncrementLock( + account, + locks[account], + policyId, + consumed, + true + ); remaining -= consumed; } } @@ -635,13 +651,20 @@ contract InterfoldToken is account, locks[account], PENDING_LOCK_POLICY_ID, - amount + amount, + true ); uint256 remaining = amount - consumed; // if we consumed from PENDING policy, add the real thing if (consumed != 0) { - _addOrIncrementLock(account, locks[account], policyId, consumed); + _addOrIncrementLock( + account, + locks[account], + policyId, + consumed, + true + ); } // Whatever is left queues under the target policy. @@ -650,7 +673,8 @@ contract InterfoldToken is account, queuedLocks[account], policyId, - remaining + remaining, + false ); } } @@ -659,7 +683,8 @@ contract InterfoldToken is address account, Lock[] storage entries, bytes32 filterPolicyId, - uint256 amount + uint256 amount, + bool isActive ) internal returns (uint256 consumed, bytes32 consumedPolicyId) { uint256 len = entries.length; uint256 i; @@ -689,26 +714,39 @@ contract InterfoldToken is entries[i].amount = remaining; } - emit LockUpdated(account, consumedPolicyId, remaining); + if (isActive) { + emit ActiveLockUpdated(account, consumedPolicyId, remaining); + } else { + emit QueuedLockUpdated(account, consumedPolicyId, remaining); + } } function _addOrIncrementLock( address account, Lock[] storage entries, bytes32 policyId, - uint256 amount + uint256 amount, + bool isActive ) internal returns (uint256 newAmount) { uint256 len = entries.length; for (uint256 i = 0; i < len; i++) { if (entries[i].policyId == policyId) { entries[i].amount += amount; newAmount = entries[i].amount; - emit LockUpdated(account, policyId, newAmount); + if (isActive) { + emit ActiveLockUpdated(account, policyId, newAmount); + } else { + emit QueuedLockUpdated(account, policyId, newAmount); + } return newAmount; } } entries.push(Lock(policyId, amount)); - emit LockUpdated(account, policyId, amount); + if (isActive) { + emit ActiveLockUpdated(account, policyId, amount); + } else { + emit QueuedLockUpdated(account, policyId, amount); + } return amount; } diff --git a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts index 086c87977b..4cbc4e70d0 100644 --- a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts +++ b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts @@ -1245,6 +1245,274 @@ describe("InterfoldToken", function () { // Bonding transfer should succeed. await bondingRegistry.connect(owner).bondLicense(bondAmount); }); + + it("locked tokens can be bonded (pre-credit visible to token)", async function () { + const signers = await ethers.getSigners(); + const [, beneficiary, slasher] = signers; + const beneficiaryAddress = await beneficiary.getAddress(); + const slasherAddress = await slasher.getAddress(); + const sys = await deployInterfoldSystem({ + useMockCiphernodeRegistry: true, + setupOperators: 0, + wireSlashingManager: false, + mintUsdcTo: [], + }); + const { bondingRegistry, licenseToken } = sys; + const bondingRegistryAddress = await bondingRegistry.getAddress(); + + await bondingRegistry.setSlashingManager(slasherAddress); + + // Create a lock policy and mint locked tokens. + const policyId = ethers.encodeBytes32String("LOCKED_BOND"); + await licenseToken.createLockPolicy(policyId, { + holdUntil: 0n, + unlock: { + anchor: 1, // Tge-anchored + start: 0n, + cliffDuration: 0n, + vestDuration: 2n * YEAR, + }, + }); + const lockAmount = ethers.parseEther("1000"); + // Mint locked allocation directly (balance = locked). + await licenseToken.mintAllocations([ + { + recipient: beneficiaryAddress, + amount: lockAmount, + policyId, + label: ethers.encodeBytes32String("locked"), + }, + ]); + + // Before bonding: balance = 1000, locked ≈ 1000, bonded = 0. + // transferable ≈ 0 (Tge-anchored, no time has passed). + const tbBefore = + await licenseToken.transferableBalanceOf(beneficiaryAddress); + expect(tbBefore).to.be.lt(ethers.parseEther("0.01")); + + // Bond all locked tokens. Should succeed because BondingRegistry + // pre-credits `operators[beneficiary].licenseBond` before calling + // `safeTransferFrom`, so the token sees bonded = lockAmount during + // `_update()`. + await licenseToken + .connect(beneficiary) + .approve(bondingRegistryAddress, lockAmount); + await bondingRegistry.connect(beneficiary).bondLicense(lockAmount); + + // After bonding: wallet = 0, locked ≈ 1000, bonded = 1000. + // Bond covers lock, so no mustRetain. + expect(await licenseToken.balanceOf(beneficiaryAddress)).to.equal(0n); + expect(await bondingRegistry.totalBonded(beneficiaryAddress)).to.equal( + lockAmount, + ); + }); + + it("after bonding locked tokens, cannot transfer below locked floor", async function () { + const signers = await ethers.getSigners(); + const [, beneficiary, slasher] = signers; + const beneficiaryAddress = await beneficiary.getAddress(); + const slasherAddress = await slasher.getAddress(); + const sys = await deployInterfoldSystem({ + useMockCiphernodeRegistry: true, + setupOperators: 0, + wireSlashingManager: false, + mintUsdcTo: [], + }); + const { bondingRegistry, licenseToken } = sys; + const bondingRegistryAddress = await bondingRegistry.getAddress(); + + await bondingRegistry.setSlashingManager(slasherAddress); + + const policyId = ethers.encodeBytes32String("LOCKED_FLOOR"); + await licenseToken.createLockPolicy(policyId, { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: 2n * YEAR, + }, + }); + const lockAmount = ethers.parseEther("1000"); + await licenseToken.mintAllocations([ + { + recipient: beneficiaryAddress, + amount: lockAmount, + policyId, + label: ethers.encodeBytes32String("locked"), + }, + ]); + + // Bond 600 out of 1000 locked. + const bondAmount = ethers.parseEther("600"); + await licenseToken + .connect(beneficiary) + .approve(bondingRegistryAddress, bondAmount); + await bondingRegistry.connect(beneficiary).bondLicense(bondAmount); + + // Wallet = 400, locked ≈ 1000, bonded = 600. + // mustRetain = max(0, 1000 - 600) = 400. + // transferable = max(0, 400 - 400) = 0. + const tb = await licenseToken.transferableBalanceOf(beneficiaryAddress); + expect(tb).to.equal(0n); + }); + + it("slashing does not reduce locked balance", async function () { + const signers = await ethers.getSigners(); + const [, beneficiary, slasher] = signers; + const beneficiaryAddress = await beneficiary.getAddress(); + const slasherAddress = await slasher.getAddress(); + const sys = await deployInterfoldSystem({ + useMockCiphernodeRegistry: true, + setupOperators: 0, + wireSlashingManager: false, + mintUsdcTo: [], + }); + const { bondingRegistry, licenseToken } = sys; + const bondingRegistryAddress = await bondingRegistry.getAddress(); + + await bondingRegistry.setSlashingManager(slasherAddress); + + const policyId = ethers.encodeBytes32String("SLASH_LOCK"); + await licenseToken.createLockPolicy(policyId, { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: 2n * YEAR, + }, + }); + const lockAmount = ethers.parseEther("1000"); + await licenseToken.mintAllocations([ + { + recipient: beneficiaryAddress, + amount: lockAmount, + policyId, + label: ethers.encodeBytes32String("locked"), + }, + ]); + + // Bond everything. + await licenseToken + .connect(beneficiary) + .approve(bondingRegistryAddress, lockAmount); + await bondingRegistry.connect(beneficiary).bondLicense(lockAmount); + + const lockedBefore = + await licenseToken.lockedBalanceOf(beneficiaryAddress); + expect(lockedBefore).to.be.closeTo(lockAmount, ethers.parseEther("0.01")); + + // Slash 500 license bond. + const slashAmount = ethers.parseEther("500"); + await bondingRegistry + .connect(slasher) + .slashLicenseBond( + beneficiaryAddress, + slashAmount, + ethers.encodeBytes32String("SLASH"), + ); + + // Bonded is now 500. + expect(await bondingRegistry.totalBonded(beneficiaryAddress)).to.equal( + lockAmount - slashAmount, + ); + + // Locked balance must NOT change due to slashing. + const lockedAfter = + await licenseToken.lockedBalanceOf(beneficiaryAddress); + expect(lockedAfter).to.equal(lockedBefore); + }); + + it("after slashing, incoming tokens are retained by lock-floor invariant", async function () { + const signers = await ethers.getSigners(); + const [, beneficiary, slasher] = signers; + const beneficiaryAddress = await beneficiary.getAddress(); + const slasherAddress = await slasher.getAddress(); + const sys = await deployInterfoldSystem({ + useMockCiphernodeRegistry: true, + setupOperators: 0, + wireSlashingManager: false, + mintUsdcTo: [], + }); + const { bondingRegistry, licenseToken, owner } = sys; + const bondingRegistryAddress = await bondingRegistry.getAddress(); + + await bondingRegistry.setSlashingManager(slasherAddress); + + const policyId = ethers.encodeBytes32String("SLASH_FLOOR"); + await licenseToken.createLockPolicy(policyId, { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: 2n * YEAR, + }, + }); + const lockAmount = ethers.parseEther("1000"); + await licenseToken.mintAllocations([ + { + recipient: beneficiaryAddress, + amount: lockAmount, + policyId, + label: ethers.encodeBytes32String("locked"), + }, + ]); + + // Bond everything, then slash half. + await licenseToken + .connect(beneficiary) + .approve(bondingRegistryAddress, lockAmount); + await bondingRegistry.connect(beneficiary).bondLicense(lockAmount); + const slashAmount = ethers.parseEther("500"); + await bondingRegistry + .connect(slasher) + .slashLicenseBond( + beneficiaryAddress, + slashAmount, + ethers.encodeBytes32String("SLASH"), + ); + + // Now: wallet = 0, locked ≈ 1000, bonded = 500. + // mustRetain = 1000 - 500 = 500. Wallet is 500 below floor. + expect( + await licenseToken.transferableBalanceOf(beneficiaryAddress), + ).to.equal(0n); + + // Send 200 unlocked tokens to beneficiary. They should be retained + // (non-transferable) because wallet is still below floor. + await licenseToken + .connect(owner) + .mint(beneficiaryAddress, ethers.parseEther("200"), ethers.ZeroHash); + expect(await licenseToken.balanceOf(beneficiaryAddress)).to.equal( + ethers.parseEther("200"), + ); + expect( + await licenseToken.transferableBalanceOf(beneficiaryAddress), + ).to.equal(0n); + + // Send enough to fill the floor gap (300 more = 500 total). + await licenseToken + .connect(owner) + .mint(beneficiaryAddress, ethers.parseEther("300"), ethers.ZeroHash); + expect(await licenseToken.balanceOf(beneficiaryAddress)).to.equal( + ethers.parseEther("500"), + ); + // Now wallet = 500, locked ≈ 1000, bonded = 500 → transferable = 0. + expect( + await licenseToken.transferableBalanceOf(beneficiaryAddress), + ).to.equal(0n); + + // Send one more wei above the floor. + await licenseToken + .connect(owner) + .mint(beneficiaryAddress, 1n, ethers.ZeroHash); + // Now wallet = 500 + 1, mustRetain = 500 → transferable = 1. + expect( + await licenseToken.transferableBalanceOf(beneficiaryAddress), + ).to.equal(1n); + }); }); // ═════════════════════════════════════════════════════════════════════════ From 24b83daf30cfa22309a2410d30ca860979239b6f Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 12 Jun 2026 22:40:52 +0500 Subject: [PATCH 17/24] fix: remaining items --- .../IBondingRegistry.json | 2 +- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IInterfold.sol/IInterfold.json | 2 +- .../ISlashingManager.json | 2 +- .../InterfoldTicketToken.json | 2 +- .../contracts/token/InterfoldToken.sol | 122 +++++++++++++++++- .../test/Token/InterfoldToken.spec.ts | 4 +- 7 files changed, 126 insertions(+), 10 deletions(-) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 98ea8d874f..651f77bce1 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1081,5 +1081,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-79cef1a89bb3d7a1a91353c32084bda8128e6af4" + "buildInfoId": "solc-0_8_28-8facb7e897e6b871d6535c23335b76111c7915ad" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index f68df375c3..ff77e3a537 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1332,5 +1332,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-79cef1a89bb3d7a1a91353c32084bda8128e6af4" + "buildInfoId": "solc-0_8_28-8facb7e897e6b871d6535c23335b76111c7915ad" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 711cec80e1..55ff9c5196 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -2427,5 +2427,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-79cef1a89bb3d7a1a91353c32084bda8128e6af4" + "buildInfoId": "solc-0_8_28-8facb7e897e6b871d6535c23335b76111c7915ad" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index c431e6075d..a875cd24cd 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1233,5 +1233,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-79cef1a89bb3d7a1a91353c32084bda8128e6af4" + "buildInfoId": "solc-0_8_28-8facb7e897e6b871d6535c23335b76111c7915ad" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json index fa5545b179..321cd53d89 100644 --- a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json +++ b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json @@ -1486,5 +1486,5 @@ ] }, "inputSourceName": "project/contracts/token/InterfoldTicketToken.sol", - "buildInfoId": "solc-0_8_28-79cef1a89bb3d7a1a91353c32084bda8128e6af4" + "buildInfoId": "solc-0_8_28-8facb7e897e6b871d6535c23335b76111c7915ad" } \ No newline at end of file diff --git a/packages/interfold-contracts/contracts/token/InterfoldToken.sol b/packages/interfold-contracts/contracts/token/InterfoldToken.sol index 5aebf65d4c..6b821839a9 100644 --- a/packages/interfold-contracts/contracts/token/InterfoldToken.sol +++ b/packages/interfold-contracts/contracts/token/InterfoldToken.sol @@ -41,12 +41,12 @@ contract InterfoldToken is // ───────────────────────────────────────────────────────────────────────── /// @notice Global token lifecycle phase, derived from the immutable CCA - /// window and the TGE: Virtual (pre-sale), PublicSale (CCA + /// window and the TGE: Virtual (pre-sale), CCA (CCA /// bidding window), Cooldown (sale ended, TGE not yet fired), /// Live (TGE fired). enum Phase { Virtual, - PublicSale, + CCA, Cooldown, Live } @@ -69,6 +69,10 @@ contract InterfoldToken is uint64 vestDuration; } + /// @notice Token-level lock policy with a single unlock curve. + /// @dev This token enforces token unlock schedules only. Service vesting + /// schedules that continue after TGE may live in a separate vesting + /// contract. /// @param holdUntil Optional absolute timestamp before which nothing is /// transferable, whatever the unlock curve has accrued; /// @param unlock Unlock curve. @@ -125,6 +129,18 @@ contract InterfoldToken is /// @notice Policy parameters are internally inconsistent. error InvalidPolicy(); + /// @notice The requested relink amount exceeds the amount available under + /// the source policy. + error RelinkAmountExceeded(); + + /// @notice An account has reached the maximum number of active lock + /// policy entries. + error TooManyLocks(); + + /// @notice An account has reached the maximum number of queued lock + /// policy entries. + error TooManyQueuedLocks(); + /// @notice The policy id is already defined; policies are write-once. error PolicyAlreadyDefined(bytes32 policyId); @@ -168,6 +184,14 @@ contract InterfoldToken is bytes32 public constant PENDING_LOCK_POLICY_ID = "PENDING"; + /// @notice Maximum number of distinct active lock policies an account may + /// hold. Protects against unbounded gas costs in {_update}. + uint256 public constant MAX_LOCKS_PER_ACCOUNT = 8; + + /// @notice Maximum number of distinct queued lock policies an account may + /// hold. + uint256 public constant MAX_QUEUED_LOCKS_PER_ACCOUNT = 8; + /// @notice Start of the CCA auction window, fixed at deployment. uint64 public immutable CCA_START; @@ -240,6 +264,15 @@ contract InterfoldToken is uint256 amount ); + /// @notice Emitted when an active lock is moved from one policy to + /// another via {relinkActiveLock}. + event ActiveLockRelinked( + address indexed account, + bytes32 indexed fromPolicyId, + bytes32 indexed toPolicyId, + uint256 amount + ); + /// @notice Emitted once, when {tge} fires. event TgeTriggered(uint64 timestamp); @@ -304,6 +337,9 @@ contract InterfoldToken is /// @notice Mints allocations locked under their policies; the path the /// minter role uses to distribute vested supply. Only allowed /// during the Virtual phase. + /// @dev Team / GG "vested as of TGE" amounts must be calculated + /// off-chain using the expected TGE date, since {tgeTimestamp} is + /// not known when Virtual minting closes at {CCA_START}. function mintAllocations( MintAllocation[] calldata allocations ) external onlyRole(MINTER_ROLE) { @@ -337,9 +373,10 @@ contract InterfoldToken is /// earlier phases derive from the immutable CCA window. function phase() public view returns (Phase) { if (tgeTimestamp != 0) return Phase.Live; + uint64 current = uint64(block.timestamp); if (current < CCA_START) return Phase.Virtual; - if (current < CCA_END) return Phase.PublicSale; + if (current < CCA_END) return Phase.CCA; return Phase.Cooldown; } @@ -404,6 +441,12 @@ contract InterfoldToken is * linkClaim. {_claim} and {_linkClaim} manipulate {locks} and * {queuedLocks} to link balances to policies in a resilient * way: it doesn't matter who calls what first. + * + * Each wallet is expected to have at most one CCA policy bucket. + * If a wallet has multiple queued CCA policies, claim matching + * is not business-order aware and should be treated as + * undefined. The importer must not create multiple queued CCA + * buckets for the same wallet. */ function linkClaim( address account, @@ -417,6 +460,60 @@ contract InterfoldToken is _linkClaim(account, amount, policyId); } + /** + * @notice Corrects an active lock that was incorrectly linked to the + * wrong policy. Only allowed before TGE (Live phase). + * @dev This is a safety hatch for admin mistakes during lock import; + * it is not intended for routine use. The {PENDING_LOCK_POLICY_ID} + * policy cannot be used as source or target. + */ + function relinkActiveLock( + address account, + bytes32 fromPolicyId, + bytes32 toPolicyId, + uint256 amount + ) external onlyRole(LOCK_MANAGER_ROLE) { + if (tgeTimestamp != 0) revert AlreadyLive(); + if (account == address(0)) revert ZeroAddress(); + if (amount == 0) revert ZeroAmount(); + if (fromPolicyId == bytes32(0) || toPolicyId == bytes32(0)) { + revert InvalidPolicy(); + } + if (fromPolicyId == toPolicyId) revert InvalidPolicy(); + if ( + fromPolicyId == PENDING_LOCK_POLICY_ID || + toPolicyId == PENDING_LOCK_POLICY_ID + ) revert InvalidPolicy(); + if (!_policyDefined(fromPolicyId)) { + revert PolicyNotDefined(fromPolicyId); + } + if (!_policyDefined(toPolicyId)) { + revert PolicyNotDefined(toPolicyId); + } + + if (_activeLockAmount(account, fromPolicyId) < amount) { + revert RelinkAmountExceeded(); + } + + (uint256 consumed, ) = _consumeLock( + account, + locks[account], + fromPolicyId, + amount, + true + ); + + _addOrIncrementLock( + account, + locks[account], + toPolicyId, + consumed, + true + ); + + emit ActiveLockRelinked(account, fromPolicyId, toPolicyId, consumed); + } + // ───────────────────────────────────────────────────────────────────────── // Lock views // ───────────────────────────────────────────────────────────────────────── @@ -741,6 +838,10 @@ contract InterfoldToken is return newAmount; } } + if (isActive && len >= MAX_LOCKS_PER_ACCOUNT) revert TooManyLocks(); + if (!isActive && len >= MAX_QUEUED_LOCKS_PER_ACCOUNT) { + revert TooManyQueuedLocks(); + } entries.push(Lock(policyId, amount)); if (isActive) { emit ActiveLockUpdated(account, policyId, amount); @@ -760,6 +861,21 @@ contract InterfoldToken is return unlock.cliffDuration != 0 || unlock.vestDuration != 0; } + /// @dev Returns the active lock amount for `account` under `policyId`, + /// or zero if no such lock exists. + function _activeLockAmount( + address account, + bytes32 policyId + ) internal view returns (uint256) { + Lock[] storage accountLocks = locks[account]; + for (uint256 i = 0; i < accountLocks.length; i++) { + if (accountLocks[i].policyId == policyId) { + return accountLocks[i].amount; + } + } + return 0; + } + /// @dev Validates the unlock curve: it must lock something and be /// consistent with its anchor mode. function _validateCurve(Curve calldata curve) internal pure { diff --git a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts index 4cbc4e70d0..863cf3baff 100644 --- a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts +++ b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts @@ -299,10 +299,10 @@ describe("InterfoldToken", function () { expect(await token.phase()).to.equal(0); // Phase.Virtual }); - it("enters PublicSale during CCA window", async function () { + it("enters CCA during CCA window", async function () { const { token, ccaStart } = await loadFixture(deploy); await time.increaseTo(ccaStart); - expect(await token.phase()).to.equal(1); // Phase.PublicSale + expect(await token.phase()).to.equal(1); // Phase.CCA }); it("enters Cooldown after CCA_END before TGE", async function () { From 659c186130713c5cf102c9a08ed54da91ccd179a Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 12 Jun 2026 22:48:26 +0500 Subject: [PATCH 18/24] fix: linting issues --- .../contracts/token/InterfoldToken.sol | 78 ++++++++++++------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/packages/interfold-contracts/contracts/token/InterfoldToken.sol b/packages/interfold-contracts/contracts/token/InterfoldToken.sol index 6b821839a9..301902ed3c 100644 --- a/packages/interfold-contracts/contracts/token/InterfoldToken.sol +++ b/packages/interfold-contracts/contracts/token/InterfoldToken.sol @@ -474,23 +474,7 @@ contract InterfoldToken is uint256 amount ) external onlyRole(LOCK_MANAGER_ROLE) { if (tgeTimestamp != 0) revert AlreadyLive(); - if (account == address(0)) revert ZeroAddress(); - if (amount == 0) revert ZeroAmount(); - if (fromPolicyId == bytes32(0) || toPolicyId == bytes32(0)) { - revert InvalidPolicy(); - } - if (fromPolicyId == toPolicyId) revert InvalidPolicy(); - if ( - fromPolicyId == PENDING_LOCK_POLICY_ID || - toPolicyId == PENDING_LOCK_POLICY_ID - ) revert InvalidPolicy(); - if (!_policyDefined(fromPolicyId)) { - revert PolicyNotDefined(fromPolicyId); - } - if (!_policyDefined(toPolicyId)) { - revert PolicyNotDefined(toPolicyId); - } - + _validateRelinkParams(account, fromPolicyId, toPolicyId, amount); if (_activeLockAmount(account, fromPolicyId) < amount) { revert RelinkAmountExceeded(); } @@ -776,26 +760,34 @@ contract InterfoldToken is } } - function _consumeLock( - address account, + /// @dev Finds the index of `filterPolicyId` in `entries`. Returns + /// (0, false) when not found, or when `filterPolicyId` is zero + /// and `entries` is empty. + function _findLockIndex( Lock[] storage entries, - bytes32 filterPolicyId, - uint256 amount, - bool isActive - ) internal returns (uint256 consumed, bytes32 consumedPolicyId) { + bytes32 filterPolicyId + ) internal view returns (uint256 index, bool found) { uint256 len = entries.length; - uint256 i; - if (filterPolicyId != bytes32(0)) { - for (; i < len; i++) { + for (uint256 i = 0; i < len; i++) { if (entries[i].policyId == filterPolicyId) { - break; + return (i, true); } } - if (i == len) return (0, bytes32(0)); - } else if (len == 0) { - return (0, bytes32(0)); + return (0, false); } + return (0, len > 0); + } + + function _consumeLock( + address account, + Lock[] storage entries, + bytes32 filterPolicyId, + uint256 amount, + bool isActive + ) internal returns (uint256 consumed, bytes32 consumedPolicyId) { + (uint256 i, bool found) = _findLockIndex(entries, filterPolicyId); + if (!found) return (0, bytes32(0)); consumedPolicyId = entries[i].policyId; consumed = entries[i].amount; @@ -861,6 +853,32 @@ contract InterfoldToken is return unlock.cliffDuration != 0 || unlock.vestDuration != 0; } + /// @dev Validates {relinkActiveLock} parameters. Extracted to keep the + /// external function below the solhint cyclomatic-complexity cap. + function _validateRelinkParams( + address account, + bytes32 fromPolicyId, + bytes32 toPolicyId, + uint256 amount + ) internal view { + if (account == address(0)) revert ZeroAddress(); + if (amount == 0) revert ZeroAmount(); + if (fromPolicyId == bytes32(0) || toPolicyId == bytes32(0)) { + revert InvalidPolicy(); + } + if (fromPolicyId == toPolicyId) revert InvalidPolicy(); + if ( + fromPolicyId == PENDING_LOCK_POLICY_ID || + toPolicyId == PENDING_LOCK_POLICY_ID + ) revert InvalidPolicy(); + if (!_policyDefined(fromPolicyId)) { + revert PolicyNotDefined(fromPolicyId); + } + if (!_policyDefined(toPolicyId)) { + revert PolicyNotDefined(toPolicyId); + } + } + /// @dev Returns the active lock amount for `account` under `policyId`, /// or zero if no such lock exists. function _activeLockAmount( From 113200a224624cc1eb190ecd01cea7ccd21e745c Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sat, 13 Jun 2026 00:44:55 +0500 Subject: [PATCH 19/24] fix: update contract addresses --- examples/CRISP/interfold.config.yaml | 20 +- .../crisp-contracts/deployed_contracts.json | 254 ++++-------------- examples/CRISP/server/.env.example | 6 +- .../scripts/deployAndSave/interfoldToken.ts | 45 +++- .../scripts/deployInterfold.ts | 65 +++-- .../interfold-contracts/tasks/ciphernode.ts | 6 +- templates/default/deployed_contracts.json | 110 ++++---- templates/default/interfold.config.yaml | 18 +- 8 files changed, 218 insertions(+), 306 deletions(-) diff --git a/examples/CRISP/interfold.config.yaml b/examples/CRISP/interfold.config.yaml index f35d59feb7..7c2b28a473 100644 --- a/examples/CRISP/interfold.config.yaml +++ b/examples/CRISP/interfold.config.yaml @@ -5,20 +5,20 @@ chains: rpc_url: ws://localhost:8545 contracts: e3_program: - address: "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0" - deploy_block: 39 + address: "0xc351628EB244ec633d5f21fBD6621e1a683B1181" + deploy_block: 40 interfold: - address: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" - deploy_block: 15 + address: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + deploy_block: 16 ciphernode_registry: - address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" - deploy_block: 10 - bonding_registry: - address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" - deploy_block: 11 - slashing_manager: address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + deploy_block: 8 + bonding_registry: + address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" deploy_block: 9 + slashing_manager: + address: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" + deploy_block: 7 fee_token: address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" deploy_block: 5 diff --git a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json index 289257c553..bb4cc4e3f0 100644 --- a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json +++ b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json @@ -11,29 +11,22 @@ "blockNumber": 5, "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, - "InterfoldToken": { - "constructorArgs": { - "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "blockNumber": 6, - "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" - }, "InterfoldTicketToken": { "constructorArgs": { "baseToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 8, - "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" + "blockNumber": 6, + "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "SlashingManager": { "constructorArgs": { "initialDelay": "172800", "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 9, - "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + "blockNumber": 7, + "address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" }, "CiphernodeRegistryOwnable": { "constructorArgs": { @@ -43,19 +36,19 @@ "proxyRecords": { "initData": "0xcd6dc687000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000000a", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", - "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", - "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" + "proxyAddress": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", + "proxyAdminAddress": "0x61c36a8d610163660E21a8b7359e1Cac0C9133e1", + "implementationAddress": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, - "blockNumber": 10, - "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + "blockNumber": 8, + "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "BondingRegistry": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "ticketToken": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", - "licenseToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", - "registry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", + "ticketToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "licenseToken": "0x0000000000000000000000000000000000000000", + "registry": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", "slashedFundsTreasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "ticketPrice": "10000000", "licenseRequiredBond": "100000000000000000000", @@ -63,236 +56,99 @@ "exitDelay": "604800" }, "proxyRecords": { - "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000dc64a140aa3e981100a9beca4e685f962f0cf6c90000000000000000000000009fe46736679d2d9a65f0992f2272de9f3c7fa6e0000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c853000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", - "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", - "proxyAdminAddress": "0x8aCd85898458400f7Db866d53FCFF6f0D49741FF", - "implementationAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" - }, - "blockNumber": 11, - "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" - }, - "Interfold": { - "constructorArgs": { - "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "registry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", - "bondingRegistry": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", - "e3RefundManager": "0x0000000000000000000000000000000000000001", - "feeToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", - "maxDuration": "2592000", - "timeoutConfig": "{\"dkgWindow\":7200,\"computeWindow\":86400,\"decryptionWindow\":3600}" - }, - "proxyRecords": { - "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c8530000000000000000000000008a791620dd6260079bf849dc5567adc3f2fdc3180000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", - "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "proxyAdminAddress": "0x524F04724632eED237cbA3c37272e018b3A7967e", - "implementationAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508" - }, - "blockNumber": 18, - "address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" - }, - "E3RefundManager": { - "constructorArgs": { - "owner": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B", - "interfold": "0x670eFE043d1D340148037b4b76c4F9dfED294309", - "treasury": "0x8837e47c4Bb520ADE83AAB761C3B60679443af1B" - }, - "proxyRecords": { - "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000b306bf915c4d645ff596e518faf3f9669b97016000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE", - "proxyAdminAddress": "0x2b961E3959b79326A8e7F64Ef0d2d825707669b5", - "implementationAddress": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" - }, - "blockNumber": 20, - "address": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" - }, - "MockComputeProvider": { - "blockNumber": 34, - "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" - }, - "MockDecryptionVerifier": { - "blockNumber": 35, - "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" - }, - "MockPkVerifier": { - "blockNumber": 36, - "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" - }, - "MockE3Program": { - "blockNumber": 37, - "address": "0xb7278A61aa25c888815aFC32Ad3cC52fF24fE575" - }, - "MockRISC0Verifier": { - "address": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", - "blockNumber": 41 - }, - "HonkVerifier": { - "address": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", - "blockNumber": 42 - }, - "CRISPProgram": { - "address": "0xFD471836031dc5108809D173A067e8486B9047A3", - "blockNumber": 42, - "constructorArgs": { - "interfold": "0x670eFE043d1D340148037b4b76c4F9dfED294309", - "verifierAddress": "0x05c415E39AF18B2cc118fFC98258afe38636EAb0", - "honkVerifierAddress": "0x70AA319AA8a305Eb65a78b0791b2526BE7193Cfa", - "imageId": "0x23734b77b0f76e85623a88d7a82f24c34c94834f2501964ea123b7a2027013a2" - } - }, - "MockVotingToken": { - "address": "0x1429859428C0aBc9C2C47C8Ee9FBaf82cFA0F20f", - "blockNumber": 44 - } - }, - "localhost": { - "PoseidonT3": { - "blockNumber": 4, - "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" - }, - "MockUSDC": { - "constructorArgs": { - "initialSupply": "1000000" - }, - "blockNumber": 5, - "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" - }, - "InterfoldToken": { - "constructorArgs": { - "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "blockNumber": 6, - "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" - }, - "InterfoldTicketToken": { - "constructorArgs": { - "baseToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", - "registry": "0x0000000000000000000000000000000000000001", - "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "blockNumber": 8, - "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" - }, - "SlashingManager": { - "constructorArgs": { - "initialDelay": "172800", - "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "blockNumber": 9, - "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" - }, - "CiphernodeRegistryOwnable": { - "constructorArgs": { - "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "submissionWindow": "10" - }, - "proxyRecords": { - "initData": "0xcd6dc687000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000000a", + "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000009fe46736679d2d9a65f0992f2272de9f3c7fa6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f875707000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "proxyAddress": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" }, - "blockNumber": 10, + "blockNumber": 9, "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, - "BondingRegistry": { + "InterfoldToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "ticketToken": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", - "licenseToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", - "registry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", - "slashedFundsTreasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "ticketPrice": "10000000", - "licenseRequiredBond": "100000000000000000000", - "minTicketBalance": "1", - "exitDelay": "604800" + "ccaStart": "1781296729", + "ccaEnd": "1781901529", + "claimSource": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "bondingRegistry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, - "proxyRecords": { - "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000dc64a140aa3e981100a9beca4e685f962f0cf6c90000000000000000000000009fe46736679d2d9a65f0992f2272de9f3c7fa6e0000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c853000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", - "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", - "proxyAdminAddress": "0x8aCd85898458400f7Db866d53FCFF6f0D49741FF", - "implementationAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" - }, - "blockNumber": 11, - "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" + "blockNumber": 12, + "address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, "Interfold": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "registry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", - "bondingRegistry": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", + "registry": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", + "bondingRegistry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", "e3RefundManager": "0x0000000000000000000000000000000000000001", "feeToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", "maxDuration": "2592000", "timeoutConfig": "{\"dkgWindow\":7200,\"computeWindow\":86400,\"decryptionWindow\":3600}" }, "proxyRecords": { - "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c8530000000000000000000000008a791620dd6260079bf849dc5567adc3f2fdc3180000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", + "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f875707000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c8530000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "proxyAdminAddress": "0x1F708C24a0D3A740cD47cC0444E9480899f3dA7D", - "implementationAddress": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + "proxyAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "proxyAdminAddress": "0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08", + "implementationAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" }, - "blockNumber": 15, - "address": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + "blockNumber": 16, + "address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" }, "E3RefundManager": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "interfold": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "interfold": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", "treasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, "proxyRecords": { - "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a51c1fc2f0d1a1b8494ed1fe312d7c3a78ed91c0000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000dcd1bf9a1b36ce34237eeafef220932846bcd82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508", - "proxyAdminAddress": "0x8e80FFe6Dc044F4A766Afd6e5a8732Fe0977A493", - "implementationAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + "proxyAddress": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "proxyAdminAddress": "0x524F04724632eED237cbA3c37272e018b3A7967e", + "implementationAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508" }, - "blockNumber": 17, - "address": "0x9A676e781A523b5d0C0e43731313A708CB607508" + "blockNumber": 18, + "address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" }, "MockComputeProvider": { - "blockNumber": 31, - "address": "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00" + "blockNumber": 32, + "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" }, "MockDecryptionVerifier": { - "blockNumber": 32, - "address": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570" + "blockNumber": 33, + "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" }, "MockPkVerifier": { - "blockNumber": 33, - "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" + "blockNumber": 34, + "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" }, "MockE3Program": { - "blockNumber": 34, - "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" + "blockNumber": 35, + "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" }, "MockRISC0Verifier": { - "address": "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90", - "blockNumber": 38 - }, - "HonkVerifier": { "address": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", "blockNumber": 39 }, + "HonkVerifier": { + "address": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", + "blockNumber": 40 + }, "CRISPProgram": { - "address": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", - "blockNumber": 39, + "address": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", + "blockNumber": 40, "constructorArgs": { - "interfold": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "verifierAddress": "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90", - "honkVerifierAddress": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", + "interfold": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "verifierAddress": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", + "honkVerifierAddress": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", "imageId": "0x0ad904cfaec1eeefa9b89a11020086ec51454423db1fee3b1ab614fff97368d6" } }, "MockVotingToken": { - "address": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", - "blockNumber": 41 + "address": "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc", + "blockNumber": 42 } } } \ No newline at end of file diff --git a/examples/CRISP/server/.env.example b/examples/CRISP/server/.env.example index 1b34b28c49..8c800f6dfb 100644 --- a/examples/CRISP/server/.env.example +++ b/examples/CRISP/server/.env.example @@ -15,10 +15,10 @@ CRON_API_KEY=1234567890 # Interfold stack: updated automatically on each `pnpm dev:up` deploy (do not edit by hand unless debugging). # Stale E3_PROGRAM_ADDRESS causes requestE3 to revert with empty data `0x`. -INTERFOLD_ADDRESS=0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0 +INTERFOLD_ADDRESS=0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82 FEE_TOKEN_ADDRESS=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -E3_PROGRAM_ADDRESS=0xFD471836031dc5108809D173A067e8486B9047A3 -CIPHERNODE_REGISTRY_ADDRESS=0xa513E6E4b8f2a923D98304ec87F64353C4D5C853 +E3_PROGRAM_ADDRESS=0xc351628EB244ec633d5f21fBD6621e1a683B1181 +CIPHERNODE_REGISTRY_ADDRESS=0x5FC8d32690cc91D4c39d9d3abcBD16989F875707 # CRISP voting eligibility token (MockVotingToken) — NOT the fee token above CRISP_VOTING_TOKEN=0x5081a39b8A5f0E35a8D959395a630b68B74Dd30f diff --git a/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts b/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts index d75cf11d61..b1370e6ec1 100644 --- a/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts +++ b/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts @@ -20,6 +20,10 @@ import { */ export interface InterfoldTokenArgs { owner?: string; + ccaStart?: bigint; + ccaEnd?: bigint; + claimSource?: string; + bondingRegistry?: string; hre: HardhatRuntimeEnvironment; } @@ -36,19 +40,13 @@ async function disableTransferRestrictionsForLocal( console.log("Disabling transfer restrictions for chain", chain); console.log("Contract address", await contract.getAddress()); - try { - const tgeTs = await contract.tgeTimestamp(); - if (tgeTs === 0n) { - // Attempt TGE — will succeed only if CCA_END + 45 days has passed. - // On local chains the deployer should advance time manually first. - await (await contract.tge()).wait(); - console.log("TGE fired for local development"); - } - } catch (error: any) { + const tgeTs = await contract.tgeTimestamp(); + if (tgeTs === 0n) { console.warn( - "TGE not yet available (CCA cooldown may not have passed):", - error.reason ?? error.message ?? error, + "TGE not yet fired — call tge() after advancing time past CCA_END + 45 days.", ); + } else { + console.log("Token is already Live (TGE timestamp:", tgeTs.toString(), ")"); } } @@ -59,6 +57,10 @@ async function disableTransferRestrictionsForLocal( */ export const deployAndSaveInterfoldToken = async ({ owner, + ccaStart, + ccaEnd, + claimSource, + bondingRegistry, hre, }: InterfoldTokenArgs): Promise<{ interfoldToken: InterfoldToken; @@ -69,7 +71,14 @@ export const deployAndSaveInterfoldToken = async ({ const preDeployedArgs = readDeploymentArgs("InterfoldToken", chain); - if (!owner || preDeployedArgs?.constructorArgs?.owner === owner) { + if ( + !owner || + ccaStart === undefined || + ccaEnd === undefined || + !claimSource || + !bondingRegistry || + preDeployedArgs?.constructorArgs?.owner === owner + ) { if (!preDeployedArgs?.address) { throw new Error( "InterfoldToken address not found, it must be deployed first", @@ -87,7 +96,13 @@ export const deployAndSaveInterfoldToken = async ({ const interfoldTokenFactory = await ethers.getContractFactory("InterfoldToken"); - const interfoldToken = await interfoldTokenFactory.deploy(owner); + const interfoldToken = await interfoldTokenFactory.deploy( + owner, + ccaStart, + ccaEnd, + claimSource, + bondingRegistry, + ); await interfoldToken.waitForDeployment(); @@ -99,6 +114,10 @@ export const deployAndSaveInterfoldToken = async ({ { constructorArgs: { owner, + ccaStart: ccaStart.toString(), + ccaEnd: ccaEnd.toString(), + claimSource, + bondingRegistry, }, blockNumber, address: interfoldTokenAddress, diff --git a/packages/interfold-contracts/scripts/deployInterfold.ts b/packages/interfold-contracts/scripts/deployInterfold.ts index 457da1c3fc..875cab4aff 100644 --- a/packages/interfold-contracts/scripts/deployInterfold.ts +++ b/packages/interfold-contracts/scripts/deployInterfold.ts @@ -137,6 +137,7 @@ export const deployInterfold = async ( const encodedSecure = encodeBfvParams(BFV_PARAMS.secure8192); const THIRTY_DAYS_IN_SECONDS = 60 * 60 * 24 * 30; + const SEVEN_DAYS_IN_SECONDS = 60 * 60 * 24 * 7; const SORTITION_SUBMISSION_WINDOW = 10; const addressOne = "0x0000000000000000000000000000000000000001"; @@ -187,21 +188,25 @@ export const deployInterfold = async ( ); } - console.log("Deploying INTF token..."); - const { interfoldToken } = await deployAndSaveInterfoldToken({ - owner: ownerAddress, - hre, - }); - const interfoldTokenAddress = await interfoldToken.getAddress(); - console.log("InterfoldToken deployed to:", interfoldTokenAddress); - - if (interfoldTokenAddress.toLowerCase() === feeTokenAddress.toLowerCase()) { + // ── CCA window ────────────────────────────────────────────────────────── + // For local dev the CCA window starts soon after deployment and runs for + // 7 days. Production deployments must set INTERFOLD_CCA_START. + const ccaStartEnv = process.env.INTERFOLD_CCA_START; + let ccaStart: bigint; + if (ccaStartEnv?.trim()) { + ccaStart = parseRequiredUint64(ccaStartEnv.trim(), "INTERFOLD_CCA_START"); + } else if (isLocalDeploymentChain(networkName)) { + const now = BigInt(latestBlock.timestamp); + ccaStart = now + 3600n; // 1 hour from now + console.warn( + `[WARN] INTERFOLD_CCA_START not set; using ${ccaStart} (block.timestamp + 1h) for local deployment.`, + ); + } else { throw new Error( - "MockUSDC and InterfoldToken resolved to the same address. " + - "Start a fresh Anvil on http://127.0.0.1:8545 (e.g. `anvil --chain-id 31337`) " + - "and rerun deploy so token nonces advance separately.", + "INTERFOLD_CCA_START must be set for non-local token-lock deployment", ); } + const ccaEnd = ccaStart + BigInt(SEVEN_DAYS_IN_SECONDS); console.log("Deploying InterfoldTicketToken..."); const { interfoldTicketToken } = await deployAndSaveInterfoldTicketToken({ @@ -231,11 +236,14 @@ export const deployInterfold = async ( const ciphernodeRegistryAddress = await ciphernodeRegistry.getAddress(); console.log("CiphernodeRegistry deployed to:", ciphernodeRegistryAddress); + // BondingRegistry is deployed before INTF so its address can be passed to + // the token constructor. The license token is set to address(0) temporarily + // and fixed after INTF is deployed via setLicenseToken(). console.log("Deploying BondingRegistry..."); const { bondingRegistry } = await deployAndSaveBondingRegistry({ owner: ownerAddress, ticketToken: interfoldTicketTokenAddress, - licenseToken: interfoldTokenAddress, + licenseToken: ethers.ZeroAddress, registry: ciphernodeRegistryAddress, slashedFundsTreasury: ownerAddress, ticketPrice: ethers.parseUnits("10", 6).toString(), @@ -247,8 +255,35 @@ export const deployInterfold = async ( const bondingRegistryAddress = await bondingRegistry.getAddress(); console.log("BondingRegistry deployed to:", bondingRegistryAddress); - // BONDING_REGISTRY is immutable (set at construction). - // Bonding transfers are always allowed pre-TGE per the phase-based gate. + // INTF is deployed with BondingRegistry's real address. claimSource uses + // the deployer as a placeholder; the actual Interfold protocol contract is + // deployed later (its address is not known at this point). CLAIM_SOURCE is + // immutable, so local deployments use the deployer as the claim source. + console.log("Deploying INTF token..."); + const { interfoldToken } = await deployAndSaveInterfoldToken({ + owner: ownerAddress, + ccaStart, + ccaEnd, + claimSource: ownerAddress, + bondingRegistry: bondingRegistryAddress, + hre, + }); + const interfoldTokenAddress = await interfoldToken.getAddress(); + console.log("InterfoldToken deployed to:", interfoldTokenAddress); + + // Fix up BondingRegistry's license token now that INTF exists. + console.log("Setting license token in BondingRegistry..."); + await (await bondingRegistry.setLicenseToken(interfoldTokenAddress)).wait(); + + if (interfoldTokenAddress.toLowerCase() === feeTokenAddress.toLowerCase()) { + throw new Error( + "MockUSDC and InterfoldToken resolved to the same address. " + + "Start a fresh Anvil on http://127.0.0.1:8545 (e.g. `anvil --chain-id 31337`) " + + "and rerun deploy so token nonces advance separately.", + ); + } + + // Whitelist BondingRegistry so bonded transfers work pre-TGE. console.log("Whitelisting BondingRegistry in INTF..."); await ( await interfoldToken.setTransferWhitelisted(bondingRegistryAddress, true) diff --git a/packages/interfold-contracts/tasks/ciphernode.ts b/packages/interfold-contracts/tasks/ciphernode.ts index 0ed448247f..393b6ae701 100644 --- a/packages/interfold-contracts/tasks/ciphernode.ts +++ b/packages/interfold-contracts/tasks/ciphernode.ts @@ -279,7 +279,7 @@ export const ciphernodeMintTokens = task( const intfTx = await interfoldTokenContract.mint( ciphernodeAddress, ethers.parseEther(intfAmount), - ethers.encodeBytes32String("Ciphernode allocation"), + ethers.encodeBytes32String("cn-alloc"), ); await intfTx.wait(); console.log(`${intfAmount} INTF minted`); @@ -422,9 +422,7 @@ export const ciphernodeAdminAdd = task( const intfTx = await interfoldTokenConnected.mint( adminWallet.address, licenseBondWei, - ethers.encodeBytes32String( - "Admin allocation for ciphernode registration", - ), + ethers.encodeBytes32String("admin-cn-reg"), ); await intfTx.wait(); diff --git a/templates/default/deployed_contracts.json b/templates/default/deployed_contracts.json index 3d993352b5..32392c5d9b 100644 --- a/templates/default/deployed_contracts.json +++ b/templates/default/deployed_contracts.json @@ -1,39 +1,32 @@ { "localhost": { "PoseidonT3": { - "blockNumber": 14, + "blockNumber": 30, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 15, + "blockNumber": 31, "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, - "InterfoldToken": { - "constructorArgs": { - "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "blockNumber": 16, - "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" - }, "InterfoldTicketToken": { "constructorArgs": { "baseToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 17, - "address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" + "blockNumber": 32, + "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "SlashingManager": { "constructorArgs": { "initialDelay": "172800", "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 18, - "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" + "blockNumber": 34, + "address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" }, "CiphernodeRegistryOwnable": { "constructorArgs": { @@ -43,19 +36,19 @@ "proxyRecords": { "initData": "0xcd6dc687000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000000a", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F", - "proxyAdminAddress": "0x3B02fF1e626Ed7a8fd6eC5299e2C54e1421B626B", - "implementationAddress": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + "proxyAddress": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", + "proxyAdminAddress": "0x61c36a8d610163660E21a8b7359e1Cac0C9133e1", + "implementationAddress": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, - "blockNumber": 19, - "address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" + "blockNumber": 35, + "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "BondingRegistry": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "ticketToken": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", - "licenseToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", - "registry": "0x0165878A594ca255338adfa4d48449f69242Eb8F", + "ticketToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "licenseToken": "0x0000000000000000000000000000000000000000", + "registry": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", "slashedFundsTreasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "ticketPrice": "10000000", "licenseRequiredBond": "100000000000000000000", @@ -63,74 +56,85 @@ "exitDelay": "604800" }, "proxyRecords": { - "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000cf7ed3acca5a467e9e704c703e8d87f634fb0fc90000000000000000000000009fe46736679d2d9a65f0992f2272de9f3c7fa6e00000000000000000000000000165878a594ca255338adfa4d48449f69242eb8f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", + "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000009fe46736679d2d9a65f0992f2272de9f3c7fa6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f875707000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", - "proxyAdminAddress": "0x94099942864EA81cCF197E9D71ac53310b1468D8", - "implementationAddress": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + "proxyAddress": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", + "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", + "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" + }, + "blockNumber": 36, + "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + }, + "InterfoldToken": { + "constructorArgs": { + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "ccaStart": "1781297058", + "ccaEnd": "1781901858", + "claimSource": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "bondingRegistry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, - "blockNumber": 20, + "blockNumber": 40, "address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, "Interfold": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "registry": "0x0165878A594ca255338adfa4d48449f69242Eb8F", - "bondingRegistry": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", + "registry": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", + "bondingRegistry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", "e3RefundManager": "0x0000000000000000000000000000000000000001", "feeToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", "maxDuration": "2592000", "timeoutConfig": "{\"dkgWindow\":7200,\"computeWindow\":86400,\"decryptionWindow\":3600}" }, "proxyRecords": { - "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000165878a594ca255338adfa4d48449f69242eb8f0000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe60000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", + "initData": "0x4d600e5d000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f875707000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c8530000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", - "proxyAdminAddress": "0x8dAF17A20c9DBA35f005b6324F493785D239719d", - "implementationAddress": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + "proxyAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "proxyAdminAddress": "0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08", + "implementationAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" }, - "blockNumber": 24, - "address": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + "blockNumber": 44, + "address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" }, "E3RefundManager": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "interfold": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", + "interfold": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", "treasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, "proxyRecords": { - "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000b7f8bc63bbcad18155201308c8f3540b07f84f5e000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000dcd1bf9a1b36ce34237eeafef220932846bcd82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - "proxyAdminAddress": "0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08", - "implementationAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + "proxyAddress": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "proxyAdminAddress": "0x524F04724632eED237cbA3c37272e018b3A7967e", + "implementationAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508" }, - "blockNumber": 26, - "address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + "blockNumber": 46, + "address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" }, "MockComputeProvider": { - "blockNumber": 40, - "address": "0x9d4454B023096f34B160D6B654540c56A1F81688" + "blockNumber": 60, + "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" }, "MockDecryptionVerifier": { - "blockNumber": 41, - "address": "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00" + "blockNumber": 61, + "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" }, "MockPkVerifier": { - "blockNumber": 42, - "address": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570" + "blockNumber": 62, + "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" }, "MockE3Program": { - "blockNumber": 43, - "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" + "blockNumber": 63, + "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" }, "ImageID": { - "address": "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90", - "blockNumber": 48 + "address": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", + "blockNumber": 68 }, "MyProgram": { - "address": "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1", - "blockNumber": 50 + "address": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", + "blockNumber": 70 } } } \ No newline at end of file diff --git a/templates/default/interfold.config.yaml b/templates/default/interfold.config.yaml index 22f3290045..68f7f7442f 100644 --- a/templates/default/interfold.config.yaml +++ b/templates/default/interfold.config.yaml @@ -3,23 +3,23 @@ chains: rpc_url: 'ws://localhost:8545' contracts: e3_program: - address: "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1" - deploy_block: 50 + address: "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650" + deploy_block: 70 interfold: - address: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" - deploy_block: 24 + address: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + deploy_block: 44 ciphernode_registry: - address: "0x0165878A594ca255338adfa4d48449f69242Eb8F" - deploy_block: 19 + address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + deploy_block: 35 bonding_registry: - address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" - deploy_block: 20 + address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + deploy_block: 36 slashing_manager: address: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' deploy_block: 11 fee_token: address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" - deploy_block: 15 + deploy_block: 31 program: dev: true # risc0: From 3eec18d5329d830bda1a98e37bbefa4894bb0112 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sat, 13 Jun 2026 02:41:23 +0500 Subject: [PATCH 20/24] fix: update contract address --- templates/default/deployed_contracts.json | 34 +++++++++++------------ templates/default/interfold.config.yaml | 12 ++++---- templates/default/package.json | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/templates/default/deployed_contracts.json b/templates/default/deployed_contracts.json index 32392c5d9b..0898fbfef7 100644 --- a/templates/default/deployed_contracts.json +++ b/templates/default/deployed_contracts.json @@ -1,14 +1,14 @@ { "localhost": { "PoseidonT3": { - "blockNumber": 30, + "blockNumber": 8, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 31, + "blockNumber": 10, "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, "InterfoldTicketToken": { @@ -17,7 +17,7 @@ "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 32, + "blockNumber": 12, "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "SlashingManager": { @@ -25,7 +25,7 @@ "initialDelay": "172800", "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 34, + "blockNumber": 14, "address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" }, "CiphernodeRegistryOwnable": { @@ -40,7 +40,7 @@ "proxyAdminAddress": "0x61c36a8d610163660E21a8b7359e1Cac0C9133e1", "implementationAddress": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, - "blockNumber": 35, + "blockNumber": 16, "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "BondingRegistry": { @@ -62,18 +62,18 @@ "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" }, - "blockNumber": 36, + "blockNumber": 17, "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, "InterfoldToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "ccaStart": "1781297058", - "ccaEnd": "1781901858", + "ccaStart": "1781303982", + "ccaEnd": "1781908782", "claimSource": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "bondingRegistry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, - "blockNumber": 40, + "blockNumber": 20, "address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, "Interfold": { @@ -93,7 +93,7 @@ "proxyAdminAddress": "0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08", "implementationAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" }, - "blockNumber": 44, + "blockNumber": 25, "address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" }, "E3RefundManager": { @@ -109,32 +109,32 @@ "proxyAdminAddress": "0x524F04724632eED237cbA3c37272e018b3A7967e", "implementationAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508" }, - "blockNumber": 46, + "blockNumber": 28, "address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" }, "MockComputeProvider": { - "blockNumber": 60, + "blockNumber": 42, "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" }, "MockDecryptionVerifier": { - "blockNumber": 61, + "blockNumber": 43, "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" }, "MockPkVerifier": { - "blockNumber": 62, + "blockNumber": 44, "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" }, "MockE3Program": { - "blockNumber": 63, + "blockNumber": 45, "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" }, "ImageID": { "address": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", - "blockNumber": 68 + "blockNumber": 50 }, "MyProgram": { "address": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", - "blockNumber": 70 + "blockNumber": 52 } } } \ No newline at end of file diff --git a/templates/default/interfold.config.yaml b/templates/default/interfold.config.yaml index 68f7f7442f..0575ecfd6a 100644 --- a/templates/default/interfold.config.yaml +++ b/templates/default/interfold.config.yaml @@ -4,22 +4,22 @@ chains: contracts: e3_program: address: "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650" - deploy_block: 70 + deploy_block: 52 interfold: address: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" - deploy_block: 44 + deploy_block: 25 ciphernode_registry: address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" - deploy_block: 35 + deploy_block: 16 bonding_registry: address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" - deploy_block: 36 + deploy_block: 17 slashing_manager: - address: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' + address: '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9' deploy_block: 11 fee_token: address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" - deploy_block: 31 + deploy_block: 10 program: dev: true # risc0: diff --git a/templates/default/package.json b/templates/default/package.json index 868d1fd8b3..8bf76c42af 100644 --- a/templates/default/package.json +++ b/templates/default/package.json @@ -9,7 +9,7 @@ "lint": "eslint .", "clean:deployments": "hardhat utils:clean-deployments", "deploy": "pnpm clean:deployments && hardhat run scripts/deploy-local.ts --network localhost", - "deploy:dev": "hardhat run scripts/deploy-local.ts", + "deploy:dev": "hardhat run scripts/deploy-local.ts --network localhost", "dev:all": "./scripts/dev_all.sh", "dev:ciphernodes": "./scripts/dev_ciphernodes.sh", "dev:setup": "bash ./scripts/setup.sh", From d7fe161b3fd13e9899f7ee75042040b7fc80e618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crist=C3=B3v=C3=A3o?= <12870300+cristovaoth@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:40:22 +0200 Subject: [PATCH 21/24] feat: lock TTL (#1599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cristóvão --- agent/flow-trace/02_TOKENS_AND_ACTIVATION.md | 9 +- .../contracts/token/InterfoldToken.sol | 63 +++- .../ignition/modules/interfoldToken.ts | 2 + .../scripts/deployAndSave/interfoldToken.ts | 5 + .../scripts/deployInterfold.ts | 27 +- .../test/Token/InterfoldToken.spec.ts | 331 +++++++++++++++++- .../test/fixtures/system.ts | 3 + 7 files changed, 408 insertions(+), 32 deletions(-) diff --git a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md index c10e4d27fa..00315123b8 100644 --- a/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md +++ b/agent/flow-trace/02_TOKENS_AND_ACTIVATION.md @@ -56,7 +56,14 @@ Before a node can register, it must stake two types of collateral: │ BONDING_REGISTRY.totalBonded(account)) │ │ Transfer reverts with InsufficientUnlockedBalance │ │ if value > transferable │ -│ │ +│ │ +│ Lock sunset (NO_MORE_LOCKS, immutable): │ +│ - Absolute timestamp set at deployment │ +│ - createLockPolicy rejects any policy that could │ +│ outlast the sunset (curves and holdUntil) │ +│ - From NO_MORE_LOCKS on, _update skips all lock │ +│ accounting (vanilla ERC20); PENDING locks die too │ +│ │ │ Whitelisting: │ │ - setTransferWhitelisted(addr, bool) │ │ WHITELIST_ROLE — pre-TGE transfer gate │ diff --git a/packages/interfold-contracts/contracts/token/InterfoldToken.sol b/packages/interfold-contracts/contracts/token/InterfoldToken.sol index 301902ed3c..67c222b3eb 100644 --- a/packages/interfold-contracts/contracts/token/InterfoldToken.sol +++ b/packages/interfold-contracts/contracts/token/InterfoldToken.sol @@ -126,9 +126,13 @@ contract InterfoldToken is /// future. error InvalidCcaWindow(uint64 ccaStart, uint64 ccaEnd); - /// @notice Policy parameters are internally inconsistent. + /// @notice Policy parameters are internally inconsistent, or the policy + /// could keep tokens locked past the lock sunset. error InvalidPolicy(); + /// @notice The lock sunset timestamp must be after the earliest TGE. + error InvalidNoMoreLocks(uint256 noMoreLocks, uint256 minimum); + /// @notice The requested relink amount exceeds the amount available under /// the source policy. error RelinkAmountExceeded(); @@ -198,6 +202,9 @@ contract InterfoldToken is /// @notice End of the CCA auction window, fixed at deployment uint64 public immutable CCA_END; + /// @notice Absolute timestamp after which token locks no longer apply. + uint64 public immutable NO_MORE_LOCKS; + /// @notice The CCA auction contract address public immutable CLAIM_SOURCE; @@ -288,6 +295,7 @@ contract InterfoldToken is * @param initialOwner_ Initial owner; receives all roles. * @param ccaStart_ CCA auction window start; * @param ccaEnd_ CCA auction window end; after `ccaStart_`. + * @param noMoreLocks_ Absolute timestamp after which token locks no longer apply. * @param claimSource_ The CCA auction contract * @param bondingRegistry_ Registry whose bonded INTF */ @@ -295,6 +303,7 @@ contract InterfoldToken is address initialOwner_, uint64 ccaStart_, uint64 ccaEnd_, + uint64 noMoreLocks_, address claimSource_, IBondingRegistry bondingRegistry_ ) @@ -312,8 +321,14 @@ contract InterfoldToken is if (registry.code.length == 0) { revert InvalidBondingRegistry(registry); } + if (noMoreLocks_ == 0) revert ZeroAmount(); + uint256 earliestTge = uint256(ccaEnd_) + TGE_COOLDOWN; + if (noMoreLocks_ <= earliestTge) { + revert InvalidNoMoreLocks(noMoreLocks_, earliestTge + 1); + } CCA_START = ccaStart_; CCA_END = ccaEnd_; + NO_MORE_LOCKS = noMoreLocks_; CLAIM_SOURCE = claimSource_; BONDING_REGISTRY = bondingRegistry_; } @@ -428,6 +443,7 @@ contract InterfoldToken is revert PolicyAlreadyDefined(policyId); } _validateCurve(policy.unlock); + _validatePolicyMaturity(policy); lockPolicies[policyId] = policy; emit PolicyDefined(policyId, policy); @@ -510,11 +526,16 @@ contract InterfoldToken is /// @notice Locked balance of `account` at `timestamp`, evaluated against the /// current configuration (an unset TGE keeps {Anchor.Tge} policies - /// fully locked for any timestamp). + /// fully locked for any timestamp). Always zero from the lock + /// sunset onwards. function lockedBalanceAt( address account, uint64 timestamp ) public view returns (uint256 lockedBalance) { + if (timestamp >= NO_MORE_LOCKS) { + return 0; + } + Lock[] storage accountLocks = locks[account]; for (uint256 i = 0; i < accountLocks.length; i++) { Lock storage accountLock = accountLocks[i]; @@ -575,17 +596,23 @@ contract InterfoldToken is /** * @dev Applies, in order: - * 1. The pre-TGE transfer gate (_isTransferRestricted) - * 2. The lock check, the sender can move at most its transferable + * 1. The lock sunset short-circuit. + * 2. The pre-TGE transfer gate (_isTransferRestricted) + * 3. The lock check, the sender can move at most its transferable * balance - * 3. The transfer itself, via parent contract - * 4. Claim-lock creation, unless the recipient is claim-lock exempt. + * 4. The transfer itself, via parent contract + * 5. Claim-lock creation, unless the recipient is claim-lock exempt. */ function _update( address from, address to, uint256 value ) internal override(ERC20, ERC20Votes) { + if (block.timestamp >= NO_MORE_LOCKS) { + super._update(from, to, value); + return; + } + bool isMint = from == address(0); bool isBurn = to == address(0); @@ -894,6 +921,30 @@ contract InterfoldToken is return 0; } + /// @dev Ensures the policy cannot keep anything locked past the lock + /// sunset. Ending exactly at the sunset is allowed: the curve has + /// fully released and the hold has lapsed at that moment, which is + /// also when the sunset takes effect. + function _validatePolicyMaturity(LockPolicy calldata policy) internal view { + Curve calldata curve = policy.unlock; + // The curve validation guarantees cliff <= vest when vest != 0, so + // the curve fully releases at cliff (vest == 0) or at vest end. + uint256 curveEnd = curve.vestDuration == 0 + ? curve.cliffDuration + : curve.vestDuration; + + uint256 policyEnd = curve.anchor == Anchor.Tge + ? uint256(CCA_END) + TGE_COOLDOWN + curveEnd + : curve.start + curveEnd; + + if (policyEnd > NO_MORE_LOCKS) { + revert InvalidPolicy(); + } + if (policy.holdUntil > NO_MORE_LOCKS) { + revert InvalidPolicy(); + } + } + /// @dev Validates the unlock curve: it must lock something and be /// consistent with its anchor mode. function _validateCurve(Curve calldata curve) internal pure { diff --git a/packages/interfold-contracts/ignition/modules/interfoldToken.ts b/packages/interfold-contracts/ignition/modules/interfoldToken.ts index 1e8e6d8989..9c43b7ef16 100644 --- a/packages/interfold-contracts/ignition/modules/interfoldToken.ts +++ b/packages/interfold-contracts/ignition/modules/interfoldToken.ts @@ -11,11 +11,13 @@ export default buildModule("InterfoldToken", (m) => { const ccaEnd = m.getParameter("ccaEnd"); const claimSource = m.getParameter("claimSource"); const bondingRegistry = m.getParameter("bondingRegistry"); + const noMoreLocks = m.getParameter("noMoreLocks"); const interfoldToken = m.contract("InterfoldToken", [ owner, ccaStart, ccaEnd, + noMoreLocks, claimSource, bondingRegistry, ]); diff --git a/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts b/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts index b1370e6ec1..d6fb104e8c 100644 --- a/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts +++ b/packages/interfold-contracts/scripts/deployAndSave/interfoldToken.ts @@ -24,6 +24,7 @@ export interface InterfoldTokenArgs { ccaEnd?: bigint; claimSource?: string; bondingRegistry?: string; + noMoreLocks?: bigint; hre: HardhatRuntimeEnvironment; } @@ -61,6 +62,7 @@ export const deployAndSaveInterfoldToken = async ({ ccaEnd, claimSource, bondingRegistry, + noMoreLocks, hre, }: InterfoldTokenArgs): Promise<{ interfoldToken: InterfoldToken; @@ -77,6 +79,7 @@ export const deployAndSaveInterfoldToken = async ({ ccaEnd === undefined || !claimSource || !bondingRegistry || + noMoreLocks === undefined || preDeployedArgs?.constructorArgs?.owner === owner ) { if (!preDeployedArgs?.address) { @@ -100,6 +103,7 @@ export const deployAndSaveInterfoldToken = async ({ owner, ccaStart, ccaEnd, + noMoreLocks, claimSource, bondingRegistry, ); @@ -118,6 +122,7 @@ export const deployAndSaveInterfoldToken = async ({ ccaEnd: ccaEnd.toString(), claimSource, bondingRegistry, + noMoreLocks: noMoreLocks.toString(), }, blockNumber, address: interfoldTokenAddress, diff --git a/packages/interfold-contracts/scripts/deployInterfold.ts b/packages/interfold-contracts/scripts/deployInterfold.ts index 875cab4aff..6666ee28e1 100644 --- a/packages/interfold-contracts/scripts/deployInterfold.ts +++ b/packages/interfold-contracts/scripts/deployInterfold.ts @@ -84,30 +84,6 @@ function parseRequiredUint64(value: string, label: string): bigint { return parsed; } -function resolveInterfoldTgeTimestamp( - networkName: string, - latestBlockTimestamp: number, -): string { - const configured = process.env.INTERFOLD_TGE_TIMESTAMP; - if (configured?.trim()) { - return parseRequiredUint64( - configured.trim(), - "INTERFOLD_TGE_TIMESTAMP", - ).toString(); - } - - if (!isLocalDeploymentChain(networkName)) { - throw new Error( - "INTERFOLD_TGE_TIMESTAMP must be set for non-local token-lock deployment", - ); - } - - console.warn( - "[WARN] INTERFOLD_TGE_TIMESTAMP not set; using latest local block timestamp for INTF token locks.", - ); - return latestBlockTimestamp.toString(); -} - /** Circuit names required for BFV ZK verification in this script */ const DKG_AGGREGATOR_VERIFIER = "DkgAggregatorVerifier"; const DECRYPTION_AGGREGATOR_VERIFIER = "DecryptionAggregatorVerifier"; @@ -138,6 +114,8 @@ export const deployInterfold = async ( const THIRTY_DAYS_IN_SECONDS = 60 * 60 * 24 * 30; const SEVEN_DAYS_IN_SECONDS = 60 * 60 * 24 * 7; + const TGE_COOLDOWN_SECONDS = 60 * 60 * 24 * 45; + const FOUR_YEARS_IN_SECONDS = 60 * 60 * 24 * 365 * 4; const SORTITION_SUBMISSION_WINDOW = 10; const addressOne = "0x0000000000000000000000000000000000000001"; @@ -266,6 +244,7 @@ export const deployInterfold = async ( ccaEnd, claimSource: ownerAddress, bondingRegistry: bondingRegistryAddress, + noMoreLocks: ccaEnd + BigInt(TGE_COOLDOWN_SECONDS + FOUR_YEARS_IN_SECONDS), hre, }); const interfoldTokenAddress = await interfoldToken.getAddress(); diff --git a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts index 863cf3baff..d9453b90e8 100644 --- a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts +++ b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts @@ -15,6 +15,12 @@ const { loadFixture, time } = networkHelpers; const DAY = 24n * 60n * 60n; const YEAR = 365n * DAY; +const NO_MORE_LOCKS_DELAY = 4n * YEAR; +const TGE_COOLDOWN = 45n * DAY; + +function noMoreLocksFor(ccaEnd: bigint) { + return ccaEnd + TGE_COOLDOWN + NO_MORE_LOCKS_DELAY; +} describe("InterfoldToken", function () { // ── Helpers ───────────────────────────────────────────────────────────── @@ -43,11 +49,13 @@ describe("InterfoldToken", function () { const now = BigInt(await time.latest()); const ccaStart = now + 10n * DAY; // far future — Virtual phase const ccaEnd = ccaStart + 7n * DAY; + const noMoreLocks = noMoreLocksFor(ccaEnd); const token = await new InterfoldTokenFactory(deployer).deploy( await admin.getAddress(), ccaStart, ccaEnd, + noMoreLocks, await claimSource.getAddress(), await mockRegistry.getAddress(), ); @@ -65,6 +73,7 @@ describe("InterfoldToken", function () { mockRegistry, ccaStart, ccaEnd, + noMoreLocks, }; } @@ -173,12 +182,14 @@ describe("InterfoldToken", function () { const now = BigInt(await time.latest()); const ccaStart = now + DAY; const ccaEnd = ccaStart + 7n * DAY; + const noMoreLocks = noMoreLocksFor(ccaEnd); await expect( new InterfoldTokenFactory(deployer).deploy( await deployer.getAddress(), ccaStart, ccaEnd, + noMoreLocks, ethers.ZeroAddress, await mockRegistry.getAddress(), ), @@ -193,12 +204,14 @@ describe("InterfoldToken", function () { const now = BigInt(await time.latest()); const ccaStart = now + DAY; const ccaEnd = ccaStart + 7n * DAY; + const noMoreLocks = noMoreLocksFor(ccaEnd); await expect( new InterfoldTokenFactory(deployer).deploy( await deployer.getAddress(), ccaStart, ccaEnd, + noMoreLocks, await deployer.getAddress(), ethers.ZeroAddress, ), @@ -213,12 +226,14 @@ describe("InterfoldToken", function () { const now = BigInt(await time.latest()); const ccaStart = now + DAY; const ccaEnd = ccaStart + 7n * DAY; + const noMoreLocks = noMoreLocksFor(ccaEnd); await expect( new InterfoldTokenFactory(deployer).deploy( await admin.getAddress(), ccaStart, ccaEnd, + noMoreLocks, await deployer.getAddress(), await admin.getAddress(), // EOA, not a contract ), @@ -241,6 +256,7 @@ describe("InterfoldToken", function () { await deployer.getAddress(), now, // in the past (or now) now + 7n * DAY, + noMoreLocksFor(now + 7n * DAY), await deployer.getAddress(), await mockRegistry.getAddress(), ), @@ -259,12 +275,14 @@ describe("InterfoldToken", function () { const now = BigInt(await time.latest()); const ccaStart = now + DAY; const ccaEnd = ccaStart; // equal, not greater + const noMoreLocks = noMoreLocksFor(ccaEnd); await expect( new InterfoldTokenFactory(deployer).deploy( await deployer.getAddress(), ccaStart, ccaEnd, + noMoreLocks, await deployer.getAddress(), await mockRegistry.getAddress(), ), @@ -274,6 +292,57 @@ describe("InterfoldToken", function () { ); }); + it("reverts when noMoreLocks is zero", async function () { + const [deployer] = await ethers.getSigners(); + const mockRegistry = await new MockBondingRegistryFactory( + deployer, + ).deploy(); + await mockRegistry.waitForDeployment(); + const now = BigInt(await time.latest()); + const ccaStart = now + DAY; + const ccaEnd = ccaStart + 7n * DAY; + + await expect( + new InterfoldTokenFactory(deployer).deploy( + await deployer.getAddress(), + ccaStart, + ccaEnd, + 0n, + await deployer.getAddress(), + await mockRegistry.getAddress(), + ), + ).to.be.revertedWithCustomError( + { interface: InterfoldTokenFactory.createInterface() }, + "ZeroAmount", + ); + }); + + it("reverts when noMoreLocks is not after the earliest TGE", async function () { + const [deployer] = await ethers.getSigners(); + const mockRegistry = await new MockBondingRegistryFactory( + deployer, + ).deploy(); + await mockRegistry.waitForDeployment(); + const now = BigInt(await time.latest()); + const ccaStart = now + DAY; + const ccaEnd = ccaStart + 7n * DAY; + const earliestTge = ccaEnd + TGE_COOLDOWN; + + await expect( + new InterfoldTokenFactory(deployer).deploy( + await deployer.getAddress(), + ccaStart, + ccaEnd, + earliestTge, // must be strictly after + await deployer.getAddress(), + await mockRegistry.getAddress(), + ), + ).to.be.revertedWithCustomError( + { interface: InterfoldTokenFactory.createInterface() }, + "InvalidNoMoreLocks", + ); + }); + it("initial owner receives all roles", async function () { const { token, admin } = await loadFixture(deploy); const adminAddress = await admin.getAddress(); @@ -720,6 +789,93 @@ describe("InterfoldToken", function () { "AccessControlUnauthorizedAccount", ); }); + + it("reverts when Tge-anchored vest outlasts noMoreLocks", async function () { + const { token, admin } = await loadFixture(deploy); + await expect( + token + .connect(admin) + .createLockPolicy(ethers.encodeBytes32String("TOO_LONG"), { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: NO_MORE_LOCKS_DELAY + 1n, + }, + }), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("reverts when Tge-anchored cliff-only release outlasts noMoreLocks", async function () { + const { token, admin } = await loadFixture(deploy); + await expect( + token + .connect(admin) + .createLockPolicy(ethers.encodeBytes32String("TOO_LONG"), { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: NO_MORE_LOCKS_DELAY + 1n, + vestDuration: 0n, + }, + }), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("accepts Tge-anchored vest of exactly noMoreLocks", async function () { + const { token, admin } = await loadFixture(deploy); + await expect( + token + .connect(admin) + .createLockPolicy(ethers.encodeBytes32String("FULL_TAIL"), { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: NO_MORE_LOCKS_DELAY, + }, + }), + ).to.emit(token, "PolicyDefined"); + }); + + it("reverts when Absolute curve ends past the earliest sunset", async function () { + const { token, admin, ccaEnd } = await loadFixture(deploy); + const earliestMaturity = noMoreLocksFor(ccaEnd); + await expect( + token + .connect(admin) + .createLockPolicy(ethers.encodeBytes32String("TOO_LONG"), { + holdUntil: 0n, + unlock: { + anchor: 0, + start: earliestMaturity - YEAR, + cliffDuration: 0n, + vestDuration: YEAR + 1n, + }, + }), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("reverts when holdUntil is past the earliest sunset", async function () { + const { token, admin, ccaEnd } = await loadFixture(deploy); + const earliestMaturity = noMoreLocksFor(ccaEnd); + await expect( + token + .connect(admin) + .createLockPolicy(ethers.encodeBytes32String("TOO_LONG"), { + holdUntil: earliestMaturity + 1n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: YEAR, + }, + }), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); }); // ═════════════════════════════════════════════════════════════════════════ @@ -842,7 +998,7 @@ describe("InterfoldToken", function () { }); it("transferableBalanceOf = 0 when fully locked and no bond", async function () { - const { token, alice, amount } = await deployWithLockAndTge({ + const { token, alice } = await deployWithLockAndTge({ mintAmount: ethers.parseEther("1000"), }); expect( @@ -944,6 +1100,79 @@ describe("InterfoldToken", function () { }); }); + // ═════════════════════════════════════════════════════════════════════════ + // Lock sunset + // ═════════════════════════════════════════════════════════════════════════ + + describe("lock sunset", function () { + it("NO_MORE_LOCKS is fixed at deployment", async function () { + const { token, noMoreLocks } = await loadFixture(deploy); + expect(await token.NO_MORE_LOCKS()).to.equal(noMoreLocks); + }); + + it("locked balance becomes fully transferable at the sunset", async function () { + const { token, alice, bob, amount, noMoreLocks } = + await deployWithLockAndTge({ mintAmount: ethers.parseEther("1000") }); + const aliceAddress = await alice.getAddress(); + + await time.increaseTo(noMoreLocks); + + expect(await token.lockedBalanceOf(aliceAddress)).to.equal(0n); + expect(await token.transferableBalanceOf(aliceAddress)).to.equal(amount); + await token.connect(alice).transfer(await bob.getAddress(), amount); + }); + + it("unlinked PENDING locks sunset too", async function () { + const { token, alice, bob, claimSource, amount } = + await deployWithUnlockedAndTge(ethers.parseEther("500")); + const aliceAddress = await alice.getAddress(); + + await token + .connect(alice) + .transfer(await claimSource.getAddress(), amount); + await token.connect(claimSource).transfer(aliceAddress, amount); + expect(await token.lockedBalanceOf(aliceAddress)).to.equal(amount); + + await time.increaseTo(await token.NO_MORE_LOCKS()); + + expect(await token.lockedBalanceOf(aliceAddress)).to.equal(0n); + await token.connect(alice).transfer(await bob.getAddress(), amount); + }); + + it("lockedBalanceAt reports 0 from the sunset onwards", async function () { + const { token, alice, claimSource, amount } = + await deployWithUnlockedAndTge(ethers.parseEther("500")); + const aliceAddress = await alice.getAddress(); + + await token + .connect(alice) + .transfer(await claimSource.getAddress(), amount); + await token.connect(claimSource).transfer(aliceAddress, amount); + + const maturity = await token.NO_MORE_LOCKS(); + expect(await token.lockedBalanceAt(aliceAddress, maturity - 1n)).to.equal( + amount, + ); + expect(await token.lockedBalanceAt(aliceAddress, maturity)).to.equal(0n); + }); + + it("CLAIM_SOURCE transfers past the sunset create no locks", async function () { + const { token, alice, claimSource, amount } = + await deployWithUnlockedAndTge(ethers.parseEther("500")); + const aliceAddress = await alice.getAddress(); + + await token + .connect(alice) + .transfer(await claimSource.getAddress(), amount); + + await time.increaseTo(await token.NO_MORE_LOCKS()); + + await token.connect(claimSource).transfer(aliceAddress, amount); + expect(await token.lockCount(aliceAddress)).to.equal(0n); + expect(await token.lockedBalanceOf(aliceAddress)).to.equal(0n); + }); + }); + // ═════════════════════════════════════════════════════════════════════════ // Claim-source auto-lock & linkClaim // ═════════════════════════════════════════════════════════════════════════ @@ -1057,6 +1286,106 @@ describe("InterfoldToken", function () { expect(lb2).to.be.closeTo(linkAmount, ethers.parseEther("0.01")); }); + it("linkClaim partly consumes PENDING and queues the remainder", async function () { + const { token, admin, alice, claimSource, amount } = + await deployWithUnlockedAndTge(ethers.parseEther("300")); + const aliceAddress = await alice.getAddress(); + const policyId = await createLinearPolicy(token, admin, "PART_QUEUE", { + vestDuration: 2n * YEAR, + }); + + await token + .connect(alice) + .transfer(await claimSource.getAddress(), amount); + + expect(await token.lockCount(aliceAddress)).to.equal(0n); + expect(await token.queuedLockCount(aliceAddress)).to.equal(0n); + + await token.connect(claimSource).transfer(aliceAddress, amount); + + const linkAmount = ethers.parseEther("1000"); + await token.connect(admin).linkClaim(aliceAddress, linkAmount, policyId); + + expect(await token.lockCount(aliceAddress)).to.equal(1n); + expect(await token.queuedLockCount(aliceAddress)).to.equal(1n); + + const activeLock = await token.locks(aliceAddress, 0); + expect(activeLock.policyId).to.equal(policyId); + expect(activeLock.amount).to.equal(amount); + + const queuedLock = await token.queuedLocks(aliceAddress, 0); + expect(queuedLock.policyId).to.equal(policyId); + expect(queuedLock.amount).to.equal(linkAmount - amount); + }); + + it("claim after link consumes the queued link", async function () { + const fixture = await loadFixture(deploy); + const { token, admin, alice, claimSource, ccaEnd } = fixture; + const aliceAddress = await alice.getAddress(); + const policyId = await createLinearPolicy(token, admin, "CLAIM_LINK", { + vestDuration: 2n * YEAR, + }); + const linkAmount = ethers.parseEther("500"); + + await token.connect(admin).linkClaim(aliceAddress, linkAmount, policyId); + expect(await token.queuedLockCount(aliceAddress)).to.equal(1n); + + await token + .connect(admin) + .mint(await claimSource.getAddress(), linkAmount, ethers.ZeroHash); + + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + await token.tge(); + + await token.connect(claimSource).transfer(aliceAddress, linkAmount); + + expect(await token.queuedLockCount(aliceAddress)).to.equal(0n); + expect(await token.lockCount(aliceAddress)).to.equal(1n); + + const activeLock = await token.locks(aliceAddress, 0); + expect(activeLock.policyId).to.equal(policyId); + expect(activeLock.amount).to.equal(linkAmount); + }); + + it("claim after link fully consumes queued link and adds excess as PENDING", async function () { + const fixture = await loadFixture(deploy); + const { token, admin, alice, claimSource, ccaEnd } = fixture; + const aliceAddress = await alice.getAddress(); + const policyId = await createLinearPolicy(token, admin, "LINK_PENDING", { + vestDuration: 2n * YEAR, + }); + const linkAmount = ethers.parseEther("500"); + const claimAmount = ethers.parseEther("700"); + const pendingPolicyId = await token.PENDING_LOCK_POLICY_ID(); + + await token.connect(admin).linkClaim(aliceAddress, linkAmount, policyId); + await token + .connect(admin) + .mint(await claimSource.getAddress(), claimAmount, ethers.ZeroHash); + + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + await token.tge(); + + await token.connect(claimSource).transfer(aliceAddress, claimAmount); + + expect(await token.queuedLockCount(aliceAddress)).to.equal(0n); + expect(await token.lockCount(aliceAddress)).to.equal(2n); + + const firstLock = await token.locks(aliceAddress, 0); + const secondLock = await token.locks(aliceAddress, 1); + const locksByPolicy = new Map([ + [firstLock.policyId, firstLock.amount], + [secondLock.policyId, secondLock.amount], + ]); + + expect(locksByPolicy.get(policyId)).to.equal(linkAmount); + expect(locksByPolicy.get(pendingPolicyId)).to.equal( + claimAmount - linkAmount, + ); + }); + it("linkClaim reverts with undefined policy", async function () { const { token, admin, alice } = await loadFixture(deploy); await expect( diff --git a/packages/interfold-contracts/test/fixtures/system.ts b/packages/interfold-contracts/test/fixtures/system.ts index b98cc58239..66a0c25935 100644 --- a/packages/interfold-contracts/test/fixtures/system.ts +++ b/packages/interfold-contracts/test/fixtures/system.ts @@ -363,6 +363,8 @@ export async function deployInterfoldSystem( const ccaStart = deployTime + 1000n; // keep Virtual phase during setup const ccaEnd = ccaStart + 7n * 24n * 60n * 60n; // 7-day CCA window const claimSource = ownerAddress; // owner as placeholder claim source + const noMoreLocks = + ccaEnd + 45n * 24n * 60n * 60n + 4n * 365n * 24n * 60n * 60n; const { interfoldToken } = await ignition.deploy(InterfoldTokenModule, { parameters: { InterfoldToken: { @@ -371,6 +373,7 @@ export async function deployInterfoldSystem( ccaEnd, claimSource, bondingRegistry: bondingRegistryAddress, + noMoreLocks, }, }, }); From f3bfd889dae26ea23d98e5720619e1990fa06c74 Mon Sep 17 00:00:00 2001 From: Giacomo Date: Mon, 15 Jun 2026 09:31:43 +0200 Subject: [PATCH 22/24] fix: lock fhe.rs and revs to tags [skip-line-limit] (#1596) --- Cargo.lock | 40 +- Cargo.toml | 8 +- .../crisp_verify_gas.json | 96 ++-- .../integration_summary.json | 132 ++--- .../results_insecure_minimum/report.md | 128 ++--- .../benchmark_run_meta.json | 14 + .../crisp_verify_gas.json | 108 ++++ .../integration_summary.json | 250 +++++++++ .../benchmarks/results_secure_micro/report.md | 216 ++++++++ .../lib/src/configs/insecure/threshold.nr | 2 +- circuits/lib/src/configs/secure/threshold.nr | 2 +- .../actors/threshold_plaintext_aggregator.rs | 27 +- crates/evm/src/domain/interfold_events.rs | 8 +- crates/fhe-params/src/lib.rs | 4 +- crates/fhe-params/src/presets.rs | 53 ++ crates/fhe-params/src/search/bfv.rs | 7 +- .../keyshare/src/actors/threshold_keyshare.rs | 2 +- crates/multithread/src/multithread.rs | 28 +- crates/support/Cargo.lock | 467 +++++++++++----- crates/support/Cargo.toml | 12 +- crates/support/host/Cargo.toml | 2 +- crates/support/host/src/bin/profile_risc0.rs | 12 +- crates/support/methods/guest/Cargo.lock | 503 +++++++++++++++--- crates/support/methods/guest/Cargo.toml | 2 +- crates/support/program/Cargo.toml | 4 +- crates/test-helpers/src/usecase_helpers.rs | 4 +- crates/tests/tests/integration.rs | 18 +- crates/trbfv/src/calculate_decryption_key.rs | 4 +- .../trbfv/src/calculate_decryption_share.rs | 2 +- .../src/calculate_threshold_decryption.rs | 2 +- crates/trbfv/src/gen_esi_sss.rs | 2 +- crates/trbfv/src/gen_pk_share_and_sk_sss.rs | 15 +- crates/trbfv/src/helpers.rs | 4 +- crates/trbfv/src/trbfv_request.rs | 187 ++++++- crates/zk-helpers/src/bin/zk_cli.rs | 1 - .../dkg/share_computation/computation.rs | 7 +- .../circuits/dkg/share_computation/sample.rs | 12 +- .../circuits/dkg/share_decryption/sample.rs | 10 +- .../circuits/dkg/share_encryption/codegen.rs | 2 - .../dkg/share_encryption/computation.rs | 3 - .../circuits/dkg/share_encryption/sample.rs | 14 +- .../decrypted_shares_aggregation/sample.rs | 31 +- .../threshold/pk_generation/computation.rs | 6 +- .../threshold/pk_generation/sample.rs | 14 +- .../threshold/share_decryption/sample.rs | 10 +- .../tests/common/node_fold_witness.rs | 18 +- .../tests/fold_accumulators_e2e_tests.rs | 2 - crates/zk-prover/tests/local_e2e_tests.rs | 2 - examples/CRISP/Cargo.lock | 34 +- examples/CRISP/Cargo.toml | 8 +- examples/CRISP/interfold.config.yaml | 17 +- .../crisp-contracts/deployed_contracts.json | 73 +-- .../bfv_vk_binding/folded_artifacts.json | 8 +- scripts/build-circuits.ts | 12 +- templates/default/Cargo.lock | 26 +- templates/default/Cargo.toml | 4 +- 56 files changed, 2086 insertions(+), 593 deletions(-) create mode 100644 circuits/benchmarks/results_secure_micro/benchmark_run_meta.json create mode 100644 circuits/benchmarks/results_secure_micro/crisp_verify_gas.json create mode 100644 circuits/benchmarks/results_secure_micro/integration_summary.json create mode 100644 circuits/benchmarks/results_secure_micro/report.md diff --git a/Cargo.lock b/Cargo.lock index 59857ff754..9717fc8716 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -925,7 +925,7 @@ dependencies = [ "alloy-rlp", "alloy-serde", "alloy-sol-types", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "serde_json", "serde_with", @@ -4188,7 +4188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4274,8 +4274,8 @@ dependencies = [ [[package]] name = "fhe" -version = "0.2.0" -source = "git+https://github.com/gnosisguild/fhe.rs#55bd90e5c789d08263a31c60635e6bb4d5b8e64c" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "bincode 1.3.3", "doc-comment", @@ -4300,8 +4300,8 @@ dependencies = [ [[package]] name = "fhe-math" -version = "0.2.0" -source = "git+https://github.com/gnosisguild/fhe.rs#55bd90e5c789d08263a31c60635e6bb4d5b8e64c" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "ethnum", "fhe-traits", @@ -4325,16 +4325,16 @@ dependencies = [ [[package]] name = "fhe-traits" -version = "0.1.1" -source = "git+https://github.com/gnosisguild/fhe.rs#55bd90e5c789d08263a31c60635e6bb4d5b8e64c" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "rand 0.9.2", ] [[package]] name = "fhe-util" -version = "0.1.1" -source = "git+https://github.com/gnosisguild/fhe.rs#55bd90e5c789d08263a31c60635e6bb4d5b8e64c" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "num-bigint-dig", "num-traits", @@ -5502,7 +5502,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7292,7 +7292,7 @@ checksum = "9cd31dcfdbbd7431a807ef4df6edd6473228e94d5c805e8cf671227a21bad068" dependencies = [ "anyhow", "clap", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "rand 0.8.5", @@ -7664,8 +7664,8 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ - "heck 0.4.1", - "itertools 0.13.0", + "heck 0.5.0", + "itertools 0.14.0", "log", "multimap", "once_cell", @@ -7684,8 +7684,8 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ - "heck 0.4.1", - "itertools 0.13.0", + "heck 0.5.0", + "itertools 0.14.0", "log", "multimap", "petgraph 0.8.3", @@ -7704,7 +7704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.116", @@ -7717,7 +7717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.116", @@ -8485,7 +8485,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -9365,7 +9365,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 29911bd26b..4f609f1daf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -153,10 +153,10 @@ dirs = "=5.0.1" dialoguer = "=0.11.0" duct = "=1.0.0" eyre = { version = "=0.6.12" } -fhe = { git = "https://github.com/gnosisguild/fhe.rs" } -fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs" } -fhe-math = { git = "https://github.com/gnosisguild/fhe.rs" } -fhe-util = { git = "https://github.com/gnosisguild/fhe.rs" } +fhe = { git = "https://github.com/gnosisguild/fhe.rs", tag = "v0.2.2" } +fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", tag = "v0.2.2" } +fhe-math = { git = "https://github.com/gnosisguild/fhe.rs", tag = "v0.2.2" } +fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", tag = "v0.2.2" } figment = { version = "=0.10.19", features = ["env", "yaml", "test"] } futures = "=0.3.31" futures-util = "=0.3.31" diff --git a/circuits/benchmarks/results_insecure_minimum/crisp_verify_gas.json b/circuits/benchmarks/results_insecure_minimum/crisp_verify_gas.json index b62a8dd8f7..677e7aa880 100644 --- a/circuits/benchmarks/results_insecure_minimum/crisp_verify_gas.json +++ b/circuits/benchmarks/results_insecure_minimum/crisp_verify_gas.json @@ -1,8 +1,8 @@ { "verify_gas": { - "dkg": 3119663, - "user": 2973049, - "dec": 3641167 + "dkg": 3119529, + "user": 2973001, + "dec": 3641094 }, "source": "folded_proof_export_plus_crisp_verify_test", "artifact_sizes_bytes": { @@ -17,14 +17,14 @@ }, "calldata_gas": { "dkg": { - "proof": 170184, + "proof": 170052, "public_inputs": 4992, - "total": 175176 + "total": 175044 }, "dec": { - "proof": 170136, + "proof": 170064, "public_inputs": 17304, - "total": 187440 + "total": 187368 } }, "integration_summary": { @@ -47,56 +47,56 @@ "dkg_fold_attestation_verifier": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", "multithread": { "rayon_threads": 13, "max_simultaneous_rayon_tasks": 13, "cores_available": 14 }, "operation_timings": [ - { "name": "CalculateDecryptionKey", "avg_seconds": 0.004977264, "runs": 3, "total_seconds": 0.014931792 }, - { "name": "CalculateDecryptionShare", "avg_seconds": 0.02192918, "runs": 3, "total_seconds": 0.065787542 }, - { "name": "CalculateThresholdDecryption", "avg_seconds": 0.021156875, "runs": 1, "total_seconds": 0.021156875 }, - { "name": "GenEsiSss", "avg_seconds": 0.006590902, "runs": 3, "total_seconds": 0.019772708 }, - { "name": "GenPkShareAndSkSss", "avg_seconds": 0.013461222, "runs": 3, "total_seconds": 0.040383667 }, - { "name": "NodeDkgFold/c2ab_fold", "avg_seconds": 18.744304166, "runs": 3, "total_seconds": 56.2329125 }, - { "name": "NodeDkgFold/c3a_fold", "avg_seconds": 71.531527444, "runs": 3, "total_seconds": 214.594582333 }, - { "name": "NodeDkgFold/c3ab_fold", "avg_seconds": 7.875492361, "runs": 3, "total_seconds": 23.626477083 }, - { "name": "NodeDkgFold/c3b_fold", "avg_seconds": 72.106292778, "runs": 3, "total_seconds": 216.318878334 }, - { "name": "NodeDkgFold/c4ab_fold", "avg_seconds": 8.018841388, "runs": 3, "total_seconds": 24.056524166 }, - { "name": "NodeDkgFold/node_fold", "avg_seconds": 18.354963041, "runs": 3, "total_seconds": 55.064889125 }, - { "name": "ZkDecryptedSharesAggregation", "avg_seconds": 1.546223542, "runs": 1, "total_seconds": 1.546223542 }, - { "name": "ZkDecryptionAggregation", "avg_seconds": 46.937590542, "runs": 1, "total_seconds": 46.937590542 }, - { "name": "ZkDkgAggregation", "avg_seconds": 5.529830709, "runs": 1, "total_seconds": 5.529830709 }, - { "name": "ZkDkgShareDecryption", "avg_seconds": 1.262524458, "runs": 6, "total_seconds": 7.57514675 }, - { "name": "ZkNodeDkgFold", "avg_seconds": 106.358997153, "runs": 3, "total_seconds": 319.076991459 }, - { "name": "ZkNodesFoldStep", "avg_seconds": 5.945438833, "runs": 2, "total_seconds": 11.890877666 }, - { "name": "ZkPkAggregation", "avg_seconds": 0.489922708, "runs": 1, "total_seconds": 0.489922708 }, - { "name": "ZkPkBfv", "avg_seconds": 0.225607319, "runs": 3, "total_seconds": 0.676821959 }, - { "name": "ZkPkGeneration", "avg_seconds": 3.476952194, "runs": 3, "total_seconds": 10.430856583 }, - { "name": "ZkShareComputation", "avg_seconds": 2.514028367, "runs": 6, "total_seconds": 15.084170207 }, - { "name": "ZkShareEncryption", "avg_seconds": 3.844478227, "runs": 24, "total_seconds": 92.267477458 }, - { "name": "ZkThresholdShareDecryption", "avg_seconds": 3.306334611, "runs": 3, "total_seconds": 9.919003833 }, - { "name": "ZkVerifyShareDecryptionProofs", "avg_seconds": 0.080639583, "runs": 3, "total_seconds": 0.24191875 }, - { "name": "ZkVerifyShareProofs", "avg_seconds": 0.274481449, "runs": 5, "total_seconds": 1.372407249 } + { "name": "CalculateDecryptionKey", "avg_seconds": 0.003846458, "runs": 3, "total_seconds": 0.011539375 }, + { "name": "CalculateDecryptionShare", "avg_seconds": 0.021596236, "runs": 3, "total_seconds": 0.064788708 }, + { "name": "CalculateThresholdDecryption", "avg_seconds": 0.021793167, "runs": 1, "total_seconds": 0.021793167 }, + { "name": "GenEsiSss", "avg_seconds": 0.006900819, "runs": 3, "total_seconds": 0.020702458 }, + { "name": "GenPkShareAndSkSss", "avg_seconds": 0.01082625, "runs": 3, "total_seconds": 0.03247875 }, + { "name": "NodeDkgFold/c2ab_fold", "avg_seconds": 18.129162805, "runs": 3, "total_seconds": 54.387488416 }, + { "name": "NodeDkgFold/c3a_fold", "avg_seconds": 71.186463291, "runs": 3, "total_seconds": 213.559389875 }, + { "name": "NodeDkgFold/c3ab_fold", "avg_seconds": 7.876658916, "runs": 3, "total_seconds": 23.62997675 }, + { "name": "NodeDkgFold/c3b_fold", "avg_seconds": 70.951759472, "runs": 3, "total_seconds": 212.855278417 }, + { "name": "NodeDkgFold/c4ab_fold", "avg_seconds": 7.872269472, "runs": 3, "total_seconds": 23.616808416 }, + { "name": "NodeDkgFold/node_fold", "avg_seconds": 18.285537124, "runs": 3, "total_seconds": 54.856611374 }, + { "name": "ZkDecryptedSharesAggregation", "avg_seconds": 1.53384025, "runs": 1, "total_seconds": 1.53384025 }, + { "name": "ZkDecryptionAggregation", "avg_seconds": 46.886299625, "runs": 1, "total_seconds": 46.886299625 }, + { "name": "ZkDkgAggregation", "avg_seconds": 5.350731209, "runs": 1, "total_seconds": 5.350731209 }, + { "name": "ZkDkgShareDecryption", "avg_seconds": 1.001284562, "runs": 6, "total_seconds": 6.007707375 }, + { "name": "ZkNodeDkgFold", "avg_seconds": 105.400482805, "runs": 3, "total_seconds": 316.201448416 }, + { "name": "ZkNodesFoldStep", "avg_seconds": 5.041216854, "runs": 2, "total_seconds": 10.082433708 }, + { "name": "ZkPkAggregation", "avg_seconds": 0.408154917, "runs": 1, "total_seconds": 0.408154917 }, + { "name": "ZkPkBfv", "avg_seconds": 0.217929541, "runs": 3, "total_seconds": 0.653788625 }, + { "name": "ZkPkGeneration", "avg_seconds": 2.247310694, "runs": 3, "total_seconds": 6.741932083 }, + { "name": "ZkShareComputation", "avg_seconds": 2.368657458, "runs": 6, "total_seconds": 14.211944749 }, + { "name": "ZkShareEncryption", "avg_seconds": 4.003050597, "runs": 24, "total_seconds": 96.073214335 }, + { "name": "ZkThresholdShareDecryption", "avg_seconds": 3.246631902, "runs": 3, "total_seconds": 9.739895708 }, + { "name": "ZkVerifyShareDecryptionProofs", "avg_seconds": 0.082421444, "runs": 3, "total_seconds": 0.247264333 }, + { "name": "ZkVerifyShareProofs", "avg_seconds": 0.253021858, "runs": 5, "total_seconds": 1.265109292 } ], - "operation_timings_total_seconds": 1113.09553554, + "operation_timings_total_seconds": 1098.460620331, "operation_timings_metric": "tracked_job_wall", "phase_timings": [ { "label": "Starting trbfv actor test", "seconds": 0e-9, "metric": "wall_clock" }, - { "label": "Setup completed", "seconds": 0.998652792, "metric": "wall_clock" }, - { "label": "Committee Setup Completed", "seconds": 7.02109625, "metric": "wall_clock" }, - { "label": "Committee Finalization Complete", "seconds": 0.003099875, "metric": "wall_clock" }, - { "label": "Aggregator P2: PkAggregation pending -> PublicKeyAggregated (wall)", "seconds": 121.265344, "metric": "wall_clock" }, - { "label": "ThresholdShares -> PublicKeyAggregated", "seconds": 133.793474083, "metric": "wall_clock" }, - { "label": "E3Request -> PublicKeyAggregated", "seconds": 134.299480125, "metric": "wall_clock" }, - { "label": "Application CT Gen", "seconds": 0.009636541, "metric": "wall_clock" }, - { "label": "Running FHE Application", "seconds": 0.000061208, "metric": "wall_clock" }, - { "label": "Aggregator P4: Aggregation pending -> PlaintextAggregated (wall)", "seconds": 48.497227, "metric": "wall_clock" }, - { "label": "Ciphertext published -> PlaintextAggregated", "seconds": 52.0973055, "metric": "wall_clock" }, - { "label": "Entire Test", "seconds": 194.427969, "metric": "wall_clock" } + { "label": "Setup completed", "seconds": 0.9476265, "metric": "wall_clock" }, + { "label": "Committee Setup Completed", "seconds": 7.02067775, "metric": "wall_clock" }, + { "label": "Committee Finalization Complete", "seconds": 0.003086, "metric": "wall_clock" }, + { "label": "Aggregator P2: PkAggregation pending -> PublicKeyAggregated (wall)", "seconds": 120.643449, "metric": "wall_clock" }, + { "label": "ThresholdShares -> PublicKeyAggregated", "seconds": 132.69507875, "metric": "wall_clock" }, + { "label": "E3Request -> PublicKeyAggregated", "seconds": 133.201828291, "metric": "wall_clock" }, + { "label": "Application CT Gen", "seconds": 0.009273208, "metric": "wall_clock" }, + { "label": "Running FHE Application", "seconds": 0.000058375, "metric": "wall_clock" }, + { "label": "Aggregator P4: Aggregation pending -> PlaintextAggregated (wall)", "seconds": 48.434143, "metric": "wall_clock" }, + { "label": "Ciphertext published -> PlaintextAggregated", "seconds": 51.995006459, "metric": "wall_clock" }, + { "label": "Entire Test", "seconds": 193.176080792, "metric": "wall_clock" } ], "folded_artifacts": { "dkg_aggregator": { - "proof_hex": "0x00000000000000000000000000000000000000000000000d40b640292037485500000000000000000000000000000000000000000000000e013a55061005862600000000000000000000000000000000000000000000000df66475045cf2145500000000000000000000000000000000000000000000000000006b402cd405e80000000000000000000000000000000000000000000000088dac7863b055694300000000000000000000000000000000000000000000000712fb7da233c478dd000000000000000000000000000000000000000000000003cf964c70e07b8765000000000000000000000000000000000000000000000000000038ac24735e980000000000000000000000000000000000000000000000064a66aa4d3608d2760000000000000000000000000000000000000000000000098fcfbe919172471b00000000000000000000000000000000000000000000000290ed9dd97921e215000000000000000000000000000000000000000000000000000302c7ebbb9138000000000000000000000000000000000000000000000004012de0ed8142cc580000000000000000000000000000000000000000000000087216e554f3ebfad8000000000000000000000000000000000000000000000003baa67f4d46f478d70000000000000000000000000000000000000000000000000001d50ea7c7c287165b7fede784cb79cadf6580912b2988fdeb42ce54e7f391bf679ab9a66ece7821eab249944824928fd0c02d5646c093a7cc8b002698234c635954b0eb13edd0079d8707715ffced9b59ead2d528fc6e9b0ecffec6728b8530f803d3d0cafc7a043f574f9b17558991193e7548639d892743044078e9b0c5a0597a413bcfcb662fe795a27f2c1b77b011fdc4eb28e4183f5862815d85ebaec975e8f9e07ef18e0afea638c43868fa1e25fa97a99eeb6d0437270d3e994bed93e1820008a99a7e27b4edc391188be914d9d9259d25828b5f4e971460619ae6708932b2cf0e602410899e95a26ea70acc3ac7ea8363c2d73149c06e14256363bf0e19751231e8ca1751822545b6a5994393b7a1d33419ef3af71f250e6aab81194b76a5c7de943b231b103523d9d9b07e0b8f9d4e9db56a08adabbf9b17d9c1e0945a3941379ce015c0fae30c484973db6068c9681f3d57d6ebd5a820169f6cc40e6e761c7d05dc09e051a03df59398cfb89e32b5f2adfa0e344a33e660fff996b08c89ceaf4f1a11c0530117285014e4b70f37e7f66dbee9061b6d1490044a1db6478ab1c83bc629c523f14f226267f1035b6b0ae5f7dd80875f6816d8121fafa448908b708625184955bcbae4cdce272bddb97dcf930b9b27a7d9fb866ed7a3bd86e883488e49014a2a0fbba849da7992682d55da80ee20d9d4b2c025867a7590e56d934557470b038824f651dce95aac46d204582a7fb23e3959539d525567952549f574fed80a0d06549577332ba9666fba17ec302303c4864bfe30a81ef5c0b5e7b5b929e91d4cdf3c912da2aa921c0a317c80283621b9342647a6e1e0d6d6371815803f42034cddf41d88ef437097c0cad537128ba24646d6ee133b0c347cdf22033413ee2e7b545f475e7e33ceb8d1273dcef965264c7101d1fcd72d8c6d80250ee190e516c20d3fd17ba136f1dcbf7f19b1538fa9476ce8a90527e41f8fac711649571a1a10fc31232a3e6fa123ca98694e2b5ba6cbef46915b98782f24070c1965c0900bcbfbadfcde3bbf41a763dcfc1b7ba366bd2ceb94456e642f244efc2033e7bb12f955d116cf1c177c29e80d544e0cccc36f5ab38418466b94191f0387c267ea2427805f1c283fc3b6abd9144308a78fccd63e0a3c6725756efe35ecc626c81309771693eefb56d05f71ea85cd632f17428e1b09c0fae64a3dd6126006462d9809beee4db34fe70bb3c4897ad267428750547cde8edcfee3a1ae1584588ab8e71b396e34821866574d4a120cb1300142d1bfc4ec060a2cf216399237c805164a11ec60e2dbad9cfec202e139a9a37ec628d1997562537c47a6e1c69b36ab12521cfa79c60ea974914538bd4b327171e2754d2fa4a1aba05cdea506ddeb8b0bc80d78d4ffa01927f4305743a2f3f3f1dad7cb96652482fa914ad41acbb2abce5e2e0e826c01a4c6adfccadb2088c9f41d04c2b68a793fca0efe33704cc6900f2416085fa06bce76dce6e76311ca08d5dd3226d78140d209eb32aef0d2fcf951ab0748f907fa2aab94dab8f052177f047f764caef711a635a188d82ead0b45e5a82745a02e9622dc0156f5569246c9142a879cbd5aea7cb5e6b26e88cf44b8ea4929807da246855d55d9e68039f3e4b7274683e67007b1ada2cf02b4a51e0c73a208d43d23f6ac91256cff142f9e6437c076b75efcb7d578a363475ca2b5772ffd1a1d9fb8353f4af4d0ae15a2f9d8320c2a1ce7a5ba4cfb9e885320cc5c11a49415a335cb2ae9e28346ae2d697c54c2afdb1bc813569566972df5d93d551570722f3379a24283d123d57a240382b10b5be5e33a4e49201d85200f6637cf593cd103cd4f8691b0fcff9b1f9a740279b56046f264b121aa0467d0640f481171d43f0468368a2e55a0d9a1cd5c035bf09c799b5931d285be5197796e0cd594ccee8b1838548852640231d0555f4218b9672e4d01d50e52840f0f18a48347036fd11d2692b21503337f42b31ae1aa00881af1ba79a4c4422fa20632a00395363851fd0e2c67f59c285d1eab34d4575a76ebc77d433681712e196684a0acaf2fddc4401b0bffbc1b8590c88682ee0cefe9a3c5dda489d01a2ae0183613b5bd9a1cc3e0297b9311850ff676074f55e28e05bc2c551d35ae3c4e95fe802ccb3ca84d3f231de83444928b37de079b260aa497ced448587529431fdfd7d395f70e57baa6b7283413219283c15b14be525706433e162e7f30aa177b7078d6da2ee21fa67adb15375bf599c207e38cce91d7dc0766d9224bcc32d863ced1ebc3420250c8b0142d4d32263331acb3b04381c1402381cd86f5a76cbf436e38e80432e516b4c86d2320a1aede18540f87051948680ee477668d8907a767be0bf4a1abd0af570af80ace408a697a201104e11e9167b153de258e305f0423979dc37be9e8be4d01511b0e89eb67264a8e65676a124682a356242f003d408808b34ebe19c752eacfaf140fa44e370451401924a02f97fb647fdbf9d76a6bfcd8a5f058bf9e179511d700f29320a7a6c2aa45ceb9b49e3ef68ac8863664e1d40b4f3805c0cf94ee859d12e0e9940ab596361cc766ea553dc2a6b924c7024aba394ecd8797f99ee41d7629cac872ffdc4d14225337da53af492a42d04a4892be7f3103ec1cd701296b842fc852842ea02c8868bff79d7bb5b8be42205124523f619dcc17505bdf7ba15903bb0cd9f3a5df2bf5b9a02f252385920c78387cc9182768a4adbb2e74de3bca2cf084826f4709c1b11d287de4814eb4e83198a587ec88db94f3cdae7e3169cf2328a74c89d801f5cb61de8c859f7805d8b7e2520ac13392e59784bda06b4bae1d22fc67dab449c57e4d857fdba747abde5e7da7a24ba2bcaa0ea10a69f4e4430c7d6c09fe679c525600f3ce2e1c4060cb28983b4832333d01e058eb4aaf7f7f12bdf214205aa6dfe8b4c6b5b61262b401a054de68bdf874d45992e358c4e37815a1ce0645cc90b1cad6f45e2565f6c90088960aa98069d9c4b7bff92fdb958111e534100bbc41390f4555362ce7ab9825eb22fbd4d9ea4db0bd3795318b12cd2de1c96d38d0baa60761771d3def691a0043a38dca228adb6736d9ac55e34c56044db1574ce259d7c6744cb87af4cf9fffa8fff02e3b4d75791f6d5755231db30e905f55b313af101d2065b895b085c69cd65d1523e27a35cec132932bf0937416b4bdb3f357117644e2351937ca6d05e5a9e840d5441c36f09aee5e0dc9618a231856bc58b7020d0cf923040d45535c8f7d9c670ee85c5266f61f55031acf4e12edc91db549bff6b8f3f650bda9d77627b3af9de1f9d5de6db8e998e87a8f672c2caad5095fec179a1cb7ddabc27a9a75f33327fb01e1242c779b918e227b0a23b5b1503b12a6729f9f870dde75836c097762b08f6615ea824eb2acbccbde070a2f64b56862599eade7ae5468a6312d27b79f6c6389295957d3df159bbaac760b8f0fd8722e85e9b9659aeec9b577439cf36618c680877d3870870f70d4d4251ba7bfc4fca6eb847eaa71f5144544854fd6c399fc2d18b5a5c05405bd2f179e0a889578e0c297db2845932bab911e76468fe4559ab0d6221900fd1434af038c262ad5104cc0cfc6d02b1d8f45374a84a9acee52382810369069e1db871bf7fd1e313c099d46bbbf8a3c4a3efcff8fef4915523311935bc3f166564a783cf1fa30130605b57e2803c7e3ac5aa373dec14b3166e6ab7eb4cc8fbed26b702651512052687a086447f5ddde111e16e5dbe1588719ba88ff3684266e92b1cbd00488161d933bd2a4077cbc73ed7a7f0f1d6d01caea02ead4928d6aed867d17c523a41059fa178a3ae010ea37c1261bda560576335bc48dbe1ca12c65003f5cc24e0b2082d8d67c62d8377bedab3fc4b4445dbb336fa0c043afc11477698ffc4465512d05d2bcd5f030bd1b84c7a10f6c5be35933f28bbe513ab3f18a43c740bf811d2dfea2e21a0b3865b720418ab4c64b5d6625c6dc156b428f8e7a25119a37da8d27b1133ca9c0f8b66a66e604e2a89b7f70e50f11335e40cee8aa649ec8be12b905bf8c5739e2425205ad8ace017aa76756a2da6eaf35a47cd1bd7c6540c3cc6d1407d2345892f5e448bf45af7377d229ac597ffff7ff43e0c39cbb5a97731b5b298f63adcad80ff024cc4dadb60ce5ccbc03378be66ba7ab7332d39149adfc2a0a9af606c4784cae59eeeaea3a96ced73a1afe40fcb6513e12a9f0239f19197d26ecc525c7858b3ad57349926e4fb4fc745085b39d5205928027f7e5585f195421bd609e595ba99bb70de80abc57d275a5149f283cfdf462aba7bcdc98d02bbf2644c7f498f3683dd927694e1a02b9827f76a645da15bbafce0d9b0294a2a8f3282f545d03147683a5ee21fb76398d2c66dd450182a1a1055bab5241ba157da11a8ff6cacefbed1f2ec7f6f3a5ef29c9003ec1f3a395e4fd17d552f644342f3d2713dbbbd0f81a7980f6b720e647ce0619dd78435170599c44f52877366b466f2051dd759ae7fd27d1c9941a2328fa859f62a4101561d4df222da9f20249531101855c4063f695155e6eae20e182d92843b3e208f5d950caffa49754668809862278fb0b6bb20bb911fca9f314edfe6049c679587320349065c0431d981302c41e888c3662240d7810ee0e9006ce4b725061666873a7f36e193e986933149db6261a52a383a620448302a5e8062a0e99e37e720564425bf514bbc74257c0151811ea75f770e26d75919fa3f3850bbcc9af993fba980871885dad8014bd61de782ebbeae1be2dba9c6c5faaae73761d0a23ddf30a3726a76e29a392cf311fb5cd18f7405b78f060da2f7667ccd91af53a7f6008404b1b1d292215a365741dc8bb220db90cb27a42ce95278a660fb5c02174e8fe4b872146592b4b1a6abf6dad0d23f9d408ca96df0b12024bba35f2f956942d1aa68daed91d2d9b7ba5b811b42d2d5997044e48dc3c6b7d7e84538fd19d1d6c6f07a3f6386e5d07b7554c6330d00993fe72cc27fdc482bde73aee8aad8a0c1a674fe9c80dc07f8142ed8747ffda15779aa83fa9cbead07b5ca8f34afa08f006defb94fbe26492f107574badfc081317a22554ad10ca7a09223b3a9c8440fe7eb8b3381989c7db31f9f1f1c6d0b305d3be09042a5f7212de5d680e2d91058d8df4de8107950e85d30735fca633d4278578f6535baca22e67d9e5a1dafae38c21918dcb8bdaa04896c176030cc934255e8a84baf084d8f826c9abc4b936b1920d518e214404443003bd108180d3de236e4e31063fbbe63c15fd875e3e8c51e1c0daa7329603851b07ef6416e6fd790f19b2370ce15cec25bb2e58cb9229a4eb68872ad054bbc092fe8347201518f4280eac24ec6eabcf3210f3892001e86abdbb7b012e15c1e539ef3a2a8d41a7a1112fc5e20a89fe3faf913be190acad0da906ecf689d2a414e1eeba10873b110d14cc312bcde9f4332a857b9e9bc12b0e5d1bf92803f516b51d47ee269af04ab11c7053ed6cea41d55c88d460a83eafc560484d1cdbcc3c98382ddbc9de7952e224cd829081f01b29e12af540a810162208c51a9bf9f812489259e47881ea9d871f2f77540287e3a52de99b651590547d45c89fc26e339abf13263863755f732b112416cd80f64c3b7f6fcad8b1f9874e93ed7375f004139a9fb9e6bfa2078f661c11f3f73c844fb43525e0c1fc428b2b89aea4a65c36fef3ad29e3e5ebd4983f1cf2e58ccd40d5f1bcea898e59b3e9ae31d5d16ff3c454d8ad9938f54b0bc35f2f169dc6a400cc793bbde44fd0dcf3c957fdf8605b1850140a0b48bd0766e7421dcdad2ae3f917d125a0fb0f843f6b62d0f046d37911259e3e9a3745138e7ca629ee29e287c714e3837294849a47c42f20e6ac74b07940e7d42ba6e7711764672bf5e0ced486385881661cdf7fc0bad18b573785eda1dffd8cea52e1706c02be09486b233f886885c4a20ce9c592cf44fa1b389af571ad7c95e72f8bff07d43e176bdfa018c7f722c9c860c70246325b6922b4ff045828e0b553fb4bebf827601fcee22dac4cf38fb87b6fb13e316b9d007be667f7882456e806ca49479028062c6960159f49bde5e5211dd8fd000713489cac20b1b9bdfad44fc9292edf11b4186796d5c2455297c9bf3aefdf41b169dced5398cdfaf6c71b1422c8374ba40912d19a6b6765ca3457101caa863d21911edf345b42ce8610b4ea4bb1184943d51c6d8f5ffd5a8ee22a980b1ba379be11be1b35dc366d4f646c9e995dda08bfb52457854d4ef3628598fda31c9c71b513fc50b0f52c5607d16d4464ab57fad4ed022289af86c813747386ec1b19bb900cdb368c862a851541b3480d9d445783ea0419adb37416d3fdf66906112952bcf5ce14dc0afe874d3a7d9f3eed3a1dc9fb0a881b3d22c1b9537c0d54448b252e02aa873d7cce22a8075eff0cdf83b1977f1c335852560b8c49ef9a3ae1d38798b17e81e77d2809bd55cb7e27556ab8d27d2c7507f1e664680ce460bd6e8b19dca256ade4d65eba1d75200307a97785a9fc20c84d83608551ae6a6d5af2172b04a9560e47fd90987bb14890ab3fe786607c17ed5213a37d661dafe7de8f107363355c1de7ffc6f622ce398ba845f127233b2c120c8386b86e4f49ec6ec694bdaa95b8141681393d1998db9da81e082a18422adbc9ba48606955f943c98ae633f846c1a86a226f5b12bb59eaf43f432e3be6110df3d8bd26a0ff66f09a3c63a69309bdc3cabf2ae71e7a8b70ba7676b8a1a70dfd6de933dfae3fab2d67c3282d8f3c5067b477e46c80372778cf190710803b2e951f3369ebaea902d4559485ddcaa647d17d4083e017af5390ca90b32066472bc8fa47822c331caea9d551c96fc99eb9c15dacd032dd11896eeff9de47e88a0a9a74583ea6e0f2364c38120a9b4ccb6bf1e776a41e9d79ca097d0fb95160bb093ff7978d2ac276d93a1cd8e35e8e65fda7d5d1f1aa0eeddcc20b3777247bf811174b750b8b36c0de073e3393fe0a8cd71d9367bdf9b431b9aa898cd1015435101ec5802f0d608f8b6e73730eaaf9237bbbe9cc2e627a1e3d6bb012913333410697506a77dbf86f258fe7626738c00770f395ee096f01d5d6c61ff310684ecb16e972eb20234f959deae2bd77a43bf0d2e77a4eca3a27dfd1fd3a6d26d3dc4406dcf993aac3f0dd29bd64107f4479eec6def086d0d13e3ef04be1522be6afa0166311e191055e0b47317ebdb1c715560ac3573a0a484380cd1aa884017331a32ec9542cd657a6db0a573bd14989837ba91ce99f7da2bcd5595d2883cfcd5dc82add6e730ba39e41aa4182aab9251b6124f88e5042521d47a9f0e74adf5d0af0219514b2b36879cec67d23d7856ef85d93b0aaeac5a4e0a40ad0e70c848b737117a083dd9a5056c402a8aa4df6e636bdde0afc2108815ac3c865eabc49b088a60889d3c65f322d68ac11b26a5046d9a7fe3d229fca7614e2dfd6f38466834fa909370753af10e464a8fc648e03249895b862d524f8af9f68b37de6125e6e75c30ce4eb722604dac1b1b4d7564c11d5e4735be16cbfa53ab4d0db0def3373ed690899de270812ce3b89359eb92962b32467246846ad9f55ea695d1b18e2735fc7210aee7a94555795d267f76802117cf04507137b03fea99f0e760f9ed5518a752cd5efce3c1d40b3513af08d8c24e0ffe15d9688c12e781bb8bdcf837f5811f30a91fecab860171942d310a512ff8099ca5429a40cdd18c2008b353b3b70e74924cb1183c9de09b0629eab79557b9812e5570ad31cb1aa66c4cc89845500de602a1ab9a697d09da65c89faef7840004d4cac64c321f7ef83ccdbae89eb6bc4471d70d7698d0e5e435bb5d2f7db6061bb080e1722acd4d3ce37f25816a36cce60293fa06fcb41ec2cdcdea76d0148ee6419ab7c56ef432ebf0b3cd767ac5ef0e8294be4b435c739565dde491b7eeef6a2dafcb9345d4fa07d8c6afd28afbe44fd2651777f1a5f3a78efe4e0709870a58e5e4f33e0e3743e3d671d0c6e1e313f961ca9cff93e4f8a2278448ca0d7b41fd17855319a24b59f1f3c4fe3212e97c0eb11dccc2026f1d50380c36b70334ade800d04a6448ab539c21fccf068b642085505bac178f565cce415d7a074390bd16c37b9a28049e8d112ed1a3c32ac30d15f1272bda3091e1dc8b51b3b613af57f290703c505b36fcecc5a066ef2abff4c1f2b4475402aad821572534988b8c05b3f097aae8e82f9fdd316a5a7016b859b6202c541f40ac7edbda0b6b1acdf65b761b91a66ddd29417c8aeedfc816203b9032c35e6a0276854b679e1ddc47b41a21b6d71399a61fcf51f097c08f92cc12c291fd2dc48ccb0de0be133b3fda6c6a9bc04ece8a8ad02229fe99ad035a28189d82f35fd133455d9066b102b6748b9d4c2bd8eeb6e371e1ffbdf4b1a6c29a3a0af020e5447d7bd790db03e89fdb9d262547862e6377a32ac02e0b91d2ad44b992f1d7db08caa3656fdbc5f430448877092ede59bd105068b6d8a8bd142a70894ab2b56cfd0292fea152bcbb1adbb117ef968dd60c1004511229e2180c98abe417d11cdb19d8b157a54ff930a2466a77ca6ddbc6f2398f4e493e6b7c097e6744a9915147beb465870e736dbd8ade86a5e04a3db47f24ba6da3f5654560976c5b17108da7ecba21da62f07243db9ecc7a29fb0eb2d28cac24ef4dde738262eb344fb1f5a778138d3239d82f1471d79ee588cae6334c52bedafbb4bcebe375af3054409b9dd53eedad4de84465c599e3a1eda867e61cf569a91af0a8e2565cc51077803e8ce6de0303602e178620ebcf84f3db7a24812d0a91941d231c354efbb7e0019e71ff8d261ff37e057cca2d17ac2e5349433957d92482a268fec90364e0615020792754f75f88e1751288ed857ab891e46a9cac9359c0cf5cfef219d8ab0541f2914a2a6aa019aa28621273067f5902de9d27e6c1ef74bd7f804723359edd022cee65b9c003bbcc30b6f6356d6ecad3965e1ad4f6000b85e7f7e32dfc1bc6b189d93272340775e676389912e49f9af4a10084d8a75135d9adfa17355b90e921b85717e5f723f1b3e8bb9a715c4b4117ec1b26d98f049bb0f542f3afde7fbf724984c7fa83dfcb6776413479e3b15379d5a60abb3db4d713d711ea444882fac1c273f60643cf18588dbf67a37c04ca5e4aeb4a5181ee696e48f33a1b1b8cb231686d6b58eabc9155b6f7bc8b337db2e6646cd305e0f95fd127432f2f67e2af026534963befe4d398caaba921a4985a51a714de7df682f049be356ea06a0062f02ba1a5ff63a970e15746b52bced960e42fb975d1f0f245904f307a07721416e0deb72a9420179cf205754ae147ac0c1d83483d7ff9db4f5dd528b409be937e929dc59c6cc9c2507616c1f1b74d6c1837a325055f480d05403e240d9ac68229f27d3a7cd095d405e8a241b818fd860461e32a183842be658cadd13e7253b2f0323bda80349145a1337244b58a19beb039480ef9b5c8869dc1778e06fda4485542954c7673c0dd74ed185afd0ed95f3b2878758b138241d848bd63acf9114065f2e38db9edc0503bb0b8ee56c651216441b7880053ff22c93412eb6627b5146ff164934d8690c715f3ee9f3a860be67a8349f7edd84d1704cbfc07a784b106dfe29d070ad43834c309a93d749b52d2ae801841ea7e5783c16ba497a8060617c2a2815f8e1c7a96a419ecb11e1559121be988da7a6c27bc26e65e165e56162a1fe142512dca4b4e59a540c6fecc77b6a7381910399d96d6899824253ea7a106f9c10a475b11f52fac6ad0ab92131cb7648ad3531ac236f8eeb42daada27871af6b06d02e6fdbed16a2ab65c1cb726c47497f235fc1f4aa4c6bd712150c6fb7ee81098cb22f4320849d701bb99aad74afa8ef395d7b105eb62bb55a9ec69baf15c30abb195977557b71b92ddd465370d9a5a4b75ce32fb6595177329fda5ab42df123f412d24f70a495a3c86c1731ef55c38b14352ef2579b6fe98fb4da693fe7c10452aea04254fd8833605ca54874b751da26650517ea0821a196ac7b66e10bb604d8ad90129bd940461a0c2060f4799e9bd70d159594a704d171dff7587df5161036e9669db09c1cfa469ea776b520c34e383b814c8df6fdbb0780fbc272e5e8032be0857f253f2253b5aa9445a0ce0e517d3599d810dc9733ee4828d091282300917a658e9a574d6d20743c899c8885d8cc7b23ae994960341872283a9d4da5081a1ecee62447a303627a1969e9ae73b2a1a7688d8541416f49f96c975d9cbf06df0ce599d84803e8b126fa6fb9696fc9099fb4341ee10c6011506f796e5f52286a516f77ecbaeb27c9fc0cb693fa24e06cac55b985d62ba53561233d1ad53a2d543782a2f4eb70bb7ce83b05a9e062077857b2c7749e3dc1007e845d15f8be24eb493d1148b440e56c0841f8e2d3c6a442c8f0175e07b2c9cca522a7e3dde3105ca00de87c294517b084a1780f9630dd2f114d36f149c02dc92693c5f58df80d052c005ace253ed194b58606b5cd08ea81e853f09d4ccf57d9fae5aadab68a00d6a792c01cebce5c049bcad0170884dbbe0121ac65a43340a0547a124f13d3186a6406fecf35e4e2b64fc1782ddbba140ca62ca9f42c59a607425dbc365378095e4f7019e382f9195ac59abd6857740c46f847a82d50943fd2cc60564283bb20452f1bc58eb3f4859f77acdf67a7be589907845303cd316cda7f8c37de528d1a6e7fb3e3aded9188bd2379fbfee5e0a5d13456ceafbf17f81064c5e3d7511700feff703430e2c652d235f3938cbba7bb05499f7de8c3ae607468d69e443756245462ae438bebb5529578130a6e20ae725bfd8d11858d6a60404f709d6080950583427472332603fd017fafc7c088c316e336c7044c79731a335b2020948c341cf08823d04902110e4f9ea8abd1f46bb50a0afd1e4462899e69bd6ff5d289fa13a052c61f50ae765b9271f5b109929a59d27720683335829e7e3e05e01f8066213f99cf2b030903f5ff09e48bdbb5d916158df9e50f99727076a645fc515e0c174fe49fb24e33c79c49be6fc3774520812b72c5b31cacbbc295e35d1813da752020ca170e31fc3f8c34a4e62a4edf4959ac76c316f54f30ae118bbee4d1c8c01c65e82dec54f4702d8754f7c8c05c58b066f1fa3492406da04e163fcc17affc1f1da2b6753203348de9d462598624dd2ccd4078b699b51bd4e6f28a57ab86011157f4acbd8f16ad42211481ee64e4c83b92d989f94e8bebf7db23304fe512e12002529ed325dcff472c2c80c5b189141053804b55f8a7696657b34cce04b7371fec396ef59641d03d83aa12cc633eadf7281f591e70c1479d480c448f1f2e372839151cd1a94da661d1f15104e2c22cc1292adbf0bca846fe2d4103a7b0b32522ce9997385b48cf38fee7766c8a8936a7adf660a6dcd2153bb5da514fe7b5b0144abcb0142860d4db639824d35745387e8a8e95ae5b0beb30a13fae28b7c48022e2583b07d03788848ef1f114f04834785e6304e2b062c352cb5d4d12cb331421f00f710a669aba85299882a43506c74b318f518ea9f0e14ff3b79511669b4a21ade3543dbd159a5e9514e42d02ff723c6717c8e0d1dc96b55ea6e4972aa98d2fce3c1907bc15c676937cbf32f478c66160dcdbb1fc4077d713c1f0ad4aa47214f5588a06d6991250f5d93342e2d84c50fb310d8834187b9e83a5e3e230a1b31353704f758a730bd6446aa9d1419ad87abd6326cb6db4db5fc7e0943b111b4b23fa577ce023391f673542e44ff057655b5bba09f3b5486cba48d90d7a89a8800cc4f250d9ed664fba635986fa2ddbce7063c11d5534abfa1918e527be32c77f1d506d3b821e512bed75c3831880b3315137536487ecef20c2b64e0f7b1064810a5a0f74d19b57d8409effc46d5acd083fa4f1d861c57e634f50750ad0f574981b32aacd0f657d61d28bdce7d06b4cebdcb8580a289e992df85f2ebdefcbfcef16e104ab6be6c95cb6e32dd0e6711b49186ec15cf3bed20a8c84d775031f79a02b145b9c08303b069ba0cd302c60da6cd74bcb04024f29855f2d34c1f6aedfb50ac040f7ad915bba39dea3ed4ab1042cb994c214740267adf97a299f35e597b000ffe05aff6d762e31a3cd90378c7c075982797e869c57e4dfe7702fbc5f74341b40a9287c35b2fa1f735de08204bc3ef84bd3df1c66b28f38300108ca8bdcf72c3ed1d7905044f754fea08756d150f6ac88bbab31228e40e0eba859fc68c8e8118b71069af14fb780bc05991194886472d0a44bba06024e5754763fa1e261941ee35cc2775bd35a66c65bebf7c5e31f15b9d5a7da70e7af7b977e057c8fde371dc6046aa71c1c14e2de124421be04c8111172efc4ef9dd4acc571c10ac5076c0a249d87a01a2e8888d6994aedd31ae0966558438d2f3ba0de1d29a427b14487127be3c66f1d3b1cbb945544767d4e95d56972fe5d7ba34b03a4f92ea92783932d128c1c4d0d056abc8f38c50eec5ed87798a594c50e6747bc9f308d9ab9baaa052f112758734b371e6dbcc2dc8aa2e60bcad9d001dce3d02de7b408c977c30b0fd3c6d530597a9c5f2b646a2b0f6f37e837ee8df3ce4d643e9048cc75bb6f6f2c76506e23f035f3f75d43d1a1b626f0f241151fee627294fac95006070eb967150fbce7e1411849762d1a55e4530301bd14b0976102f04b901b65d450ad4325289bc1c0b27140ec24f729a3950b242fb4aaa303cf377a8c4c0ef81407dcf3112b28ddd33f3dff292eaaf6ef3b1ae0743ebeae5c5fbb7780effcddebcd31209215c7b6bb9f5a53b98086afdfd7a20d791a37f8eb10fdfa552df4c019a929516b19b4fe75d6da7838e2afd82feb5c5cd6f46633b6d607eb9c08b032eab501565c2659d526e90a56a650b568bac4d8848bb16379677dc85e9faf844210ec753cae13762ab84e11541047f1536dbd56fd71c6fbc0575c1a2abc646db9b8268ffba416a5a26909f67da15e1772ea1bf516fe534c779afc8da5484b3caa5f0660381926fdb69d5d7e284f884c0a639c09a13356e417673c80cf5ecb6af911c7d58c9a1869705d7086dc7e897e1658403e7ec186d036d7e08f02ddc2be124b33ca9ab905c84e0f126e1651af373d3f02500262eceb24081a8d297ea3543cd9dce1f570233e05b9e8f747717545fc56b11bc2b6f6480802277fbd402bac8fcdbe2e715b206abcd7b45ec5b4afcb43da465d114785782efab654df1d44d8edcae8224b1407610ea653938dec4ac4bf3be2f5f78924a7132e9ce06f70e63f39cda0d37c1d06c1b21358eab23523f5393f9f0fea21dc073d27c99eaf5b22e2067186965e6e1e9a9fd6a6ec83802c06e8ede9abd8980a1cd8ba99e40cbccbe7337513b3e6c81df310405a333252c912859253613a95c74fa76a361b8553e661b51697e1f6910d6a7b89595b193ff7ed1ddb2edc081e3be310f357643b69f069c9109451e2ae3017ae984a8ff7467a7d9eb78b2c57a9485426fb5fc7bc9c356720288c21c8430ab70bd587e8a66af28e589b2d52a29b4d3e3608844d437c0ecacea2fb54837a0b5311cd793005cf078a8dbefdeceb82037176b5b0f714419b179066fc42e85e19dbd2cb19c94e9b33dde9624447c9aa6703d5a32ece2d36959b0658edac44dd0b7cfad9772bd459d3f9584775c68eaa3753bd3f0b62aab4364b122577484a41070e78095d771b31324ef881729d127e022f1725d3aa3be809fdcc84e29bffc2201ca5fc4ba88c7292dd57b294440ac99ef5bba25b677ad94aa490cea9a1bf1b00fb2751cf6492cc61e9bd683fe4173eae9c04317398446b11ceb90d83054f4a1970de0a0ccac63218601afcfdf9a69f49fbb6f0ffea39e15d515d33eb369a6f0edc03905488fba74cb5d3c428cd5347b6d9b0d3d02a7fdaa0bfca6cc97b164403cb6267ecadaae9efc05ab3992a2d58830fc5fd32bba5afc4500d21464925b50844113b0ad71d0781c62154ed6e9367915fa70ea40c8e061f1045473d4af5160f5180b4acd4cc9f68735ee48bbed6c4e572b3a77c9b9c82cf85624263c74cc80453074af8b24b850eabee47badd6bee6e5d3873ea26312eca837552ec8d3c042d9bc2c72c9f5956c9b845e3ba5b4901503f5dcc4cac00ec530cfc0036ef92be11db406234f53d6ca59aa9dbcf35d38a79da93b6ca7699e1dde4b2e1a37949051efc64042770efaa8d6420f52712533bee9e928a52b365025fe30d9f78375db21f5dbbddc49127312827bfb00018ac0e2d5786f59d588e7a47f0c52470080cf309859432f45648ac3766443cc411877e0c97667fc1b035ae0891c9de98bbf4e01971c3031b317d0382cfafd11bcaf151825bc8a1823edf5f9e59ffe612fec75f1dfba34c8b1e6ee61b09b85644b958d8104131775438292349be8a08b6937fb81089089fa26c5ea6ccae00ffd749e739852c949702ef72442605f3e73feb09161487ebd701dd0c58b7ea24dd0c18293b1cd43f0a9c42011cf71d3be8f57084390d70475bb7b72bdfa2c703ac5e7f1a80f56e0c0f817e4f07db8585b5aa84b43c22b359b6b6e8d0646dbc3d528b6fa19a3f802c9e16f3ad10b04c4b39458bc1710afe1ea5173379a6887702810b0b972218a3c47e836ccc261073d0ab07592cca1668ea695ef8d4a62eb1c7bd122bd0dadb5bc95313851cfe097945ff086cc532", - "public_inputs_hex": "0x25832c071d6d99ad42c7b885e48b10100726dc0de03c0a779b282c519ccf538a25fc312a5f0009edd084811b739bcbd529987df1cea36f5a245153866625739600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000007ec37a8a654c78ed3a8e7c4d6e933fa600000000000000000000000000000000520f0c28dc3d0e79e73f3f0ea8d8dcc02208484402633db0cfcb2af11d3b66ff07cd7b6b12c3a35e9581183e399d53f7195e6ee0539776806e913a0259520d29c75b0f8496a9cf04f16a674bf1ea4e640d0a0b42b415ea987c680bb2fb8d9891c2b7934374e865271df6f818d1b089c32f3529d2257b7810036b3d044fc8c3ce879ccbe2a182cadf83c8ffc73f3bdf9c2d81dfbd8a3f5f3a78af2e6f5ec2028880040866451dd20e92f58bc0790494862cc39c01748d1ffb677d7a2df15e2aa5674d56359e30c82c375055a1a837a49b" + "proof_hex": "0x000000000000000000000000000000000000000000000001e75e6e6bf316ec7900000000000000000000000000000000000000000000000e5e66538093519ec700000000000000000000000000000000000000000000000ea1d99185bf57a67100000000000000000000000000000000000000000000000000027a81f6b520c100000000000000000000000000000000000000000000000300a975ba6f13dbee000000000000000000000000000000000000000000000004a477a61f80a9e6d100000000000000000000000000000000000000000000000fd219fd84fb4defd70000000000000000000000000000000000000000000000000002fef0aaa80f5700000000000000000000000000000000000000000000000587f4bf0b7f2c5edb00000000000000000000000000000000000000000000000e89f1f317a49cf14f00000000000000000000000000000000000000000000000176869939c2c359c0000000000000000000000000000000000000000000000000000129747f04f15f000000000000000000000000000000000000000000000002d178d9b11de53f8f0000000000000000000000000000000000000000000000009044587fc21f92c1000000000000000000000000000000000000000000000009fdf61347211a781000000000000000000000000000000000000000000000000000008cdfd42e77de1980c43e1a2bdb19cf0131ff4fad06d6f11d7fd3279bd66410f2388c088f70a31e30bb64c3895db6a3301e9be27e79c07a3027013b0e68eb9f15025acf14e1972ecf23db1d3f80e4db8d3a8ce5e61a085775421a4ef5a8e770f626dc0fce3db71d0f0ff62f59bef95993de4ff0fc655fba80d47d6dd49ac6a99222e6b74f833800c35d744198e39965e71b95e55f6b34b670494a4279fb653f46cdce9e7d9e910a06fdd4d860032388619541f32f14d021660e8fa5497b849a703d63a58e930a195f63db46d4043c35c1fc90f21d2251f27e90874b191252eea17c8946d69d060959a597a665f3f225822e7cd537ed3be57beb9ea1ca04fc1baf6135541631ab1500bf70062fecf66545666e5f092e35c6655aa6eaf8d8bf20727575f7fc6c2c26c51b65b1a2161c60550ff91579866608e5f84a22bd9d525db9c3c0910d5a9d257d8b9fb3278bf7e3983384d4569ef101962e973fdc50af677e61249f7659180a6357f6d55ff9c3d0c3ded3bbcb37b1c49d08a76bba3d8ade40e097310dc2d2162b769f4d49f4a18af470a46bbe04ee29c054b4d644f034bed1b330b7f8624916ab19c29d424d52039e2f724aece13e882b30e012c588d515212a73f8539ef1167250177895980aea7471392e3ba028ec0715fae1cf2539f351dcf30b5d48790b4c6789f819bad95e55d1789e239dbd28d7cdcc56ad0bc1359ba6868a569d5d0bee03d62aec658dfd96444f35a5254fb150e5273acc5c643f0c6232b71c27d7109e197faa332c23d8b7c3f590a4a9972c5dc7f072121d62db7e63389c59c03726436b338944ea9825f719c9763ef1c8633e4ba2ee18556f162d735ae3c56d9e09e8ac442f6d947881166eef26a7b6fa538cbd84f3eebe8dbb6853e7399ec4520677fd7801b63bd7fa27430251fa14ea07fc97371a20021362a971be8ff709462c2e2be304a8baf3c6f57cecfb7e2dcb804656a16b3533262110bfd6bb8983cb16f23b3025152a5a67b3ba9d5f073fde73e67ff05ee11a29924d74412cf1555709498cba2023bf4d40d0fdbc85670c51082e006b173761c4183d75815fb12cda2b696fad8923295ae21e8958d220435e0a1b7bed0da50cee047f64c599848aab17beebb6cc9e802e65596603823a70658891f4d5da2d768e5acba89a796f3b8b3034f1a2c01add7ccb7fed20cc7068b76c2738cdc9f4c0cb7b752dba7393173a28f16aafb2f8423dc1c661fe8bfb5339cc45482f1df073b995b7e97f2a6c2a5824adc1dc1cfcdfeb2a64daef86e37da39bc9c8197716f12be4903f3228fbaab02bd7959a5ef295739062e15917703d8fd32f6a2a7c7667561282eb80fdb3fb6b2b123ef2586fd658281d49dca1b320e2b10851631c9b11f42550bf7a0bf737c3005918962545897986fb73a7cfacd6960fdef24f2a08d0d9909425f1b401e0572f6c2d7f7f1b16621e22948e190ffada5611905e724e14031cf80a38fd2431500edd1c857e0d04bfe118ba0e6620899d7e77a2d87d2a74adcca704a1b2c0186526acfe031abc4da8b5acfa7d50b2c69790ebf6c47f70cc3f43c6984704592a4e28316ef07835a8b598db038d52417ec5f81bf7f1b3697f5f1bc45e95a34112300f0b77c2213b2a827c30939cfd9282d84dcaa386c25bee8d7da7ca98b02968b31e1c714facf0d484cf017398d694cc979513571261b7df8c6b2c32a28820004f27df14770753cc82ad82fc23820185c486444867a8080bb57075d9c3cf32e23d1d643fda915e0ad53332c302f5e2804bdc2581874b0104eedc116923256237f41adb04fe2fb7c053b4716f2bbfada9b87cc1b4c22af9b77248e0a26bb622c3521f0e4244c66a7af714d4f840273335d1aa8d2e3159cddfd166e3d567c9268b472dc2d46705f03557130da85c7d69b53de20961e3850b6e8fb8591beaa86d0fd10b765c64e3a18d3a3171538b4a236aa5a0598d8e0678617a8cb90c62c8f941010680b196b8fefd31a1948ffda6faa57093c0e5bdb15ab793644f278ceb0ff43f12090bdafbc4674113de9a445ce1aaa725587a8e3d5aaa1ba49ff4930ec7447e0dbc9bfbdceafd180cfa38214128df7f02b436865f4ab2e5dc449557d5386c301affca4aeac69ec4f8167d8db4ad57d0e4ea4b4e60ac2f62187edfef5efbc7b2009d2829cf84a14526960e56b022d48ede9b144f6440242904da9cacb7f8db180bbed0140d8a53ef6cebbf37bb856bf73bd8fa5317e59044f72aa111b65483550994ca20ca1b0b1236fc724c0863806e5f7382ad322d66b21a8e1756f01ad84504a8c28f257e89a6369320f29a2369a69ea93a8a5afc6781483cbf31c33091a400513cca8308b55905d0cbdb38a453c8db8833cdba8c6ae2ff057fcd60e43a561ddfc683a046f7584dc4e2861eb9fcce302bf211e9693589c6e77a11cccedefb2dd16cf71ac62163775ddb1c3832a7b7ec669461968961ceccb928d387ee35e10a040c3f1cb63e84e1d359e36fe76052c8c44d4c7448e50cdd148dccfd5868a009d8b8e6fb0ca9f7e41ff668cde4ebe82df366177918e950849d8107e9a338c822b015efb43ba64949f8bcb267a544cd2e2485d66cc1525ee4e54f0d3dd0b3221e77b6ca1ad4a95d905c06145fd1a9d2821708e1ae3000e868b4f4707f38af1404d399bb89c3f4d4c4a5730271048b5d96072f147d106ac16cc13f20367e67b8142a20c858c21ee4e5e081ea05813323e4b76020518dd83c4e3eb040aac1ba862a4870e2f07eb53071ef39daa2cd981381a6adc2fe595944b16b8f3f487a870f2dfb23a1114ebbd6a5de16cc1f0fb09af8d174afba1902c520a90117ecb45afc04f60d3d3105d013af7696668a02aa56e31ac9cc3670265bb8a0d2103cfc3d072cc2af914aa524cdbf165a4f3190afc28b5810774e6c6b77669f24c3b35fc3f4119e0006c49411d8ae9918d6c3ff4207bea8185e836c24ba66c4f6c6caea6d341386396dde428fe86014248ef438f5ab688a18dbb82b516e360a706f83e414f119573fe6cc28a0d6d52ec6c133bdfcecfd4e547d43816e6fa064b6d7e93f532825ab2a81e8db45f8efb64ae9972a7cca6a163df9f2033de5a2b79240304f782d2dd93fa04491da05424e25dc8c6cbefcbffae80b6c8e792c0e5f43edc39dd7df23f9d0f14541bafae611a59f5c8aa26e03380b723934162c19a459608d98b347299086598038e2a91c73b7e0782ce39331f6d8512fbe07a7fc7b08c9b98f35082cbe354c1fb739964ac30f61d4b1f71d08a8b6a196574934238a87028427dfec27fc4436566de283beb42dbdc55e87188162e72844fcba35e452d654f06d80840a0f19ab6ecb4bab013f2e50d3069b47664158b0c63fe98f22dda6ce21cc87ad039f328fbc56d4394b0c8d0c42d6acd596912931ed363a6054458ae54789198e2d3bbc4b536f974d0b8bd865e9909e2e5bd297d45b64da2dfd9ae5c9a3a687c208e2a4ff1134488d967ddbc64b3107a9b23e7b7ca83402be13743a67d66c9a5e18418af5a327d3d8ddcd5e15421465c4b889bfa0e31c1126a743aa2f515d3c8b2a2f5598ad35c7bde623ae2bd70be128018bab38332186f3c8ea5b0ed790046421e604996d3d77c0257ce4598a66ffa6c39c755cc330dd0f827298ff230a11502223b2d5017042fb406f1cd386e642521d9f761c3c6d99b16cd03b8253982d331303a9d35dd122d9f49cc010e33c00eb51cf199abdcaf56f8648edea566a574d2e69611d85210c46ad4702245353f3616f899c74847da4456bd3fc09b84d396e234d15ca8b10caf34411fa4d9b16c44c0ae469bdeef548b7ad3a26c772810cf225a4af0997e2f4c9145a0b0e91ab7a1cc519dd271a6c3b58f2bc45c24a790bcb0bebca54fbef6125eb192cb92b1fa3c6bcb0a61c73d1efbd57d138b402953e5100ae1c1e7d107647636bee7a9309e1b1eb66e1cc1faf8ab5e8f77ddc06121a3f0244f4f4304b06453fdc75fa41db3e1b15bc1b04256ae0896b00f3268f67ff511f363f7632cadbbbac7997eb2722ddfc9d59e21c8d842b5251c4d3d76aed8fc50042272b8de59329dff728a309ff3d13bccc8913f2a664e2ba6fd8bdc0d0cbf42d453ece719b816784dc9aebe3e3e9053b6c22d14aabbec84bd9fd670e1ccbe7055be7145f3995da1a2187f9f16cbcd1b3d757624bedbf93346352a131c1840f187b4964ab692aad4d6107b75b2d22cdf9efeb5536c24a0ce84b4a57d30b42262d404e0f3178cbc291d51a96ba7ee2f7e14025059d8ff407aac7d03758f4c4260114dda8a818c704dfd406e98d804128cab1c04f35bb681799ee4a30b656ec202fb62bccc933a1416ad7b85aad49eb3180167ca52842b89ba82ac83b8d6554950ac7cf180f14c8bf1fb3b4362be359524d47bfac31b78affb5dc5c6d3d3026ef181ea0ec299ebdf35bf046ad6e9c45b6f18a4010de317e26295723ba5743fd700d3ad9a07c060be8eaf1c903412063aece07c05331fd2dfa95ef1060d2f601fd08bf69a7b0a6f2a4527320ca4f4969abce4099492c007166abe37419d20826cc1f0a166adb3e36c8f5a8e072a4aff8ca9bec1181beaa2b87e9a0f70f1c18c2db10c751f37041d1362319dcfb04fc5793083903e75cb4b510b777e81fc4ae7e3d27d853dc7a2f500f08e29ec406bd29da17b3c2477fc65aa413c13ae19d66f9b120edca4a240d2c8848d9fe4e33f695538043af26e4f6f3f19fed0f13f01ec1f103eb9cd40eafecb1d2fa1baab9e43ae7d150426827416f64a93865dc1f97b0610eb497f1d8364fdf8cb9a23478d77d66aa4a120752fa843f0c7dd4b37ed3d3d8117864f2c913d928bee9f700024ce3e597abd468289f4ba5acf754817f751a831e80aa16566b4f413037a9b7fd9fdd21400a23091807b764e3010226586b9ab929c21f256d595a95444a4e09cc4e7a5ff59d047ea54260df1d4a7a603a3a18a108984ca89e76f6ffc7369eb3215e572e865e2c05ca1c108dd3e592371a7f7bb626a0dc75a6b3d61c907c9d3f35895b219ba5d50f573e739942737cacfcaef3f70fea9682b9104dae31342cfafc5832621000bc409403687fef64c904c7d4e9a3249c19a2b07c46eb1d0667837580a380a5bf8d4501792b51fdbd8e215e44e307251e8b962cc1c1cbe05e792db1e0d4870bceacd545709283af3a9b5c3e917c690e4cff620d02eda82401c1349026a6a9809c49f899d86499d1c7900523454b7623569eeca508db51e54938b1ff954bd5abfd4b2ab5c7f67e7a11adb60119a07f0bfdb09da284b1450ba5b230fff60c7f4be779eb8d3cde7c0580fb5205f4802e0b8d987dfa249b777f42cd3d213f5945759a31c79211426f0bae9a138c9b90892c8f6912ac582724062ee8b6192acb157553f51f6317fd6ebb50af0e51419e6f25c2fbeb3e2814a21016c8489f37fe016d82b563409565692bedab6449f7095c24e77e51ee36c1d4daf80c9ef363a2f9b3673a0b3da7ae46993667adfabb47d80d6d7a3d15e84d0c42cffbdf6a2e75dc2815f6d5e0a1f64aa7d3dd22a838b11f1f6293522395fc29b60109bbadcf3de1d3a461d75131580ed56675b7648064e12626c930ca131799f18d1a050b8f2bf5e84366b8ae90d4dc77f15e98a84a3a290b13dde5201fa4ba401b2959be9d60793da5cafc8c11cf3e891aea323b5bf15d0e24a049c82597ac0781bab9dd41edfb58268a690625911be4f01c7cb94d934c1b25176d969dfe9b34d6fed67f9ae47d57d833d9af528dbaaf91285ddc45eaf9122dbe16539cf24a0c3dacc44ec98f8d88ab7adcb74bfee1a543c88f4e2e62d022541213eaeef891e4debbe5ea26f90f3474e55099d4b7f5ee86eef7772989fe1d1c25d9420a2e0d332e755af7b835a05e47a493edbd291f92126cf8f1855f5c1a244c568838828c4d494aaf5b88e13acbd8c3d7c4be49b0b3fd8b7ab4c9fb630e8afa9e4bf719bf5632d8910b7fb79b4cbba77f5810c1e6fe7401b5b41b91c61f9a5940657a8bcbafcf34afc0e9a434569a4f1919307a325dd86c8c10e1aa2a133fd94a58aac7c3be622a6f994261eac893484f9aea9826feaf71c375f073520cc58a69a35ceb6dbb2c1c65df95947118cd406728a12eb3854ee662074247b417b5a11b5daf6791ed17986906542a20f94515f08f218a8ab502e1b11e0aac770081d293b47933c806076012139832a8e64423c19a1074e9e0de79531196f2ea0bfcc8bc150670f2be8274e8ae922f2bbfad37eebc9077f98aa4ee75a28f4d7905a64017a72c409e12e9cf6ada08ac13b2dba318d32bd083c672a6c614d0bf9822bf7306835cc4c6de854e0d688c9f9cbb2e1232afd03be44541fa60eaac5c8a1f63971da1ccfa9936179c5019c8ff81a58b59edf5999d04508ed13c89645c6613ab155d18e2619002fd0a72004b09d224cb4148f271591cb2a4426ed904fb2d2212ad06ad516d91c6bf5327f0eb369bce8c8b21e7565d02cea64b2d7f3c34c91a5a0c816b61884489c8b3276e5561350da01cb1a119a6731003f2b17e86c1b30bdc6dc3b26d9151f5431e081c634ea1a9b614d7ae882a25d795af76b912eb841c150e88557ca4b44af991b82f7de755d4194ffd7451cdce97a08e72519e2bda060743c8fdba876bba64d622d3a327b26d7b2afa91a01a50d1d01d185360b8ae0170c51968c9f474009653bd419140cad4617e717a1bfa931648040b94e327da062e27394520550f3306ef2e382620771b6f8fbeee6cd62843a3250439de314019c4b41cc44de9007f07a2e5080ab05ac265f887744c70327792f6049a1fcccb27da87d4ab427b9b4c85af0927600f93fdc236d2289da94542315d39b7bdb768060b03b1d14a9cdc60b1557c6fd8b6bac4542231bca105e635f3281e7c62cc2b1f48c7159380fd0d6216d9554aba836c201b849f2a605040e9121314be4b866d1a2fae8c523bbdff12f569e21fce88ffa46cb9ced93c840921992dea2efd19460757f5c2f64cd24c7715a82de62612724ae70c8ca5d3cc85b63cc9c7fc299ce7233846d0922dcc43f9add1a83bbef63484e773b158ee55a5d30f4578266b1dfa2373becaaff6cf6b7170b4c2085fc791eff1de8917f17c9431bf4cc8ee95f61713c630086899abd3d7f20e0e8268815fd7983d5f3537e1c3c468c2c230cd39f0139087845c94cb7b81f812f650f9380f4cb21477b3a38b803134e9a22c24f67d29dfa788f5121f5f0d9eb5525984578f241189170113154c11b2254cab4bc5341007ddf7059c3331775cfb74f91f6191831e9cb87307dd294081121ffada8aaa073cc372846273c03b391c56748ddf263f87d4a60fc4b995ae8cb2b20221305b1aa0cc91d7da4aab51b807dde0675cc29c484c3c02c3456307b7eeb667a831d523c695ef210c609ba4cba27c99da6e51dd302b8fdae82d6d9f6a9aa396a6c3e30795648859df738869d97752561a1d88841995749366464fdfcbd661586ceadb144bb1217b3c7c2f44ecbe837472b2664dc8332fef90f9510cc2487d88b5927c2433456419d301db11a097d3a25997a3d2eeefbef2a7a8db4f2c7245b8485e932c50919631c4ce6b5ce255fb3a5cc858c3e3544c506117a619f707bc19c3efba1d95b287af15f587081deebabcdfb4c4f908b3b8c8693691b096df4f6dc52a41305543367023d60fd114cc4bf0affd9b5d9ffa07ae31d8c7f25cc412930046350c203cf0ae151b4ca11593001a15458bbce8485d4482fce3d2cc3403c04c77aa2f20209eaf549ebd9cc1a981171ababf3ec2ba1ed8e65ca6551c7788d52d515a0b089e0b1524de80ab0fd091d1214e4d5bdba3565a7b0efcc62d8e0fa9f404dd21f4dec3ff782e6ab2b5ae3ee011ee9d11a0d1a78b1e210366fd5b3f8edc769c2db8fe731d318984acb0ac6f4623ee3da9efdc6f9ff6ff3ba48567db397df4ea289db1c6510d93632c53679ff55101b789440208421573fd0c444df98007f3492ae39f895998fbf498f649ed257cda5f907d6ed197e2fff53a5419db41ac81a50d483775cecb08ae4c0bf98c73fced128605c54cdc366204ea89b06d2334a7ac22c92bd95e7c976ce697aa0c4f2dd1c14f78a292df8a922e8fef5e70f3a71e6e12a3ecba8a005ff5b5f0907cde6addd618f87dc3af2282c7fd224c4b81489e5c21e04a303f14fd115821b8d2c9c0c87ddef37c2f8b75a38706ace5b753a14cd404d0070d8090b5b1a0c558531a7626b40b0793f8ba8222bf81c038a3873d56881446ccea637d62a1649fb1ef247d0a342eb4a80b80e50d9b3bde954cb36da862292fc562e6ea29b4e4540670f6091f8e208f3654dc8775fe7cb2d0396c9098912f3841a820514ab2888d1336989d54d731f343bc117e3e5ea9c44273500bdf4c1ade9f6df986799bdea8d7f971c6914e70d406ca623d827a38c06e8b3824e9792853e50587a4b6d3dd71d34debc7e16ca04ca6b341b156b7393f409ded9f6f2c15cdf3390434653ccc1001e4122760cedec3a26aef2447d1ea5ee1af1035e28e1c6bdfeb3b32736479f04b19a4a1145dc0c458661d5b7d1318bb173d15385635180271dc91dd237698a29c921bd0894c593b7baa8ae6a7acd3611b5d0f4412852d476bb32f265ad24625d3e0682a8287d30a28e80d927243ebd4d31f233fc390176f79482c591fb0dc511b571e2c5f3542d52f32262ce0dd0226d4a0289de5c40550e19c3a7ae98cb29bc08cf3b6839097693797d5ca83fe66d684f44f9e9044232f95842896713cf8d85fadad0cd8292644b1a544e5a1313668f27af4f17cd600b28b913f5decc071740c52d7743edc53cda78277d9aff4e5ee8155c0854b4f135d1f7ac80785ddf791093fd7f722f19b2adcfd954f7aa838f2fe64c22214ce19c24064dc86e1c55333c3c32439bc8e8a71e8124af4078a7e9e50762bd0f4331327baf15995e2559ba95f44067b3e51a2038379b66ca898763030856451c29b2ea529401bcaf41b1b223ef9984d1c50793dfbb0744b871ac7385b1bed63140508a63c8806d470c88868cd900442157edcab4575b1a4a4841dc59e05610a17881163a53193b4f46038ec16d3a2acbfb22a828e1a66624123169cb5a837c2b2011ad3085e87c50e9c2090861347696909b69c5e58b7815ddcd0643ba8878b1e8f036cee3022f1da9114688c17a2ebf421eaafd7361287c4c521181d54cba83b3817308c53918ee0943f330dbbc80cf4cc32b9ba6b6a4c062a8f43b4376f1d14b52559c7ad92155bd042d3c5eebfb3507f296f6d43c94c22498614015eff3695f70e0ff18ee349bdf38f952f695bf514c1df01bf656937c556ac772dd985c440922409ee81e639296fdc8c12260c32447264782affadcc5bf4a79dd22811caab34269b381426ce376540fc588cf3619c022e7eadc42b5922677c22c2ff713ca3cf107db447a08cf0613c5bc706a050c83c06471958fa6469a56d77e3f45e8d3c331a535823cee65ac7c8c4afdb19949f24181d7c7f21e1aa4799980622831538c8151cc5566f52a7b13833b80415eac513ec1c379d80deb19bd856e201a18dfebc2eee4f168918883e7340b94437016cfa000c73edfdcb415138dd4f2e7d1f1b5500564a9eac0cf699202924402233d3cf17fb0cc71c2d36a5756e6c9a66a4d0e82e54d53a42d0f6c6e68b4fc6201138af40afdafdb86dd19c3743f5c1bd756b3a0129e5d2912421adc8599fd2153517102b90b3e61e6057536869bc28db874754062177ae022f1f1b0d740d39449cd7180f6e9e5a1ae0f570ab236b4296d4306c1a3fd3f59866db6bb3853c890f8993b31fadd42ad1c44c9c054f18a41db4facf2a7a8a5a2fc146bd14040dcd9dee96424fcff3a3d942093bb298b8a23f9721ee12abfb3f889e0bf1fdb61379cf4b45319e071b6e2a6d6c37be28099addceef0a0b65e63b0f17706ad47fb2cc73a40de4dfe018a4e3fa93c7b873a2456c35610a1272f488091e34387fd1db5b9e5b3fa673dd169652af9eafd7070bd4e353ce961b0b0f58259760aba424f2216f090ca085bfcce32299bce70cf9b1c4167f08ec2842f0022eeb47eccd16484a32e08299e9f7f6688c3d21d74780c284d98af5d22077f2b2d8792de1960e21ed61e1af6df5948ffb2f92d82cd8d00efae054c6730f75d06e2287eba7416dcefe8bddc2fc68b222de614f5642a8a1787b4bf652411071c6e00b058da7aedceb2923578667f49be6451e2f68336c6a63a3f1c8dbbf2126794ee71fd02d449d1b0febcdfd42713447a8023cd91866df9eb83cb0c84b2ea1de622cb3cf144613d6f7a002d159653c9f96d02de3d21bc23192c9f78f911f2c41152c4066d401b95984d314f961f38b6743f440a3f04550d4d4e150b7110d1b91be8944aa03a26fa598db4a5a0f69e75b533a2207c46bc39c351c9ba7f223fda2324aea7101eafad809321f8415dbab2aff593cb49291b7e8945641d8f425cb352ec3a15786cdc3597cd032670839a4c551ab6574edb82c6e76dcba9333268c043a2982fa0d4317237502c90a1200ee11e53feb3637ec36f551f122e1e50083ae2b39be1cd219b0d78c6958e6b5cfd88996fd748a14e99a6d2fc2962bc114690032e6d81fa9d2ac6aed38b94d92af501ff32c9b3ab5aee0b0e76d84d5cd235088e4efd4d536a88ce0b512f98777f9b0198e44e865876669cd498d3a55ab0dd10534d18582c409a543c9c9f87a00a79e62382382765eba9bef2b1c5e91be0031fd9f3b1fb2d76b661d1d0f7b9a0cd08ad31881809c625913a6eb18fe23f50f119e9782aecc36fee9ac6f56a3b3fe0c5240297a01afc7cb0f0675af297a790755a4ccbcd4f1866e1f561b5f03e8a9448e57528caf7148b9725cac6831ca982740bc1864a832bf1c7df354412f5e31780a951386ff364ea1f01ce880fc974c176e1209efff827a6e99ff93a5626451cbf51c4bc89b2f14e5aa376ad61e9fc22f7fd42b0f4378cd0326b21031805568ac8fd7d64ccf4e872e7cdff021f48f7d162205911801d4a6ac2cd45882dc7e02c465034d976c358a781079831f87a889242de0921ca6e9ec53f785ec9b695b3cd0bb080a6109a2d5158e9d9573bf42ab2c6f3fb93baeb4cb45d84d16ff83ebcf879cacef6a2c47f6eddd47f08cb9649b0b1ab3c4334de9a157252c42ed94d64ce65ab4666fdd06e256489ccef93a9fe5041380b0aba4610c03a7cad4f8d205c8c7477cf65e79edf520e85f0752dc4f9427bc42b9359c1a8498f935dc47fbc843d4fff6eaa3d0eb3a361edfe1e31a10502f073561801dae02a117682fcbd044310886caf0f756bee1388d1c260d1e0dd320cc47a959354d31a31e6f8aa49c74a388f2e6515dfb55b712cfc9e54397d24918b47a2576194194a6efcdc203bb3e67524336a62e288fb251ba07d348954ef92fea075d7e50785d56376bcff1157cc85e2670076c58a9d2ce39cb1be88abbc00413d4ccd164c11e57b2f8c0e122cebd75ad03376dc7da3355b0ccb87dc125a31c3176bfdd507b2fe2eab4a020b0bb85a2e75a40b46f7e25b4b7df5dd0a92ccd01bb041eb5abfce247954c34fa5a4b5cbec62d0cd0860db0f1a6cbf9d5f106e202c3c76759bd8ed2d8b45dee6b1470428729ea03d0dac9a65e9da2048c7f99ce04c3329559fa0290eac1a9a42e1bae853424cfd3fada637d50067aa58d1d4d943015b03c2309ccaca5a209459c4bf2c301e4274b1e3045453a918d8a2e214db618dff718f0e8287927480d5a3ae118be697a50cff71b45211e65eacf0357d8d8084a5a7663313d67403bd7ecb9140c76fbaf2387e480f89302b47bcc31d1e6d7005b0b9e5c5d626e9a6619c4ce5587637b45c44a486f7c62e075adc727eb01cb2213bcb92edf2e81be9f2a49ad512fea35fd981727f3140cc114f24f28e9ce950f6e1d41030ca825b20c02bafa07da5283d31726260659ea0575a470e9f3862612594ae7da751efe342fbf919a5ac503522de63fd4cdb644d7560d435f237f7c22eabffdcda551aacaa33bfbfd7b264b0de538421e87c5b6112d4c10fa837a670872ab3fd883ca6be74ec4a41888ea76efccbda23a48f3876c20007bb5e8fd6608bcf1f22036ccf763f19291926e1ae21061c1381f42bb82af11bc3bb8f34f1d25c566966289b286af824e626be44fa609fb9f8e5c280b11ab25da9eae47547d1134ba4a20c37fa9f9c9119b3f0946765dc30ae229db3828469d37c4af2a3d43255889f4da75691e9009aed63ec762f684754cc16909e6f55c5269ff4ed7626917c8a264883124b54e4c661f42400179867e83256dd6c8b5c70a6eecf33f69fd2e3c04920a72f5f2249b63e1e08881e8273da02e502582e13dbe9d0b5d79b1b226a8698609fc8716a362b03401118b9ffd1a8d4151e25d3aab6864751a3f82db0affa91866f5748099a7a04ecf6c582401622c3aee7b56e9926ed231141c26f1034408199b216c95816741706ac3a241e10725fb30888123fbcd7609ae4cc233041e412d88d21b96c2c899f9d3464d3be3ada75bfa0a939522cb1222f6fd2d201250460ce0f1a7fb9074a0f6fa3b172e262e1724003855416d8485c2d780f07f1195cae1b14cd3e8b70a23cf4667c9913433b4f2ca4488685c0d83f58f24902023ce9091da8bd491e977134e6d24ba3e0d62afba9b9f0c9596ce5e207a75f02e05a488621b6b1b2f82eab998582f6da1b507d69d16de20539f5ea50ca1e89bf3043c3d2bb158ea3be35187e000b92298549448cd4d5b896af4c10ff7c292a9b6234888e4059dd68e03f9d2f6ba50284c55a63594c727efccb615a7dc0336d3641caec9c366f569ff0f2cecb46452264a800c5963aafcc862f4ef840e4839dc1c25c64913bb18e4943f1bca953e7bc4ca7238ffb61f87ccb9b4f411cca65172d82a9b37ff2faf9238ab9a8cd548f01a5c06a2f0c852b173ed8bb2f16e4e49e7491a0841bc464caab75ddb9afac16a3c4c9979a16067072e41ec38ac45384fc7db175ce8432e258234b709460386d667b25e97f32010e41cec2cab1c959c68d23b09d014d1421ec2b3fecd14fa25e9c288f5e508fa5606c89a8678bf87d7b7c938002486328b9b29b627d0f6ebf8a77cb2e8a8038986c07da3a8b718c3fb80602705be98a904bb2f54859cc16434cde0b7e8a6de36b5ec8a6ea22f10f227be5b060f1dcb994fad2f44350307e50d5481ce1a3cde7147806383acb56cf1cb59bd25244acc1d62e14a82954b17b519a1cb03b5d4103beb62feb96e13350c9c7fa5bb07bd3a2c8ea44417d8a935a9e6099406c7b36215c35285a0614326bf40fccab901a9a0fcb2319ffbade7f8d15ced7f3f51f979809c892ac9f6bfd4c9a2a1a5aa04a128248d3cb749415d418a4384da9bebaeca135d0f28ba169e4947a16ca1f52b82b51b5263c99d50552714a9a2fea1a5d0bd680d2fe441f8abe121960053680300e3f56615a750fc1d8e8bbdc1a74feb42de4f686920a55b33c9d37364c1261f9637c6e7ca508cd210914189000700c7299658e9f007bacaa37980f64bd6cb249cc42f44e8f86a0afca5ea0f982453b72208aa9fbb69df5b2d91e1cbcefebc21027e3be6e9bafc8efa9000169e7fdc38fe27e534e786e5b363fd2fb3c706ca24bb1c43e8e4d331239503d6c0820a75a673526b2b4c8e33d810f0cd432caeac2961cae418d33d3f5fc24a83452e897af28d3d2cb3e94d177fd1eccfd030ebc1186974863f6fbeed1d5b16ff8eb4fff18629f25349caeda0f588bee9bf6342c42f5b264f5b6c882fa61c8e9d80c4fd68019c6220c9c04a4fb50fda44c1294f0417e5c37beb768112252becc0cb87f4f7b3668e7acf882e119b83517186944b6402d290f7c84aca9901c8c2e39bf76cdc6e6dc58affb9a31cc2eda3b04ffb86700a2cffaa7330da0604b73af6be18bb8c028958709e814773086372e6f300e1a31df307488514ba7a83b1776aadac1d20c306b12776e8050edc6d21e3cd6e23910a90de67c3d41d621aa294394bfad19c0cade227c268ab93bd9c69fea9d554c4253a34614ea3eb7565f89a7b76c6294edc4359819409b436da27f11206bd097b26c2e4d4e0b551afde56514b47c71407c1b1faf7470958c3cd29ddc98d4d57782f6fafc9c9b673e30ba8387fc5d09b2d6af4fb50260aad9cd5efbc5232f9c79b30612b7f03f037f16a8a5b587aa1556ceacd8c1e3cb7a936a7dc132341ecb5d0067578f8637ba3fdb938deedfe74d89f87891021566578a7f2c238794d73d0571a77b344f2989ebe251e7e1ff9029bd686e1608af6d7c5b275671f67d2083830134ab23a331c5f5d8208748b39f75da694aa518406fcb20273227668c7e598121a8f82dfb5d84f876fba9a258d1a1817968395ee75da396f59abbd3bc8d3143a11a434282eeac18c25ed67af3b2733e69375ecc8b0e604e168f2f6bdf41a4c9f0ba4b7b622754e332144a32a7678575beeffc8cd293964509b5d9bc054a0792e2d967ff97e72a34fcde286456d9a94e248963c6fd50338647d8996d23f27298118e03f44699f28d7ff31f31c5cc651b47df9ec3b9b869ac0bc4f66bef4e5b14c0e186b0227bba2e618527749c1c041b9e4b5c39abb3a77e9328fcbc0cb025d4225f3bb62ce3504f467d07fc5ece3b1ba8a5ad7682a503bc7a6502f37f4f0fe13106355c8bda71d12797aaca34008c3a006fdd066b64da246671b4d0f37152bd4", + "public_inputs_hex": "0x25832c071d6d99ad42c7b885e48b10100726dc0de03c0a779b282c519ccf538a25fc312a5f0009edd084811b739bcbd529987df1cea36f5a24515386662573960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000c946233fabee579beb961008103a4c8d00000000000000000000000000000000026a297ba8fb72294cced25b49dae7c92208484402633db0cfcb2af11d3b66ff07cd7b6b12c3a35e9581183e399d53f72b942ad53baa7c6e1a7616e4f0aa2eedf7d67eacc63696345525988225847479273fc58215abae6a73503410e8209a542c92bbea2ba8e6fb129c971def4ca8452ddcd2c1e0b08c268d942b52a49e7597873ccad9df1b02ea1ab854e7111eb2dd188f793a89424a83a3e746a72d4cdfd5702a7944deb0fa2ee04f84489f4af3c22f5ffac9352fe35d950a4ce548798309f5d896dd11f304f93d83cdfe284b37a6" }, "decryption_aggregator": { - "proof_hex": "0x00000000000000000000000000000000000000000000000987adb4e5f7f3bde4000000000000000000000000000000000000000000000005ed378603dc5d82b100000000000000000000000000000000000000000000000a42fd21b16d083942000000000000000000000000000000000000000000000000000094a09fbdc32f00000000000000000000000000000000000000000000000255c07c89c05d8fa0000000000000000000000000000000000000000000000003af09308293d20718000000000000000000000000000000000000000000000007d947bb153b660457000000000000000000000000000000000000000000000000000222011da6fc88000000000000000000000000000000000000000000000007205f5f8f0fb2b10900000000000000000000000000000000000000000000000d600ae130c582aeea0000000000000000000000000000000000000000000000067e0fe450a682f2fa000000000000000000000000000000000000000000000000000031d4ffa4172e00000000000000000000000000000000000000000000000783b4771b3064cfe900000000000000000000000000000000000000000000000bbff8345d387212a300000000000000000000000000000000000000000000000ebbe0d5f8dec02c6c00000000000000000000000000000000000000000000000000014e6eb98612aa01ec19d83d482649c0940fb8146c6999d65c8686684592d5d5208aca9869eaca13973e9570bc8399530a1c135f547d32460f4b83cd330227ca818217f45e06672bd62e28e76bdf130767205a1757b84e2032baf1f39912a88c28845b6b88592c305211427894c22e10d8b134f28d77610246dfcf641560e33ce08d551764c7111c8b24ecfdef12d3806fa419d342fc550079772985cc25cbac908d16d4999adb0ce7010e598efdb7a4ee94186dfd1b659d16e74ebd1edf874a6ab72b7084835e2774f06ba5c953ffa51c58bf88aace20a6e85f086b962a403eda2293f27ed9fc12a42258c030971841ff6be72c5b691807f3fd7a347f41521f379baa51c4410e04f5d9ecb7dd6da287ba0c4a94c7cd0830dad532f110cb376d56e3b19ddbe99906820707c23159cb64fd6601babc62871c34c81d83962973c6a6bf113debc1b016744cb4fb1304dd416f0bdc283797f0fcb354d00afb6430910cb84915d9fb430b6736964c0a095fc5cf78670da5be0bc0999076d4d91deb91199296bb5736690edc71f7a3c22526e633f430ff0fde859777d093618372f32148a6ab4aa2888428a33637ed2000dcc33f2f8d16942ff6eb8f698bdd836f80d55293b95e7a99211a5a302b0d8af93f7b3c6c172943ed24c95684aacb9e05b5e94452595574d2d1089e8b01965628b761223708dc74d5a9d0b5b553b1e26ee36ecc131419cc79c41f2679c5df7dceecf959d8ce01d067b83bb6b2728eeec8c5688777cbffbb563812cc1611ee9a5fa8db99c324d518c74d039c72d4807bf95eca4654d90a89b88610440e1cbea0ce1162f6664535ebcb901ce0d9b027cf2a1dafc575910c45dcdb1b777cb1aacd1963dde0dd73ede742e6f68a87d8099cb5395ba1b96076512cdf007ce7f1c2a07000779477c344a41ed27621a8e04b59c1e9a343b0a5241cb068240439abd43685f0d47bdcd80f84e6b2f7fd615a26e58a5d229ad304877914ac12820eba8fc2b8ce0530e6f7160328020d64b4b0a97ad669d9b1e89a85fbd2d1147d3219a422ca5a031b11b25f0ac984be5e161d6a86c2f18225fdba0e422b6c20c15f87172a4cf9a0bff8312203c532f55c7bf3b6363abbf1db4963749592dd1162f0be6f6bb5e9397b782828bf28f13ee6fa783f6842d4210379bd259bb06904808dda1eb88f992e17612753c200d61e1fa4bb883c8f89cb3d4c8d50bc5e8f2d411aef522f83adc808749e5188f3f94738b6cd966e3f227ef06b9d2170597e27354de22efc34361db2337b4977759a456a2456643e0b9abee14dd0351a231518d3312e2c485d0294ccbab0767dc0ea2d482e35e43b928abfd80e22426cc61b0990991c297fab36bea3d6a6e5121edf800de01b5568b670aec0e155d871647a03ca79072efc7981c49afd26a60e3a3788be8916edfeb28a85e43667c48f82da2ed4200bcda4c9f1a6daafa636741ac2da644e6c091d7fbd1afcd426b6c4aa380958f3d019b802f57524cdbfc04d385326d7354da80d9cdef27d3e850dd109fa225349708faa85a46788f81812db14e7d6e3c6c1705b90feabe3bd7d9ab0023408b9fa716c28d9c5e105178f5199bcdfd0d7d6ca6254b834a0cb97c72df2c04d1f6eb5464ec07fd582c6c5dc63e38be5e9a25d1decb1f70a1d96dbba28e127d10003e1fbd3dbdd69e0510d25d4239efd048448b3e1aa1ff0829f8031c67e951d26a2d7e19534b8228a85de9f8be03cd431b0d709f4fc552de727c023909620442a19ebf0350ff6d20427a659ddf78b469d4715de3accfd32965a06df4442610e238ca3c86af918e56dbf54d0d2902339819a612c36f92139a386a62b805f00ba13f93755bb7ea2752515c1e829f3299ccc3642c85076d23947383e2bb86743cd1259204db0d6b58e4bfa4ff740136009e559dc53f8db6dc79f48cfdab3118bea277c9b3102ff22396cf2f2e5b1148088ff8c6873f2bfffa22e5ec6a8f065684d269fa15d4242e598a4a8547536067058882b9bed9165770bcc6a0d0d1dd14dea28360c24cc5a2bc444ca3b2d11b130edfbb84657766aad5098991915a30a19ef1ffae1405c50e81ee28a7427c6deddeec547f2c1284085f37d470523496334660707643d838f0a7776da97e8d5fd6056e2f1028e8d6edb2aa632d70f9fc2edfa274c4c389c0b38211cc6fdbed398394b25a2c8d2f27fd503e070a80941f7f4ca0e4ac64e75715ff0a32bd82e8427e053e4ab684fb36b52dc1f461a5639c735bc2a7db02e3d65828f6a57dec91a2d66ef814da83aff62e0e9af86f321a9e747412c93fa14b231cd2e8239a3afdcf73700887256e03235324fe52ca81c53af91fb26a9353e95bb3c64d914fdde38f20739ecabef1414de4376871f2b156aaf9f8a231508ed88cc5ded4876520b2f24c7d3cbb3def31567cfe59af381afc7a2558e0b6544920d1df558a90ae224abf2830c34dba6d2acec7124276c1585f5aa0dc20288240cba9c6e33a450b528b69658a5e44fc0040dc0686fb21c1a98b5493ec101dacfa48eaa3b4f94af550cf11c12a039a781668d98911b37b18024faa63c4c0d00427633d6353982b01eac200cf41f52d13c810604a1d2be337b87566867d609d87e2bd85f185c75cae95e040740c3f1d14e755f58ef75569b73a32a0888b50dfd0d9bc436dad4aecbf13faf42b2563e753cd192e1e44137a294924cc0fcd72f3039b9fd750b948d0672b8cb8db8c9813e406e4347123fa73c5723ce32f92809d4c3ce2b8082bd89b2477314f2df6decf8cce655acf23677ff9f0e0f9c56fb023f704619b5586e9f5adc1ba2cc93ff33cf0d5f67365b5ede94cca1d289f75d20dcae4403a891838ab0bc57b6514253a974d134d0df60a01c90cd32ae1bcde8156b2cd206edaab9617449960b0ada4e78464bd8f335024e2f76b8a81d1bc28312dae65ce554a26afc518452cc9a5ad6466cd6bd6ac68f51e458c9a50f3a0c5c102c54f9958859eca24310d792f7832cb491565dc08f07a50648b03a02e8071b03eb63a207b1dab3978b69916c106c8326bd50931fab5651fa3ef8203570db961f1f02db4e02e89dd192586588c41411208317f3c306fb9332d983935d344d0110e59c46b088a47bb92d79c87c523f1793f90744125aa283fee3f6117aa87fa823030b6ce1a1d2a5d6755ffc721a0a136ebd208d95f34410c314aa865954bb83098c00b938ee1affd83f40ce57717b5082887338868e5c9422e4bf7fc1857af6083e2e945c543ff8bd3995dffdd5188fbded020c0687fd8da2aa7ef45952b6b9084beea4b251a6ee016ebf5fcb3cca3e96bfc742ab35817fd7282333357a38a010ed8f90f0e8a511e0909296d99545825ae9d1cec7223b36fb85f12bd25a53df0247e3676d29508009322287c2ee3d29e7e4e542a48dfdc950af5035f257eddd13c58ba58ec7fcefe7643644d6160c93fe5df679c2727d3c71e17ce37cd89cc9066dbeb34bea5f22d72ff8652582ca63bd52fe6f74b401446fd606a6b9af4d7a27547054912b101ce8723ad1f6d1f6a7512ef31973465a58a5349d3a3504d088057e40eeb8c2d7fa3fb66730e9768dd008950113787fff06c297938d8e62db230fd0cb14fbe1661f0a51c74301482237bb51f298a0693005c82877c4f827d0b01f07109324bcdee8663f14478265b167a07b508f1975c2c4358d5159402bfbf02b88f14d69954e18a3f7a8ad4bce3d5c5090adfe821e9abc674618191ea50e4b29ad959b5612d728b13f16b3dacf420b4c33cfa74ead666017258f5e6209de0b21ccd0b2a630d74724adc86e7139809bb45b11e8a26ba6921d8d4e08e5b7215a265ca6806ec10495132eca643d375a96f0cb2d729dd25828c199728af83083c90bcec7383b70df0618975159063464eb8c1043cbc82568a090cc4535262df76f269d2c2af4377465d29fbec0d3e44b5b8e9d6358a403726f8dac0345dca084b50691dc8a1178611a2556c3422e52372fb4358d88a86902982551eb9da886eaac0488e3e4e59bbc43ae08e4cf0e79d1e1e2bcf1c26611bc880df4cc88bbb5d89e01fa3f3c586e6e9f5e73f28b25fe95c18e025f3650d7af98f4c44d979cd74ea90f9152d4c31e0581648aab438fda289abe66b8f8248b45e2c53762edb16286442dd380fbf70ef1a66ba55cbe10340ce2e97c6ed356c105b7b70d4d282fa0b08b0525ee02118f1c47c16c265c519aba3e53dc6bceb9fa44e09028337866e8f429229f9e2108a5ef6ab1179c6cae4a1b8fa8fd28d141aa7d73f522db5f823cbab0155ac17aaa428399b6713f5b1f4544f59060be4e24e292a962918f926b12e4d326921ce206f28e9d6b25582e1cda7c45a2316ec6faa24baac50df12a1fbe7188258f96ec5db61da6d2dbae2799154a8e9b37679e5ec1e9b53fa50dc56c42d53322272ebfae69c26e475cbb8dbd5b362dfa9806515e285d8de724a3aecf10c203016db67ada7055801363d45e2b8c271e20545ae707439cff7fdcd84bb7b000b82d3ccf9874d39155d1b69cea14902c8bdc4f1ba7cc29187dd1fb87c56ba68df8008473db80657888115bb42de35bbfa7dd5d58079418d776722bc8d49cbe65a104bcb9b0e9c3710c9f9125d1a8dba6a39129d869384bb0f3fa7026476541549c0bb13b5305f0deee708278b40603c86c87d3d0913c0d7d040f3a0292cbd3f1ac18ac5d0e9ae4081a6d00b0111ad769394c7739399aead4cd7a460be9f4c234f51a8880bb5fd88def16a8816a30b4ea2ca30337f4fcba6aefbcf0bf841ab5b20208c9785c48ee6c6464b0e347b29268361c07453ca42fe14ad61e72b484312188029c4211184a7e59516f73f3adfe1c8bf465f1b246ba4b284648629a0f5cc0590cb8158e7ed1f4cbbd72050d04626560012251c0362476131f27e04dc8d62c9115fa79bdfdcb1534d8ec5cf83b9972c7f31a7f82e96694bd8f061accaf63248a137d89b3acba69e060286da68218868ed9d88f0b1d4f8479775a8d952726e8670c2a562315b76f076a558136be65159d968d33e353198423948c032f4e0aa0d60fbe63e7880348c1d052e02026d1e8d16227713d906d60ca0485eb3d20fd778e01fdeda5f294a03c49629de13e48873396c6d596c4a82c91a6416111fcb8379917e7f210c179a210440e42a50adc3d3591788c2a7be64f861cc546cc054cdc6a15a003b2adbf6fea10dd259477b3beb808aeaf953fde6b99ae7255ec2be91a980b2f9ed5dfa4696711e650688d562abff4be6ec16dea6b36b2a6ee2f2a578b8609e0bffd2bbb2f8f5a743b55507e0e47193af4f1978ed153b2a2093851a43a4b16fd1444082712c372d928c943170f451834630c40ab6d41d1dd601f97fa43a50648b3d01a7db07cac6a683417a1dfb2d710d1984f703e9b6346036f78fbdbdf0b737d94ee509a2170ab6cbd7371389c6e734ce3ac0af0e7446b58217810dda51c03eab1b07dff7856144c1b30702e57fece38c690b7a0bc23af9b034a99eaf61d73a33d4e5ac4d393f406ffea8b0aa995d582a140dbad5b0b99600fce88362b158d1a1879f02f07332d971ad0ae3692a8c61de7456edf33303a36ffaa0134c70ae4f05d894e4a415b1297132b9455bf940921a8236c582b8ce2fba038a5b98a244eae77377810f668bf685d8cd3e8e5ef2f44fc45e319594165bfde57e22bf0213c80d8459bc13c913ee92ee02ab4439f0ea27e876b15d66e0fd2d12cf1830111cc9835393ef58d7ec1974dc30db64bff1cd98e1d9f7e773f44068cee5c67281a83b948f419e187494113e32e221eeacd9a67429ba9c2a931927122668c06992d8b776c3f6259a6ba9eab8957b8340078dfbce002bec4e4cbe6451f74f8d126088b18fd65009fc64bc223dc16d0591d0c5a6f085eadf072ab002804983f238d2a2595616dca5a09a1c1cbfc87cc4af532057518d2885219bde38710f368d9dc07e6aede1da3deca14a7fbb5f1f1292c6a11077fcbb15e0c0a049084252eba8e2c33ce421d37e7986f86e6b18c1135ba95486218f26d961d7877fa449253058f0a592a27254d7c7d01698b85b57a478b94830921a2c94f831846bb38d5c151d42a89f8a13e6462df64b4c55e1864afd0c1d6303ab066d3b2b7da1b801705e9dc186b1ab2fd62328d46d17daa0177a4589a162daa1b01bbea02fa2332219d5629283b89b170dc081d5d94846c966ae430768bd3340779628d33f8bfc7c70ec7de087a5ec32b0b50e5c8e3c71cea77c00d64d964a59902f536dbf6effcf8e6f62e136a001aa16046a8a84ab1e5dace849b0c19cad1389a48c17b61a898896c4b972ceabaa87cb8ed850760c27b357b10fb5f0cd415bc24993084e905c53ad1abe507d734b58ce1841dbd1626bda546ff7b236184d24be658701ed93ede7c83df6a068d48d5286d6113de65e26e31d2eba4a6ba53fdb2e13ee9b30b36c8ca5b28b90f9a5bd292d365e037fa54ec2722a008b0ebe3f418ac9dd7e623d4f417b7e6c6213c6cbd6b6b825accc918a0fd279e9274744088cd2a7a04b4de1501971a2d8e123b45ccc643948dd02f7d076494284c857bfa7e6535746768758b132e4e6aef14c220607c4dd929a32fc1a8ff7ab86652dddb25941330b576feb58086e6e92f23d02cce7c05c8eb211bac021917733b18cabb0058c3203271fa89027a8ee43920efce36b221115afe2ea3acefa050a4cc91ad573a73f4b967159617cce2c4fd2bb366d6b2bbb5334f800810769eeb9fee335b7525299c9298326e04ef0df5af1a34b4f6ce3289e480c55342f167667c1a4c431cfc72236eec4d87a2d69c263c13e05c0693e6e8b3551d90299bed01829233a35c69669f8818dafb8a206027a30385ede0ecf18df90d4d92d16b5d80b82b7a5500c59a7d4ea1a2c16b047e60701d7992e950675a71240fdd6ff91eb509f5a39a20674ba5c411a0ba53b3473bd803134675da21d443fa2c3bb8c5e01fa3fcab64403410f5b860dfd8267199a52a183aca4b265de9aca19b520ee6855497223d443e4ec8edc21bdcdcfc7dfbef2c28f866d113698da8c7a00a755016e6448fac410a460ad08cd4338183ff00f4100d0f2cf928452e13c621ce4bb42b5416863758a28b3a339768f9c0837867217f2afb8393a2535a60c37441dcf0a34e5350938204b71dc6f6beb9f7d20ede917612fa175145d986a7cb5a79fb1857643f33b475fe4f78cb67c4bf23dacfccd77b300d930a85039e9223bf8ab887a4b6fd6dab6e08fa21103aae7e357f84c5a8382373a566882eb882db182aea4896b289efee7c4f2dbfbf0b5cb34c078513023026d7e167c7d23fbe1f06d2c7d6cdb35a560776fc32368172eeb52c06a69cb0b9008e1974f8ceb08312a24effafa9bdc929bdcc2b1720a1abf3392e03948962262a8b5ba87690be615f3963991776b9277f9dfc9bd70fd4e4439fcf2522781e1322ec3850b41844f7814165adf6460f419d3443a0f475a65b2521dd4189ae25cb2d239c179b48ac67225294d53360305249476c2bc432722a46537a87c9db0d5611406c6e8056aa43cbafc6aa655b9bf09e6418a111f39af9bac0d433303d90341cd4ecdfac812a37dfa87113ac195b133930ce95aafda2fd69b574dba16251ac24f8b91d7a2f9c0b08d7c7e1f2516c3e8b1700d84d79a02ec51203b42a56bee6172b19a9f44043443879fa4addcdf80ec12c125ba6a6d6ffa61d5cfb1f2aed5811ca7bd161b2c47d667e3381032d36d45899d3e28f61e31807709be6e331b779062b846c04856208873d7d5f06fada63f0cd74fff984abf8116dae8ffa214ef41fd8427a3125e3920a055259214c2c787b16b779bb3c5113245bdd0f05d9237c175c7fb05feec1534533f361b4923799506b19762afb1e6da73d250a2a062b37263dff74f97fb78328b360f951ff7157322f3fbee45013d25822fbdf218dfdac203e8c3ad3efc7145f6fc8dbd5048e5d0fb1325d5f55ba6a18e3bdf7fd199fdf22d01714a6d892d0bd6822a70ca3a6eff923989f90a115a27a2888e6b01deda6024e86697d916e45b0cb7fb4532d3dd72d4cf2c6e69652f1963364cfa54490a3105a5a6d8b78b58d96044c8dedb23d90ef78bd2feeed8fe795710ca4a4b882ee1c2f3ca91b7f0d7b08c14466061d36915e54b39dc3ca263088d5174dc5940d411ebec5f2684a7f58e9f8ea7f59e3cce71eb6eef697c7408b6e9e5287a5eeb5aa245cc9891529dcc72a0ef044e0ba35d820554e7a97a22b4ed0f88e9bcf3232b30ad148cf4576883069f1a135e306e855574c879d246eb6a617a8d9c4580d14a103663bb9b8e3f9804750f9e5fba5ca9fbab59b5413fd3d40813eeeb2e34154e52bcc6b738a03f19039633168d2b59930f4c02e9043d4edd4dfd8d878777a35202452537d88b6cf7baeda8e5c6e1b23b677604d2cb85affb8df573a0a598bd20c18a6a7cc1786dfeff162ff44fc9431fbd9abc24f088917e64874b0096be181322cf02a63af628f04ef516f7cb724a9a35516d61c96277f0cc15d0b182228b0c71cf44cb9a3ea931b9e6addb9233e2bc471073797e7ecbfcd8579bb7b0d28c8410bb7146ee398663ce27c538a615bee997d2f71ce6026876d03c2e8e0b71ed67d0fdefafd4b06b5b655e0cfa6fb2412fb56da25e5ebf8680fd9dd62cbffa8e44a2c6413eaa5abb7bc68d9b9f8be03e4450d64f62131ed3000fe79f88912063f401146e2613d9198d6016196e69285ac9bed6ab4be2a2784eff94d55678bc5498c182fb91d5e0ab365aec19505cc814c4af5a0bdac19ac72768d10df32e694de0724f0572a14568a1d9355968d15e32c3f7ad44441bb17ccd570c47a09b1ab46c0233af8e34d41efb4c7198e5088c97760f57d783362b94b8fc421c6ff2eb3ebad1ed2ac7511556abe5261f991fb1b9c88ef44e8988e522eb84ffe680d128aa6560f0424641d8caaeaf04c200b67ac903f6f10ce8f17da0b3ecdd1a7fdc8094b82163f7155b8ff4082026a0783c4f9400c740d110a2d0841fe5d8f4d1f8c858c08051fa256b26399626b7b2760bd7240cdcd6f631af28bfa939e0c470a2d36cd2c1c43355810a346d44f6d182b676b82fa4c3f67c9b31d4bffaba4284c4643ea220202d6a6fc7297f85a403f5e06c0cc5b951c0307a022bb05920a711f9290c0372b3080998a4c6f9d2e080a6f8a1a68c2fca31dc0f4261b20afcc1efdfdda88ea07c033515be762c3414d2b33bd71fd2b4e04a2a50a82c0c9661f20832549220519f0a9447115f541eb9f0e9805a507e979f6360aad42bc004427c601572fe15f09960a2a8d7c9ddd7ec6ac56ca55ba09f3ad299059e78357437a5e2f2d3012222cc7cf4461422484becf2bb7975e8fabfedccdd8d352c4af57367a95a5ba35f704aa0939286266d9b7ed4db582a346a2d6ac8f50f57f72d813c380616f0202f021ab05c2de28a42ffb9c38e18a59c60b45ddc9656131239d6bb695593429b89a237861828bf85d73b3f21f46646bdab705b94c3ba83b5bcdc203944e2ec3066f2422ba38d2e24ae9bc5288f3188fb47c63129cbf72edd6acf5136608976715c31193a6d0e51fce9df805738437ee6e24778cf60d56eb02dd9010120403b9cd7c26dcb5dd54355d6ffeb9cf56f26e9e27dc248880a074bd43a2db819019298b0c21dad0e7601c3e2c37671b657a55cff16482dd636d7d1cce2a62b7bdeaa6d2a20b1a4a473aed9939c53801c816cfbb96107afc7c91dab29acbd15b2c15c1ee530da08de8a41dff2efb2dbb0ad65368abec946efdb840e52fd9224a7ac749adf128c7182d4d49072bce90990ae3363920f644cafb988c04e7778b6a2d783540b20e6ba849a24e2cee5badc3d9f87bb08c42df5237bb6511447b519dd001b9b74e1b830244bbeb508512443adc2cf24df2d8bced7ccb616f41a5f0ab835e693c2604d42e60585241fd5b5b0cd7416d31ce9c795f9e4d91d5548f9cdb5012b2c2151360c0004ffa0e1fc3a86eedb19c4551143069618d8b752b51c3ced1da1a9adc2589f4c79a8c917efde3948a5f1da4b6325d6ff7be04877a5fa0af07339129330c486d306b139377720c3e46ef640214d752fbe99be0df92666eb455461c951c18696ff47983e13c5e8e854c7579cc0942ecc67e1d44437b7d578c2f03e6b9fd0f795c65d8ea5afa5bd4db2806b3836f1c79d8ec6f2a456b85eb2f8101b76cf92f85596214fd06b74991fcd21a78a30c4d09799e3a3e92102bed6167f848413910f60f919455f945be3a0ba52e639fe62123d0b31d6b2a0e99691f42d7f7de8928ac7541bd85e698ff43cfa2f7a62481f74f182c3f7006b996943ece228e462e0b6daa7ea37b654ea51687de1c67e57caf994ab65fd55ed20e5d42712a07867328b0baaa572613c68e80dddbb64f9c9e04ffeecd0d8f399f86275f0dbaa968cf0d623c1b792b7619397d70c7724ae40a9d494eafa4d262baade9bccf9a0e14991d0108e154ecc03b179d0178525fa035e854e7bbd74696f426dd580322b6e2bf265c9a5286a5c12875859d816d842a11447239f36c61c7b3a22d697384b91b351efd8b5b8f70b568c069b46e3ff51b60921b2d8e69425b8b51e41974094110c1113ae0bc2b13cbad58589169eb5206c7e0f7fcce4e5158bf64ca3f47ebfc60c81d9f777a020d6789ec71e49e71726722c3921ff07017ffdc93d4949552fef0582496acea2fb767932e2cb43009e45fe9a175ea419a784dde46625504c935ac7f04d8dd62ce096d9df45cbb084e9cbdd782c16c595e9976a83abb9479bcfb326a1fd9ca61d8dbc31c1ac4366c460f7697267e568ccb666fac304a35dd0cea6e2b18c9b3ef6ab01fb02c43244cdabdc0670b658f2349dfef788ff7f3fd8bcff8742c77f8496f8056be9ecafe87e7991458b4a7fd7e9b7723d598ceb43b335bf80a0a3bb5369dc655e704605d802022478ae8fadab73a965376984c163eb0442611244a96313b749816e31483e71c811ed28680ca3c960585c3beb73348a2828f6c1e3de18e2c7ffb2373d7ce07f6af30ad8b5bf7543a67b217ab5508fdc9dae47229007060728ae3ed418a50c27e8d4d3fb38a16e2d198707b0c49f0b0f0981c0c144c50a4d320caf2dbc63f0939a34c9620772ec1dc579564da414a5488f7bf1d24fda2eb29d0329bcaa90d7b370d70359bfce785aa0389b9b64a1d805aeecb2d0668f74723cd50cbf272b439ef58fcf5b9627c3476636244133618cfb18ee67f1d14e3b6780bf49040c8e3713b5da7b4c765ef0f44a4ba2607c6bffe961b804c1a7f983fc86278302a961de07bcab2b1d8c87f42d3e4ec1e26017dcff5a78dd107db774fe31e3dbbb44b2c056fcb2edaba797ef7ffc745491f538168e3cec98f1432cdbccf2735422706e8dd16816491309885e98f6bf001b3e8a8aae5a2e81a16ae509aa335273b37e951bd871959fcb6c812cfef30aa2049b5c3450d9d6b6b09686de1ad299c8130472428f40ebc22c49e21314eb25d6386f95674d34738e72cf0a030170b1ec04af0d3abfb76ede04b388ed2d3f2f977be26b98b714d497f2c6efc0037d324eb08f1f4a2974565b8746dda11ca2944301494a800909bbee806963e92178b4f8d290c517a9751adabe3671dbdf156a49a0920d5111fe7d0640dcb304f7042bf5344eb960e361cd05b802ac2e19ea9439e2b77f5fb09baf20203d23c9d38f1a53a57d924af7d4a8af8a5ec4eee064fed2d5b58a3c43d281a97117c0e30563d429f943d3e3a7d5ba47f7e40af412f7c117ba802403f7258816220d421dda84c76f16e7ca7827a1f85b9d8a79da43c867fcff91594e7dfa847a619a1674e05f8278446c73f7a96fe8138f3ea452dd92fb39b3f7dc52d170e98e5029b951e8e9ad6730f08fc023bcf6fc1425a5a08fd9da207d1b34cdd591e750611313989d33d1bef87d8269e329a8e8f8dff7145bd0942aa674cd84f3d4e5fcb27fd22cd699118671d582bf55fa4e03bd30ae1ce1c90736a1607880446695fae0a3186ce85230bb095847c374632954949d54b6200d61bc3b85b0b95053279961c8c4fdf0ad9df1563eaa7dbdf189c793bd9721391e4c4aacd20aadfb172e15a2efaf8dd0face72b1495f7d5a8bdfa475cfaa7249f23842a937f0a23e68eda9c0a297ea8da1484178e9f20c89cf63a2ddb18610968f0b242ccd4713b4ba492a824ff0532fce504017a5a19ce0ffd959608d883d709623210f1edd71893ae96052c0244f5408f977814136ac5eb32633b453c8126ddb79cb45e659c7b89f427031ff0eae342074abb87887716659092e809ee3843892f7e5902f24b0c2327f7281862cf758082ffe45f7983ca3114ab354faf722e540a2ed3bbad4e37223accc928e0cfd1e75ffa47e5cdb82f7e3cacbc94e75b7ffe926f668d51213993fe39400cbed495ab13350c2028153d9b8f67707fa2028269aa356919b439b4e653e55021c5abb24ace804cc33edac36ee037ff72393c5a29f8d3495a8bb5b1135ebe8e2966a4472202c2f9c802e5fc0ba01e551964c5e92d8e4bdea64b63c593dd52f81fe02a5b8b4ed1fbb9300579ac90fce48304909ffa28a5dbaaf3fcce95501dd003ae91d84f05d0d665c8392a314220eeb1fbf83b6906409d6b4b8814700673c211ba063507bbc57161e68a01e20517006f22ee4bc198c0d9e8a146d26c9603f123533f284db9506d19fe33769f11888e29578d90c0820b5a556ec97a6264910f2d3b13fac13a54292ee27a0fe0e4aa79c44844508a6ba2f337af57e5e344d5dc17c40f8c5c385435407e5636d79490945f0ac6a5f8a13acd61c10d0bf446e1e81cf595d1e46b9cbc6f9eb5d7aa295b62c0f54a52f461c8e626c7649f81bb8f232931fa4058aa75ea5ed549f857dac0b49adaa14577039f02b3243119b05d53ac233b99872d3675a8bf8228877a5a14187cbb910b5423e3a809e31dc82e92ecc80d9a298c94fe9859fef768021fae718e8627b182643a5aece4a823ab14f5c9d0264d6e19c2bdd24d928e2b67e4cd485d77c4eb922d42b0a58db95fdba75728a225ab01c812bb1b17123d6dfcede7860a0d8daea2cbe20569f6f52a524c1860a41a05b5ca27d6b195f07699ee1fe12586119f46314243b6d1e0159676450fe60428a94f172cbc56a36ca5101edfb59ea4c57aa926f082af225764fed67f471d62174ea6046eee3c5f30acc47fc2c11eca36324fb64012e18797b91ba360d7649a15c7069c3b142369923f4ecec193715a094d1ec51166dd88ae415269d9c145250d27f9abf8631ac89fbb45a880c4f573ba095073c7dc7db9815eef4f3fce7bb019f55b969aa0cb3ec360b14705926937ec2bbe3bdc37a55b0a780d53226085bf132f11b6087e33ffb5c18d6219ab19ccc5f9084466d2998670ab2e3cfe7e6448122ceef08ccd7cf0df77b65021a717e56aa59953d78e697eab50d960763a74250ab727d201408dfd574dc10fb918dde0a24fa61be0fe3dca2133d2f5271e23c704c8b56adc72302cde25ab56afe4fc3933241a887524b266c9f800911ef41245284a6c5723f9f4e24802203fdf45224dce5ee3f08185b5bbb4ed881133ed81f317e8a44153c581a1e28869b48e63dd7dc2775d4275d84dd6472704d5333a129410f07b10ef581d8126cde0120a1f14f4e4b61f0d81f0d88364baeff0dc7dc5bc2345fb3fbf3a59012ac3ac1f89badb0730c83d1a19f3511bc6f37d7b13815d3309df10fdf80e914c251813aae350b7116dc63d71659e5497bfb8d4010bcd3ceb11c403112c24e7dd07b5bf70d840151ec0aa6cb17b28512140a8bc133786fc601dd3025e0b6852f1533f24bc7ac931076703cc8c9bec73d17740259f66054d352ceea965f5f992ce0ec353136d496c4b00eb9a577bd96267fea080e76023725228ffc6fc256598331478f366740c1bb8acea00b4c866fbc355fdcf66e6085e691c29e033f95a7ccc66e8bde9975a26db8a3cd918d1c4668b26c3aee106fd6c532d97d6fddb106d6a6e5ae72721bf655d17f117930eb975ae18f93713178ac3500be30edad0bc671bfe377a0459f66270817516ad141658fa900858d38f3171991cf90409230996b193b80e09f6dcc3f8593866ac2e7aed13e2d047f42b9e95462ce6b49ddaf821af5e20b7b0bd48a193a8ee2acd3fb28e4e43a3e947aea85915248bfe8c192530676aae57fb5c16ad9fa02e276d390caf4680ee7eaf831cd30a2f8eeb51f179ba6684883477f96aa4db9411f26ff7b660924d5aa2431399ba3d21a71b3a496e0707d66ec53847b777e56e467151967f155f15471853158f467e11872267467f0878f9b474b25ea29aa42f55358f4c334d6955e16ad67ff7fe612f3931ec6fb062b7bde517bbdbdce100682542e66f47a57e1ddbf32735942ed22eb95440bee718132a5af75299c75cf4f8c22858730bbc75b105c127ecc0b9a814e378689916333fb0fde87908f9ede468c9f730e562372fd874b02769ea2ea729e84bebcddf16f69b9a674cc8f49284e964f698e4c299bdd6d6d5c398aff63f2c3004719b2fb970165aee0f9b90e776c15282d3f876e52ac75fad455656577215db9c9e287abe3a1442f31aae7b13136cfa88fb144a6557523a6b1238d90ad62e9c2203925990843c7621c69ad975d03b4490e9b76682b8e04ac4ecca5dfbca0005184a25d7c3bd6028dac0c4e9037df0e21639b600fb596c8a639ea0d91084", - "public_inputs_hex": "0x1a207628cc6936816ccb62a7b56fdbbf8e975293b677c988644e018fc402e4411dfdc7cb6265f100524012f038ee1f205bf8789b70b46c57463dedb4998f7ac8000000000000000000000000000000007ec37a8a654c78ed3a8e7c4d6e933fa600000000000000000000000000000000520f0c28dc3d0e79e73f3f0ea8d8dcc01806572c19ee2f3532e970d617919b3aa8cdc582782dfad0699badc631f7512800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002195e6ee0539776806e913a0259520d29c75b0f8496a9cf04f16a674bf1ea4e640d0a0b42b415ea987c680bb2fb8d9891c2b7934374e865271df6f818d1b089c32f3529d2257b7810036b3d044fc8c3ce879ccbe2a182cadf83c8ffc73f3bdf9c2d81dfbd8a3f5f3a78af2e6f5ec2028880040866451dd20e92f58bc0790494860000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "proof_hex": "0x0000000000000000000000000000000000000000000000046462eded1bbde4bf000000000000000000000000000000000000000000000009f2fdc2ae91c95310000000000000000000000000000000000000000000000000b2ea26392102492c00000000000000000000000000000000000000000000000000004afc85764e79000000000000000000000000000000000000000000000004c469c087756779e800000000000000000000000000000000000000000000000d3dc12dc70ceba4e500000000000000000000000000000000000000000000000d09dfd159e66bd2360000000000000000000000000000000000000000000000000001d0742afdbf00000000000000000000000000000000000000000000000009104c8f5d91fa2bf500000000000000000000000000000000000000000000000836b1660e0992fc3c0000000000000000000000000000000000000000000000031380efa2b7ff3ce80000000000000000000000000000000000000000000000000001d837d5ff0a5500000000000000000000000000000000000000000000000db4b2385e73fbe68b00000000000000000000000000000000000000000000000e4f7388c73406d61700000000000000000000000000000000000000000000000e8623e231e09b7f3a000000000000000000000000000000000000000000000000000132c5f9251f0e1f3f0033a84e44de526080bdfdef3d2c4e9be76761bf24b2d0e31ac487ab12b12f3b1b559ce6d9ce7c97b5f31aae0e6529acf2d329858733b8c0215b3e3314c51eb174b690f7d4f832df6574dd9e4bfdd41cc47539b0301c66a88984cc0347a312b53b6e93390fa5cff0fbb34c9a55b3bfa61ab7841f31779fec5644f07e5f9c17d0eb1194403db98035cef3a0d47e70ddedfbfb47bd33c44fc0f7f4921d50180f10ba9e083af9917dde07ffbe45a53e65983092dc0f35e1c84f92b7189595390810dddf63209c3f94b856eae91571feb451a665cd1a1035b990c887ecc43cba2433686433d56359e92f6bacf0501d6d7662ce00d33aebf14dbe64164c036caf16519af50842a2f1478375a011705e1b6e3cc8d1427b463a370002e3a70896ad27996d20e8ff12e70916e72471a5dbaef7f378d7232905aba8cecc91e8c077c72a272b8af0c6bc297b6b8563e218a17a1d510b271bf12dd8d3aa90e56d6519a32d29635466a7bd4dfdf51438ccceb51d486058596bd2e201d320c9b63fe239251273008b48d56b8a01c916ad52aace95cfaaae3232df4a22af016a830f00b2280dc4090249ebc6c4f4977b3d2f3e92603ca1e86d0c65fb91f757747e5b3c64f229c98b47e1adbdae651e8c93d1b5e22b26a27a4004b26a4cc6dece078c1e1f420e928f4810f467c371466a5ad780e27de4b7cc7d34c4bcf9bc4d1a8d69382b5c26a661451205b6074193728e248ef7585707509e09eae306e78710f1fb3288251b20d0edf70c5a978f32b92a869fe73975d955f5281b23392981a6f19a2f0f58271d29171ed9a120fb70186dd4f920f56f767149d663c103ad123bb86de7d5ab1678aaa63cc7e6039049df417f336220a6115e3e860bf85aa96a44d22587562527980f69c45463860ee9df44482e0f52c5a10107aa1bd1fa8a3ffa215868740307471504d1532b017118223f73cec88bdb2514519f0eb31f1c14aa42fccc0d541f5b3ea28bd0f5a00d90defbeeb8e25bdb73835f623fa6bfee1e5ea3032e649608c59db0e1d6d963d552f4b4633c5767b0e8bddd6f350c179ed032448ecb129c15f26bb9e1ca1f3529eef918c8ff9788b597b6befad98ef7b67aa0eb9134d5b4210870b8989b0adbbeabe3dfebe6ea30bd7d60659f4e39e266184b8243691b2e0552aae18e9cd627f7c5ec8693c5fa0d609eded0c8d047ec9ea6c224418278250112560148af7398eef1f9dd48561fa9c0441801dbdd69c8bd46e5c4af99ffe4109f7ed54f9cb7f83e617e6b2f93b922bce7e1c07151e676a7ece630dd747bcc142aeccbc157a93950eed8401606e1b16e66571b7f8ef663ccf4e93fb82995212bc9e169ea1c0594356b171e607e54fc02180a1059edf5acb8983064734fd9f8209e394bb72f81f207edcfaf9f2ed0b8c0689b85c47181abcfc8d36e08b0c5ae05832a231dcb4f6da7f1ff2ac341a08b210acdf2bf48f315d5920175f3d5cec908b4961c1e62cb178fd865c31734ac843d19fe58175f36c3bd84dd20e8069f6b0a624f48a0e47210130d1e6697959e2fc351c28cdc4228fe9fe37fd5abaa897e269391d032b12bcccdfe6c5b4157526f2325f20b13b8a81f8ee1492e0008cc770dc6aa7a1be35998418d9d54e3eeb7bc87d1ae47d20d47645c4973519428a79b15d9121c942e03cc0ccd93c2158ec1e33c2b59bb09173e7b7c9662f2baba474b01d7183e0570580d82094361ca63a614ebb95d875ff403af22cabd79cc4e8f400424df7b0904cb8d32decc3e907e6a0b511bec4ce2fe9331afdf13b26b4b010b0b3f755f1a444cc372cbbb860872fa0493f8c492a7f23867ff93dda7b637a2e90efee7b19c99a3476e8f399e1c68944d4bac5b988ae883dac4f406ec18abb7ac0c45dca453ba41f5b9c28dcca7905816fc9600d2298b31a000bda8fd06731f6f26b119a02a7fc2a4ae33307e9f2798f2502ba08f0cba86fa82624386a8a7cf1a1bcc4583eb73ca4ac0b5285cef38d0a4a6302218e5ff0184f9c9908eca82c01a26e711272a4d2dc6c57ad49857ecb1b4adab7807061cb4f5edfdde5930058ee71e8bc31fedd59323de5af8e5713c7ceb21d48c3f73611979e7e2712684589c4711f24d5a2b83af9233bea1cf2268234eac399129feda79cc48c5cd77735d9b000d2d0a40be4f80ee8003228f36bd9602fff33d890bec95c8c23e00e7cae127c41075162afa6c2b18a81f299e7eae0f590c18989adf321dfd19a846924be23a7918c08a3dd27cd71b39d3e3562973ca37074ec000f69aa3824710bc344bd5ea292ae856b14a9a14635879ccbf87f082eddf2dbd240b9fa4702f5fc5d88ee72b2a096808a9862cbf8ae30240058d22dd69272b346467520339fda95a07f6a8403325be8b2eb1744fef4514ad7158c52488019b3eafa4d8cf7df694c3bf47c3a59c1a1fc4a36060bc9af14ebf2eb79d07cfda2528c398b145317c2cc84de6137d44221226dbd233ab0c7c0263ddef6811487072f0e218b94a6c72fc7e39bc27141401b11d54d55aba111509422d31d6cb92dea31534ace379c0427c6c3a92d4f90f02251ec156fd36045aa90adf78403f49e8479b8ca9778326da1425427815fbd6274bf80015317e3ecdca8795d60e025b58a56dc77f5a4b9acb453d3e78deee5c14a5cd11d5d16c49fef05d37c81548bcc270ef9f21a79b449a30d540ee0b9a8307e991ea9b46f57eb69d7c1ebe87a8fdf4f8048b4f41d4808162f8e0f7cfb3e11a5c3cdec6eecb39b1351814f82418b9170740fff3f5681d48fce7310e527b6a03d5ae1282295d1a77b314268efd8cac07fc028290a1c8657e45227f4b6ee63a24de15adf0cd89fa38336b41277d5fbb3dd19e281b815b2c0d2e9c3e5511797e00e3f664895d68974f56c39c9889994ab59f8cab6b8a8ecb2d703646a2236aa91198ae08f77f02dfc879c800f7c84e398ed8a43f20f2aea496b004cc58fcf4780b4966b97a4be732ee4358cbdfa657c322cd78549ee3780e0350374ea4a3d8900f6418d683ec071a1273be730e362daf287de8c335727321143ba40ee0115c241f9ecad9f72e9925e3de109f1e566341752dd39b45d512d2388dacf23b281ce3108c6659ba45864d7bb6836042b9097f20f3f2a053046045dc613ef501a2eea81281834f5e8f9d7140f709b79d53fd264556d70139ed47d25666027aa69c58e42b34757fdfd0b9d122191696b4b4bf4eca578e24fd9b844b34d73c31ac3ddfa801ff012ca13ccd6b0f5e561e0e6430b4be2b0b2d374ea129e470491ed7c3b13b183e35a8ce4cd1853425fe864158971415c7a65f11dfe69d5074f950809a122a28063e5d27dab57ae4f0b3ae9fc361028adf438677dfb5dfb988f8b8b6ae57a3076e5ea1a0b2f974df0ffbfe0a060e3637c3127776fa81d9b01ea0d657dabda901d6807a490c87e46e4e394192ec98b42ed04f334b5a1c0f5057e4a4c557c01a1043c947cc968219522c1abe82b20d6a4db9c2b388a24b71cced988a4b883a481d25e3810be1b850a40508bc3fa632b2e5fcff0a7b1a49340915865bd52f501f1f78f68122727eb366d619ffc67d08d4d38cf0e91ea66920699c3a2dcd37656724c44f6274e12fa976dcaa3d6cb603e2b323f8dcacbcfda2d2371a78596fd7e42e52e2ba095e30b7021a480d05dad2f2838df1fbbaf38e0df2af304c8b1175b80cb447d4e3ba6d26637a624eec49a5617a8380020addb7879bc212fe1c600425147bf057f7596b5f2c938d61049a8cd1889ab5c40ab9386a13aa7022d00ad42d0e1b1338ff64fa766858440e350fa32f5fc613ccd8b46c95090843de78343271006a68bf23479c2223f60d11d64a98c2865fd34f92e687eb8c2b7a28deca830818a2208adc5a09a52dcfdc04f6e0e53bbc232b046d60528ffb794238b3e64b5417d6dfa423205c8a1ab17a90537a19ef9e5931c33a757771a62c7288865270fa1762f450463e4256850c78446525c13e0f55928414615620e1c41fc5f2a62bbe2ec7351b1d969bf8399ed677fcdd79a6689cb8c08682aa486ed19431e2fea30326c3b9cb74eb998fdd93bd366ca54253b2ac0e213d65275b7e15be6f55b87e5326ae1e2391bfc71e63cb96fe8badbb8eea59b1bfbbc89d9fa9c76214c968a9ef10f4697708d257e2caa80176fce9c9dd0bf7f08db787efff22e8c50a2b3d851c03bc1a26a9f96c6c7519fa1f9f69e89d21f3a846cfe8c7825b32cd3b1df30dba1d5dbd1e3883130ed44f9f1708164d7c037f7110fc42a2569005b099cd6c03561737abb36b4e6751c47f50a1749e53575fb2d60336dfb156eea527c39e6044fd0fa4c2d07af7ebb89646540fb42fce47fe70faaf75eeef6741a570a90a39e2212e25594c3141f72b1caabdbcdd33ae4e941fe07d0a9fd16698c6856b74619a810c0cfff04663e7bee110fefa623a4db43fad9bd5d72829e65d9a71427a0d5c2f0bc57df006430e7edbc07970858f1f006b42a6b8e510300fc604821abe5d9ed612879f61da683c655452ab2d76bf7345c63299b02b7ccaf83dcb03552c5048020354ed28001f40fb66a7fc55ff272aa8de1844409e56a6c2a3fcb89c94218f082674c00541fe4443178d227abef44644d4abfd5e9303fff2e68bf3e1d140235910cd28a2f0ac9cd54303522a68c80e618d937536c1d26897c8f85b4acf4aa1060c9d7cd154657431320a619ea0a603fd86a52edf39d34cbfd2327b95c4d02dac2df4af3fda5d0fb1411f7499e9228e69f7baba0aeed7b1e31f6d17aad11c2c4a1fda0b5b8b20f3f9963100e0dcd1ab2346b12f602bc30a2bb453e615a7c7df7a1e209bafa91194c0e3da621aea9637db24336c649d82aed522c6ebdc9e3079df264481411d0846d3f82043e90df085bb974d2ff8c5094b56b0721e622a3ece1e2c72afee9cce381b1ab0a042d7a2a0d32bfe34d76e43ae24cfa00802038ce3482e328423d2b01a87447801dd0fc1e2c33563bf35c6bf7ee27f8ea7808e26da630559777a16193d3cbaa060b686fdf5756e395ca3c942d5415ea321930d9bc4581da056043fe7c53176c8fcd66835b9ef69bd815564d1bc7fc71c1373bfc03d480b7aacb1b71a1d5cb97773bd8420ecedb4abeac3762884e73eb9701d5e192c1e2798ea30398aa7c6aa711720294d914419284a52a9794048ad73895e21c73f2e27ee0aeb6cbfc1ed89266847420a154aa825df70f24eb1b1f0b958c046771a911baa6453cba72219ac18b8e31e8b660828ed849b2cd1b642f2494e7430d440552ccde3ff0ea6860a58505c5d2c029cbb4f5fa598fe4ac011751790e0e606322c13d130dfeb16f00ef3a180f8e4886c9a3f367a36a4b2fa215a60511d77a4d1ab2e1d16ee35174f5333bacc3170eb79641811ab8d999ca78bd6aeb778c04bfe4b022405d81d43d828fa1363837759b1d688f9bc423f18067c1e63e7ddcae77b252d284dd9ebdebff77e6f0f9e799a7461a0f4899e54c7d34d148cb21cc3d29c76292c218095c7386baa2c5304e29fd4c70bd82e6062cf160ebb110230f444c2d71f3187f7f4932fda50d850dc56b3ef554e7745311c76955f8eb116e15d3f72d81a267864acfa25775724ef1f370daa11bc9983df1f6d897ed8f040899ecaad740b34dfb34c45c325bd21c38b8634103c639653053c1a72bf63e3a074b437a978169d1d7cf13831621c414bd3d03f307e7bf3c2bc654afdd857c43c0e1e47c4e726f6703cf414f9bef59c90a72eb596013447cc3cf7a8576cecd1674d8607f22e0cf5d470df13e216be6c2e9e8ea0ff0a179c00cac95375a14e02787cacb54a901fd68c2ae7eb0ee85c442aae3d8304039a8209c8090040c83dc3e287ce5187731d9b8e516f91b73a1475c4c4077544dbd325ee6ccfa3aabd1144352afdbd457c2f39054b92189663deb023ab48081a80b8e28841188838be3981e7e93840ac35199412daa396d472a6a2cd403a92a8a80995a1092e99f0dab79a2c024258bff1009161d190a6eff111d46fd2e4fb3528475d89ed0f4acbd2a83ed488cd0d14fb2864e012328fa9fa8234ef040bd7e64c94d8e5dd435f85564aed36b3c59bf50b0611681a82df36ab4b44b009b20fe02e8689ea64df791546cd3db0588d08074c241768008e3feedafd677164b7e19c9dfb2a1f5b4c91abe6b2716dda9027a8cc020340435acab05014bc0b5fba39be8a5dfe4751ec0cd65a54e3acaf559fc45310001c7116a956f02d2c919493c1972c2a8c37c5c7018371de046603f943861d069b44e45d5c59aa7b8e14620ef592b42e43dcd8022db7fd3d652bc292fdebed066f0db566282ce6fd05432d2bf063cc05c2372a442e4eb1ae95d943750660e129ab4a81c1e699cb974b471bb885af4c42f8be4be4a44f6c16cf33b22bfacc7122532e5f78e3da9ca7b8da40ad3651c7a3cde3c028a39f76ee378a5df8e7122d117b0ada3274420f4169996ff8ad7f0644294b964bd02e43b573e027e56c31f30281ded22c23f5318715d0f675c4c697a2c257f22112738f30ba3d9abfab6ffa1b183c5a511100513b515950e7995ccf0f082cb5b0d62b57269e2c7cf3e4c0eb0bffb7398d6bcbc8c3a742ce3bfb2975d365c391409d9590552733692a1a62b7138cea6bdb50758166ea8db309d0ad52dd680140c79d82fe429ceced64c6c214103a90d27fdc490bf99777ffd713275db4329c3cc42f5d631a29af86d0e449a5298a95d8f509c980f7b11abc90efb5b6ec019ba2abe05ff97971e3cc4494a8d11c320855d17be84acbdabcc29634e10451cc55f7b18e63385dee746c70927b271c9d654c07f30212ba3d0b34ab1c7d81484353e7303ba8739122ea0b4d054c5905b3b6fc0e3482c9d1f92261d1caf129228350cf179238a409ae49a4887645050da8ffcf50fa4ef02b22e5cd98fee5625348f8dd72c4806e6a6028da2514e05314cfd201047def07dbac92fc3f31197a0f94659a6264cd08c7b7b1b73f5cc19d074a8c82be071ccd4ad401a5907a3816735668a55c2c2254cc94d9fd42d2990419d4ce2b03bae535d55557127ecd66370f5a555fa5b3d3255b0e3e18040e7c9718c174a2ecd7916c33c950e946764b5a2490252a6c8b4e8a3f4e4200160e12472f73ca99a627089fc5affdb380da4506c458ac642d6a8f151f60b09261abddb2092edcbd085881db6d5b96013108dabdc80a3b38fa8d2e638be26c78465bbeaf2c06f7a360a2cc343d6d9dc2db8f6424302e9718b8884ec3d80e99407770f64a15777f2da7f4aab9408e3ae2f3414bbffd74a290cd14195ef7fff6b31d737bc22f2a1491ff76b75114b2105c8ced13258e504a7b36c5102321daa348581a551729aa4fd8e5c8a411c89f29eb17ccbbdb02141008bbdf46c7274a2c719595fb7a2f5854c0f6543b85ec7221d1e15b8e867c80848016f25e72617f27e537cd721c05b0e96a7801ee3fc43a534ff819962b1e8282ed7fe5437df8026f12931affc92a45a488eb039c2285d69ebb9cc721d0c0481ade8e27986cc2424bd63f09ea13281fa330a68c69d21ea8262d617a8784c5c3130f9fe2638a28d3cdda46fa9c3f110d509bb1dfefd5f72c7af0c2d6cf3036ea02de515f6876005bbac8d8c51d6300ac7fc8e451e423e4ba7c25eb016814dbbe5a9847c8d72a519dee86749635c52fc29948f9db22787989d3d2630dabd1b4a74528011a8a2df437b4fa220098850a3e4753fadb485e012a8cc00da2cd5919c51598f8ff98d82ff813d03bedaedf2fea1e5e0c489ac9b0400e0d949e92115373e6350c76f49c345b79f2ff56bcd001b956017661a2f6b6b167f5f12b4894a32157334c60f29d0521166d13d15de9302c9b0c0bb5c7880f6acbf2acb78d97a66d12e898b7ec0d8c7322bf62e51aec263770b749a733e1842af368cb129d636ca2c23890cd74c44f69a427e6dcd095119e080b300e82de5fc8b08396e97d027b8e781f0cbd63489c5d496949b3daee08a8d6b94c056c4b29bdbefdfb87f715725d48e2aa2c8ecb2e72e4a6329b861c11a1733f664320f80bb5e0624c847534380dd10bf3b9d7b0b3f3cbc00ed6d8d72ea19cc0831d47f5142d7699110a13d154190e29363598288d9e87582621ec8518aa844817a33cf7385a4867a624e6d35d19f08689cb4694d9eb4068a5a89cee14fe3cfd331d9edeba664f921f4e2af1b182eff2ffca37f65408d3d1b42eb324193f27aea86433f3a6aaa1f0ccc589243d364a6d284678652a5727709a417c212fda27231f6e20f52417f284a5641a6d80362cf3dd402b81e6dda971168329fc14aa355a2874208110adb7169be2c53ef263bdf59336ba78618087e36be1f3c9277257150ffe847f6d86895f1197dcaa651987c33d02f5ba5b482311f817d2752f96ec1b41838e338b2af7008a0d5949ff9f18dc7be79ef5739211659f266d621462e12c1be7bc4b4934fdb8699f993d2caf7b1c570cf25b802c98a25d69b994073b68b01ae79b6a69eb211a914afceb059e78d2a636d304a48f6b05bc32993f1eb3e3e642813c85af8b5f1de0888cb13c53fdc5d5d479c70ccbbe61744c1239002bf3e62cebcaeb5e7303b3e12a1f30fd0db795b804875570e7fed3230bd8052871b4aea05a90ad7b63f4113cef0be4dda60693a503b2378172a58c4624a2eb2d5737e779f841a33c28eb444b62d3d23520278eb740181fda57f544f3aee7e30252680255206ff7d0a4a519afb865069d8a05da1132d795895891fa94f8f29904f4324c26aceac3104c97d39759801a293e22e65a381aa86de3b50159ca9fca09cf8f2106c1ecba895d547e1dc71ed0d9e8db14b9db90888777e29ef793b337179e7d5a6852b64d326b7fff80c4434942d3585902b1c534450562c6ffd93e8c0d157d7f9386ead62dbe90615399b0c8fb87e5fca00721a2a3fb68d14d36819b09983dca3f3792b69abc99b3f3f5afd33d28cba0999b8968f72566f05de37d660436b2b1da3001d81f914ab0dc20eb913507e7e526d5f96f842dbc6d461b96df2c259d17a61cc499cc16d0ecb910225076caef6a3d6c2fa282b2bf34a1deb87129450563c1427f7e543ebbd7aa31c01a16f7f9ef377414182129f183ba1246f91048c125b304aec8c136a897807710b6a9a73cf827b2c53e733c2eb1df3fb2fb0210be8239b1631db74571a2e30ff1dc5e77a7be632890b5bed671bdaf6e2a960bdf75213571caa91dd52fab661a9e4fa96e23b6d814748fc4bd6a032ca1cbc71a6e2915cc8eb694d63a591eae3f5aec153656fb2311b4ba339a7e871ef872702461a3b251b88bd55cea092af0c1cf7ec9f96f35e46cd359223da833d8e518990775f13a8a47b5df373d0d46a9098a0b23c1dd6aa76b286f6708e7713811025a23a510820428c3768c4dfc8965fb9d34ed574ea7ca0d2eb426afe5f8fe0681ef02a7a0700344716e9466f589aaee49d2860b604052684091a9e0ea23dc12661a18dc0d314a36ae8c600e6a886d4343a2b42f3419ba19e793ede386baaa303a402f234bee6497b9e517ac24aaab0fb01afb0b1707423554a869d1bea6a5bf743f11f32baa913c5500c9df9db6f92f49d30b7adb7cda475c5cbcfbdab514f391b12c3e705687b7ca3381e1363090cbf57ff7a86d1fc0c9b110893d6537435e223b0966964c5e464aa76eb466b04c4d5c7037f19229aa437e7e490d1bf9b19eec700cee2eaf6b694fa0b6b912b86ac7bddd35f7270baf326abc5234e02798047f6100dd53510a41dc8fe6369350a1b458bf042e8204d4453a359a5d107ad266d8bd0a393b07ac21e86143568945dea47579f961da100dcd17c89e800299d1dac22413736f9bcfe75432de59c190fec427a659e0878ae078596ccfca7ce18070706a27da89553eba9079775003ae2cb47c214b82ea2eefefe40da9638e912cfe60822ae649c2057442471a471a9f952eff46a2babf1a83e19a4ddab3794cdac562db101a09b7a278b2dd6fb6e2a9d393579c92db556817f6dbdc7733599f140cb4382e6b319f574a62dbebd2af4cc151e0773ab2fec6239c69681263c76fd83cff19220271f850682cc592aaaa6eea28d6bc0cb73007fd4bd1bd995a099bd82c024f0a2f782d188ed96e6e3bf4087c26d3d237a2b12204a57053ad307c344ae34fa4213bd4ee4f056cb822fe91f44421f97b9553e372050526ddbec9a9810f0b77cd05e7dd08def408b601eea156f02988103cdc6af0fb020894d84e00680fc6cb690af351f694b00379f093c09cf78cb4261d49857bea4be6f8dd3f4a7b32f59a230e3189ede5dddf5124a8caf0482b511ba11705b8e97b0ae8daff89770da3fb7e20cb1d9be77cebb1509930887487b1599d90171d64bffc6e929ec317bf7e5aec2f7740dbe894189de87675d7e978196e17b528ac35b67d091c6b144dccebf6770a01a0077e8d2c021ad57fb50a34e3158e55e57f94290cb6db1438e73c26dd5f03367dc364867a8930faff385c5e3805b3f5d61b5fe90d04e51b1e14373054971a06811bbc967bd9eec33c892ae9c4ef7274d5e4351abbabd2bcb803a5f5c70f02860e11bdf399a1dfe38969bf66e7da76086fd1368b68e47963db7f9565b3130d69008e708fc5f8961db2d87f77fe3aa722795a6c711cdaa2d4da8f38eaef83196be74e4cf2a837a79259f60cff97b4901f7f48941807f07784fcbf4db57ba80c897759c9ca3474d1f1c03abf18050430fe347a17998ad5498c2afd48c8a98415b32a411758c283b4c28c1d5ca62fc3dc246c62fae1b7450b168c5854c3f6f81f25ec496179eca02a8934cceb7f9ab9fc81a0b5c0982e045b5742e3d21791562bf709a403a8ff7f4c3d3ed483bd94c60426e37242a53fb501d465f6771bdd1c01e4364a0bcd48a20d4fff5363f75c85d02ce552a1c66784f8c393df093885b02286068214029f61fe4946410855f18276aac139a43f0b328f281a40f607dc65006694ad288bedeb4d931e515e3d739eb1c0cc95d7c5284f3ae66f43286a98a92480eb742d842cf382e23e07211c6767bc86b65efcaf58199ad5bbcfaeb67b8116ab42ba4929e61ef40822b47b3b77e1411f2f8dbc72c1e930cd08e4eb38f37b0bea7c2020d7ee87ea54c1b3696fdbc146aa584a23042c69e6efe33f7556c1762b3e3ba8c773badbf5104256d6a599fd5b20145beefa188347e080716230875a1f23ad5fd8f118d14793db2a001b386410d01cae4501bde987126554aa1f943809b93a34284851e0092f0ddadd9d27066c2568c8ebf91bbd5e1554c3ebbc6eb509d2ea1544f9c6b1dce97cd8584418ced461121ffcdec9e3dd1fa14f95d2e4ea08cf6a13709220c27a0a49bb9334e11e182a621cde1bde5f384b1b0ad51d19833033a68bb8ad808d6c9c7b07ab154119abbcbb85ef10d40fb251969dc2108c90221aec3e7930b31580525bb5871112787777aea6ad16614536a5a667520debea18baad0cb9f041125b510f5f1fd48f41cd60268c6ec4149908e7f9c9a1c3fcf813a79c776a2eea726579809ef20c267d839f3a208120f8736779796e9dfa7bca253a82820d90f4f5f7fb4b94a870394dbb688fdcc0bb429e43546b8b88743f3f114951cfc8e34a21110a24cfb809985668ea681e55f068d8f260436dfcbaffc91c32edff5ee1777d2ff82c80ea797077225f691258424cf5f5d64b3c312d6f48257b1ca15daafc6d830d0d38e74669add7907e870f0f8e896837af7e0104b15d103b7ad3d25f8de54bcc7ba08a0b3bb9f30e238fe4e3321a523728c554b162110489739821241ca55e4ecbcc5f6f25bfb44763a32e19fe977940ce8800db666317bdcdf1f29834dc6b2f07c60f7076ea4f4c73881e89734d46a31a160380704e0e4d0b30a60fae2bd517e3c939cf57682a076d4f6f364126bb2975f940ee8fdd0976ffd75e34827735162044bff075b04735ee3468e51d80a7a09bed81b48942205981554c5a78f504f763531fdb93d2861ef929be2f873e59dcec85631807b70484a7f6ec144b234ea375bdd48188e15ada0eb56ad1b987d75ed356bb65d5e01bdf10e80176f7be41f9d31008b8274f1b0a8e37108cfd37fa80c5be57fdc6450207b4b4209e983e6aa97e26c81afb4d57059de74276ffde689f883d5ed67abd2ccd9fbc681e291fed67130e43f755601bae4c8849909e9030484cf3c71aea07227f95a36dffb88c8d0131284fba36b597c0607f0ab3b1bb6c215a19fd80cdc92c0ae72660453b0973ebfa890718c2939f1207e3ae5ae18bde7208392f708a9a093cbd866e4426034e015fa66d8b9379509021996d41841c44374f7e4ec77a8e0bcb2604e0783d0a7981f77addc0d9d00dcdba536950e38f1893b138696ca20828cc6f843d63ce7c068774807ac2f6a52bd2adc3649c130a35b854e2965dda9a2a39eab89126cc289a350e2bf0eda765a5bc5da8640047bf12423fd54224d0d6053505f8e689a403440ad11e799b653515ad5ebadac03c3c17a2e6a40ad8b829025a520eabe6104fefc0349b83a1292f1713d4e56131708398777532728fcd2b0b712b5802b896281422c1bc219dfd0d9df3be491a20443ed5119ae62c468d2a22adda3834369b2cefc68f1033b217baa21720c4e4813cd4b52388f3bce5eb3e21ea381f1ee6305814a07f4742b52311720f7aa836766eb57fc2504d89fa68be03b747454dcc12f3b4434342eab6d95eb64c2dccd735983c553b76a009c18e6e2e8e783a9067cda1f5109f7371fcaa2cc1658c50fc513effba99a62f84479ede1c2afee100f375462e12d611ca1e58b350f4713e43436f8d44ed129d36d741772485b57e2cde8766e19956bf15fe1a3f715f462ff70e3e66911ca8b0349ccac50d0c4d59a71edabc1844172fce54e10bcba6f74cbd36c2c6cb8cdf86b07fcf272bbeae03157930ff5bf215a8fbeae00610efde36e8803758c44b71611ad8b7530f77a243c70d35bcbdf3d390e0e1f358a53bb7b271748486ae81cecf1d3bd7b312e91ec7312eb62f7f4465e9f133c90b40b5aabadf897db276170982e3682959027d4f12eee5a6720a6863c259d5569d30604063277d5afa87a04ff1db7cbbd8048b5f6ba32e0972746c42fada98ecc8139f01726b225c661b7131c962a6af431c46da1b18603580e0eb36b1b3a78803784752e95178856a52cdf293be7d20ca28afd91216a390b4c5fa200d13796d66a09e67c0dcad2e5cc6ba969843f35de110a26385709f5eaa333c3dbc672805554507e4492dd88e4e1fb37d30f00b100b0b73ef0f2805b3dcee77ee3c518d4d2b5e10a6e74139d13ec437fb20caf21f6b14258b219d2d7c874ff865b46668c9a98a6f95f7513c4840eac1fcd8c0d5983f2fc417fa028662ef9622af0c8b9a8370044569515953bc3351e7879256a1682f1913c6f71242142890f49878f50a88254967744ee98fc8f39b077df24f896475138b353992d43a58ab679391c189def4abb585c95fc78f8560ed1578334a54ef06c4e242e99763949a6214d2f1e6b2e0084716b5acb458f0132451aca43dbe9002d447989ac471fc47f751e9ab748f01dba71e41478c7ba6cba186c59ab2b22e0ae2cd982005400350f2b1fd5804f85a943324dcd60e01aec98058ec872048fc2a3aa79499437917db1f087117dc5627fb97486c24adaf4cdea43ec70a5ecb1115482b3eccdfff6508c76655387abd8db57b01b170d5e1870faa1384ba4365e7054331d92e9f9f0b1ad97b7b530669a248a4c26ceaf174cf6479efbc7da6ea54099c32957b8e46d8efb77b50d25e3c705ec7679f33abae0b097fd615c45f9d461b03dc3ca49cfa79c813d003bc5fe4ec9cfa94a1acd523d23b88d35ebb5a6e9a2fea8939d0a2391f5ba2f337b17972354cbee48f9f4316611ca4747267fb899a2c3deec9cbe9907b63384af2f8949c18e82d0599a14c794393c329254a5497322b8f054ff0c0c835e7176bef90eebe63c9caf6838cf97942832984d19839ab9a15ff2ee3f4e15a05e49a92af53f986375f372836e8b0871f4c4ec28a690e5e631d2dd1341b55a31770bbeb958ca73627dcb539875b11846f8129714880e708ee260d85ed2385e58c520b25866ee5635882ab26c9019999d6fbf748f1e799852121d87673b8e00f15076cdc02771e293c1ca49972b74ab8790702fbc2d08d89be1c26e413b5ff7ff00f30fdc45ed794caecb4f8e943d341f7ec1f9cbe686e8408302d278a2da6ae5b6328b996f93480f99dcbf66ac56244f780859f9bc5e6d61413b5b5724c4eaff5aa4d5113e19f2072cc4486528b4f864c0b2fdd8dc12c07f70799dcb83bccdf08e82e1fb2354171bec979c58e2dc82f05fb9018859906ff3803fc845ea83bfb869fca929234cedf2c7ed172a4a857031bd37d4a65886916451a01d80e3d40970692f0ab31cc53c737a8ffd3408641dd18b12b59d00880a1a51bf1201219c156f4687c89b3bba7966515c263a6da337c70219b18cbd7e1a7ca07f77287b04f62adf3ba0a00f903a4de156b402aa92faec6737f1be36050a4810eb34741a55cfc86f22b7e734e338f600f09df00574221e9f861fe9b68b5f8501ec4c809a6c85ede1d9410633478be10ac32a14f2d77664f050a16f2b0a73b470a99616c27a96a7b747c365d9191b9a0a92f96eb10e8cfd399d35a186ab9ee7f1680f3102a604fc69d1b1e5058113ac9d917ec32e1cb339755b5536e4715758f", + "public_inputs_hex": "0x1a207628cc6936816ccb62a7b56fdbbf8e975293b677c988644e018fc402e4411dfdc7cb6265f100524012f038ee1f205bf8789b70b46c57463dedb4998f7ac800000000000000000000000000000000c946233fabee579beb961008103a4c8d00000000000000000000000000000000026a297ba8fb72294cced25b49dae7c91806572c19ee2f3532e970d617919b3aa8cdc582782dfad0699badc631f75128000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000022b942ad53baa7c6e1a7616e4f0aa2eedf7d67eacc63696345525988225847479273fc58215abae6a73503410e8209a542c92bbea2ba8e6fb129c971def4ca8452ddcd2c1e0b08c268d942b52a49e7597873ccad9df1b02ea1ab854e7111eb2dd188f793a89424a83a3e746a72d4cdfd5702a7944deb0fa2ee04f84489f4af3c2000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } } }, diff --git a/circuits/benchmarks/results_insecure_minimum/integration_summary.json b/circuits/benchmarks/results_insecure_minimum/integration_summary.json index 2b586412aa..83fd87c6b2 100644 --- a/circuits/benchmarks/results_insecure_minimum/integration_summary.json +++ b/circuits/benchmarks/results_insecure_minimum/integration_summary.json @@ -24,156 +24,156 @@ "operation_timings": [ { "name": "CalculateDecryptionKey", - "avg_seconds": 0.004977264, + "avg_seconds": 0.003846458, "runs": 3, - "total_seconds": 0.014931792 + "total_seconds": 0.011539375 }, { "name": "CalculateDecryptionShare", - "avg_seconds": 0.02192918, + "avg_seconds": 0.021596236, "runs": 3, - "total_seconds": 0.065787542 + "total_seconds": 0.064788708 }, { "name": "CalculateThresholdDecryption", - "avg_seconds": 0.021156875, + "avg_seconds": 0.021793167, "runs": 1, - "total_seconds": 0.021156875 + "total_seconds": 0.021793167 }, { "name": "GenEsiSss", - "avg_seconds": 0.006590902, + "avg_seconds": 0.006900819, "runs": 3, - "total_seconds": 0.019772708 + "total_seconds": 0.020702458 }, { "name": "GenPkShareAndSkSss", - "avg_seconds": 0.013461222, + "avg_seconds": 0.01082625, "runs": 3, - "total_seconds": 0.040383667 + "total_seconds": 0.03247875 }, { "name": "NodeDkgFold/c2ab_fold", - "avg_seconds": 18.744304166, + "avg_seconds": 18.129162805, "runs": 3, - "total_seconds": 56.2329125 + "total_seconds": 54.387488416 }, { "name": "NodeDkgFold/c3a_fold", - "avg_seconds": 71.531527444, + "avg_seconds": 71.186463291, "runs": 3, - "total_seconds": 214.594582333 + "total_seconds": 213.559389875 }, { "name": "NodeDkgFold/c3ab_fold", - "avg_seconds": 7.875492361, + "avg_seconds": 7.876658916, "runs": 3, - "total_seconds": 23.626477083 + "total_seconds": 23.62997675 }, { "name": "NodeDkgFold/c3b_fold", - "avg_seconds": 72.106292778, + "avg_seconds": 70.951759472, "runs": 3, - "total_seconds": 216.318878334 + "total_seconds": 212.855278417 }, { "name": "NodeDkgFold/c4ab_fold", - "avg_seconds": 8.018841388, + "avg_seconds": 7.872269472, "runs": 3, - "total_seconds": 24.056524166 + "total_seconds": 23.616808416 }, { "name": "NodeDkgFold/node_fold", - "avg_seconds": 18.354963041, + "avg_seconds": 18.285537124, "runs": 3, - "total_seconds": 55.064889125 + "total_seconds": 54.856611374 }, { "name": "ZkDecryptedSharesAggregation", - "avg_seconds": 1.546223542, + "avg_seconds": 1.53384025, "runs": 1, - "total_seconds": 1.546223542 + "total_seconds": 1.53384025 }, { "name": "ZkDecryptionAggregation", - "avg_seconds": 46.937590542, + "avg_seconds": 46.886299625, "runs": 1, - "total_seconds": 46.937590542 + "total_seconds": 46.886299625 }, { "name": "ZkDkgAggregation", - "avg_seconds": 5.529830709, + "avg_seconds": 5.350731209, "runs": 1, - "total_seconds": 5.529830709 + "total_seconds": 5.350731209 }, { "name": "ZkDkgShareDecryption", - "avg_seconds": 1.262524458, + "avg_seconds": 1.001284562, "runs": 6, - "total_seconds": 7.57514675 + "total_seconds": 6.007707375 }, { "name": "ZkNodeDkgFold", - "avg_seconds": 106.358997153, + "avg_seconds": 105.400482805, "runs": 3, - "total_seconds": 319.076991459 + "total_seconds": 316.201448416 }, { "name": "ZkNodesFoldStep", - "avg_seconds": 5.945438833, + "avg_seconds": 5.041216854, "runs": 2, - "total_seconds": 11.890877666 + "total_seconds": 10.082433708 }, { "name": "ZkPkAggregation", - "avg_seconds": 0.489922708, + "avg_seconds": 0.408154917, "runs": 1, - "total_seconds": 0.489922708 + "total_seconds": 0.408154917 }, { "name": "ZkPkBfv", - "avg_seconds": 0.225607319, + "avg_seconds": 0.217929541, "runs": 3, - "total_seconds": 0.676821959 + "total_seconds": 0.653788625 }, { "name": "ZkPkGeneration", - "avg_seconds": 3.476952194, + "avg_seconds": 2.247310694, "runs": 3, - "total_seconds": 10.430856583 + "total_seconds": 6.741932083 }, { "name": "ZkShareComputation", - "avg_seconds": 2.514028367, + "avg_seconds": 2.368657458, "runs": 6, - "total_seconds": 15.084170207 + "total_seconds": 14.211944749 }, { "name": "ZkShareEncryption", - "avg_seconds": 3.844478227, + "avg_seconds": 4.003050597, "runs": 24, - "total_seconds": 92.267477458 + "total_seconds": 96.073214335 }, { "name": "ZkThresholdShareDecryption", - "avg_seconds": 3.306334611, + "avg_seconds": 3.246631902, "runs": 3, - "total_seconds": 9.919003833 + "total_seconds": 9.739895708 }, { "name": "ZkVerifyShareDecryptionProofs", - "avg_seconds": 0.080639583, + "avg_seconds": 0.082421444, "runs": 3, - "total_seconds": 0.24191875 + "total_seconds": 0.247264333 }, { "name": "ZkVerifyShareProofs", - "avg_seconds": 0.274481449, + "avg_seconds": 0.253021858, "runs": 5, - "total_seconds": 1.372407249 + "total_seconds": 1.265109292 } ], - "operation_timings_total_seconds": 1113.09553554, + "operation_timings_total_seconds": 1098.460620331, "operation_timings_metric": "tracked_job_wall", "phase_timings": [ { @@ -183,68 +183,68 @@ }, { "label": "Setup completed", - "seconds": 0.998652792, + "seconds": 0.9476265, "metric": "wall_clock" }, { "label": "Committee Setup Completed", - "seconds": 7.02109625, + "seconds": 7.02067775, "metric": "wall_clock" }, { "label": "Committee Finalization Complete", - "seconds": 0.003099875, + "seconds": 0.003086, "metric": "wall_clock" }, { "label": "Aggregator P2: PkAggregation pending -> PublicKeyAggregated (wall)", - "seconds": 121.265344, + "seconds": 120.643449, "metric": "wall_clock" }, { "label": "ThresholdShares -> PublicKeyAggregated", - "seconds": 133.793474083, + "seconds": 132.69507875, "metric": "wall_clock" }, { "label": "E3Request -> PublicKeyAggregated", - "seconds": 134.299480125, + "seconds": 133.201828291, "metric": "wall_clock" }, { "label": "Application CT Gen", - "seconds": 0.009636541, + "seconds": 0.009273208, "metric": "wall_clock" }, { "label": "Running FHE Application", - "seconds": 0.000061208, + "seconds": 0.000058375, "metric": "wall_clock" }, { "label": "Aggregator P4: Aggregation pending -> PlaintextAggregated (wall)", - "seconds": 48.497227, + "seconds": 48.434143, "metric": "wall_clock" }, { "label": "Ciphertext published -> PlaintextAggregated", - "seconds": 52.0973055, + "seconds": 51.995006459, "metric": "wall_clock" }, { "label": "Entire Test", - "seconds": 194.427969, + "seconds": 193.176080792, "metric": "wall_clock" } ], "folded_artifacts": { "dkg_aggregator": { - "proof_hex": "0x00000000000000000000000000000000000000000000000d40b640292037485500000000000000000000000000000000000000000000000e013a55061005862600000000000000000000000000000000000000000000000df66475045cf2145500000000000000000000000000000000000000000000000000006b402cd405e80000000000000000000000000000000000000000000000088dac7863b055694300000000000000000000000000000000000000000000000712fb7da233c478dd000000000000000000000000000000000000000000000003cf964c70e07b8765000000000000000000000000000000000000000000000000000038ac24735e980000000000000000000000000000000000000000000000064a66aa4d3608d2760000000000000000000000000000000000000000000000098fcfbe919172471b00000000000000000000000000000000000000000000000290ed9dd97921e215000000000000000000000000000000000000000000000000000302c7ebbb9138000000000000000000000000000000000000000000000004012de0ed8142cc580000000000000000000000000000000000000000000000087216e554f3ebfad8000000000000000000000000000000000000000000000003baa67f4d46f478d70000000000000000000000000000000000000000000000000001d50ea7c7c287165b7fede784cb79cadf6580912b2988fdeb42ce54e7f391bf679ab9a66ece7821eab249944824928fd0c02d5646c093a7cc8b002698234c635954b0eb13edd0079d8707715ffced9b59ead2d528fc6e9b0ecffec6728b8530f803d3d0cafc7a043f574f9b17558991193e7548639d892743044078e9b0c5a0597a413bcfcb662fe795a27f2c1b77b011fdc4eb28e4183f5862815d85ebaec975e8f9e07ef18e0afea638c43868fa1e25fa97a99eeb6d0437270d3e994bed93e1820008a99a7e27b4edc391188be914d9d9259d25828b5f4e971460619ae6708932b2cf0e602410899e95a26ea70acc3ac7ea8363c2d73149c06e14256363bf0e19751231e8ca1751822545b6a5994393b7a1d33419ef3af71f250e6aab81194b76a5c7de943b231b103523d9d9b07e0b8f9d4e9db56a08adabbf9b17d9c1e0945a3941379ce015c0fae30c484973db6068c9681f3d57d6ebd5a820169f6cc40e6e761c7d05dc09e051a03df59398cfb89e32b5f2adfa0e344a33e660fff996b08c89ceaf4f1a11c0530117285014e4b70f37e7f66dbee9061b6d1490044a1db6478ab1c83bc629c523f14f226267f1035b6b0ae5f7dd80875f6816d8121fafa448908b708625184955bcbae4cdce272bddb97dcf930b9b27a7d9fb866ed7a3bd86e883488e49014a2a0fbba849da7992682d55da80ee20d9d4b2c025867a7590e56d934557470b038824f651dce95aac46d204582a7fb23e3959539d525567952549f574fed80a0d06549577332ba9666fba17ec302303c4864bfe30a81ef5c0b5e7b5b929e91d4cdf3c912da2aa921c0a317c80283621b9342647a6e1e0d6d6371815803f42034cddf41d88ef437097c0cad537128ba24646d6ee133b0c347cdf22033413ee2e7b545f475e7e33ceb8d1273dcef965264c7101d1fcd72d8c6d80250ee190e516c20d3fd17ba136f1dcbf7f19b1538fa9476ce8a90527e41f8fac711649571a1a10fc31232a3e6fa123ca98694e2b5ba6cbef46915b98782f24070c1965c0900bcbfbadfcde3bbf41a763dcfc1b7ba366bd2ceb94456e642f244efc2033e7bb12f955d116cf1c177c29e80d544e0cccc36f5ab38418466b94191f0387c267ea2427805f1c283fc3b6abd9144308a78fccd63e0a3c6725756efe35ecc626c81309771693eefb56d05f71ea85cd632f17428e1b09c0fae64a3dd6126006462d9809beee4db34fe70bb3c4897ad267428750547cde8edcfee3a1ae1584588ab8e71b396e34821866574d4a120cb1300142d1bfc4ec060a2cf216399237c805164a11ec60e2dbad9cfec202e139a9a37ec628d1997562537c47a6e1c69b36ab12521cfa79c60ea974914538bd4b327171e2754d2fa4a1aba05cdea506ddeb8b0bc80d78d4ffa01927f4305743a2f3f3f1dad7cb96652482fa914ad41acbb2abce5e2e0e826c01a4c6adfccadb2088c9f41d04c2b68a793fca0efe33704cc6900f2416085fa06bce76dce6e76311ca08d5dd3226d78140d209eb32aef0d2fcf951ab0748f907fa2aab94dab8f052177f047f764caef711a635a188d82ead0b45e5a82745a02e9622dc0156f5569246c9142a879cbd5aea7cb5e6b26e88cf44b8ea4929807da246855d55d9e68039f3e4b7274683e67007b1ada2cf02b4a51e0c73a208d43d23f6ac91256cff142f9e6437c076b75efcb7d578a363475ca2b5772ffd1a1d9fb8353f4af4d0ae15a2f9d8320c2a1ce7a5ba4cfb9e885320cc5c11a49415a335cb2ae9e28346ae2d697c54c2afdb1bc813569566972df5d93d551570722f3379a24283d123d57a240382b10b5be5e33a4e49201d85200f6637cf593cd103cd4f8691b0fcff9b1f9a740279b56046f264b121aa0467d0640f481171d43f0468368a2e55a0d9a1cd5c035bf09c799b5931d285be5197796e0cd594ccee8b1838548852640231d0555f4218b9672e4d01d50e52840f0f18a48347036fd11d2692b21503337f42b31ae1aa00881af1ba79a4c4422fa20632a00395363851fd0e2c67f59c285d1eab34d4575a76ebc77d433681712e196684a0acaf2fddc4401b0bffbc1b8590c88682ee0cefe9a3c5dda489d01a2ae0183613b5bd9a1cc3e0297b9311850ff676074f55e28e05bc2c551d35ae3c4e95fe802ccb3ca84d3f231de83444928b37de079b260aa497ced448587529431fdfd7d395f70e57baa6b7283413219283c15b14be525706433e162e7f30aa177b7078d6da2ee21fa67adb15375bf599c207e38cce91d7dc0766d9224bcc32d863ced1ebc3420250c8b0142d4d32263331acb3b04381c1402381cd86f5a76cbf436e38e80432e516b4c86d2320a1aede18540f87051948680ee477668d8907a767be0bf4a1abd0af570af80ace408a697a201104e11e9167b153de258e305f0423979dc37be9e8be4d01511b0e89eb67264a8e65676a124682a356242f003d408808b34ebe19c752eacfaf140fa44e370451401924a02f97fb647fdbf9d76a6bfcd8a5f058bf9e179511d700f29320a7a6c2aa45ceb9b49e3ef68ac8863664e1d40b4f3805c0cf94ee859d12e0e9940ab596361cc766ea553dc2a6b924c7024aba394ecd8797f99ee41d7629cac872ffdc4d14225337da53af492a42d04a4892be7f3103ec1cd701296b842fc852842ea02c8868bff79d7bb5b8be42205124523f619dcc17505bdf7ba15903bb0cd9f3a5df2bf5b9a02f252385920c78387cc9182768a4adbb2e74de3bca2cf084826f4709c1b11d287de4814eb4e83198a587ec88db94f3cdae7e3169cf2328a74c89d801f5cb61de8c859f7805d8b7e2520ac13392e59784bda06b4bae1d22fc67dab449c57e4d857fdba747abde5e7da7a24ba2bcaa0ea10a69f4e4430c7d6c09fe679c525600f3ce2e1c4060cb28983b4832333d01e058eb4aaf7f7f12bdf214205aa6dfe8b4c6b5b61262b401a054de68bdf874d45992e358c4e37815a1ce0645cc90b1cad6f45e2565f6c90088960aa98069d9c4b7bff92fdb958111e534100bbc41390f4555362ce7ab9825eb22fbd4d9ea4db0bd3795318b12cd2de1c96d38d0baa60761771d3def691a0043a38dca228adb6736d9ac55e34c56044db1574ce259d7c6744cb87af4cf9fffa8fff02e3b4d75791f6d5755231db30e905f55b313af101d2065b895b085c69cd65d1523e27a35cec132932bf0937416b4bdb3f357117644e2351937ca6d05e5a9e840d5441c36f09aee5e0dc9618a231856bc58b7020d0cf923040d45535c8f7d9c670ee85c5266f61f55031acf4e12edc91db549bff6b8f3f650bda9d77627b3af9de1f9d5de6db8e998e87a8f672c2caad5095fec179a1cb7ddabc27a9a75f33327fb01e1242c779b918e227b0a23b5b1503b12a6729f9f870dde75836c097762b08f6615ea824eb2acbccbde070a2f64b56862599eade7ae5468a6312d27b79f6c6389295957d3df159bbaac760b8f0fd8722e85e9b9659aeec9b577439cf36618c680877d3870870f70d4d4251ba7bfc4fca6eb847eaa71f5144544854fd6c399fc2d18b5a5c05405bd2f179e0a889578e0c297db2845932bab911e76468fe4559ab0d6221900fd1434af038c262ad5104cc0cfc6d02b1d8f45374a84a9acee52382810369069e1db871bf7fd1e313c099d46bbbf8a3c4a3efcff8fef4915523311935bc3f166564a783cf1fa30130605b57e2803c7e3ac5aa373dec14b3166e6ab7eb4cc8fbed26b702651512052687a086447f5ddde111e16e5dbe1588719ba88ff3684266e92b1cbd00488161d933bd2a4077cbc73ed7a7f0f1d6d01caea02ead4928d6aed867d17c523a41059fa178a3ae010ea37c1261bda560576335bc48dbe1ca12c65003f5cc24e0b2082d8d67c62d8377bedab3fc4b4445dbb336fa0c043afc11477698ffc4465512d05d2bcd5f030bd1b84c7a10f6c5be35933f28bbe513ab3f18a43c740bf811d2dfea2e21a0b3865b720418ab4c64b5d6625c6dc156b428f8e7a25119a37da8d27b1133ca9c0f8b66a66e604e2a89b7f70e50f11335e40cee8aa649ec8be12b905bf8c5739e2425205ad8ace017aa76756a2da6eaf35a47cd1bd7c6540c3cc6d1407d2345892f5e448bf45af7377d229ac597ffff7ff43e0c39cbb5a97731b5b298f63adcad80ff024cc4dadb60ce5ccbc03378be66ba7ab7332d39149adfc2a0a9af606c4784cae59eeeaea3a96ced73a1afe40fcb6513e12a9f0239f19197d26ecc525c7858b3ad57349926e4fb4fc745085b39d5205928027f7e5585f195421bd609e595ba99bb70de80abc57d275a5149f283cfdf462aba7bcdc98d02bbf2644c7f498f3683dd927694e1a02b9827f76a645da15bbafce0d9b0294a2a8f3282f545d03147683a5ee21fb76398d2c66dd450182a1a1055bab5241ba157da11a8ff6cacefbed1f2ec7f6f3a5ef29c9003ec1f3a395e4fd17d552f644342f3d2713dbbbd0f81a7980f6b720e647ce0619dd78435170599c44f52877366b466f2051dd759ae7fd27d1c9941a2328fa859f62a4101561d4df222da9f20249531101855c4063f695155e6eae20e182d92843b3e208f5d950caffa49754668809862278fb0b6bb20bb911fca9f314edfe6049c679587320349065c0431d981302c41e888c3662240d7810ee0e9006ce4b725061666873a7f36e193e986933149db6261a52a383a620448302a5e8062a0e99e37e720564425bf514bbc74257c0151811ea75f770e26d75919fa3f3850bbcc9af993fba980871885dad8014bd61de782ebbeae1be2dba9c6c5faaae73761d0a23ddf30a3726a76e29a392cf311fb5cd18f7405b78f060da2f7667ccd91af53a7f6008404b1b1d292215a365741dc8bb220db90cb27a42ce95278a660fb5c02174e8fe4b872146592b4b1a6abf6dad0d23f9d408ca96df0b12024bba35f2f956942d1aa68daed91d2d9b7ba5b811b42d2d5997044e48dc3c6b7d7e84538fd19d1d6c6f07a3f6386e5d07b7554c6330d00993fe72cc27fdc482bde73aee8aad8a0c1a674fe9c80dc07f8142ed8747ffda15779aa83fa9cbead07b5ca8f34afa08f006defb94fbe26492f107574badfc081317a22554ad10ca7a09223b3a9c8440fe7eb8b3381989c7db31f9f1f1c6d0b305d3be09042a5f7212de5d680e2d91058d8df4de8107950e85d30735fca633d4278578f6535baca22e67d9e5a1dafae38c21918dcb8bdaa04896c176030cc934255e8a84baf084d8f826c9abc4b936b1920d518e214404443003bd108180d3de236e4e31063fbbe63c15fd875e3e8c51e1c0daa7329603851b07ef6416e6fd790f19b2370ce15cec25bb2e58cb9229a4eb68872ad054bbc092fe8347201518f4280eac24ec6eabcf3210f3892001e86abdbb7b012e15c1e539ef3a2a8d41a7a1112fc5e20a89fe3faf913be190acad0da906ecf689d2a414e1eeba10873b110d14cc312bcde9f4332a857b9e9bc12b0e5d1bf92803f516b51d47ee269af04ab11c7053ed6cea41d55c88d460a83eafc560484d1cdbcc3c98382ddbc9de7952e224cd829081f01b29e12af540a810162208c51a9bf9f812489259e47881ea9d871f2f77540287e3a52de99b651590547d45c89fc26e339abf13263863755f732b112416cd80f64c3b7f6fcad8b1f9874e93ed7375f004139a9fb9e6bfa2078f661c11f3f73c844fb43525e0c1fc428b2b89aea4a65c36fef3ad29e3e5ebd4983f1cf2e58ccd40d5f1bcea898e59b3e9ae31d5d16ff3c454d8ad9938f54b0bc35f2f169dc6a400cc793bbde44fd0dcf3c957fdf8605b1850140a0b48bd0766e7421dcdad2ae3f917d125a0fb0f843f6b62d0f046d37911259e3e9a3745138e7ca629ee29e287c714e3837294849a47c42f20e6ac74b07940e7d42ba6e7711764672bf5e0ced486385881661cdf7fc0bad18b573785eda1dffd8cea52e1706c02be09486b233f886885c4a20ce9c592cf44fa1b389af571ad7c95e72f8bff07d43e176bdfa018c7f722c9c860c70246325b6922b4ff045828e0b553fb4bebf827601fcee22dac4cf38fb87b6fb13e316b9d007be667f7882456e806ca49479028062c6960159f49bde5e5211dd8fd000713489cac20b1b9bdfad44fc9292edf11b4186796d5c2455297c9bf3aefdf41b169dced5398cdfaf6c71b1422c8374ba40912d19a6b6765ca3457101caa863d21911edf345b42ce8610b4ea4bb1184943d51c6d8f5ffd5a8ee22a980b1ba379be11be1b35dc366d4f646c9e995dda08bfb52457854d4ef3628598fda31c9c71b513fc50b0f52c5607d16d4464ab57fad4ed022289af86c813747386ec1b19bb900cdb368c862a851541b3480d9d445783ea0419adb37416d3fdf66906112952bcf5ce14dc0afe874d3a7d9f3eed3a1dc9fb0a881b3d22c1b9537c0d54448b252e02aa873d7cce22a8075eff0cdf83b1977f1c335852560b8c49ef9a3ae1d38798b17e81e77d2809bd55cb7e27556ab8d27d2c7507f1e664680ce460bd6e8b19dca256ade4d65eba1d75200307a97785a9fc20c84d83608551ae6a6d5af2172b04a9560e47fd90987bb14890ab3fe786607c17ed5213a37d661dafe7de8f107363355c1de7ffc6f622ce398ba845f127233b2c120c8386b86e4f49ec6ec694bdaa95b8141681393d1998db9da81e082a18422adbc9ba48606955f943c98ae633f846c1a86a226f5b12bb59eaf43f432e3be6110df3d8bd26a0ff66f09a3c63a69309bdc3cabf2ae71e7a8b70ba7676b8a1a70dfd6de933dfae3fab2d67c3282d8f3c5067b477e46c80372778cf190710803b2e951f3369ebaea902d4559485ddcaa647d17d4083e017af5390ca90b32066472bc8fa47822c331caea9d551c96fc99eb9c15dacd032dd11896eeff9de47e88a0a9a74583ea6e0f2364c38120a9b4ccb6bf1e776a41e9d79ca097d0fb95160bb093ff7978d2ac276d93a1cd8e35e8e65fda7d5d1f1aa0eeddcc20b3777247bf811174b750b8b36c0de073e3393fe0a8cd71d9367bdf9b431b9aa898cd1015435101ec5802f0d608f8b6e73730eaaf9237bbbe9cc2e627a1e3d6bb012913333410697506a77dbf86f258fe7626738c00770f395ee096f01d5d6c61ff310684ecb16e972eb20234f959deae2bd77a43bf0d2e77a4eca3a27dfd1fd3a6d26d3dc4406dcf993aac3f0dd29bd64107f4479eec6def086d0d13e3ef04be1522be6afa0166311e191055e0b47317ebdb1c715560ac3573a0a484380cd1aa884017331a32ec9542cd657a6db0a573bd14989837ba91ce99f7da2bcd5595d2883cfcd5dc82add6e730ba39e41aa4182aab9251b6124f88e5042521d47a9f0e74adf5d0af0219514b2b36879cec67d23d7856ef85d93b0aaeac5a4e0a40ad0e70c848b737117a083dd9a5056c402a8aa4df6e636bdde0afc2108815ac3c865eabc49b088a60889d3c65f322d68ac11b26a5046d9a7fe3d229fca7614e2dfd6f38466834fa909370753af10e464a8fc648e03249895b862d524f8af9f68b37de6125e6e75c30ce4eb722604dac1b1b4d7564c11d5e4735be16cbfa53ab4d0db0def3373ed690899de270812ce3b89359eb92962b32467246846ad9f55ea695d1b18e2735fc7210aee7a94555795d267f76802117cf04507137b03fea99f0e760f9ed5518a752cd5efce3c1d40b3513af08d8c24e0ffe15d9688c12e781bb8bdcf837f5811f30a91fecab860171942d310a512ff8099ca5429a40cdd18c2008b353b3b70e74924cb1183c9de09b0629eab79557b9812e5570ad31cb1aa66c4cc89845500de602a1ab9a697d09da65c89faef7840004d4cac64c321f7ef83ccdbae89eb6bc4471d70d7698d0e5e435bb5d2f7db6061bb080e1722acd4d3ce37f25816a36cce60293fa06fcb41ec2cdcdea76d0148ee6419ab7c56ef432ebf0b3cd767ac5ef0e8294be4b435c739565dde491b7eeef6a2dafcb9345d4fa07d8c6afd28afbe44fd2651777f1a5f3a78efe4e0709870a58e5e4f33e0e3743e3d671d0c6e1e313f961ca9cff93e4f8a2278448ca0d7b41fd17855319a24b59f1f3c4fe3212e97c0eb11dccc2026f1d50380c36b70334ade800d04a6448ab539c21fccf068b642085505bac178f565cce415d7a074390bd16c37b9a28049e8d112ed1a3c32ac30d15f1272bda3091e1dc8b51b3b613af57f290703c505b36fcecc5a066ef2abff4c1f2b4475402aad821572534988b8c05b3f097aae8e82f9fdd316a5a7016b859b6202c541f40ac7edbda0b6b1acdf65b761b91a66ddd29417c8aeedfc816203b9032c35e6a0276854b679e1ddc47b41a21b6d71399a61fcf51f097c08f92cc12c291fd2dc48ccb0de0be133b3fda6c6a9bc04ece8a8ad02229fe99ad035a28189d82f35fd133455d9066b102b6748b9d4c2bd8eeb6e371e1ffbdf4b1a6c29a3a0af020e5447d7bd790db03e89fdb9d262547862e6377a32ac02e0b91d2ad44b992f1d7db08caa3656fdbc5f430448877092ede59bd105068b6d8a8bd142a70894ab2b56cfd0292fea152bcbb1adbb117ef968dd60c1004511229e2180c98abe417d11cdb19d8b157a54ff930a2466a77ca6ddbc6f2398f4e493e6b7c097e6744a9915147beb465870e736dbd8ade86a5e04a3db47f24ba6da3f5654560976c5b17108da7ecba21da62f07243db9ecc7a29fb0eb2d28cac24ef4dde738262eb344fb1f5a778138d3239d82f1471d79ee588cae6334c52bedafbb4bcebe375af3054409b9dd53eedad4de84465c599e3a1eda867e61cf569a91af0a8e2565cc51077803e8ce6de0303602e178620ebcf84f3db7a24812d0a91941d231c354efbb7e0019e71ff8d261ff37e057cca2d17ac2e5349433957d92482a268fec90364e0615020792754f75f88e1751288ed857ab891e46a9cac9359c0cf5cfef219d8ab0541f2914a2a6aa019aa28621273067f5902de9d27e6c1ef74bd7f804723359edd022cee65b9c003bbcc30b6f6356d6ecad3965e1ad4f6000b85e7f7e32dfc1bc6b189d93272340775e676389912e49f9af4a10084d8a75135d9adfa17355b90e921b85717e5f723f1b3e8bb9a715c4b4117ec1b26d98f049bb0f542f3afde7fbf724984c7fa83dfcb6776413479e3b15379d5a60abb3db4d713d711ea444882fac1c273f60643cf18588dbf67a37c04ca5e4aeb4a5181ee696e48f33a1b1b8cb231686d6b58eabc9155b6f7bc8b337db2e6646cd305e0f95fd127432f2f67e2af026534963befe4d398caaba921a4985a51a714de7df682f049be356ea06a0062f02ba1a5ff63a970e15746b52bced960e42fb975d1f0f245904f307a07721416e0deb72a9420179cf205754ae147ac0c1d83483d7ff9db4f5dd528b409be937e929dc59c6cc9c2507616c1f1b74d6c1837a325055f480d05403e240d9ac68229f27d3a7cd095d405e8a241b818fd860461e32a183842be658cadd13e7253b2f0323bda80349145a1337244b58a19beb039480ef9b5c8869dc1778e06fda4485542954c7673c0dd74ed185afd0ed95f3b2878758b138241d848bd63acf9114065f2e38db9edc0503bb0b8ee56c651216441b7880053ff22c93412eb6627b5146ff164934d8690c715f3ee9f3a860be67a8349f7edd84d1704cbfc07a784b106dfe29d070ad43834c309a93d749b52d2ae801841ea7e5783c16ba497a8060617c2a2815f8e1c7a96a419ecb11e1559121be988da7a6c27bc26e65e165e56162a1fe142512dca4b4e59a540c6fecc77b6a7381910399d96d6899824253ea7a106f9c10a475b11f52fac6ad0ab92131cb7648ad3531ac236f8eeb42daada27871af6b06d02e6fdbed16a2ab65c1cb726c47497f235fc1f4aa4c6bd712150c6fb7ee81098cb22f4320849d701bb99aad74afa8ef395d7b105eb62bb55a9ec69baf15c30abb195977557b71b92ddd465370d9a5a4b75ce32fb6595177329fda5ab42df123f412d24f70a495a3c86c1731ef55c38b14352ef2579b6fe98fb4da693fe7c10452aea04254fd8833605ca54874b751da26650517ea0821a196ac7b66e10bb604d8ad90129bd940461a0c2060f4799e9bd70d159594a704d171dff7587df5161036e9669db09c1cfa469ea776b520c34e383b814c8df6fdbb0780fbc272e5e8032be0857f253f2253b5aa9445a0ce0e517d3599d810dc9733ee4828d091282300917a658e9a574d6d20743c899c8885d8cc7b23ae994960341872283a9d4da5081a1ecee62447a303627a1969e9ae73b2a1a7688d8541416f49f96c975d9cbf06df0ce599d84803e8b126fa6fb9696fc9099fb4341ee10c6011506f796e5f52286a516f77ecbaeb27c9fc0cb693fa24e06cac55b985d62ba53561233d1ad53a2d543782a2f4eb70bb7ce83b05a9e062077857b2c7749e3dc1007e845d15f8be24eb493d1148b440e56c0841f8e2d3c6a442c8f0175e07b2c9cca522a7e3dde3105ca00de87c294517b084a1780f9630dd2f114d36f149c02dc92693c5f58df80d052c005ace253ed194b58606b5cd08ea81e853f09d4ccf57d9fae5aadab68a00d6a792c01cebce5c049bcad0170884dbbe0121ac65a43340a0547a124f13d3186a6406fecf35e4e2b64fc1782ddbba140ca62ca9f42c59a607425dbc365378095e4f7019e382f9195ac59abd6857740c46f847a82d50943fd2cc60564283bb20452f1bc58eb3f4859f77acdf67a7be589907845303cd316cda7f8c37de528d1a6e7fb3e3aded9188bd2379fbfee5e0a5d13456ceafbf17f81064c5e3d7511700feff703430e2c652d235f3938cbba7bb05499f7de8c3ae607468d69e443756245462ae438bebb5529578130a6e20ae725bfd8d11858d6a60404f709d6080950583427472332603fd017fafc7c088c316e336c7044c79731a335b2020948c341cf08823d04902110e4f9ea8abd1f46bb50a0afd1e4462899e69bd6ff5d289fa13a052c61f50ae765b9271f5b109929a59d27720683335829e7e3e05e01f8066213f99cf2b030903f5ff09e48bdbb5d916158df9e50f99727076a645fc515e0c174fe49fb24e33c79c49be6fc3774520812b72c5b31cacbbc295e35d1813da752020ca170e31fc3f8c34a4e62a4edf4959ac76c316f54f30ae118bbee4d1c8c01c65e82dec54f4702d8754f7c8c05c58b066f1fa3492406da04e163fcc17affc1f1da2b6753203348de9d462598624dd2ccd4078b699b51bd4e6f28a57ab86011157f4acbd8f16ad42211481ee64e4c83b92d989f94e8bebf7db23304fe512e12002529ed325dcff472c2c80c5b189141053804b55f8a7696657b34cce04b7371fec396ef59641d03d83aa12cc633eadf7281f591e70c1479d480c448f1f2e372839151cd1a94da661d1f15104e2c22cc1292adbf0bca846fe2d4103a7b0b32522ce9997385b48cf38fee7766c8a8936a7adf660a6dcd2153bb5da514fe7b5b0144abcb0142860d4db639824d35745387e8a8e95ae5b0beb30a13fae28b7c48022e2583b07d03788848ef1f114f04834785e6304e2b062c352cb5d4d12cb331421f00f710a669aba85299882a43506c74b318f518ea9f0e14ff3b79511669b4a21ade3543dbd159a5e9514e42d02ff723c6717c8e0d1dc96b55ea6e4972aa98d2fce3c1907bc15c676937cbf32f478c66160dcdbb1fc4077d713c1f0ad4aa47214f5588a06d6991250f5d93342e2d84c50fb310d8834187b9e83a5e3e230a1b31353704f758a730bd6446aa9d1419ad87abd6326cb6db4db5fc7e0943b111b4b23fa577ce023391f673542e44ff057655b5bba09f3b5486cba48d90d7a89a8800cc4f250d9ed664fba635986fa2ddbce7063c11d5534abfa1918e527be32c77f1d506d3b821e512bed75c3831880b3315137536487ecef20c2b64e0f7b1064810a5a0f74d19b57d8409effc46d5acd083fa4f1d861c57e634f50750ad0f574981b32aacd0f657d61d28bdce7d06b4cebdcb8580a289e992df85f2ebdefcbfcef16e104ab6be6c95cb6e32dd0e6711b49186ec15cf3bed20a8c84d775031f79a02b145b9c08303b069ba0cd302c60da6cd74bcb04024f29855f2d34c1f6aedfb50ac040f7ad915bba39dea3ed4ab1042cb994c214740267adf97a299f35e597b000ffe05aff6d762e31a3cd90378c7c075982797e869c57e4dfe7702fbc5f74341b40a9287c35b2fa1f735de08204bc3ef84bd3df1c66b28f38300108ca8bdcf72c3ed1d7905044f754fea08756d150f6ac88bbab31228e40e0eba859fc68c8e8118b71069af14fb780bc05991194886472d0a44bba06024e5754763fa1e261941ee35cc2775bd35a66c65bebf7c5e31f15b9d5a7da70e7af7b977e057c8fde371dc6046aa71c1c14e2de124421be04c8111172efc4ef9dd4acc571c10ac5076c0a249d87a01a2e8888d6994aedd31ae0966558438d2f3ba0de1d29a427b14487127be3c66f1d3b1cbb945544767d4e95d56972fe5d7ba34b03a4f92ea92783932d128c1c4d0d056abc8f38c50eec5ed87798a594c50e6747bc9f308d9ab9baaa052f112758734b371e6dbcc2dc8aa2e60bcad9d001dce3d02de7b408c977c30b0fd3c6d530597a9c5f2b646a2b0f6f37e837ee8df3ce4d643e9048cc75bb6f6f2c76506e23f035f3f75d43d1a1b626f0f241151fee627294fac95006070eb967150fbce7e1411849762d1a55e4530301bd14b0976102f04b901b65d450ad4325289bc1c0b27140ec24f729a3950b242fb4aaa303cf377a8c4c0ef81407dcf3112b28ddd33f3dff292eaaf6ef3b1ae0743ebeae5c5fbb7780effcddebcd31209215c7b6bb9f5a53b98086afdfd7a20d791a37f8eb10fdfa552df4c019a929516b19b4fe75d6da7838e2afd82feb5c5cd6f46633b6d607eb9c08b032eab501565c2659d526e90a56a650b568bac4d8848bb16379677dc85e9faf844210ec753cae13762ab84e11541047f1536dbd56fd71c6fbc0575c1a2abc646db9b8268ffba416a5a26909f67da15e1772ea1bf516fe534c779afc8da5484b3caa5f0660381926fdb69d5d7e284f884c0a639c09a13356e417673c80cf5ecb6af911c7d58c9a1869705d7086dc7e897e1658403e7ec186d036d7e08f02ddc2be124b33ca9ab905c84e0f126e1651af373d3f02500262eceb24081a8d297ea3543cd9dce1f570233e05b9e8f747717545fc56b11bc2b6f6480802277fbd402bac8fcdbe2e715b206abcd7b45ec5b4afcb43da465d114785782efab654df1d44d8edcae8224b1407610ea653938dec4ac4bf3be2f5f78924a7132e9ce06f70e63f39cda0d37c1d06c1b21358eab23523f5393f9f0fea21dc073d27c99eaf5b22e2067186965e6e1e9a9fd6a6ec83802c06e8ede9abd8980a1cd8ba99e40cbccbe7337513b3e6c81df310405a333252c912859253613a95c74fa76a361b8553e661b51697e1f6910d6a7b89595b193ff7ed1ddb2edc081e3be310f357643b69f069c9109451e2ae3017ae984a8ff7467a7d9eb78b2c57a9485426fb5fc7bc9c356720288c21c8430ab70bd587e8a66af28e589b2d52a29b4d3e3608844d437c0ecacea2fb54837a0b5311cd793005cf078a8dbefdeceb82037176b5b0f714419b179066fc42e85e19dbd2cb19c94e9b33dde9624447c9aa6703d5a32ece2d36959b0658edac44dd0b7cfad9772bd459d3f9584775c68eaa3753bd3f0b62aab4364b122577484a41070e78095d771b31324ef881729d127e022f1725d3aa3be809fdcc84e29bffc2201ca5fc4ba88c7292dd57b294440ac99ef5bba25b677ad94aa490cea9a1bf1b00fb2751cf6492cc61e9bd683fe4173eae9c04317398446b11ceb90d83054f4a1970de0a0ccac63218601afcfdf9a69f49fbb6f0ffea39e15d515d33eb369a6f0edc03905488fba74cb5d3c428cd5347b6d9b0d3d02a7fdaa0bfca6cc97b164403cb6267ecadaae9efc05ab3992a2d58830fc5fd32bba5afc4500d21464925b50844113b0ad71d0781c62154ed6e9367915fa70ea40c8e061f1045473d4af5160f5180b4acd4cc9f68735ee48bbed6c4e572b3a77c9b9c82cf85624263c74cc80453074af8b24b850eabee47badd6bee6e5d3873ea26312eca837552ec8d3c042d9bc2c72c9f5956c9b845e3ba5b4901503f5dcc4cac00ec530cfc0036ef92be11db406234f53d6ca59aa9dbcf35d38a79da93b6ca7699e1dde4b2e1a37949051efc64042770efaa8d6420f52712533bee9e928a52b365025fe30d9f78375db21f5dbbddc49127312827bfb00018ac0e2d5786f59d588e7a47f0c52470080cf309859432f45648ac3766443cc411877e0c97667fc1b035ae0891c9de98bbf4e01971c3031b317d0382cfafd11bcaf151825bc8a1823edf5f9e59ffe612fec75f1dfba34c8b1e6ee61b09b85644b958d8104131775438292349be8a08b6937fb81089089fa26c5ea6ccae00ffd749e739852c949702ef72442605f3e73feb09161487ebd701dd0c58b7ea24dd0c18293b1cd43f0a9c42011cf71d3be8f57084390d70475bb7b72bdfa2c703ac5e7f1a80f56e0c0f817e4f07db8585b5aa84b43c22b359b6b6e8d0646dbc3d528b6fa19a3f802c9e16f3ad10b04c4b39458bc1710afe1ea5173379a6887702810b0b972218a3c47e836ccc261073d0ab07592cca1668ea695ef8d4a62eb1c7bd122bd0dadb5bc95313851cfe097945ff086cc532", - "public_inputs_hex": "0x25832c071d6d99ad42c7b885e48b10100726dc0de03c0a779b282c519ccf538a25fc312a5f0009edd084811b739bcbd529987df1cea36f5a245153866625739600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000007ec37a8a654c78ed3a8e7c4d6e933fa600000000000000000000000000000000520f0c28dc3d0e79e73f3f0ea8d8dcc02208484402633db0cfcb2af11d3b66ff07cd7b6b12c3a35e9581183e399d53f7195e6ee0539776806e913a0259520d29c75b0f8496a9cf04f16a674bf1ea4e640d0a0b42b415ea987c680bb2fb8d9891c2b7934374e865271df6f818d1b089c32f3529d2257b7810036b3d044fc8c3ce879ccbe2a182cadf83c8ffc73f3bdf9c2d81dfbd8a3f5f3a78af2e6f5ec2028880040866451dd20e92f58bc0790494862cc39c01748d1ffb677d7a2df15e2aa5674d56359e30c82c375055a1a837a49b" + "proof_hex": "0x000000000000000000000000000000000000000000000001e75e6e6bf316ec7900000000000000000000000000000000000000000000000e5e66538093519ec700000000000000000000000000000000000000000000000ea1d99185bf57a67100000000000000000000000000000000000000000000000000027a81f6b520c100000000000000000000000000000000000000000000000300a975ba6f13dbee000000000000000000000000000000000000000000000004a477a61f80a9e6d100000000000000000000000000000000000000000000000fd219fd84fb4defd70000000000000000000000000000000000000000000000000002fef0aaa80f5700000000000000000000000000000000000000000000000587f4bf0b7f2c5edb00000000000000000000000000000000000000000000000e89f1f317a49cf14f00000000000000000000000000000000000000000000000176869939c2c359c0000000000000000000000000000000000000000000000000000129747f04f15f000000000000000000000000000000000000000000000002d178d9b11de53f8f0000000000000000000000000000000000000000000000009044587fc21f92c1000000000000000000000000000000000000000000000009fdf61347211a781000000000000000000000000000000000000000000000000000008cdfd42e77de1980c43e1a2bdb19cf0131ff4fad06d6f11d7fd3279bd66410f2388c088f70a31e30bb64c3895db6a3301e9be27e79c07a3027013b0e68eb9f15025acf14e1972ecf23db1d3f80e4db8d3a8ce5e61a085775421a4ef5a8e770f626dc0fce3db71d0f0ff62f59bef95993de4ff0fc655fba80d47d6dd49ac6a99222e6b74f833800c35d744198e39965e71b95e55f6b34b670494a4279fb653f46cdce9e7d9e910a06fdd4d860032388619541f32f14d021660e8fa5497b849a703d63a58e930a195f63db46d4043c35c1fc90f21d2251f27e90874b191252eea17c8946d69d060959a597a665f3f225822e7cd537ed3be57beb9ea1ca04fc1baf6135541631ab1500bf70062fecf66545666e5f092e35c6655aa6eaf8d8bf20727575f7fc6c2c26c51b65b1a2161c60550ff91579866608e5f84a22bd9d525db9c3c0910d5a9d257d8b9fb3278bf7e3983384d4569ef101962e973fdc50af677e61249f7659180a6357f6d55ff9c3d0c3ded3bbcb37b1c49d08a76bba3d8ade40e097310dc2d2162b769f4d49f4a18af470a46bbe04ee29c054b4d644f034bed1b330b7f8624916ab19c29d424d52039e2f724aece13e882b30e012c588d515212a73f8539ef1167250177895980aea7471392e3ba028ec0715fae1cf2539f351dcf30b5d48790b4c6789f819bad95e55d1789e239dbd28d7cdcc56ad0bc1359ba6868a569d5d0bee03d62aec658dfd96444f35a5254fb150e5273acc5c643f0c6232b71c27d7109e197faa332c23d8b7c3f590a4a9972c5dc7f072121d62db7e63389c59c03726436b338944ea9825f719c9763ef1c8633e4ba2ee18556f162d735ae3c56d9e09e8ac442f6d947881166eef26a7b6fa538cbd84f3eebe8dbb6853e7399ec4520677fd7801b63bd7fa27430251fa14ea07fc97371a20021362a971be8ff709462c2e2be304a8baf3c6f57cecfb7e2dcb804656a16b3533262110bfd6bb8983cb16f23b3025152a5a67b3ba9d5f073fde73e67ff05ee11a29924d74412cf1555709498cba2023bf4d40d0fdbc85670c51082e006b173761c4183d75815fb12cda2b696fad8923295ae21e8958d220435e0a1b7bed0da50cee047f64c599848aab17beebb6cc9e802e65596603823a70658891f4d5da2d768e5acba89a796f3b8b3034f1a2c01add7ccb7fed20cc7068b76c2738cdc9f4c0cb7b752dba7393173a28f16aafb2f8423dc1c661fe8bfb5339cc45482f1df073b995b7e97f2a6c2a5824adc1dc1cfcdfeb2a64daef86e37da39bc9c8197716f12be4903f3228fbaab02bd7959a5ef295739062e15917703d8fd32f6a2a7c7667561282eb80fdb3fb6b2b123ef2586fd658281d49dca1b320e2b10851631c9b11f42550bf7a0bf737c3005918962545897986fb73a7cfacd6960fdef24f2a08d0d9909425f1b401e0572f6c2d7f7f1b16621e22948e190ffada5611905e724e14031cf80a38fd2431500edd1c857e0d04bfe118ba0e6620899d7e77a2d87d2a74adcca704a1b2c0186526acfe031abc4da8b5acfa7d50b2c69790ebf6c47f70cc3f43c6984704592a4e28316ef07835a8b598db038d52417ec5f81bf7f1b3697f5f1bc45e95a34112300f0b77c2213b2a827c30939cfd9282d84dcaa386c25bee8d7da7ca98b02968b31e1c714facf0d484cf017398d694cc979513571261b7df8c6b2c32a28820004f27df14770753cc82ad82fc23820185c486444867a8080bb57075d9c3cf32e23d1d643fda915e0ad53332c302f5e2804bdc2581874b0104eedc116923256237f41adb04fe2fb7c053b4716f2bbfada9b87cc1b4c22af9b77248e0a26bb622c3521f0e4244c66a7af714d4f840273335d1aa8d2e3159cddfd166e3d567c9268b472dc2d46705f03557130da85c7d69b53de20961e3850b6e8fb8591beaa86d0fd10b765c64e3a18d3a3171538b4a236aa5a0598d8e0678617a8cb90c62c8f941010680b196b8fefd31a1948ffda6faa57093c0e5bdb15ab793644f278ceb0ff43f12090bdafbc4674113de9a445ce1aaa725587a8e3d5aaa1ba49ff4930ec7447e0dbc9bfbdceafd180cfa38214128df7f02b436865f4ab2e5dc449557d5386c301affca4aeac69ec4f8167d8db4ad57d0e4ea4b4e60ac2f62187edfef5efbc7b2009d2829cf84a14526960e56b022d48ede9b144f6440242904da9cacb7f8db180bbed0140d8a53ef6cebbf37bb856bf73bd8fa5317e59044f72aa111b65483550994ca20ca1b0b1236fc724c0863806e5f7382ad322d66b21a8e1756f01ad84504a8c28f257e89a6369320f29a2369a69ea93a8a5afc6781483cbf31c33091a400513cca8308b55905d0cbdb38a453c8db8833cdba8c6ae2ff057fcd60e43a561ddfc683a046f7584dc4e2861eb9fcce302bf211e9693589c6e77a11cccedefb2dd16cf71ac62163775ddb1c3832a7b7ec669461968961ceccb928d387ee35e10a040c3f1cb63e84e1d359e36fe76052c8c44d4c7448e50cdd148dccfd5868a009d8b8e6fb0ca9f7e41ff668cde4ebe82df366177918e950849d8107e9a338c822b015efb43ba64949f8bcb267a544cd2e2485d66cc1525ee4e54f0d3dd0b3221e77b6ca1ad4a95d905c06145fd1a9d2821708e1ae3000e868b4f4707f38af1404d399bb89c3f4d4c4a5730271048b5d96072f147d106ac16cc13f20367e67b8142a20c858c21ee4e5e081ea05813323e4b76020518dd83c4e3eb040aac1ba862a4870e2f07eb53071ef39daa2cd981381a6adc2fe595944b16b8f3f487a870f2dfb23a1114ebbd6a5de16cc1f0fb09af8d174afba1902c520a90117ecb45afc04f60d3d3105d013af7696668a02aa56e31ac9cc3670265bb8a0d2103cfc3d072cc2af914aa524cdbf165a4f3190afc28b5810774e6c6b77669f24c3b35fc3f4119e0006c49411d8ae9918d6c3ff4207bea8185e836c24ba66c4f6c6caea6d341386396dde428fe86014248ef438f5ab688a18dbb82b516e360a706f83e414f119573fe6cc28a0d6d52ec6c133bdfcecfd4e547d43816e6fa064b6d7e93f532825ab2a81e8db45f8efb64ae9972a7cca6a163df9f2033de5a2b79240304f782d2dd93fa04491da05424e25dc8c6cbefcbffae80b6c8e792c0e5f43edc39dd7df23f9d0f14541bafae611a59f5c8aa26e03380b723934162c19a459608d98b347299086598038e2a91c73b7e0782ce39331f6d8512fbe07a7fc7b08c9b98f35082cbe354c1fb739964ac30f61d4b1f71d08a8b6a196574934238a87028427dfec27fc4436566de283beb42dbdc55e87188162e72844fcba35e452d654f06d80840a0f19ab6ecb4bab013f2e50d3069b47664158b0c63fe98f22dda6ce21cc87ad039f328fbc56d4394b0c8d0c42d6acd596912931ed363a6054458ae54789198e2d3bbc4b536f974d0b8bd865e9909e2e5bd297d45b64da2dfd9ae5c9a3a687c208e2a4ff1134488d967ddbc64b3107a9b23e7b7ca83402be13743a67d66c9a5e18418af5a327d3d8ddcd5e15421465c4b889bfa0e31c1126a743aa2f515d3c8b2a2f5598ad35c7bde623ae2bd70be128018bab38332186f3c8ea5b0ed790046421e604996d3d77c0257ce4598a66ffa6c39c755cc330dd0f827298ff230a11502223b2d5017042fb406f1cd386e642521d9f761c3c6d99b16cd03b8253982d331303a9d35dd122d9f49cc010e33c00eb51cf199abdcaf56f8648edea566a574d2e69611d85210c46ad4702245353f3616f899c74847da4456bd3fc09b84d396e234d15ca8b10caf34411fa4d9b16c44c0ae469bdeef548b7ad3a26c772810cf225a4af0997e2f4c9145a0b0e91ab7a1cc519dd271a6c3b58f2bc45c24a790bcb0bebca54fbef6125eb192cb92b1fa3c6bcb0a61c73d1efbd57d138b402953e5100ae1c1e7d107647636bee7a9309e1b1eb66e1cc1faf8ab5e8f77ddc06121a3f0244f4f4304b06453fdc75fa41db3e1b15bc1b04256ae0896b00f3268f67ff511f363f7632cadbbbac7997eb2722ddfc9d59e21c8d842b5251c4d3d76aed8fc50042272b8de59329dff728a309ff3d13bccc8913f2a664e2ba6fd8bdc0d0cbf42d453ece719b816784dc9aebe3e3e9053b6c22d14aabbec84bd9fd670e1ccbe7055be7145f3995da1a2187f9f16cbcd1b3d757624bedbf93346352a131c1840f187b4964ab692aad4d6107b75b2d22cdf9efeb5536c24a0ce84b4a57d30b42262d404e0f3178cbc291d51a96ba7ee2f7e14025059d8ff407aac7d03758f4c4260114dda8a818c704dfd406e98d804128cab1c04f35bb681799ee4a30b656ec202fb62bccc933a1416ad7b85aad49eb3180167ca52842b89ba82ac83b8d6554950ac7cf180f14c8bf1fb3b4362be359524d47bfac31b78affb5dc5c6d3d3026ef181ea0ec299ebdf35bf046ad6e9c45b6f18a4010de317e26295723ba5743fd700d3ad9a07c060be8eaf1c903412063aece07c05331fd2dfa95ef1060d2f601fd08bf69a7b0a6f2a4527320ca4f4969abce4099492c007166abe37419d20826cc1f0a166adb3e36c8f5a8e072a4aff8ca9bec1181beaa2b87e9a0f70f1c18c2db10c751f37041d1362319dcfb04fc5793083903e75cb4b510b777e81fc4ae7e3d27d853dc7a2f500f08e29ec406bd29da17b3c2477fc65aa413c13ae19d66f9b120edca4a240d2c8848d9fe4e33f695538043af26e4f6f3f19fed0f13f01ec1f103eb9cd40eafecb1d2fa1baab9e43ae7d150426827416f64a93865dc1f97b0610eb497f1d8364fdf8cb9a23478d77d66aa4a120752fa843f0c7dd4b37ed3d3d8117864f2c913d928bee9f700024ce3e597abd468289f4ba5acf754817f751a831e80aa16566b4f413037a9b7fd9fdd21400a23091807b764e3010226586b9ab929c21f256d595a95444a4e09cc4e7a5ff59d047ea54260df1d4a7a603a3a18a108984ca89e76f6ffc7369eb3215e572e865e2c05ca1c108dd3e592371a7f7bb626a0dc75a6b3d61c907c9d3f35895b219ba5d50f573e739942737cacfcaef3f70fea9682b9104dae31342cfafc5832621000bc409403687fef64c904c7d4e9a3249c19a2b07c46eb1d0667837580a380a5bf8d4501792b51fdbd8e215e44e307251e8b962cc1c1cbe05e792db1e0d4870bceacd545709283af3a9b5c3e917c690e4cff620d02eda82401c1349026a6a9809c49f899d86499d1c7900523454b7623569eeca508db51e54938b1ff954bd5abfd4b2ab5c7f67e7a11adb60119a07f0bfdb09da284b1450ba5b230fff60c7f4be779eb8d3cde7c0580fb5205f4802e0b8d987dfa249b777f42cd3d213f5945759a31c79211426f0bae9a138c9b90892c8f6912ac582724062ee8b6192acb157553f51f6317fd6ebb50af0e51419e6f25c2fbeb3e2814a21016c8489f37fe016d82b563409565692bedab6449f7095c24e77e51ee36c1d4daf80c9ef363a2f9b3673a0b3da7ae46993667adfabb47d80d6d7a3d15e84d0c42cffbdf6a2e75dc2815f6d5e0a1f64aa7d3dd22a838b11f1f6293522395fc29b60109bbadcf3de1d3a461d75131580ed56675b7648064e12626c930ca131799f18d1a050b8f2bf5e84366b8ae90d4dc77f15e98a84a3a290b13dde5201fa4ba401b2959be9d60793da5cafc8c11cf3e891aea323b5bf15d0e24a049c82597ac0781bab9dd41edfb58268a690625911be4f01c7cb94d934c1b25176d969dfe9b34d6fed67f9ae47d57d833d9af528dbaaf91285ddc45eaf9122dbe16539cf24a0c3dacc44ec98f8d88ab7adcb74bfee1a543c88f4e2e62d022541213eaeef891e4debbe5ea26f90f3474e55099d4b7f5ee86eef7772989fe1d1c25d9420a2e0d332e755af7b835a05e47a493edbd291f92126cf8f1855f5c1a244c568838828c4d494aaf5b88e13acbd8c3d7c4be49b0b3fd8b7ab4c9fb630e8afa9e4bf719bf5632d8910b7fb79b4cbba77f5810c1e6fe7401b5b41b91c61f9a5940657a8bcbafcf34afc0e9a434569a4f1919307a325dd86c8c10e1aa2a133fd94a58aac7c3be622a6f994261eac893484f9aea9826feaf71c375f073520cc58a69a35ceb6dbb2c1c65df95947118cd406728a12eb3854ee662074247b417b5a11b5daf6791ed17986906542a20f94515f08f218a8ab502e1b11e0aac770081d293b47933c806076012139832a8e64423c19a1074e9e0de79531196f2ea0bfcc8bc150670f2be8274e8ae922f2bbfad37eebc9077f98aa4ee75a28f4d7905a64017a72c409e12e9cf6ada08ac13b2dba318d32bd083c672a6c614d0bf9822bf7306835cc4c6de854e0d688c9f9cbb2e1232afd03be44541fa60eaac5c8a1f63971da1ccfa9936179c5019c8ff81a58b59edf5999d04508ed13c89645c6613ab155d18e2619002fd0a72004b09d224cb4148f271591cb2a4426ed904fb2d2212ad06ad516d91c6bf5327f0eb369bce8c8b21e7565d02cea64b2d7f3c34c91a5a0c816b61884489c8b3276e5561350da01cb1a119a6731003f2b17e86c1b30bdc6dc3b26d9151f5431e081c634ea1a9b614d7ae882a25d795af76b912eb841c150e88557ca4b44af991b82f7de755d4194ffd7451cdce97a08e72519e2bda060743c8fdba876bba64d622d3a327b26d7b2afa91a01a50d1d01d185360b8ae0170c51968c9f474009653bd419140cad4617e717a1bfa931648040b94e327da062e27394520550f3306ef2e382620771b6f8fbeee6cd62843a3250439de314019c4b41cc44de9007f07a2e5080ab05ac265f887744c70327792f6049a1fcccb27da87d4ab427b9b4c85af0927600f93fdc236d2289da94542315d39b7bdb768060b03b1d14a9cdc60b1557c6fd8b6bac4542231bca105e635f3281e7c62cc2b1f48c7159380fd0d6216d9554aba836c201b849f2a605040e9121314be4b866d1a2fae8c523bbdff12f569e21fce88ffa46cb9ced93c840921992dea2efd19460757f5c2f64cd24c7715a82de62612724ae70c8ca5d3cc85b63cc9c7fc299ce7233846d0922dcc43f9add1a83bbef63484e773b158ee55a5d30f4578266b1dfa2373becaaff6cf6b7170b4c2085fc791eff1de8917f17c9431bf4cc8ee95f61713c630086899abd3d7f20e0e8268815fd7983d5f3537e1c3c468c2c230cd39f0139087845c94cb7b81f812f650f9380f4cb21477b3a38b803134e9a22c24f67d29dfa788f5121f5f0d9eb5525984578f241189170113154c11b2254cab4bc5341007ddf7059c3331775cfb74f91f6191831e9cb87307dd294081121ffada8aaa073cc372846273c03b391c56748ddf263f87d4a60fc4b995ae8cb2b20221305b1aa0cc91d7da4aab51b807dde0675cc29c484c3c02c3456307b7eeb667a831d523c695ef210c609ba4cba27c99da6e51dd302b8fdae82d6d9f6a9aa396a6c3e30795648859df738869d97752561a1d88841995749366464fdfcbd661586ceadb144bb1217b3c7c2f44ecbe837472b2664dc8332fef90f9510cc2487d88b5927c2433456419d301db11a097d3a25997a3d2eeefbef2a7a8db4f2c7245b8485e932c50919631c4ce6b5ce255fb3a5cc858c3e3544c506117a619f707bc19c3efba1d95b287af15f587081deebabcdfb4c4f908b3b8c8693691b096df4f6dc52a41305543367023d60fd114cc4bf0affd9b5d9ffa07ae31d8c7f25cc412930046350c203cf0ae151b4ca11593001a15458bbce8485d4482fce3d2cc3403c04c77aa2f20209eaf549ebd9cc1a981171ababf3ec2ba1ed8e65ca6551c7788d52d515a0b089e0b1524de80ab0fd091d1214e4d5bdba3565a7b0efcc62d8e0fa9f404dd21f4dec3ff782e6ab2b5ae3ee011ee9d11a0d1a78b1e210366fd5b3f8edc769c2db8fe731d318984acb0ac6f4623ee3da9efdc6f9ff6ff3ba48567db397df4ea289db1c6510d93632c53679ff55101b789440208421573fd0c444df98007f3492ae39f895998fbf498f649ed257cda5f907d6ed197e2fff53a5419db41ac81a50d483775cecb08ae4c0bf98c73fced128605c54cdc366204ea89b06d2334a7ac22c92bd95e7c976ce697aa0c4f2dd1c14f78a292df8a922e8fef5e70f3a71e6e12a3ecba8a005ff5b5f0907cde6addd618f87dc3af2282c7fd224c4b81489e5c21e04a303f14fd115821b8d2c9c0c87ddef37c2f8b75a38706ace5b753a14cd404d0070d8090b5b1a0c558531a7626b40b0793f8ba8222bf81c038a3873d56881446ccea637d62a1649fb1ef247d0a342eb4a80b80e50d9b3bde954cb36da862292fc562e6ea29b4e4540670f6091f8e208f3654dc8775fe7cb2d0396c9098912f3841a820514ab2888d1336989d54d731f343bc117e3e5ea9c44273500bdf4c1ade9f6df986799bdea8d7f971c6914e70d406ca623d827a38c06e8b3824e9792853e50587a4b6d3dd71d34debc7e16ca04ca6b341b156b7393f409ded9f6f2c15cdf3390434653ccc1001e4122760cedec3a26aef2447d1ea5ee1af1035e28e1c6bdfeb3b32736479f04b19a4a1145dc0c458661d5b7d1318bb173d15385635180271dc91dd237698a29c921bd0894c593b7baa8ae6a7acd3611b5d0f4412852d476bb32f265ad24625d3e0682a8287d30a28e80d927243ebd4d31f233fc390176f79482c591fb0dc511b571e2c5f3542d52f32262ce0dd0226d4a0289de5c40550e19c3a7ae98cb29bc08cf3b6839097693797d5ca83fe66d684f44f9e9044232f95842896713cf8d85fadad0cd8292644b1a544e5a1313668f27af4f17cd600b28b913f5decc071740c52d7743edc53cda78277d9aff4e5ee8155c0854b4f135d1f7ac80785ddf791093fd7f722f19b2adcfd954f7aa838f2fe64c22214ce19c24064dc86e1c55333c3c32439bc8e8a71e8124af4078a7e9e50762bd0f4331327baf15995e2559ba95f44067b3e51a2038379b66ca898763030856451c29b2ea529401bcaf41b1b223ef9984d1c50793dfbb0744b871ac7385b1bed63140508a63c8806d470c88868cd900442157edcab4575b1a4a4841dc59e05610a17881163a53193b4f46038ec16d3a2acbfb22a828e1a66624123169cb5a837c2b2011ad3085e87c50e9c2090861347696909b69c5e58b7815ddcd0643ba8878b1e8f036cee3022f1da9114688c17a2ebf421eaafd7361287c4c521181d54cba83b3817308c53918ee0943f330dbbc80cf4cc32b9ba6b6a4c062a8f43b4376f1d14b52559c7ad92155bd042d3c5eebfb3507f296f6d43c94c22498614015eff3695f70e0ff18ee349bdf38f952f695bf514c1df01bf656937c556ac772dd985c440922409ee81e639296fdc8c12260c32447264782affadcc5bf4a79dd22811caab34269b381426ce376540fc588cf3619c022e7eadc42b5922677c22c2ff713ca3cf107db447a08cf0613c5bc706a050c83c06471958fa6469a56d77e3f45e8d3c331a535823cee65ac7c8c4afdb19949f24181d7c7f21e1aa4799980622831538c8151cc5566f52a7b13833b80415eac513ec1c379d80deb19bd856e201a18dfebc2eee4f168918883e7340b94437016cfa000c73edfdcb415138dd4f2e7d1f1b5500564a9eac0cf699202924402233d3cf17fb0cc71c2d36a5756e6c9a66a4d0e82e54d53a42d0f6c6e68b4fc6201138af40afdafdb86dd19c3743f5c1bd756b3a0129e5d2912421adc8599fd2153517102b90b3e61e6057536869bc28db874754062177ae022f1f1b0d740d39449cd7180f6e9e5a1ae0f570ab236b4296d4306c1a3fd3f59866db6bb3853c890f8993b31fadd42ad1c44c9c054f18a41db4facf2a7a8a5a2fc146bd14040dcd9dee96424fcff3a3d942093bb298b8a23f9721ee12abfb3f889e0bf1fdb61379cf4b45319e071b6e2a6d6c37be28099addceef0a0b65e63b0f17706ad47fb2cc73a40de4dfe018a4e3fa93c7b873a2456c35610a1272f488091e34387fd1db5b9e5b3fa673dd169652af9eafd7070bd4e353ce961b0b0f58259760aba424f2216f090ca085bfcce32299bce70cf9b1c4167f08ec2842f0022eeb47eccd16484a32e08299e9f7f6688c3d21d74780c284d98af5d22077f2b2d8792de1960e21ed61e1af6df5948ffb2f92d82cd8d00efae054c6730f75d06e2287eba7416dcefe8bddc2fc68b222de614f5642a8a1787b4bf652411071c6e00b058da7aedceb2923578667f49be6451e2f68336c6a63a3f1c8dbbf2126794ee71fd02d449d1b0febcdfd42713447a8023cd91866df9eb83cb0c84b2ea1de622cb3cf144613d6f7a002d159653c9f96d02de3d21bc23192c9f78f911f2c41152c4066d401b95984d314f961f38b6743f440a3f04550d4d4e150b7110d1b91be8944aa03a26fa598db4a5a0f69e75b533a2207c46bc39c351c9ba7f223fda2324aea7101eafad809321f8415dbab2aff593cb49291b7e8945641d8f425cb352ec3a15786cdc3597cd032670839a4c551ab6574edb82c6e76dcba9333268c043a2982fa0d4317237502c90a1200ee11e53feb3637ec36f551f122e1e50083ae2b39be1cd219b0d78c6958e6b5cfd88996fd748a14e99a6d2fc2962bc114690032e6d81fa9d2ac6aed38b94d92af501ff32c9b3ab5aee0b0e76d84d5cd235088e4efd4d536a88ce0b512f98777f9b0198e44e865876669cd498d3a55ab0dd10534d18582c409a543c9c9f87a00a79e62382382765eba9bef2b1c5e91be0031fd9f3b1fb2d76b661d1d0f7b9a0cd08ad31881809c625913a6eb18fe23f50f119e9782aecc36fee9ac6f56a3b3fe0c5240297a01afc7cb0f0675af297a790755a4ccbcd4f1866e1f561b5f03e8a9448e57528caf7148b9725cac6831ca982740bc1864a832bf1c7df354412f5e31780a951386ff364ea1f01ce880fc974c176e1209efff827a6e99ff93a5626451cbf51c4bc89b2f14e5aa376ad61e9fc22f7fd42b0f4378cd0326b21031805568ac8fd7d64ccf4e872e7cdff021f48f7d162205911801d4a6ac2cd45882dc7e02c465034d976c358a781079831f87a889242de0921ca6e9ec53f785ec9b695b3cd0bb080a6109a2d5158e9d9573bf42ab2c6f3fb93baeb4cb45d84d16ff83ebcf879cacef6a2c47f6eddd47f08cb9649b0b1ab3c4334de9a157252c42ed94d64ce65ab4666fdd06e256489ccef93a9fe5041380b0aba4610c03a7cad4f8d205c8c7477cf65e79edf520e85f0752dc4f9427bc42b9359c1a8498f935dc47fbc843d4fff6eaa3d0eb3a361edfe1e31a10502f073561801dae02a117682fcbd044310886caf0f756bee1388d1c260d1e0dd320cc47a959354d31a31e6f8aa49c74a388f2e6515dfb55b712cfc9e54397d24918b47a2576194194a6efcdc203bb3e67524336a62e288fb251ba07d348954ef92fea075d7e50785d56376bcff1157cc85e2670076c58a9d2ce39cb1be88abbc00413d4ccd164c11e57b2f8c0e122cebd75ad03376dc7da3355b0ccb87dc125a31c3176bfdd507b2fe2eab4a020b0bb85a2e75a40b46f7e25b4b7df5dd0a92ccd01bb041eb5abfce247954c34fa5a4b5cbec62d0cd0860db0f1a6cbf9d5f106e202c3c76759bd8ed2d8b45dee6b1470428729ea03d0dac9a65e9da2048c7f99ce04c3329559fa0290eac1a9a42e1bae853424cfd3fada637d50067aa58d1d4d943015b03c2309ccaca5a209459c4bf2c301e4274b1e3045453a918d8a2e214db618dff718f0e8287927480d5a3ae118be697a50cff71b45211e65eacf0357d8d8084a5a7663313d67403bd7ecb9140c76fbaf2387e480f89302b47bcc31d1e6d7005b0b9e5c5d626e9a6619c4ce5587637b45c44a486f7c62e075adc727eb01cb2213bcb92edf2e81be9f2a49ad512fea35fd981727f3140cc114f24f28e9ce950f6e1d41030ca825b20c02bafa07da5283d31726260659ea0575a470e9f3862612594ae7da751efe342fbf919a5ac503522de63fd4cdb644d7560d435f237f7c22eabffdcda551aacaa33bfbfd7b264b0de538421e87c5b6112d4c10fa837a670872ab3fd883ca6be74ec4a41888ea76efccbda23a48f3876c20007bb5e8fd6608bcf1f22036ccf763f19291926e1ae21061c1381f42bb82af11bc3bb8f34f1d25c566966289b286af824e626be44fa609fb9f8e5c280b11ab25da9eae47547d1134ba4a20c37fa9f9c9119b3f0946765dc30ae229db3828469d37c4af2a3d43255889f4da75691e9009aed63ec762f684754cc16909e6f55c5269ff4ed7626917c8a264883124b54e4c661f42400179867e83256dd6c8b5c70a6eecf33f69fd2e3c04920a72f5f2249b63e1e08881e8273da02e502582e13dbe9d0b5d79b1b226a8698609fc8716a362b03401118b9ffd1a8d4151e25d3aab6864751a3f82db0affa91866f5748099a7a04ecf6c582401622c3aee7b56e9926ed231141c26f1034408199b216c95816741706ac3a241e10725fb30888123fbcd7609ae4cc233041e412d88d21b96c2c899f9d3464d3be3ada75bfa0a939522cb1222f6fd2d201250460ce0f1a7fb9074a0f6fa3b172e262e1724003855416d8485c2d780f07f1195cae1b14cd3e8b70a23cf4667c9913433b4f2ca4488685c0d83f58f24902023ce9091da8bd491e977134e6d24ba3e0d62afba9b9f0c9596ce5e207a75f02e05a488621b6b1b2f82eab998582f6da1b507d69d16de20539f5ea50ca1e89bf3043c3d2bb158ea3be35187e000b92298549448cd4d5b896af4c10ff7c292a9b6234888e4059dd68e03f9d2f6ba50284c55a63594c727efccb615a7dc0336d3641caec9c366f569ff0f2cecb46452264a800c5963aafcc862f4ef840e4839dc1c25c64913bb18e4943f1bca953e7bc4ca7238ffb61f87ccb9b4f411cca65172d82a9b37ff2faf9238ab9a8cd548f01a5c06a2f0c852b173ed8bb2f16e4e49e7491a0841bc464caab75ddb9afac16a3c4c9979a16067072e41ec38ac45384fc7db175ce8432e258234b709460386d667b25e97f32010e41cec2cab1c959c68d23b09d014d1421ec2b3fecd14fa25e9c288f5e508fa5606c89a8678bf87d7b7c938002486328b9b29b627d0f6ebf8a77cb2e8a8038986c07da3a8b718c3fb80602705be98a904bb2f54859cc16434cde0b7e8a6de36b5ec8a6ea22f10f227be5b060f1dcb994fad2f44350307e50d5481ce1a3cde7147806383acb56cf1cb59bd25244acc1d62e14a82954b17b519a1cb03b5d4103beb62feb96e13350c9c7fa5bb07bd3a2c8ea44417d8a935a9e6099406c7b36215c35285a0614326bf40fccab901a9a0fcb2319ffbade7f8d15ced7f3f51f979809c892ac9f6bfd4c9a2a1a5aa04a128248d3cb749415d418a4384da9bebaeca135d0f28ba169e4947a16ca1f52b82b51b5263c99d50552714a9a2fea1a5d0bd680d2fe441f8abe121960053680300e3f56615a750fc1d8e8bbdc1a74feb42de4f686920a55b33c9d37364c1261f9637c6e7ca508cd210914189000700c7299658e9f007bacaa37980f64bd6cb249cc42f44e8f86a0afca5ea0f982453b72208aa9fbb69df5b2d91e1cbcefebc21027e3be6e9bafc8efa9000169e7fdc38fe27e534e786e5b363fd2fb3c706ca24bb1c43e8e4d331239503d6c0820a75a673526b2b4c8e33d810f0cd432caeac2961cae418d33d3f5fc24a83452e897af28d3d2cb3e94d177fd1eccfd030ebc1186974863f6fbeed1d5b16ff8eb4fff18629f25349caeda0f588bee9bf6342c42f5b264f5b6c882fa61c8e9d80c4fd68019c6220c9c04a4fb50fda44c1294f0417e5c37beb768112252becc0cb87f4f7b3668e7acf882e119b83517186944b6402d290f7c84aca9901c8c2e39bf76cdc6e6dc58affb9a31cc2eda3b04ffb86700a2cffaa7330da0604b73af6be18bb8c028958709e814773086372e6f300e1a31df307488514ba7a83b1776aadac1d20c306b12776e8050edc6d21e3cd6e23910a90de67c3d41d621aa294394bfad19c0cade227c268ab93bd9c69fea9d554c4253a34614ea3eb7565f89a7b76c6294edc4359819409b436da27f11206bd097b26c2e4d4e0b551afde56514b47c71407c1b1faf7470958c3cd29ddc98d4d57782f6fafc9c9b673e30ba8387fc5d09b2d6af4fb50260aad9cd5efbc5232f9c79b30612b7f03f037f16a8a5b587aa1556ceacd8c1e3cb7a936a7dc132341ecb5d0067578f8637ba3fdb938deedfe74d89f87891021566578a7f2c238794d73d0571a77b344f2989ebe251e7e1ff9029bd686e1608af6d7c5b275671f67d2083830134ab23a331c5f5d8208748b39f75da694aa518406fcb20273227668c7e598121a8f82dfb5d84f876fba9a258d1a1817968395ee75da396f59abbd3bc8d3143a11a434282eeac18c25ed67af3b2733e69375ecc8b0e604e168f2f6bdf41a4c9f0ba4b7b622754e332144a32a7678575beeffc8cd293964509b5d9bc054a0792e2d967ff97e72a34fcde286456d9a94e248963c6fd50338647d8996d23f27298118e03f44699f28d7ff31f31c5cc651b47df9ec3b9b869ac0bc4f66bef4e5b14c0e186b0227bba2e618527749c1c041b9e4b5c39abb3a77e9328fcbc0cb025d4225f3bb62ce3504f467d07fc5ece3b1ba8a5ad7682a503bc7a6502f37f4f0fe13106355c8bda71d12797aaca34008c3a006fdd066b64da246671b4d0f37152bd4", + "public_inputs_hex": "0x25832c071d6d99ad42c7b885e48b10100726dc0de03c0a779b282c519ccf538a25fc312a5f0009edd084811b739bcbd529987df1cea36f5a24515386662573960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000c946233fabee579beb961008103a4c8d00000000000000000000000000000000026a297ba8fb72294cced25b49dae7c92208484402633db0cfcb2af11d3b66ff07cd7b6b12c3a35e9581183e399d53f72b942ad53baa7c6e1a7616e4f0aa2eedf7d67eacc63696345525988225847479273fc58215abae6a73503410e8209a542c92bbea2ba8e6fb129c971def4ca8452ddcd2c1e0b08c268d942b52a49e7597873ccad9df1b02ea1ab854e7111eb2dd188f793a89424a83a3e746a72d4cdfd5702a7944deb0fa2ee04f84489f4af3c22f5ffac9352fe35d950a4ce548798309f5d896dd11f304f93d83cdfe284b37a6" }, "decryption_aggregator": { - "proof_hex": "0x00000000000000000000000000000000000000000000000987adb4e5f7f3bde4000000000000000000000000000000000000000000000005ed378603dc5d82b100000000000000000000000000000000000000000000000a42fd21b16d083942000000000000000000000000000000000000000000000000000094a09fbdc32f00000000000000000000000000000000000000000000000255c07c89c05d8fa0000000000000000000000000000000000000000000000003af09308293d20718000000000000000000000000000000000000000000000007d947bb153b660457000000000000000000000000000000000000000000000000000222011da6fc88000000000000000000000000000000000000000000000007205f5f8f0fb2b10900000000000000000000000000000000000000000000000d600ae130c582aeea0000000000000000000000000000000000000000000000067e0fe450a682f2fa000000000000000000000000000000000000000000000000000031d4ffa4172e00000000000000000000000000000000000000000000000783b4771b3064cfe900000000000000000000000000000000000000000000000bbff8345d387212a300000000000000000000000000000000000000000000000ebbe0d5f8dec02c6c00000000000000000000000000000000000000000000000000014e6eb98612aa01ec19d83d482649c0940fb8146c6999d65c8686684592d5d5208aca9869eaca13973e9570bc8399530a1c135f547d32460f4b83cd330227ca818217f45e06672bd62e28e76bdf130767205a1757b84e2032baf1f39912a88c28845b6b88592c305211427894c22e10d8b134f28d77610246dfcf641560e33ce08d551764c7111c8b24ecfdef12d3806fa419d342fc550079772985cc25cbac908d16d4999adb0ce7010e598efdb7a4ee94186dfd1b659d16e74ebd1edf874a6ab72b7084835e2774f06ba5c953ffa51c58bf88aace20a6e85f086b962a403eda2293f27ed9fc12a42258c030971841ff6be72c5b691807f3fd7a347f41521f379baa51c4410e04f5d9ecb7dd6da287ba0c4a94c7cd0830dad532f110cb376d56e3b19ddbe99906820707c23159cb64fd6601babc62871c34c81d83962973c6a6bf113debc1b016744cb4fb1304dd416f0bdc283797f0fcb354d00afb6430910cb84915d9fb430b6736964c0a095fc5cf78670da5be0bc0999076d4d91deb91199296bb5736690edc71f7a3c22526e633f430ff0fde859777d093618372f32148a6ab4aa2888428a33637ed2000dcc33f2f8d16942ff6eb8f698bdd836f80d55293b95e7a99211a5a302b0d8af93f7b3c6c172943ed24c95684aacb9e05b5e94452595574d2d1089e8b01965628b761223708dc74d5a9d0b5b553b1e26ee36ecc131419cc79c41f2679c5df7dceecf959d8ce01d067b83bb6b2728eeec8c5688777cbffbb563812cc1611ee9a5fa8db99c324d518c74d039c72d4807bf95eca4654d90a89b88610440e1cbea0ce1162f6664535ebcb901ce0d9b027cf2a1dafc575910c45dcdb1b777cb1aacd1963dde0dd73ede742e6f68a87d8099cb5395ba1b96076512cdf007ce7f1c2a07000779477c344a41ed27621a8e04b59c1e9a343b0a5241cb068240439abd43685f0d47bdcd80f84e6b2f7fd615a26e58a5d229ad304877914ac12820eba8fc2b8ce0530e6f7160328020d64b4b0a97ad669d9b1e89a85fbd2d1147d3219a422ca5a031b11b25f0ac984be5e161d6a86c2f18225fdba0e422b6c20c15f87172a4cf9a0bff8312203c532f55c7bf3b6363abbf1db4963749592dd1162f0be6f6bb5e9397b782828bf28f13ee6fa783f6842d4210379bd259bb06904808dda1eb88f992e17612753c200d61e1fa4bb883c8f89cb3d4c8d50bc5e8f2d411aef522f83adc808749e5188f3f94738b6cd966e3f227ef06b9d2170597e27354de22efc34361db2337b4977759a456a2456643e0b9abee14dd0351a231518d3312e2c485d0294ccbab0767dc0ea2d482e35e43b928abfd80e22426cc61b0990991c297fab36bea3d6a6e5121edf800de01b5568b670aec0e155d871647a03ca79072efc7981c49afd26a60e3a3788be8916edfeb28a85e43667c48f82da2ed4200bcda4c9f1a6daafa636741ac2da644e6c091d7fbd1afcd426b6c4aa380958f3d019b802f57524cdbfc04d385326d7354da80d9cdef27d3e850dd109fa225349708faa85a46788f81812db14e7d6e3c6c1705b90feabe3bd7d9ab0023408b9fa716c28d9c5e105178f5199bcdfd0d7d6ca6254b834a0cb97c72df2c04d1f6eb5464ec07fd582c6c5dc63e38be5e9a25d1decb1f70a1d96dbba28e127d10003e1fbd3dbdd69e0510d25d4239efd048448b3e1aa1ff0829f8031c67e951d26a2d7e19534b8228a85de9f8be03cd431b0d709f4fc552de727c023909620442a19ebf0350ff6d20427a659ddf78b469d4715de3accfd32965a06df4442610e238ca3c86af918e56dbf54d0d2902339819a612c36f92139a386a62b805f00ba13f93755bb7ea2752515c1e829f3299ccc3642c85076d23947383e2bb86743cd1259204db0d6b58e4bfa4ff740136009e559dc53f8db6dc79f48cfdab3118bea277c9b3102ff22396cf2f2e5b1148088ff8c6873f2bfffa22e5ec6a8f065684d269fa15d4242e598a4a8547536067058882b9bed9165770bcc6a0d0d1dd14dea28360c24cc5a2bc444ca3b2d11b130edfbb84657766aad5098991915a30a19ef1ffae1405c50e81ee28a7427c6deddeec547f2c1284085f37d470523496334660707643d838f0a7776da97e8d5fd6056e2f1028e8d6edb2aa632d70f9fc2edfa274c4c389c0b38211cc6fdbed398394b25a2c8d2f27fd503e070a80941f7f4ca0e4ac64e75715ff0a32bd82e8427e053e4ab684fb36b52dc1f461a5639c735bc2a7db02e3d65828f6a57dec91a2d66ef814da83aff62e0e9af86f321a9e747412c93fa14b231cd2e8239a3afdcf73700887256e03235324fe52ca81c53af91fb26a9353e95bb3c64d914fdde38f20739ecabef1414de4376871f2b156aaf9f8a231508ed88cc5ded4876520b2f24c7d3cbb3def31567cfe59af381afc7a2558e0b6544920d1df558a90ae224abf2830c34dba6d2acec7124276c1585f5aa0dc20288240cba9c6e33a450b528b69658a5e44fc0040dc0686fb21c1a98b5493ec101dacfa48eaa3b4f94af550cf11c12a039a781668d98911b37b18024faa63c4c0d00427633d6353982b01eac200cf41f52d13c810604a1d2be337b87566867d609d87e2bd85f185c75cae95e040740c3f1d14e755f58ef75569b73a32a0888b50dfd0d9bc436dad4aecbf13faf42b2563e753cd192e1e44137a294924cc0fcd72f3039b9fd750b948d0672b8cb8db8c9813e406e4347123fa73c5723ce32f92809d4c3ce2b8082bd89b2477314f2df6decf8cce655acf23677ff9f0e0f9c56fb023f704619b5586e9f5adc1ba2cc93ff33cf0d5f67365b5ede94cca1d289f75d20dcae4403a891838ab0bc57b6514253a974d134d0df60a01c90cd32ae1bcde8156b2cd206edaab9617449960b0ada4e78464bd8f335024e2f76b8a81d1bc28312dae65ce554a26afc518452cc9a5ad6466cd6bd6ac68f51e458c9a50f3a0c5c102c54f9958859eca24310d792f7832cb491565dc08f07a50648b03a02e8071b03eb63a207b1dab3978b69916c106c8326bd50931fab5651fa3ef8203570db961f1f02db4e02e89dd192586588c41411208317f3c306fb9332d983935d344d0110e59c46b088a47bb92d79c87c523f1793f90744125aa283fee3f6117aa87fa823030b6ce1a1d2a5d6755ffc721a0a136ebd208d95f34410c314aa865954bb83098c00b938ee1affd83f40ce57717b5082887338868e5c9422e4bf7fc1857af6083e2e945c543ff8bd3995dffdd5188fbded020c0687fd8da2aa7ef45952b6b9084beea4b251a6ee016ebf5fcb3cca3e96bfc742ab35817fd7282333357a38a010ed8f90f0e8a511e0909296d99545825ae9d1cec7223b36fb85f12bd25a53df0247e3676d29508009322287c2ee3d29e7e4e542a48dfdc950af5035f257eddd13c58ba58ec7fcefe7643644d6160c93fe5df679c2727d3c71e17ce37cd89cc9066dbeb34bea5f22d72ff8652582ca63bd52fe6f74b401446fd606a6b9af4d7a27547054912b101ce8723ad1f6d1f6a7512ef31973465a58a5349d3a3504d088057e40eeb8c2d7fa3fb66730e9768dd008950113787fff06c297938d8e62db230fd0cb14fbe1661f0a51c74301482237bb51f298a0693005c82877c4f827d0b01f07109324bcdee8663f14478265b167a07b508f1975c2c4358d5159402bfbf02b88f14d69954e18a3f7a8ad4bce3d5c5090adfe821e9abc674618191ea50e4b29ad959b5612d728b13f16b3dacf420b4c33cfa74ead666017258f5e6209de0b21ccd0b2a630d74724adc86e7139809bb45b11e8a26ba6921d8d4e08e5b7215a265ca6806ec10495132eca643d375a96f0cb2d729dd25828c199728af83083c90bcec7383b70df0618975159063464eb8c1043cbc82568a090cc4535262df76f269d2c2af4377465d29fbec0d3e44b5b8e9d6358a403726f8dac0345dca084b50691dc8a1178611a2556c3422e52372fb4358d88a86902982551eb9da886eaac0488e3e4e59bbc43ae08e4cf0e79d1e1e2bcf1c26611bc880df4cc88bbb5d89e01fa3f3c586e6e9f5e73f28b25fe95c18e025f3650d7af98f4c44d979cd74ea90f9152d4c31e0581648aab438fda289abe66b8f8248b45e2c53762edb16286442dd380fbf70ef1a66ba55cbe10340ce2e97c6ed356c105b7b70d4d282fa0b08b0525ee02118f1c47c16c265c519aba3e53dc6bceb9fa44e09028337866e8f429229f9e2108a5ef6ab1179c6cae4a1b8fa8fd28d141aa7d73f522db5f823cbab0155ac17aaa428399b6713f5b1f4544f59060be4e24e292a962918f926b12e4d326921ce206f28e9d6b25582e1cda7c45a2316ec6faa24baac50df12a1fbe7188258f96ec5db61da6d2dbae2799154a8e9b37679e5ec1e9b53fa50dc56c42d53322272ebfae69c26e475cbb8dbd5b362dfa9806515e285d8de724a3aecf10c203016db67ada7055801363d45e2b8c271e20545ae707439cff7fdcd84bb7b000b82d3ccf9874d39155d1b69cea14902c8bdc4f1ba7cc29187dd1fb87c56ba68df8008473db80657888115bb42de35bbfa7dd5d58079418d776722bc8d49cbe65a104bcb9b0e9c3710c9f9125d1a8dba6a39129d869384bb0f3fa7026476541549c0bb13b5305f0deee708278b40603c86c87d3d0913c0d7d040f3a0292cbd3f1ac18ac5d0e9ae4081a6d00b0111ad769394c7739399aead4cd7a460be9f4c234f51a8880bb5fd88def16a8816a30b4ea2ca30337f4fcba6aefbcf0bf841ab5b20208c9785c48ee6c6464b0e347b29268361c07453ca42fe14ad61e72b484312188029c4211184a7e59516f73f3adfe1c8bf465f1b246ba4b284648629a0f5cc0590cb8158e7ed1f4cbbd72050d04626560012251c0362476131f27e04dc8d62c9115fa79bdfdcb1534d8ec5cf83b9972c7f31a7f82e96694bd8f061accaf63248a137d89b3acba69e060286da68218868ed9d88f0b1d4f8479775a8d952726e8670c2a562315b76f076a558136be65159d968d33e353198423948c032f4e0aa0d60fbe63e7880348c1d052e02026d1e8d16227713d906d60ca0485eb3d20fd778e01fdeda5f294a03c49629de13e48873396c6d596c4a82c91a6416111fcb8379917e7f210c179a210440e42a50adc3d3591788c2a7be64f861cc546cc054cdc6a15a003b2adbf6fea10dd259477b3beb808aeaf953fde6b99ae7255ec2be91a980b2f9ed5dfa4696711e650688d562abff4be6ec16dea6b36b2a6ee2f2a578b8609e0bffd2bbb2f8f5a743b55507e0e47193af4f1978ed153b2a2093851a43a4b16fd1444082712c372d928c943170f451834630c40ab6d41d1dd601f97fa43a50648b3d01a7db07cac6a683417a1dfb2d710d1984f703e9b6346036f78fbdbdf0b737d94ee509a2170ab6cbd7371389c6e734ce3ac0af0e7446b58217810dda51c03eab1b07dff7856144c1b30702e57fece38c690b7a0bc23af9b034a99eaf61d73a33d4e5ac4d393f406ffea8b0aa995d582a140dbad5b0b99600fce88362b158d1a1879f02f07332d971ad0ae3692a8c61de7456edf33303a36ffaa0134c70ae4f05d894e4a415b1297132b9455bf940921a8236c582b8ce2fba038a5b98a244eae77377810f668bf685d8cd3e8e5ef2f44fc45e319594165bfde57e22bf0213c80d8459bc13c913ee92ee02ab4439f0ea27e876b15d66e0fd2d12cf1830111cc9835393ef58d7ec1974dc30db64bff1cd98e1d9f7e773f44068cee5c67281a83b948f419e187494113e32e221eeacd9a67429ba9c2a931927122668c06992d8b776c3f6259a6ba9eab8957b8340078dfbce002bec4e4cbe6451f74f8d126088b18fd65009fc64bc223dc16d0591d0c5a6f085eadf072ab002804983f238d2a2595616dca5a09a1c1cbfc87cc4af532057518d2885219bde38710f368d9dc07e6aede1da3deca14a7fbb5f1f1292c6a11077fcbb15e0c0a049084252eba8e2c33ce421d37e7986f86e6b18c1135ba95486218f26d961d7877fa449253058f0a592a27254d7c7d01698b85b57a478b94830921a2c94f831846bb38d5c151d42a89f8a13e6462df64b4c55e1864afd0c1d6303ab066d3b2b7da1b801705e9dc186b1ab2fd62328d46d17daa0177a4589a162daa1b01bbea02fa2332219d5629283b89b170dc081d5d94846c966ae430768bd3340779628d33f8bfc7c70ec7de087a5ec32b0b50e5c8e3c71cea77c00d64d964a59902f536dbf6effcf8e6f62e136a001aa16046a8a84ab1e5dace849b0c19cad1389a48c17b61a898896c4b972ceabaa87cb8ed850760c27b357b10fb5f0cd415bc24993084e905c53ad1abe507d734b58ce1841dbd1626bda546ff7b236184d24be658701ed93ede7c83df6a068d48d5286d6113de65e26e31d2eba4a6ba53fdb2e13ee9b30b36c8ca5b28b90f9a5bd292d365e037fa54ec2722a008b0ebe3f418ac9dd7e623d4f417b7e6c6213c6cbd6b6b825accc918a0fd279e9274744088cd2a7a04b4de1501971a2d8e123b45ccc643948dd02f7d076494284c857bfa7e6535746768758b132e4e6aef14c220607c4dd929a32fc1a8ff7ab86652dddb25941330b576feb58086e6e92f23d02cce7c05c8eb211bac021917733b18cabb0058c3203271fa89027a8ee43920efce36b221115afe2ea3acefa050a4cc91ad573a73f4b967159617cce2c4fd2bb366d6b2bbb5334f800810769eeb9fee335b7525299c9298326e04ef0df5af1a34b4f6ce3289e480c55342f167667c1a4c431cfc72236eec4d87a2d69c263c13e05c0693e6e8b3551d90299bed01829233a35c69669f8818dafb8a206027a30385ede0ecf18df90d4d92d16b5d80b82b7a5500c59a7d4ea1a2c16b047e60701d7992e950675a71240fdd6ff91eb509f5a39a20674ba5c411a0ba53b3473bd803134675da21d443fa2c3bb8c5e01fa3fcab64403410f5b860dfd8267199a52a183aca4b265de9aca19b520ee6855497223d443e4ec8edc21bdcdcfc7dfbef2c28f866d113698da8c7a00a755016e6448fac410a460ad08cd4338183ff00f4100d0f2cf928452e13c621ce4bb42b5416863758a28b3a339768f9c0837867217f2afb8393a2535a60c37441dcf0a34e5350938204b71dc6f6beb9f7d20ede917612fa175145d986a7cb5a79fb1857643f33b475fe4f78cb67c4bf23dacfccd77b300d930a85039e9223bf8ab887a4b6fd6dab6e08fa21103aae7e357f84c5a8382373a566882eb882db182aea4896b289efee7c4f2dbfbf0b5cb34c078513023026d7e167c7d23fbe1f06d2c7d6cdb35a560776fc32368172eeb52c06a69cb0b9008e1974f8ceb08312a24effafa9bdc929bdcc2b1720a1abf3392e03948962262a8b5ba87690be615f3963991776b9277f9dfc9bd70fd4e4439fcf2522781e1322ec3850b41844f7814165adf6460f419d3443a0f475a65b2521dd4189ae25cb2d239c179b48ac67225294d53360305249476c2bc432722a46537a87c9db0d5611406c6e8056aa43cbafc6aa655b9bf09e6418a111f39af9bac0d433303d90341cd4ecdfac812a37dfa87113ac195b133930ce95aafda2fd69b574dba16251ac24f8b91d7a2f9c0b08d7c7e1f2516c3e8b1700d84d79a02ec51203b42a56bee6172b19a9f44043443879fa4addcdf80ec12c125ba6a6d6ffa61d5cfb1f2aed5811ca7bd161b2c47d667e3381032d36d45899d3e28f61e31807709be6e331b779062b846c04856208873d7d5f06fada63f0cd74fff984abf8116dae8ffa214ef41fd8427a3125e3920a055259214c2c787b16b779bb3c5113245bdd0f05d9237c175c7fb05feec1534533f361b4923799506b19762afb1e6da73d250a2a062b37263dff74f97fb78328b360f951ff7157322f3fbee45013d25822fbdf218dfdac203e8c3ad3efc7145f6fc8dbd5048e5d0fb1325d5f55ba6a18e3bdf7fd199fdf22d01714a6d892d0bd6822a70ca3a6eff923989f90a115a27a2888e6b01deda6024e86697d916e45b0cb7fb4532d3dd72d4cf2c6e69652f1963364cfa54490a3105a5a6d8b78b58d96044c8dedb23d90ef78bd2feeed8fe795710ca4a4b882ee1c2f3ca91b7f0d7b08c14466061d36915e54b39dc3ca263088d5174dc5940d411ebec5f2684a7f58e9f8ea7f59e3cce71eb6eef697c7408b6e9e5287a5eeb5aa245cc9891529dcc72a0ef044e0ba35d820554e7a97a22b4ed0f88e9bcf3232b30ad148cf4576883069f1a135e306e855574c879d246eb6a617a8d9c4580d14a103663bb9b8e3f9804750f9e5fba5ca9fbab59b5413fd3d40813eeeb2e34154e52bcc6b738a03f19039633168d2b59930f4c02e9043d4edd4dfd8d878777a35202452537d88b6cf7baeda8e5c6e1b23b677604d2cb85affb8df573a0a598bd20c18a6a7cc1786dfeff162ff44fc9431fbd9abc24f088917e64874b0096be181322cf02a63af628f04ef516f7cb724a9a35516d61c96277f0cc15d0b182228b0c71cf44cb9a3ea931b9e6addb9233e2bc471073797e7ecbfcd8579bb7b0d28c8410bb7146ee398663ce27c538a615bee997d2f71ce6026876d03c2e8e0b71ed67d0fdefafd4b06b5b655e0cfa6fb2412fb56da25e5ebf8680fd9dd62cbffa8e44a2c6413eaa5abb7bc68d9b9f8be03e4450d64f62131ed3000fe79f88912063f401146e2613d9198d6016196e69285ac9bed6ab4be2a2784eff94d55678bc5498c182fb91d5e0ab365aec19505cc814c4af5a0bdac19ac72768d10df32e694de0724f0572a14568a1d9355968d15e32c3f7ad44441bb17ccd570c47a09b1ab46c0233af8e34d41efb4c7198e5088c97760f57d783362b94b8fc421c6ff2eb3ebad1ed2ac7511556abe5261f991fb1b9c88ef44e8988e522eb84ffe680d128aa6560f0424641d8caaeaf04c200b67ac903f6f10ce8f17da0b3ecdd1a7fdc8094b82163f7155b8ff4082026a0783c4f9400c740d110a2d0841fe5d8f4d1f8c858c08051fa256b26399626b7b2760bd7240cdcd6f631af28bfa939e0c470a2d36cd2c1c43355810a346d44f6d182b676b82fa4c3f67c9b31d4bffaba4284c4643ea220202d6a6fc7297f85a403f5e06c0cc5b951c0307a022bb05920a711f9290c0372b3080998a4c6f9d2e080a6f8a1a68c2fca31dc0f4261b20afcc1efdfdda88ea07c033515be762c3414d2b33bd71fd2b4e04a2a50a82c0c9661f20832549220519f0a9447115f541eb9f0e9805a507e979f6360aad42bc004427c601572fe15f09960a2a8d7c9ddd7ec6ac56ca55ba09f3ad299059e78357437a5e2f2d3012222cc7cf4461422484becf2bb7975e8fabfedccdd8d352c4af57367a95a5ba35f704aa0939286266d9b7ed4db582a346a2d6ac8f50f57f72d813c380616f0202f021ab05c2de28a42ffb9c38e18a59c60b45ddc9656131239d6bb695593429b89a237861828bf85d73b3f21f46646bdab705b94c3ba83b5bcdc203944e2ec3066f2422ba38d2e24ae9bc5288f3188fb47c63129cbf72edd6acf5136608976715c31193a6d0e51fce9df805738437ee6e24778cf60d56eb02dd9010120403b9cd7c26dcb5dd54355d6ffeb9cf56f26e9e27dc248880a074bd43a2db819019298b0c21dad0e7601c3e2c37671b657a55cff16482dd636d7d1cce2a62b7bdeaa6d2a20b1a4a473aed9939c53801c816cfbb96107afc7c91dab29acbd15b2c15c1ee530da08de8a41dff2efb2dbb0ad65368abec946efdb840e52fd9224a7ac749adf128c7182d4d49072bce90990ae3363920f644cafb988c04e7778b6a2d783540b20e6ba849a24e2cee5badc3d9f87bb08c42df5237bb6511447b519dd001b9b74e1b830244bbeb508512443adc2cf24df2d8bced7ccb616f41a5f0ab835e693c2604d42e60585241fd5b5b0cd7416d31ce9c795f9e4d91d5548f9cdb5012b2c2151360c0004ffa0e1fc3a86eedb19c4551143069618d8b752b51c3ced1da1a9adc2589f4c79a8c917efde3948a5f1da4b6325d6ff7be04877a5fa0af07339129330c486d306b139377720c3e46ef640214d752fbe99be0df92666eb455461c951c18696ff47983e13c5e8e854c7579cc0942ecc67e1d44437b7d578c2f03e6b9fd0f795c65d8ea5afa5bd4db2806b3836f1c79d8ec6f2a456b85eb2f8101b76cf92f85596214fd06b74991fcd21a78a30c4d09799e3a3e92102bed6167f848413910f60f919455f945be3a0ba52e639fe62123d0b31d6b2a0e99691f42d7f7de8928ac7541bd85e698ff43cfa2f7a62481f74f182c3f7006b996943ece228e462e0b6daa7ea37b654ea51687de1c67e57caf994ab65fd55ed20e5d42712a07867328b0baaa572613c68e80dddbb64f9c9e04ffeecd0d8f399f86275f0dbaa968cf0d623c1b792b7619397d70c7724ae40a9d494eafa4d262baade9bccf9a0e14991d0108e154ecc03b179d0178525fa035e854e7bbd74696f426dd580322b6e2bf265c9a5286a5c12875859d816d842a11447239f36c61c7b3a22d697384b91b351efd8b5b8f70b568c069b46e3ff51b60921b2d8e69425b8b51e41974094110c1113ae0bc2b13cbad58589169eb5206c7e0f7fcce4e5158bf64ca3f47ebfc60c81d9f777a020d6789ec71e49e71726722c3921ff07017ffdc93d4949552fef0582496acea2fb767932e2cb43009e45fe9a175ea419a784dde46625504c935ac7f04d8dd62ce096d9df45cbb084e9cbdd782c16c595e9976a83abb9479bcfb326a1fd9ca61d8dbc31c1ac4366c460f7697267e568ccb666fac304a35dd0cea6e2b18c9b3ef6ab01fb02c43244cdabdc0670b658f2349dfef788ff7f3fd8bcff8742c77f8496f8056be9ecafe87e7991458b4a7fd7e9b7723d598ceb43b335bf80a0a3bb5369dc655e704605d802022478ae8fadab73a965376984c163eb0442611244a96313b749816e31483e71c811ed28680ca3c960585c3beb73348a2828f6c1e3de18e2c7ffb2373d7ce07f6af30ad8b5bf7543a67b217ab5508fdc9dae47229007060728ae3ed418a50c27e8d4d3fb38a16e2d198707b0c49f0b0f0981c0c144c50a4d320caf2dbc63f0939a34c9620772ec1dc579564da414a5488f7bf1d24fda2eb29d0329bcaa90d7b370d70359bfce785aa0389b9b64a1d805aeecb2d0668f74723cd50cbf272b439ef58fcf5b9627c3476636244133618cfb18ee67f1d14e3b6780bf49040c8e3713b5da7b4c765ef0f44a4ba2607c6bffe961b804c1a7f983fc86278302a961de07bcab2b1d8c87f42d3e4ec1e26017dcff5a78dd107db774fe31e3dbbb44b2c056fcb2edaba797ef7ffc745491f538168e3cec98f1432cdbccf2735422706e8dd16816491309885e98f6bf001b3e8a8aae5a2e81a16ae509aa335273b37e951bd871959fcb6c812cfef30aa2049b5c3450d9d6b6b09686de1ad299c8130472428f40ebc22c49e21314eb25d6386f95674d34738e72cf0a030170b1ec04af0d3abfb76ede04b388ed2d3f2f977be26b98b714d497f2c6efc0037d324eb08f1f4a2974565b8746dda11ca2944301494a800909bbee806963e92178b4f8d290c517a9751adabe3671dbdf156a49a0920d5111fe7d0640dcb304f7042bf5344eb960e361cd05b802ac2e19ea9439e2b77f5fb09baf20203d23c9d38f1a53a57d924af7d4a8af8a5ec4eee064fed2d5b58a3c43d281a97117c0e30563d429f943d3e3a7d5ba47f7e40af412f7c117ba802403f7258816220d421dda84c76f16e7ca7827a1f85b9d8a79da43c867fcff91594e7dfa847a619a1674e05f8278446c73f7a96fe8138f3ea452dd92fb39b3f7dc52d170e98e5029b951e8e9ad6730f08fc023bcf6fc1425a5a08fd9da207d1b34cdd591e750611313989d33d1bef87d8269e329a8e8f8dff7145bd0942aa674cd84f3d4e5fcb27fd22cd699118671d582bf55fa4e03bd30ae1ce1c90736a1607880446695fae0a3186ce85230bb095847c374632954949d54b6200d61bc3b85b0b95053279961c8c4fdf0ad9df1563eaa7dbdf189c793bd9721391e4c4aacd20aadfb172e15a2efaf8dd0face72b1495f7d5a8bdfa475cfaa7249f23842a937f0a23e68eda9c0a297ea8da1484178e9f20c89cf63a2ddb18610968f0b242ccd4713b4ba492a824ff0532fce504017a5a19ce0ffd959608d883d709623210f1edd71893ae96052c0244f5408f977814136ac5eb32633b453c8126ddb79cb45e659c7b89f427031ff0eae342074abb87887716659092e809ee3843892f7e5902f24b0c2327f7281862cf758082ffe45f7983ca3114ab354faf722e540a2ed3bbad4e37223accc928e0cfd1e75ffa47e5cdb82f7e3cacbc94e75b7ffe926f668d51213993fe39400cbed495ab13350c2028153d9b8f67707fa2028269aa356919b439b4e653e55021c5abb24ace804cc33edac36ee037ff72393c5a29f8d3495a8bb5b1135ebe8e2966a4472202c2f9c802e5fc0ba01e551964c5e92d8e4bdea64b63c593dd52f81fe02a5b8b4ed1fbb9300579ac90fce48304909ffa28a5dbaaf3fcce95501dd003ae91d84f05d0d665c8392a314220eeb1fbf83b6906409d6b4b8814700673c211ba063507bbc57161e68a01e20517006f22ee4bc198c0d9e8a146d26c9603f123533f284db9506d19fe33769f11888e29578d90c0820b5a556ec97a6264910f2d3b13fac13a54292ee27a0fe0e4aa79c44844508a6ba2f337af57e5e344d5dc17c40f8c5c385435407e5636d79490945f0ac6a5f8a13acd61c10d0bf446e1e81cf595d1e46b9cbc6f9eb5d7aa295b62c0f54a52f461c8e626c7649f81bb8f232931fa4058aa75ea5ed549f857dac0b49adaa14577039f02b3243119b05d53ac233b99872d3675a8bf8228877a5a14187cbb910b5423e3a809e31dc82e92ecc80d9a298c94fe9859fef768021fae718e8627b182643a5aece4a823ab14f5c9d0264d6e19c2bdd24d928e2b67e4cd485d77c4eb922d42b0a58db95fdba75728a225ab01c812bb1b17123d6dfcede7860a0d8daea2cbe20569f6f52a524c1860a41a05b5ca27d6b195f07699ee1fe12586119f46314243b6d1e0159676450fe60428a94f172cbc56a36ca5101edfb59ea4c57aa926f082af225764fed67f471d62174ea6046eee3c5f30acc47fc2c11eca36324fb64012e18797b91ba360d7649a15c7069c3b142369923f4ecec193715a094d1ec51166dd88ae415269d9c145250d27f9abf8631ac89fbb45a880c4f573ba095073c7dc7db9815eef4f3fce7bb019f55b969aa0cb3ec360b14705926937ec2bbe3bdc37a55b0a780d53226085bf132f11b6087e33ffb5c18d6219ab19ccc5f9084466d2998670ab2e3cfe7e6448122ceef08ccd7cf0df77b65021a717e56aa59953d78e697eab50d960763a74250ab727d201408dfd574dc10fb918dde0a24fa61be0fe3dca2133d2f5271e23c704c8b56adc72302cde25ab56afe4fc3933241a887524b266c9f800911ef41245284a6c5723f9f4e24802203fdf45224dce5ee3f08185b5bbb4ed881133ed81f317e8a44153c581a1e28869b48e63dd7dc2775d4275d84dd6472704d5333a129410f07b10ef581d8126cde0120a1f14f4e4b61f0d81f0d88364baeff0dc7dc5bc2345fb3fbf3a59012ac3ac1f89badb0730c83d1a19f3511bc6f37d7b13815d3309df10fdf80e914c251813aae350b7116dc63d71659e5497bfb8d4010bcd3ceb11c403112c24e7dd07b5bf70d840151ec0aa6cb17b28512140a8bc133786fc601dd3025e0b6852f1533f24bc7ac931076703cc8c9bec73d17740259f66054d352ceea965f5f992ce0ec353136d496c4b00eb9a577bd96267fea080e76023725228ffc6fc256598331478f366740c1bb8acea00b4c866fbc355fdcf66e6085e691c29e033f95a7ccc66e8bde9975a26db8a3cd918d1c4668b26c3aee106fd6c532d97d6fddb106d6a6e5ae72721bf655d17f117930eb975ae18f93713178ac3500be30edad0bc671bfe377a0459f66270817516ad141658fa900858d38f3171991cf90409230996b193b80e09f6dcc3f8593866ac2e7aed13e2d047f42b9e95462ce6b49ddaf821af5e20b7b0bd48a193a8ee2acd3fb28e4e43a3e947aea85915248bfe8c192530676aae57fb5c16ad9fa02e276d390caf4680ee7eaf831cd30a2f8eeb51f179ba6684883477f96aa4db9411f26ff7b660924d5aa2431399ba3d21a71b3a496e0707d66ec53847b777e56e467151967f155f15471853158f467e11872267467f0878f9b474b25ea29aa42f55358f4c334d6955e16ad67ff7fe612f3931ec6fb062b7bde517bbdbdce100682542e66f47a57e1ddbf32735942ed22eb95440bee718132a5af75299c75cf4f8c22858730bbc75b105c127ecc0b9a814e378689916333fb0fde87908f9ede468c9f730e562372fd874b02769ea2ea729e84bebcddf16f69b9a674cc8f49284e964f698e4c299bdd6d6d5c398aff63f2c3004719b2fb970165aee0f9b90e776c15282d3f876e52ac75fad455656577215db9c9e287abe3a1442f31aae7b13136cfa88fb144a6557523a6b1238d90ad62e9c2203925990843c7621c69ad975d03b4490e9b76682b8e04ac4ecca5dfbca0005184a25d7c3bd6028dac0c4e9037df0e21639b600fb596c8a639ea0d91084", - "public_inputs_hex": "0x1a207628cc6936816ccb62a7b56fdbbf8e975293b677c988644e018fc402e4411dfdc7cb6265f100524012f038ee1f205bf8789b70b46c57463dedb4998f7ac8000000000000000000000000000000007ec37a8a654c78ed3a8e7c4d6e933fa600000000000000000000000000000000520f0c28dc3d0e79e73f3f0ea8d8dcc01806572c19ee2f3532e970d617919b3aa8cdc582782dfad0699badc631f7512800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002195e6ee0539776806e913a0259520d29c75b0f8496a9cf04f16a674bf1ea4e640d0a0b42b415ea987c680bb2fb8d9891c2b7934374e865271df6f818d1b089c32f3529d2257b7810036b3d044fc8c3ce879ccbe2a182cadf83c8ffc73f3bdf9c2d81dfbd8a3f5f3a78af2e6f5ec2028880040866451dd20e92f58bc0790494860000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "proof_hex": "0x0000000000000000000000000000000000000000000000046462eded1bbde4bf000000000000000000000000000000000000000000000009f2fdc2ae91c95310000000000000000000000000000000000000000000000000b2ea26392102492c00000000000000000000000000000000000000000000000000004afc85764e79000000000000000000000000000000000000000000000004c469c087756779e800000000000000000000000000000000000000000000000d3dc12dc70ceba4e500000000000000000000000000000000000000000000000d09dfd159e66bd2360000000000000000000000000000000000000000000000000001d0742afdbf00000000000000000000000000000000000000000000000009104c8f5d91fa2bf500000000000000000000000000000000000000000000000836b1660e0992fc3c0000000000000000000000000000000000000000000000031380efa2b7ff3ce80000000000000000000000000000000000000000000000000001d837d5ff0a5500000000000000000000000000000000000000000000000db4b2385e73fbe68b00000000000000000000000000000000000000000000000e4f7388c73406d61700000000000000000000000000000000000000000000000e8623e231e09b7f3a000000000000000000000000000000000000000000000000000132c5f9251f0e1f3f0033a84e44de526080bdfdef3d2c4e9be76761bf24b2d0e31ac487ab12b12f3b1b559ce6d9ce7c97b5f31aae0e6529acf2d329858733b8c0215b3e3314c51eb174b690f7d4f832df6574dd9e4bfdd41cc47539b0301c66a88984cc0347a312b53b6e93390fa5cff0fbb34c9a55b3bfa61ab7841f31779fec5644f07e5f9c17d0eb1194403db98035cef3a0d47e70ddedfbfb47bd33c44fc0f7f4921d50180f10ba9e083af9917dde07ffbe45a53e65983092dc0f35e1c84f92b7189595390810dddf63209c3f94b856eae91571feb451a665cd1a1035b990c887ecc43cba2433686433d56359e92f6bacf0501d6d7662ce00d33aebf14dbe64164c036caf16519af50842a2f1478375a011705e1b6e3cc8d1427b463a370002e3a70896ad27996d20e8ff12e70916e72471a5dbaef7f378d7232905aba8cecc91e8c077c72a272b8af0c6bc297b6b8563e218a17a1d510b271bf12dd8d3aa90e56d6519a32d29635466a7bd4dfdf51438ccceb51d486058596bd2e201d320c9b63fe239251273008b48d56b8a01c916ad52aace95cfaaae3232df4a22af016a830f00b2280dc4090249ebc6c4f4977b3d2f3e92603ca1e86d0c65fb91f757747e5b3c64f229c98b47e1adbdae651e8c93d1b5e22b26a27a4004b26a4cc6dece078c1e1f420e928f4810f467c371466a5ad780e27de4b7cc7d34c4bcf9bc4d1a8d69382b5c26a661451205b6074193728e248ef7585707509e09eae306e78710f1fb3288251b20d0edf70c5a978f32b92a869fe73975d955f5281b23392981a6f19a2f0f58271d29171ed9a120fb70186dd4f920f56f767149d663c103ad123bb86de7d5ab1678aaa63cc7e6039049df417f336220a6115e3e860bf85aa96a44d22587562527980f69c45463860ee9df44482e0f52c5a10107aa1bd1fa8a3ffa215868740307471504d1532b017118223f73cec88bdb2514519f0eb31f1c14aa42fccc0d541f5b3ea28bd0f5a00d90defbeeb8e25bdb73835f623fa6bfee1e5ea3032e649608c59db0e1d6d963d552f4b4633c5767b0e8bddd6f350c179ed032448ecb129c15f26bb9e1ca1f3529eef918c8ff9788b597b6befad98ef7b67aa0eb9134d5b4210870b8989b0adbbeabe3dfebe6ea30bd7d60659f4e39e266184b8243691b2e0552aae18e9cd627f7c5ec8693c5fa0d609eded0c8d047ec9ea6c224418278250112560148af7398eef1f9dd48561fa9c0441801dbdd69c8bd46e5c4af99ffe4109f7ed54f9cb7f83e617e6b2f93b922bce7e1c07151e676a7ece630dd747bcc142aeccbc157a93950eed8401606e1b16e66571b7f8ef663ccf4e93fb82995212bc9e169ea1c0594356b171e607e54fc02180a1059edf5acb8983064734fd9f8209e394bb72f81f207edcfaf9f2ed0b8c0689b85c47181abcfc8d36e08b0c5ae05832a231dcb4f6da7f1ff2ac341a08b210acdf2bf48f315d5920175f3d5cec908b4961c1e62cb178fd865c31734ac843d19fe58175f36c3bd84dd20e8069f6b0a624f48a0e47210130d1e6697959e2fc351c28cdc4228fe9fe37fd5abaa897e269391d032b12bcccdfe6c5b4157526f2325f20b13b8a81f8ee1492e0008cc770dc6aa7a1be35998418d9d54e3eeb7bc87d1ae47d20d47645c4973519428a79b15d9121c942e03cc0ccd93c2158ec1e33c2b59bb09173e7b7c9662f2baba474b01d7183e0570580d82094361ca63a614ebb95d875ff403af22cabd79cc4e8f400424df7b0904cb8d32decc3e907e6a0b511bec4ce2fe9331afdf13b26b4b010b0b3f755f1a444cc372cbbb860872fa0493f8c492a7f23867ff93dda7b637a2e90efee7b19c99a3476e8f399e1c68944d4bac5b988ae883dac4f406ec18abb7ac0c45dca453ba41f5b9c28dcca7905816fc9600d2298b31a000bda8fd06731f6f26b119a02a7fc2a4ae33307e9f2798f2502ba08f0cba86fa82624386a8a7cf1a1bcc4583eb73ca4ac0b5285cef38d0a4a6302218e5ff0184f9c9908eca82c01a26e711272a4d2dc6c57ad49857ecb1b4adab7807061cb4f5edfdde5930058ee71e8bc31fedd59323de5af8e5713c7ceb21d48c3f73611979e7e2712684589c4711f24d5a2b83af9233bea1cf2268234eac399129feda79cc48c5cd77735d9b000d2d0a40be4f80ee8003228f36bd9602fff33d890bec95c8c23e00e7cae127c41075162afa6c2b18a81f299e7eae0f590c18989adf321dfd19a846924be23a7918c08a3dd27cd71b39d3e3562973ca37074ec000f69aa3824710bc344bd5ea292ae856b14a9a14635879ccbf87f082eddf2dbd240b9fa4702f5fc5d88ee72b2a096808a9862cbf8ae30240058d22dd69272b346467520339fda95a07f6a8403325be8b2eb1744fef4514ad7158c52488019b3eafa4d8cf7df694c3bf47c3a59c1a1fc4a36060bc9af14ebf2eb79d07cfda2528c398b145317c2cc84de6137d44221226dbd233ab0c7c0263ddef6811487072f0e218b94a6c72fc7e39bc27141401b11d54d55aba111509422d31d6cb92dea31534ace379c0427c6c3a92d4f90f02251ec156fd36045aa90adf78403f49e8479b8ca9778326da1425427815fbd6274bf80015317e3ecdca8795d60e025b58a56dc77f5a4b9acb453d3e78deee5c14a5cd11d5d16c49fef05d37c81548bcc270ef9f21a79b449a30d540ee0b9a8307e991ea9b46f57eb69d7c1ebe87a8fdf4f8048b4f41d4808162f8e0f7cfb3e11a5c3cdec6eecb39b1351814f82418b9170740fff3f5681d48fce7310e527b6a03d5ae1282295d1a77b314268efd8cac07fc028290a1c8657e45227f4b6ee63a24de15adf0cd89fa38336b41277d5fbb3dd19e281b815b2c0d2e9c3e5511797e00e3f664895d68974f56c39c9889994ab59f8cab6b8a8ecb2d703646a2236aa91198ae08f77f02dfc879c800f7c84e398ed8a43f20f2aea496b004cc58fcf4780b4966b97a4be732ee4358cbdfa657c322cd78549ee3780e0350374ea4a3d8900f6418d683ec071a1273be730e362daf287de8c335727321143ba40ee0115c241f9ecad9f72e9925e3de109f1e566341752dd39b45d512d2388dacf23b281ce3108c6659ba45864d7bb6836042b9097f20f3f2a053046045dc613ef501a2eea81281834f5e8f9d7140f709b79d53fd264556d70139ed47d25666027aa69c58e42b34757fdfd0b9d122191696b4b4bf4eca578e24fd9b844b34d73c31ac3ddfa801ff012ca13ccd6b0f5e561e0e6430b4be2b0b2d374ea129e470491ed7c3b13b183e35a8ce4cd1853425fe864158971415c7a65f11dfe69d5074f950809a122a28063e5d27dab57ae4f0b3ae9fc361028adf438677dfb5dfb988f8b8b6ae57a3076e5ea1a0b2f974df0ffbfe0a060e3637c3127776fa81d9b01ea0d657dabda901d6807a490c87e46e4e394192ec98b42ed04f334b5a1c0f5057e4a4c557c01a1043c947cc968219522c1abe82b20d6a4db9c2b388a24b71cced988a4b883a481d25e3810be1b850a40508bc3fa632b2e5fcff0a7b1a49340915865bd52f501f1f78f68122727eb366d619ffc67d08d4d38cf0e91ea66920699c3a2dcd37656724c44f6274e12fa976dcaa3d6cb603e2b323f8dcacbcfda2d2371a78596fd7e42e52e2ba095e30b7021a480d05dad2f2838df1fbbaf38e0df2af304c8b1175b80cb447d4e3ba6d26637a624eec49a5617a8380020addb7879bc212fe1c600425147bf057f7596b5f2c938d61049a8cd1889ab5c40ab9386a13aa7022d00ad42d0e1b1338ff64fa766858440e350fa32f5fc613ccd8b46c95090843de78343271006a68bf23479c2223f60d11d64a98c2865fd34f92e687eb8c2b7a28deca830818a2208adc5a09a52dcfdc04f6e0e53bbc232b046d60528ffb794238b3e64b5417d6dfa423205c8a1ab17a90537a19ef9e5931c33a757771a62c7288865270fa1762f450463e4256850c78446525c13e0f55928414615620e1c41fc5f2a62bbe2ec7351b1d969bf8399ed677fcdd79a6689cb8c08682aa486ed19431e2fea30326c3b9cb74eb998fdd93bd366ca54253b2ac0e213d65275b7e15be6f55b87e5326ae1e2391bfc71e63cb96fe8badbb8eea59b1bfbbc89d9fa9c76214c968a9ef10f4697708d257e2caa80176fce9c9dd0bf7f08db787efff22e8c50a2b3d851c03bc1a26a9f96c6c7519fa1f9f69e89d21f3a846cfe8c7825b32cd3b1df30dba1d5dbd1e3883130ed44f9f1708164d7c037f7110fc42a2569005b099cd6c03561737abb36b4e6751c47f50a1749e53575fb2d60336dfb156eea527c39e6044fd0fa4c2d07af7ebb89646540fb42fce47fe70faaf75eeef6741a570a90a39e2212e25594c3141f72b1caabdbcdd33ae4e941fe07d0a9fd16698c6856b74619a810c0cfff04663e7bee110fefa623a4db43fad9bd5d72829e65d9a71427a0d5c2f0bc57df006430e7edbc07970858f1f006b42a6b8e510300fc604821abe5d9ed612879f61da683c655452ab2d76bf7345c63299b02b7ccaf83dcb03552c5048020354ed28001f40fb66a7fc55ff272aa8de1844409e56a6c2a3fcb89c94218f082674c00541fe4443178d227abef44644d4abfd5e9303fff2e68bf3e1d140235910cd28a2f0ac9cd54303522a68c80e618d937536c1d26897c8f85b4acf4aa1060c9d7cd154657431320a619ea0a603fd86a52edf39d34cbfd2327b95c4d02dac2df4af3fda5d0fb1411f7499e9228e69f7baba0aeed7b1e31f6d17aad11c2c4a1fda0b5b8b20f3f9963100e0dcd1ab2346b12f602bc30a2bb453e615a7c7df7a1e209bafa91194c0e3da621aea9637db24336c649d82aed522c6ebdc9e3079df264481411d0846d3f82043e90df085bb974d2ff8c5094b56b0721e622a3ece1e2c72afee9cce381b1ab0a042d7a2a0d32bfe34d76e43ae24cfa00802038ce3482e328423d2b01a87447801dd0fc1e2c33563bf35c6bf7ee27f8ea7808e26da630559777a16193d3cbaa060b686fdf5756e395ca3c942d5415ea321930d9bc4581da056043fe7c53176c8fcd66835b9ef69bd815564d1bc7fc71c1373bfc03d480b7aacb1b71a1d5cb97773bd8420ecedb4abeac3762884e73eb9701d5e192c1e2798ea30398aa7c6aa711720294d914419284a52a9794048ad73895e21c73f2e27ee0aeb6cbfc1ed89266847420a154aa825df70f24eb1b1f0b958c046771a911baa6453cba72219ac18b8e31e8b660828ed849b2cd1b642f2494e7430d440552ccde3ff0ea6860a58505c5d2c029cbb4f5fa598fe4ac011751790e0e606322c13d130dfeb16f00ef3a180f8e4886c9a3f367a36a4b2fa215a60511d77a4d1ab2e1d16ee35174f5333bacc3170eb79641811ab8d999ca78bd6aeb778c04bfe4b022405d81d43d828fa1363837759b1d688f9bc423f18067c1e63e7ddcae77b252d284dd9ebdebff77e6f0f9e799a7461a0f4899e54c7d34d148cb21cc3d29c76292c218095c7386baa2c5304e29fd4c70bd82e6062cf160ebb110230f444c2d71f3187f7f4932fda50d850dc56b3ef554e7745311c76955f8eb116e15d3f72d81a267864acfa25775724ef1f370daa11bc9983df1f6d897ed8f040899ecaad740b34dfb34c45c325bd21c38b8634103c639653053c1a72bf63e3a074b437a978169d1d7cf13831621c414bd3d03f307e7bf3c2bc654afdd857c43c0e1e47c4e726f6703cf414f9bef59c90a72eb596013447cc3cf7a8576cecd1674d8607f22e0cf5d470df13e216be6c2e9e8ea0ff0a179c00cac95375a14e02787cacb54a901fd68c2ae7eb0ee85c442aae3d8304039a8209c8090040c83dc3e287ce5187731d9b8e516f91b73a1475c4c4077544dbd325ee6ccfa3aabd1144352afdbd457c2f39054b92189663deb023ab48081a80b8e28841188838be3981e7e93840ac35199412daa396d472a6a2cd403a92a8a80995a1092e99f0dab79a2c024258bff1009161d190a6eff111d46fd2e4fb3528475d89ed0f4acbd2a83ed488cd0d14fb2864e012328fa9fa8234ef040bd7e64c94d8e5dd435f85564aed36b3c59bf50b0611681a82df36ab4b44b009b20fe02e8689ea64df791546cd3db0588d08074c241768008e3feedafd677164b7e19c9dfb2a1f5b4c91abe6b2716dda9027a8cc020340435acab05014bc0b5fba39be8a5dfe4751ec0cd65a54e3acaf559fc45310001c7116a956f02d2c919493c1972c2a8c37c5c7018371de046603f943861d069b44e45d5c59aa7b8e14620ef592b42e43dcd8022db7fd3d652bc292fdebed066f0db566282ce6fd05432d2bf063cc05c2372a442e4eb1ae95d943750660e129ab4a81c1e699cb974b471bb885af4c42f8be4be4a44f6c16cf33b22bfacc7122532e5f78e3da9ca7b8da40ad3651c7a3cde3c028a39f76ee378a5df8e7122d117b0ada3274420f4169996ff8ad7f0644294b964bd02e43b573e027e56c31f30281ded22c23f5318715d0f675c4c697a2c257f22112738f30ba3d9abfab6ffa1b183c5a511100513b515950e7995ccf0f082cb5b0d62b57269e2c7cf3e4c0eb0bffb7398d6bcbc8c3a742ce3bfb2975d365c391409d9590552733692a1a62b7138cea6bdb50758166ea8db309d0ad52dd680140c79d82fe429ceced64c6c214103a90d27fdc490bf99777ffd713275db4329c3cc42f5d631a29af86d0e449a5298a95d8f509c980f7b11abc90efb5b6ec019ba2abe05ff97971e3cc4494a8d11c320855d17be84acbdabcc29634e10451cc55f7b18e63385dee746c70927b271c9d654c07f30212ba3d0b34ab1c7d81484353e7303ba8739122ea0b4d054c5905b3b6fc0e3482c9d1f92261d1caf129228350cf179238a409ae49a4887645050da8ffcf50fa4ef02b22e5cd98fee5625348f8dd72c4806e6a6028da2514e05314cfd201047def07dbac92fc3f31197a0f94659a6264cd08c7b7b1b73f5cc19d074a8c82be071ccd4ad401a5907a3816735668a55c2c2254cc94d9fd42d2990419d4ce2b03bae535d55557127ecd66370f5a555fa5b3d3255b0e3e18040e7c9718c174a2ecd7916c33c950e946764b5a2490252a6c8b4e8a3f4e4200160e12472f73ca99a627089fc5affdb380da4506c458ac642d6a8f151f60b09261abddb2092edcbd085881db6d5b96013108dabdc80a3b38fa8d2e638be26c78465bbeaf2c06f7a360a2cc343d6d9dc2db8f6424302e9718b8884ec3d80e99407770f64a15777f2da7f4aab9408e3ae2f3414bbffd74a290cd14195ef7fff6b31d737bc22f2a1491ff76b75114b2105c8ced13258e504a7b36c5102321daa348581a551729aa4fd8e5c8a411c89f29eb17ccbbdb02141008bbdf46c7274a2c719595fb7a2f5854c0f6543b85ec7221d1e15b8e867c80848016f25e72617f27e537cd721c05b0e96a7801ee3fc43a534ff819962b1e8282ed7fe5437df8026f12931affc92a45a488eb039c2285d69ebb9cc721d0c0481ade8e27986cc2424bd63f09ea13281fa330a68c69d21ea8262d617a8784c5c3130f9fe2638a28d3cdda46fa9c3f110d509bb1dfefd5f72c7af0c2d6cf3036ea02de515f6876005bbac8d8c51d6300ac7fc8e451e423e4ba7c25eb016814dbbe5a9847c8d72a519dee86749635c52fc29948f9db22787989d3d2630dabd1b4a74528011a8a2df437b4fa220098850a3e4753fadb485e012a8cc00da2cd5919c51598f8ff98d82ff813d03bedaedf2fea1e5e0c489ac9b0400e0d949e92115373e6350c76f49c345b79f2ff56bcd001b956017661a2f6b6b167f5f12b4894a32157334c60f29d0521166d13d15de9302c9b0c0bb5c7880f6acbf2acb78d97a66d12e898b7ec0d8c7322bf62e51aec263770b749a733e1842af368cb129d636ca2c23890cd74c44f69a427e6dcd095119e080b300e82de5fc8b08396e97d027b8e781f0cbd63489c5d496949b3daee08a8d6b94c056c4b29bdbefdfb87f715725d48e2aa2c8ecb2e72e4a6329b861c11a1733f664320f80bb5e0624c847534380dd10bf3b9d7b0b3f3cbc00ed6d8d72ea19cc0831d47f5142d7699110a13d154190e29363598288d9e87582621ec8518aa844817a33cf7385a4867a624e6d35d19f08689cb4694d9eb4068a5a89cee14fe3cfd331d9edeba664f921f4e2af1b182eff2ffca37f65408d3d1b42eb324193f27aea86433f3a6aaa1f0ccc589243d364a6d284678652a5727709a417c212fda27231f6e20f52417f284a5641a6d80362cf3dd402b81e6dda971168329fc14aa355a2874208110adb7169be2c53ef263bdf59336ba78618087e36be1f3c9277257150ffe847f6d86895f1197dcaa651987c33d02f5ba5b482311f817d2752f96ec1b41838e338b2af7008a0d5949ff9f18dc7be79ef5739211659f266d621462e12c1be7bc4b4934fdb8699f993d2caf7b1c570cf25b802c98a25d69b994073b68b01ae79b6a69eb211a914afceb059e78d2a636d304a48f6b05bc32993f1eb3e3e642813c85af8b5f1de0888cb13c53fdc5d5d479c70ccbbe61744c1239002bf3e62cebcaeb5e7303b3e12a1f30fd0db795b804875570e7fed3230bd8052871b4aea05a90ad7b63f4113cef0be4dda60693a503b2378172a58c4624a2eb2d5737e779f841a33c28eb444b62d3d23520278eb740181fda57f544f3aee7e30252680255206ff7d0a4a519afb865069d8a05da1132d795895891fa94f8f29904f4324c26aceac3104c97d39759801a293e22e65a381aa86de3b50159ca9fca09cf8f2106c1ecba895d547e1dc71ed0d9e8db14b9db90888777e29ef793b337179e7d5a6852b64d326b7fff80c4434942d3585902b1c534450562c6ffd93e8c0d157d7f9386ead62dbe90615399b0c8fb87e5fca00721a2a3fb68d14d36819b09983dca3f3792b69abc99b3f3f5afd33d28cba0999b8968f72566f05de37d660436b2b1da3001d81f914ab0dc20eb913507e7e526d5f96f842dbc6d461b96df2c259d17a61cc499cc16d0ecb910225076caef6a3d6c2fa282b2bf34a1deb87129450563c1427f7e543ebbd7aa31c01a16f7f9ef377414182129f183ba1246f91048c125b304aec8c136a897807710b6a9a73cf827b2c53e733c2eb1df3fb2fb0210be8239b1631db74571a2e30ff1dc5e77a7be632890b5bed671bdaf6e2a960bdf75213571caa91dd52fab661a9e4fa96e23b6d814748fc4bd6a032ca1cbc71a6e2915cc8eb694d63a591eae3f5aec153656fb2311b4ba339a7e871ef872702461a3b251b88bd55cea092af0c1cf7ec9f96f35e46cd359223da833d8e518990775f13a8a47b5df373d0d46a9098a0b23c1dd6aa76b286f6708e7713811025a23a510820428c3768c4dfc8965fb9d34ed574ea7ca0d2eb426afe5f8fe0681ef02a7a0700344716e9466f589aaee49d2860b604052684091a9e0ea23dc12661a18dc0d314a36ae8c600e6a886d4343a2b42f3419ba19e793ede386baaa303a402f234bee6497b9e517ac24aaab0fb01afb0b1707423554a869d1bea6a5bf743f11f32baa913c5500c9df9db6f92f49d30b7adb7cda475c5cbcfbdab514f391b12c3e705687b7ca3381e1363090cbf57ff7a86d1fc0c9b110893d6537435e223b0966964c5e464aa76eb466b04c4d5c7037f19229aa437e7e490d1bf9b19eec700cee2eaf6b694fa0b6b912b86ac7bddd35f7270baf326abc5234e02798047f6100dd53510a41dc8fe6369350a1b458bf042e8204d4453a359a5d107ad266d8bd0a393b07ac21e86143568945dea47579f961da100dcd17c89e800299d1dac22413736f9bcfe75432de59c190fec427a659e0878ae078596ccfca7ce18070706a27da89553eba9079775003ae2cb47c214b82ea2eefefe40da9638e912cfe60822ae649c2057442471a471a9f952eff46a2babf1a83e19a4ddab3794cdac562db101a09b7a278b2dd6fb6e2a9d393579c92db556817f6dbdc7733599f140cb4382e6b319f574a62dbebd2af4cc151e0773ab2fec6239c69681263c76fd83cff19220271f850682cc592aaaa6eea28d6bc0cb73007fd4bd1bd995a099bd82c024f0a2f782d188ed96e6e3bf4087c26d3d237a2b12204a57053ad307c344ae34fa4213bd4ee4f056cb822fe91f44421f97b9553e372050526ddbec9a9810f0b77cd05e7dd08def408b601eea156f02988103cdc6af0fb020894d84e00680fc6cb690af351f694b00379f093c09cf78cb4261d49857bea4be6f8dd3f4a7b32f59a230e3189ede5dddf5124a8caf0482b511ba11705b8e97b0ae8daff89770da3fb7e20cb1d9be77cebb1509930887487b1599d90171d64bffc6e929ec317bf7e5aec2f7740dbe894189de87675d7e978196e17b528ac35b67d091c6b144dccebf6770a01a0077e8d2c021ad57fb50a34e3158e55e57f94290cb6db1438e73c26dd5f03367dc364867a8930faff385c5e3805b3f5d61b5fe90d04e51b1e14373054971a06811bbc967bd9eec33c892ae9c4ef7274d5e4351abbabd2bcb803a5f5c70f02860e11bdf399a1dfe38969bf66e7da76086fd1368b68e47963db7f9565b3130d69008e708fc5f8961db2d87f77fe3aa722795a6c711cdaa2d4da8f38eaef83196be74e4cf2a837a79259f60cff97b4901f7f48941807f07784fcbf4db57ba80c897759c9ca3474d1f1c03abf18050430fe347a17998ad5498c2afd48c8a98415b32a411758c283b4c28c1d5ca62fc3dc246c62fae1b7450b168c5854c3f6f81f25ec496179eca02a8934cceb7f9ab9fc81a0b5c0982e045b5742e3d21791562bf709a403a8ff7f4c3d3ed483bd94c60426e37242a53fb501d465f6771bdd1c01e4364a0bcd48a20d4fff5363f75c85d02ce552a1c66784f8c393df093885b02286068214029f61fe4946410855f18276aac139a43f0b328f281a40f607dc65006694ad288bedeb4d931e515e3d739eb1c0cc95d7c5284f3ae66f43286a98a92480eb742d842cf382e23e07211c6767bc86b65efcaf58199ad5bbcfaeb67b8116ab42ba4929e61ef40822b47b3b77e1411f2f8dbc72c1e930cd08e4eb38f37b0bea7c2020d7ee87ea54c1b3696fdbc146aa584a23042c69e6efe33f7556c1762b3e3ba8c773badbf5104256d6a599fd5b20145beefa188347e080716230875a1f23ad5fd8f118d14793db2a001b386410d01cae4501bde987126554aa1f943809b93a34284851e0092f0ddadd9d27066c2568c8ebf91bbd5e1554c3ebbc6eb509d2ea1544f9c6b1dce97cd8584418ced461121ffcdec9e3dd1fa14f95d2e4ea08cf6a13709220c27a0a49bb9334e11e182a621cde1bde5f384b1b0ad51d19833033a68bb8ad808d6c9c7b07ab154119abbcbb85ef10d40fb251969dc2108c90221aec3e7930b31580525bb5871112787777aea6ad16614536a5a667520debea18baad0cb9f041125b510f5f1fd48f41cd60268c6ec4149908e7f9c9a1c3fcf813a79c776a2eea726579809ef20c267d839f3a208120f8736779796e9dfa7bca253a82820d90f4f5f7fb4b94a870394dbb688fdcc0bb429e43546b8b88743f3f114951cfc8e34a21110a24cfb809985668ea681e55f068d8f260436dfcbaffc91c32edff5ee1777d2ff82c80ea797077225f691258424cf5f5d64b3c312d6f48257b1ca15daafc6d830d0d38e74669add7907e870f0f8e896837af7e0104b15d103b7ad3d25f8de54bcc7ba08a0b3bb9f30e238fe4e3321a523728c554b162110489739821241ca55e4ecbcc5f6f25bfb44763a32e19fe977940ce8800db666317bdcdf1f29834dc6b2f07c60f7076ea4f4c73881e89734d46a31a160380704e0e4d0b30a60fae2bd517e3c939cf57682a076d4f6f364126bb2975f940ee8fdd0976ffd75e34827735162044bff075b04735ee3468e51d80a7a09bed81b48942205981554c5a78f504f763531fdb93d2861ef929be2f873e59dcec85631807b70484a7f6ec144b234ea375bdd48188e15ada0eb56ad1b987d75ed356bb65d5e01bdf10e80176f7be41f9d31008b8274f1b0a8e37108cfd37fa80c5be57fdc6450207b4b4209e983e6aa97e26c81afb4d57059de74276ffde689f883d5ed67abd2ccd9fbc681e291fed67130e43f755601bae4c8849909e9030484cf3c71aea07227f95a36dffb88c8d0131284fba36b597c0607f0ab3b1bb6c215a19fd80cdc92c0ae72660453b0973ebfa890718c2939f1207e3ae5ae18bde7208392f708a9a093cbd866e4426034e015fa66d8b9379509021996d41841c44374f7e4ec77a8e0bcb2604e0783d0a7981f77addc0d9d00dcdba536950e38f1893b138696ca20828cc6f843d63ce7c068774807ac2f6a52bd2adc3649c130a35b854e2965dda9a2a39eab89126cc289a350e2bf0eda765a5bc5da8640047bf12423fd54224d0d6053505f8e689a403440ad11e799b653515ad5ebadac03c3c17a2e6a40ad8b829025a520eabe6104fefc0349b83a1292f1713d4e56131708398777532728fcd2b0b712b5802b896281422c1bc219dfd0d9df3be491a20443ed5119ae62c468d2a22adda3834369b2cefc68f1033b217baa21720c4e4813cd4b52388f3bce5eb3e21ea381f1ee6305814a07f4742b52311720f7aa836766eb57fc2504d89fa68be03b747454dcc12f3b4434342eab6d95eb64c2dccd735983c553b76a009c18e6e2e8e783a9067cda1f5109f7371fcaa2cc1658c50fc513effba99a62f84479ede1c2afee100f375462e12d611ca1e58b350f4713e43436f8d44ed129d36d741772485b57e2cde8766e19956bf15fe1a3f715f462ff70e3e66911ca8b0349ccac50d0c4d59a71edabc1844172fce54e10bcba6f74cbd36c2c6cb8cdf86b07fcf272bbeae03157930ff5bf215a8fbeae00610efde36e8803758c44b71611ad8b7530f77a243c70d35bcbdf3d390e0e1f358a53bb7b271748486ae81cecf1d3bd7b312e91ec7312eb62f7f4465e9f133c90b40b5aabadf897db276170982e3682959027d4f12eee5a6720a6863c259d5569d30604063277d5afa87a04ff1db7cbbd8048b5f6ba32e0972746c42fada98ecc8139f01726b225c661b7131c962a6af431c46da1b18603580e0eb36b1b3a78803784752e95178856a52cdf293be7d20ca28afd91216a390b4c5fa200d13796d66a09e67c0dcad2e5cc6ba969843f35de110a26385709f5eaa333c3dbc672805554507e4492dd88e4e1fb37d30f00b100b0b73ef0f2805b3dcee77ee3c518d4d2b5e10a6e74139d13ec437fb20caf21f6b14258b219d2d7c874ff865b46668c9a98a6f95f7513c4840eac1fcd8c0d5983f2fc417fa028662ef9622af0c8b9a8370044569515953bc3351e7879256a1682f1913c6f71242142890f49878f50a88254967744ee98fc8f39b077df24f896475138b353992d43a58ab679391c189def4abb585c95fc78f8560ed1578334a54ef06c4e242e99763949a6214d2f1e6b2e0084716b5acb458f0132451aca43dbe9002d447989ac471fc47f751e9ab748f01dba71e41478c7ba6cba186c59ab2b22e0ae2cd982005400350f2b1fd5804f85a943324dcd60e01aec98058ec872048fc2a3aa79499437917db1f087117dc5627fb97486c24adaf4cdea43ec70a5ecb1115482b3eccdfff6508c76655387abd8db57b01b170d5e1870faa1384ba4365e7054331d92e9f9f0b1ad97b7b530669a248a4c26ceaf174cf6479efbc7da6ea54099c32957b8e46d8efb77b50d25e3c705ec7679f33abae0b097fd615c45f9d461b03dc3ca49cfa79c813d003bc5fe4ec9cfa94a1acd523d23b88d35ebb5a6e9a2fea8939d0a2391f5ba2f337b17972354cbee48f9f4316611ca4747267fb899a2c3deec9cbe9907b63384af2f8949c18e82d0599a14c794393c329254a5497322b8f054ff0c0c835e7176bef90eebe63c9caf6838cf97942832984d19839ab9a15ff2ee3f4e15a05e49a92af53f986375f372836e8b0871f4c4ec28a690e5e631d2dd1341b55a31770bbeb958ca73627dcb539875b11846f8129714880e708ee260d85ed2385e58c520b25866ee5635882ab26c9019999d6fbf748f1e799852121d87673b8e00f15076cdc02771e293c1ca49972b74ab8790702fbc2d08d89be1c26e413b5ff7ff00f30fdc45ed794caecb4f8e943d341f7ec1f9cbe686e8408302d278a2da6ae5b6328b996f93480f99dcbf66ac56244f780859f9bc5e6d61413b5b5724c4eaff5aa4d5113e19f2072cc4486528b4f864c0b2fdd8dc12c07f70799dcb83bccdf08e82e1fb2354171bec979c58e2dc82f05fb9018859906ff3803fc845ea83bfb869fca929234cedf2c7ed172a4a857031bd37d4a65886916451a01d80e3d40970692f0ab31cc53c737a8ffd3408641dd18b12b59d00880a1a51bf1201219c156f4687c89b3bba7966515c263a6da337c70219b18cbd7e1a7ca07f77287b04f62adf3ba0a00f903a4de156b402aa92faec6737f1be36050a4810eb34741a55cfc86f22b7e734e338f600f09df00574221e9f861fe9b68b5f8501ec4c809a6c85ede1d9410633478be10ac32a14f2d77664f050a16f2b0a73b470a99616c27a96a7b747c365d9191b9a0a92f96eb10e8cfd399d35a186ab9ee7f1680f3102a604fc69d1b1e5058113ac9d917ec32e1cb339755b5536e4715758f", + "public_inputs_hex": "0x1a207628cc6936816ccb62a7b56fdbbf8e975293b677c988644e018fc402e4411dfdc7cb6265f100524012f038ee1f205bf8789b70b46c57463dedb4998f7ac800000000000000000000000000000000c946233fabee579beb961008103a4c8d00000000000000000000000000000000026a297ba8fb72294cced25b49dae7c91806572c19ee2f3532e970d617919b3aa8cdc582782dfad0699badc631f75128000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000022b942ad53baa7c6e1a7616e4f0aa2eedf7d67eacc63696345525988225847479273fc58215abae6a73503410e8209a542c92bbea2ba8e6fb129c971def4ca8452ddcd2c1e0b08c268d942b52a49e7597873ccad9df1b02ea1ab854e7111eb2dd188f793a89424a83a3e746a72d4cdfd5702a7944deb0fa2ee04f84489f4af3c2000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } } } diff --git a/circuits/benchmarks/results_insecure_minimum/report.md b/circuits/benchmarks/results_insecure_minimum/report.md index b7c84821db..8f6fccadfc 100644 --- a/circuits/benchmarks/results_insecure_minimum/report.md +++ b/circuits/benchmarks/results_insecure_minimum/report.md @@ -1,9 +1,9 @@ # Interfold ZK Circuit Benchmarks -**Generated:** 2026-06-11 10:21:15 UTC +**Generated:** 2026-06-13 10:29:25 UTC -**Git Branch:** `update/committee` -**Git Commit:** `53f36dbc3526dd1af15a17909d4eaaf1ba92f716` +**Git Branch:** `lock/fhers` +**Git Commit:** `c2dfd37d36d9e85b45b03db03bc7267964a4a6da` **Committee Size:** `H=2`, `N=3`, `T=1` @@ -72,39 +72,39 @@ Single-circuit `bb prove` on the benchmark oracle witness (not the integration a | Circuit | Constraints | Prove (s) | Verify (ms) | Proof (KB) | | -------------------- | ----------- | --------- | ----------- | ---------- | -| C0 | 6847 | 0.12 | 26.37 | 15.88 | -| C1 | 53485 | 0.33 | 25.37 | 15.88 | -| C2a | 41244 | 0.31 | 25.57 | 15.88 | -| C2b | 79591 | 0.48 | 25.57 | 15.88 | -| C3a | 120114 | 0.55 | 25.26 | 15.88 | -| C3b | 120114 | 0.55 | 25.26 | 15.88 | -| C4a | 62750 | 0.33 | 25.74 | 15.88 | -| C4b | 62750 | 0.33 | 25.74 | 15.88 | -| C5 | 21501 | 0.21 | 25.61 | 15.88 | -| user_data_encryption | 53732 | 0.32 | 25.64 | 15.88 | -| C6 | 86927 | 0.51 | 26.16 | 15.88 | -| C7 | 90841 | 0.46 | 25.87 | 15.88 | +| C0 | 6847 | 0.12 | 24.41 | 15.88 | +| C1 | 53485 | 0.32 | 24.38 | 15.88 | +| C2a | 41244 | 0.30 | 24.71 | 15.88 | +| C2b | 79591 | 0.47 | 25.55 | 15.88 | +| C3a | 120114 | 0.55 | 24.84 | 15.88 | +| C3b | 120114 | 0.55 | 24.84 | 15.88 | +| C4a | 62750 | 0.32 | 24.77 | 15.88 | +| C4b | 62750 | 0.32 | 24.77 | 15.88 | +| C5 | 21501 | 0.21 | 24.81 | 15.88 | +| user_data_encryption | 53732 | 0.33 | 24.44 | 15.88 | +| C6 | 86927 | 0.51 | 24.10 | 15.88 | +| C7 | 90841 | 0.46 | 24.55 | 15.88 | ### Artifacts | Artifact | Proof size | Public input size | Verify gas | Calldata gas | Total gas | | -------- | ---------- | ----------------- | ---------- | ------------ | --------- | -| Π_DKG | 10.69 KB | 0.38 KB | 3119663 | 175176 | 3294839 | -| Π_user | 15.88 KB | 0.12 KB | 2973049 | 170332 | 3143381 | -| Π_dec | 10.69 KB | 3.47 KB | 3641167 | 187440 | 3828607 | +| Π_DKG | 10.69 KB | 0.38 KB | 3119529 | 175044 | 3294573 | +| Π_user | 15.88 KB | 0.12 KB | 2973001 | 170404 | 3143405 | +| Π_dec | 10.69 KB | 3.47 KB | 3641094 | 187368 | 3828462 | ### Role / Phase / Activity | Role | Phase | Activity | Metric | Duration | Proof size | Bandwidth | | --------------- | ----- | ----------------------------------------- | -------------- | -------- | ---------- | --------- | -| Each ciphernode | P1 | one-time DKG participation (test harness) | wall_clock | 133.79 s | 127.00 KB | 128.06 KB | -| Aggregator | P2 | C5 + Π_DKG fold (aggregator span) | wall_clock | 121.27 s | 10.69 KB | 11.06 KB | +| Each ciphernode | P1 | one-time DKG participation (test harness) | wall_clock | 132.70 s | 127.00 KB | 128.06 KB | +| Aggregator | P2 | C5 + Π_DKG fold (aggregator span) | wall_clock | 120.64 s | 10.69 KB | 11.06 KB | | User | P3 | per user input | isolated_nargo | 0.64 s | 15.88 KB | 16.00 KB | | Each ciphernode | P4 | per computation output (C6) | isolated_nargo | 0.51 s | 15.88 KB | 16.00 KB | -| Aggregator | P4 | C7 + Π_dec fold (full publish→aggregate) | wall_clock | 52.10 s | 10.69 KB | 14.16 KB | -| Aggregator | P4 | C7 + fold only (pending→plaintext span) | wall_clock | 48.50 s | 10.69 KB | 14.16 KB | +| Aggregator | P4 | C7 + Π_dec fold (full publish→aggregate) | wall_clock | 52.00 s | 10.69 KB | 14.16 KB | +| Aggregator | P4 | C7 + fold only (pending→plaintext span) | wall_clock | 48.43 s | 10.69 KB | 14.16 KB | -_P2 **tracked_job_wall** sum (ZkDkgAggregation + ZkPkAggregation, parallelizable): **6.02 s** — not +_P2 **tracked_job_wall** sum (ZkDkgAggregation + ZkPkAggregation, parallelizable): **5.76 s** — not comparable to P2 wall_clock row above._ ## Integration test (`test_trbfv_actor`) @@ -114,73 +114,73 @@ comparable to P2 wall_clock row above._ | Phase | Metric | Duration (s) | | ------------------------------------------------------------------ | ------------ | ------------ | | Starting trbfv actor test | `wall_clock` | 0.00 | -| Setup completed | `wall_clock` | 1.00 | +| Setup completed | `wall_clock` | 0.95 | | Committee Setup Completed | `wall_clock` | 7.02 | | Committee Finalization Complete | `wall_clock` | 0.00 | -| Aggregator P2: PkAggregation pending -> PublicKeyAggregated (wall) | `wall_clock` | 121.27 | -| ThresholdShares -> PublicKeyAggregated | `wall_clock` | 133.79 | -| E3Request -> PublicKeyAggregated | `wall_clock` | 134.30 | +| Aggregator P2: PkAggregation pending -> PublicKeyAggregated (wall) | `wall_clock` | 120.64 | +| ThresholdShares -> PublicKeyAggregated | `wall_clock` | 132.70 | +| E3Request -> PublicKeyAggregated | `wall_clock` | 133.20 | | Application CT Gen | `wall_clock` | 0.01 | | Running FHE Application | `wall_clock` | 0.00 | -| Aggregator P4: Aggregation pending -> PlaintextAggregated (wall) | `wall_clock` | 48.50 | -| Ciphertext published -> PlaintextAggregated | `wall_clock` | 52.10 | -| Entire Test | `wall_clock` | 194.43 | +| Aggregator P4: Aggregation pending -> PlaintextAggregated (wall) | `wall_clock` | 48.43 | +| Ciphertext published -> PlaintextAggregated | `wall_clock` | 52.00 | +| Entire Test | `wall_clock` | 193.18 | ### Multithread job timings (`tracked_job_wall`) | Name | Avg (s) | Runs | Total (s) | | ----------------------------- | ------- | ---- | --------- | | CalculateDecryptionKey | 0.00 | 3 | 0.01 | -| CalculateDecryptionShare | 0.02 | 3 | 0.07 | +| CalculateDecryptionShare | 0.02 | 3 | 0.06 | | CalculateThresholdDecryption | 0.02 | 1 | 0.02 | | GenEsiSss | 0.01 | 3 | 0.02 | -| GenPkShareAndSkSss | 0.01 | 3 | 0.04 | -| NodeDkgFold/c2ab_fold | 18.74 | 3 | 56.23 | -| NodeDkgFold/c3a_fold | 71.53 | 3 | 214.59 | +| GenPkShareAndSkSss | 0.01 | 3 | 0.03 | +| NodeDkgFold/c2ab_fold | 18.13 | 3 | 54.39 | +| NodeDkgFold/c3a_fold | 71.19 | 3 | 213.56 | | NodeDkgFold/c3ab_fold | 7.88 | 3 | 23.63 | -| NodeDkgFold/c3b_fold | 72.11 | 3 | 216.32 | -| NodeDkgFold/c4ab_fold | 8.02 | 3 | 24.06 | -| NodeDkgFold/node_fold | 18.35 | 3 | 55.06 | -| ZkDecryptedSharesAggregation | 1.55 | 1 | 1.55 | -| ZkDecryptionAggregation | 46.94 | 1 | 46.94 | -| ZkDkgAggregation | 5.53 | 1 | 5.53 | -| ZkDkgShareDecryption | 1.26 | 6 | 7.58 | -| ZkNodeDkgFold | 106.36 | 3 | 319.08 | -| ZkNodesFoldStep | 5.95 | 2 | 11.89 | -| ZkPkAggregation | 0.49 | 1 | 0.49 | -| ZkPkBfv | 0.23 | 3 | 0.68 | -| ZkPkGeneration | 3.48 | 3 | 10.43 | -| ZkShareComputation | 2.51 | 6 | 15.08 | -| ZkShareEncryption | 3.84 | 24 | 92.27 | -| ZkThresholdShareDecryption | 3.31 | 3 | 9.92 | -| ZkVerifyShareDecryptionProofs | 0.08 | 3 | 0.24 | -| ZkVerifyShareProofs | 0.27 | 5 | 1.37 | - -Sum of tracked job wall time: **1113.10 s** — **not** end-to-end latency (jobs run in parallel up to +| NodeDkgFold/c3b_fold | 70.95 | 3 | 212.86 | +| NodeDkgFold/c4ab_fold | 7.87 | 3 | 23.62 | +| NodeDkgFold/node_fold | 18.29 | 3 | 54.86 | +| ZkDecryptedSharesAggregation | 1.53 | 1 | 1.53 | +| ZkDecryptionAggregation | 46.89 | 1 | 46.89 | +| ZkDkgAggregation | 5.35 | 1 | 5.35 | +| ZkDkgShareDecryption | 1.00 | 6 | 6.01 | +| ZkNodeDkgFold | 105.40 | 3 | 316.20 | +| ZkNodesFoldStep | 5.04 | 2 | 10.08 | +| ZkPkAggregation | 0.41 | 1 | 0.41 | +| ZkPkBfv | 0.22 | 3 | 0.65 | +| ZkPkGeneration | 2.25 | 3 | 6.74 | +| ZkShareComputation | 2.37 | 6 | 14.21 | +| ZkShareEncryption | 4.00 | 24 | 96.07 | +| ZkThresholdShareDecryption | 3.25 | 3 | 9.74 | +| ZkVerifyShareDecryptionProofs | 0.08 | 3 | 0.25 | +| ZkVerifyShareProofs | 0.25 | 5 | 1.27 | + +Sum of tracked job wall time: **1098.46 s** — **not** end-to-end latency (jobs run in parallel up to `BENCHMARK_MULTITHREAD_JOBS`). ### NodeDkgFold sub-steps (`tracked_job_wall`, per fold prove) | Step | Avg (s) | Runs | Total (s) | | --------- | ------- | ---- | --------- | -| c2ab_fold | 18.74 | 3 | 56.23 | -| c3a_fold | 71.53 | 3 | 214.59 | +| c2ab_fold | 18.13 | 3 | 54.39 | +| c3a_fold | 71.19 | 3 | 213.56 | | c3ab_fold | 7.88 | 3 | 23.63 | -| c3b_fold | 72.11 | 3 | 216.32 | -| c4ab_fold | 8.02 | 3 | 24.06 | -| node_fold | 18.35 | 3 | 55.06 | +| c3b_fold | 70.95 | 3 | 212.86 | +| c4ab_fold | 7.87 | 3 | 23.62 | +| node_fold | 18.29 | 3 | 54.86 | ### Aggregation jobs (`tracked_job_wall`) | Operation | Avg (s) | Runs | Total (s) | | ---------------------------- | ------- | ---- | --------- | -| ZkDecryptedSharesAggregation | 1.55 | 1 | 1.55 | -| ZkDecryptionAggregation | 46.94 | 1 | 46.94 | -| ZkDkgAggregation | 5.53 | 1 | 5.53 | -| ZkNodeDkgFold | 106.36 | 3 | 319.08 | -| ZkPkAggregation | 0.49 | 1 | 0.49 | +| ZkDecryptedSharesAggregation | 1.53 | 1 | 1.53 | +| ZkDecryptionAggregation | 46.89 | 1 | 46.89 | +| ZkDkgAggregation | 5.35 | 1 | 5.35 | +| ZkNodeDkgFold | 105.40 | 3 | 316.20 | +| ZkPkAggregation | 0.41 | 1 | 0.41 | -Sum of aggregation job tracked time: **373.58 s** (parallel CPU work; not P1/P2 wall clock). +Sum of aggregation job tracked time: **370.38 s** (parallel CPU work; not P1/P2 wall clock). ### Folded on-chain artifacts (exported for Π_DKG / Π_dec gas) diff --git a/circuits/benchmarks/results_secure_micro/benchmark_run_meta.json b/circuits/benchmarks/results_secure_micro/benchmark_run_meta.json new file mode 100644 index 0000000000..8a69581d93 --- /dev/null +++ b/circuits/benchmarks/results_secure_micro/benchmark_run_meta.json @@ -0,0 +1,14 @@ +{ + "benchmark_mode": "secure", + "bfv_preset_subdir": "secure-8192", + "committee": "micro", + "proof_aggregation": true, + "multithread_jobs": 13, + "verbose": true, + "nodes_spawned": 20, + "committee_size_n": 9, + "committee_size_h": 5, + "committee_threshold_t": 4, + "network_model": "in_process_bus", + "testmode_harness": true +} diff --git a/circuits/benchmarks/results_secure_micro/crisp_verify_gas.json b/circuits/benchmarks/results_secure_micro/crisp_verify_gas.json new file mode 100644 index 0000000000..a53f27b389 --- /dev/null +++ b/circuits/benchmarks/results_secure_micro/crisp_verify_gas.json @@ -0,0 +1,108 @@ +{ + "verify_gas": { + "dkg": 3136910, + "user": 2972965, + "dec": 3658366 + }, + "source": "folded_proof_export_plus_crisp_verify_test", + "artifact_sizes_bytes": { + "dkg": { + "proof": 10944, + "public_inputs": 672 + }, + "dec": { + "proof": 10944, + "public_inputs": 3840 + } + }, + "calldata_gas": { + "dkg": { + "proof": 169956, + "public_inputs": 8484, + "total": 178440 + }, + "dec": { + "proof": 169872, + "public_inputs": 20784, + "total": 190656 + } + }, + "integration_summary": { + "integration_test": "test_trbfv_actor", + "benchmark_config": { + "mode": "secure", + "bfv_preset_subdir": "secure-8192", + "bfv_preset": "SecureThreshold8192", + "lambda": 50, + "proof_aggregation_enabled": true, + "multithread_concurrent_jobs": 13, + "committee_h": 5, + "committee_n": 9, + "committee_t": 4, + "nodes_spawned": 16, + "network_model": "in_process_bus", + "testmode_harness": true + }, + "proof_aggregation_enabled": true, + "dkg_fold_attestation_verifier": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", + "multithread": { "rayon_threads": 13, "max_simultaneous_rayon_tasks": 13, "cores_available": 14 }, + "operation_timings": [ + { "name": "CalculateDecryptionKey", "avg_seconds": 0.041923838, "runs": 9, "total_seconds": 0.377314543 }, + { "name": "CalculateDecryptionShare", "avg_seconds": 0.169763022, "runs": 9, "total_seconds": 1.527867206 }, + { "name": "CalculateThresholdDecryption", "avg_seconds": 0.188830667, "runs": 1, "total_seconds": 0.188830667 }, + { "name": "GenEsiSss", "avg_seconds": 0.517591101, "runs": 9, "total_seconds": 4.658319917 }, + { "name": "GenPkShareAndSkSss", "avg_seconds": 0.519758712, "runs": 9, "total_seconds": 4.677828416 }, + { "name": "NodeDkgFold/c2ab_fold", "avg_seconds": 28.564699092, "runs": 9, "total_seconds": 257.082291836 }, + { "name": "NodeDkgFold/c3a_fold", "avg_seconds": 665.751029958, "runs": 9, "total_seconds": 5991.759269624 }, + { "name": "NodeDkgFold/c3ab_fold", "avg_seconds": 13.677920254, "runs": 9, "total_seconds": 123.101282292 }, + { "name": "NodeDkgFold/c3b_fold", "avg_seconds": 627.044043615, "runs": 9, "total_seconds": 5643.39639254 }, + { "name": "NodeDkgFold/c4ab_fold", "avg_seconds": 12.85089624, "runs": 9, "total_seconds": 115.658066167 }, + { "name": "NodeDkgFold/node_fold", "avg_seconds": 29.335869893, "runs": 9, "total_seconds": 264.022829042 }, + { "name": "ZkDecryptedSharesAggregation", "avg_seconds": 5.934307209, "runs": 1, "total_seconds": 5.934307209 }, + { "name": "ZkDecryptionAggregation", "avg_seconds": 97.371421875, "runs": 1, "total_seconds": 97.371421875 }, + { "name": "ZkDkgAggregation", "avg_seconds": 5.380075417, "runs": 1, "total_seconds": 5.380075417 }, + { "name": "ZkDkgShareDecryption", "avg_seconds": 76.340271504, "runs": 18, "total_seconds": 1374.124887084 }, + { "name": "ZkNodeDkgFold", "avg_seconds": 904.325249439, "runs": 9, "total_seconds": 8138.927244957 }, + { "name": "ZkNodesFoldStep", "avg_seconds": 4.391163124, "runs": 5, "total_seconds": 21.955815624 }, + { "name": "ZkPkAggregation", "avg_seconds": 31.237663333, "runs": 1, "total_seconds": 31.237663333 }, + { "name": "ZkPkBfv", "avg_seconds": 9.470989805, "runs": 9, "total_seconds": 85.238908249 }, + { "name": "ZkPkGeneration", "avg_seconds": 87.110795134, "runs": 9, "total_seconds": 783.997156209 }, + { "name": "ZkShareComputation", "avg_seconds": 94.84146134, "runs": 18, "total_seconds": 1707.146304126 }, + { "name": "ZkShareEncryption", "avg_seconds": 105.638965935, "runs": 432, "total_seconds": 45636.033284039 }, + { "name": "ZkThresholdShareDecryption", "avg_seconds": 187.468467925, "runs": 9, "total_seconds": 1687.216211332 }, + { "name": "ZkVerifyShareDecryptionProofs", "avg_seconds": 0.583209972, "runs": 9, "total_seconds": 5.248889749 }, + { "name": "ZkVerifyShareProofs", "avg_seconds": 2.362957439, "runs": 11, "total_seconds": 25.992531833 } + ], + "operation_timings_total_seconds": 72012.254993286, + "operation_timings_metric": "tracked_job_wall", + "phase_timings": [ + { "label": "Starting trbfv actor test", "seconds": 0e-9, "metric": "wall_clock" }, + { "label": "Setup completed", "seconds": 2.393101875, "metric": "wall_clock" }, + { "label": "Committee Setup Completed", "seconds": 16.092655959, "metric": "wall_clock" }, + { "label": "Committee Finalization Complete", "seconds": 0.001054875, "metric": "wall_clock" }, + { "label": "Aggregator P2: PkAggregation pending -> PublicKeyAggregated (wall)", "seconds": 406.98499, "metric": "wall_clock" }, + { "label": "ThresholdShares -> PublicKeyAggregated", "seconds": 5251.559821667, "metric": "wall_clock" }, + { "label": "E3Request -> PublicKeyAggregated", "seconds": 5252.065132542, "metric": "wall_clock" }, + { "label": "Application CT Gen", "seconds": 0.288574125, "metric": "wall_clock" }, + { "label": "Running FHE Application", "seconds": 0.001384625, "metric": "wall_clock" }, + { "label": "Aggregator P4: Aggregation pending -> PlaintextAggregated (wall)", "seconds": 103.520856, "metric": "wall_clock" }, + { "label": "Ciphertext published -> PlaintextAggregated", "seconds": 334.1983295, "metric": "wall_clock" }, + { "label": "Entire Test", "seconds": 5605.042426708, "metric": "wall_clock" } + ], + "folded_artifacts": { + "dkg_aggregator": { + "proof_hex": "0x00000000000000000000000000000000000000000000000954c24ebda205a1890000000000000000000000000000000000000000000000047891ca0a612eea510000000000000000000000000000000000000000000000032cd6988555d9375700000000000000000000000000000000000000000000000000020a71945a61890000000000000000000000000000000000000000000000013f378b0dc552f37000000000000000000000000000000000000000000000000578d3973b8a5ff2e600000000000000000000000000000000000000000000000e6230cb8ef3f2874b00000000000000000000000000000000000000000000000000005fb56392be3b0000000000000000000000000000000000000000000000037d2ab4998b6b2f2b00000000000000000000000000000000000000000000000753c7e1e41947b7e100000000000000000000000000000000000000000000000c913f223f3cc93109000000000000000000000000000000000000000000000000000015ad2d9cb057000000000000000000000000000000000000000000000009bf71e3c026d6750600000000000000000000000000000000000000000000000a7b65e0bdbb03a37a00000000000000000000000000000000000000000000000bcfd70795f55b571d0000000000000000000000000000000000000000000000000000fd6d2df268130479d738c4ee38577d65b445b420ec265f9c9d743e868525978879a50fb2676526d4c60047e4b4b5b366ab2903818a6e23f01a9a19dcf15005c869f763f0d3a718603f91d677484afd67bdf49862b7a0d8a7885eb94dbf0fdce2a0f2e8e3fea707a3d0275aef9b17f71236978cb2d218add2a58fe2bb5d47f0c77792d4b03de917df3288ea86583d86a6c706ccd356ff13ac09352c7fdb9ee1b7eb870144a5a0032279b0f26b994415350699f0584f685cdcc4fa9c6012523e64b359b3a0a315180949863db19097494da78086af7ab28a4f30051af93bd8b457341fa4e3a4b82c51f9ca9639322c8b2cc935c697a662cfd7e1bdc24b820869db5c02d4224bba03b871237ad99247a84e3f4ae2aee3397f2ac4b1e67d13d9992d2355b35d473e1caf903eb3681d0612cf7b84fe084821333d5a7a5ede332609f37c05f346a5972c7b427180c02d447856d1a00e12b702cc7808b995b49ae9d0886881532077771470d6d7b36709f696e70fd45260daad0dd7c05f60714838cac65febcf5920b40afaae846ac976914cd712ea5525e57ab68c7fb495ca0b6565a2461aa6987ff00d6f129c8f0052cc9f3593a4e650c2aaad3f7438876d40ca5a2f2744e3d6a2b121cabbd1a2de7dd994ecf1515a7542cddc8021886e53e71214c4c49fe0019b931df5198d8f3c27deb3cb9278452f7fd8be2bd3ab23e32357aaab0dcc639d43a50e87245b3eb0b4d17c3d94e4489c1116440347973ac5d1b3ffdbdb5bda4bd4a317115e9b5ddaced77884f0873a639ea45c628ca552fdd96aa39716fbe01ff66e29d5f35b4dfe51961e9cf22346931a9a9fb30c45c41d94c687ccfd9c19163b1e20f390f8e062e1c738b0a3a4bba05672aa3048f39921e74b5f07d1d7185e2dd321115ac66fa437573482a2818e18ef87eee118edc795d190b7633af3b407a46a1e34a9856ce1037c5bebafea03332cc8eb47d8a50ad8130bb7fd81013c311297244810ea5dc9183c9d13c47a8e666863d80c64c05baa20c9c227903425e2c9e52988b98d1c607835da46b657ab73038f9fc26deb30d37c2a6de109bf1b03140519d9c12414a86a64ca1ddb999e61db596798fa182d101a32a40344bbe759dc070fb4c4faf6cad2d5f53df2abcbdf4485058be55470c36a2cd6ebaefeeebef439001882b6f4b8d3a0a9b74dc3ade61be5f3aebd4230cd36e7d7b6a5e9374b03b411c376a48ffa289b5cd0382fb39c1183f2c227c5ca891ff7ecf504b566914e030a1db94582c46e2f1ff6ff257c3e9416096b49a61fa2c435cb5248abaa6b758b0457295b1f05f279ed93cd004f1b5c814f5930f61d95d0164061c4310d3c08681ba02bc39592fb7636ffc11815de6e56fe6d757674658670d7e92f80acd7681e212be07766aac444e30079100453253785ed9f3bc2e264c0705bcf655f4a18dd2224af0f6d6cf1ae1a9950e751a011863db40254bf6a2ef87d564a245d51e5f11c2542d9e1797e8faeb7d4ad847f94985829650ad4927c98f2e5cbfe53c50f1120b905eb3f2a192d3ddbbdad5be7bf6e139bdd9eba0b72f063103ddcf3169daa28f9993f0052a2714b0108368bb1bb68b3b69bb7c43711e3ede8fec1fbf54e440bdf8f48b3c5ab0d5816ec621766b5bad522677ed2dcd4e30b34b0f5e8bf56b006d4b03918e8137a4b84714d55cd3a9f9157c07609d443a9b3a10447e76c4cf31831a1e30b388e8086e8aeb6251fc2eaca53ffb56b036b3ec9c675ed816d877911f7396949a5a45782d14ae2f8fb0d4496cb76be92f3123c4494fcb88bfa88782cd0b15b923c1063ce41d0be55059cca8900361437b919b821beb588f752c49e1a3306d750f05ea5eb43fd147d7754169f9cbb137cbe051f26964070cb8248c1152ee00b158348359946dd6a052b56b812cde74bdcdfb0d8706bba44093ea007225560f11fae351928424dab17684a8146db2a1870dd9e0766422fc8dee7c930181fd2a5f180a80981e004f48600a546163bc214e0dab04eccd47acde05e2f08265430e92984148699b8ad94d8b237249e845e61ecca660b38a9ecbd28b4a57f13f9063635b386d27e21a6890bdf58841ce614dd0b019ee4a95c35ed5e5841261823380c9dd2307bfbfca69b30afc09ef68afd3bc4bb6826fc673ede3921013f21fef391da29cfc93e74b4eda833bd820e760a9dcf30baf8348ee2efe2c801b8233826304bff681879221e2762b80640e5e70b065d879d4892aacb8040e3b94a0fe3fc8764545d4cd3a06ca5eb919e773b8ccf6e77544b77b08b5c2c3e6c52c3215ff7fdd83fbcdce4d1c48035f505f9918ff2c91d74ff3dac351affc1e09cc81dd589e2cf4edc0a0a30c660bc592e296037dfe165f9c9ef958d9510ed93931206760b3f2dbfac735bbcd4fea1596e6ece49bb58eca25fcaf2193db71c1bfbc407dd9482cc4e4353dcc6af030062ec7306a92e143da95f847998e0baaf690eed0cd3c79f560db4ebfbde0c969eaf9990785b8f105742a8ef523fea869992e1790b932637305e08eea241cb84740016d04d1181e1ee258e0dceef1373f4ee9b492692c9dd0c0aca9f9c1169ebd6cc3ea15e1dcdf9afaf00d9ef34da272beb108f1b6934a6e6cd0162853152259ea61f7152d01006ee9d4cdb7f39c4f468095375295e5d021cfc1cb744b26cb49fbe2de5889e996502439a7918a7853ab85986872be94b747ea2ff3b4fe25c9742fae8556c4d881b339205fb8d92e8ee31873d3303a87e48f886ad41a6ad621508757282b3490e8d8fe2da22844614359cfee3c623eb476c158647f316928f903ad86c67e6d04b905a788caef1a4066c7a3e36ac24db37b6d796c23d77e7a4a80e7fbe5312e3290b80a3097a65fc13f955b2edef0b72616a71073e5ab28ae16343cac84591281313d71eb8b676d324b4cf3bf7e02a5454fc081168e6070b9ef6683990545b34069c5bfd3079efff96f4d8503a6c25ccb480b918e97636b68e2152daafd94bbfded22e615f3f0fb3ab18ec022d832cb930987ec9bd0c8d90705b362dc20123a6edf2267359b7692061ed7390e7722f714ee99dbd02816dec267537222a4efe2260684077840542ad5608d226c458214961234d6b9df88ac960bb145da06ee967330c2f941441dde2860de102b56003dc5dd86bc2691a6136f98dffbd6a6bde83059e71dad335abfd47ee7f265715242692b67dd1ca15da007b880531fee6d551fbf63978d744445e7c047001d27e2c5a20ffe2e0412e14eabd0cac98df969d10364918cfb0c14b09db809991c80710dd08b4dca4bb4752983e00ac7d80929ba4261dc45433f657d06c79d8d3411f1e33e768ffbe9f120620405857b4d2009236536d8ba4f960c30633b60f9807b2255cf915b29b6d204ad6bda65eb98a76584d048729a2aa6950bd5497f31834812f9f03764a12918634d150dadfef911bdb6b9345b654bf5077c65a37df70cb2409c682dbdc44f157e14e9a538e1724609bcdf9f19223a2d2ad5441a3249d5cad021befff0c472ff884d903a8e263c0d6ec25e771df369378b77e2c7c49bd9ed32eac8025ed81eec539e40a29167412ce07768f524ab397345d6b583d7491181b2061fec9445e09787f5ba53674471a06613034d8e3e977c6aaf1921300143bb6263c15dbfebe951fc8fe5d5fee441e1a61259e24b9ba8539738eae9dca67a93226aad08653953fa4671e46be63098b3a7af0569ebcb58754c1c2ce89291112c6238de55e26acb61dd66c6e141d63045cc092931d884495b05f01742e6d0da04708b1a336f53b269a0ed09920fb119177d4a57e46865d739292e02d1963b72b222140070855347cef1811823f94e295526e5debb4b8a9b8e6ff071b0c3da6f3c50a82030a7b71a6188c761bfae5555dd8fcc8052494099fedc0dac2e2149621a618ee11b2de3283ff999e1e7b75f0dc41c3cab1f82982fd25d4c5219285335e3a08b5d32f53149e2c627d124203d708f4bf689c0bfd205c23bd7287864f43fc5505f5db8722397932ede80faa4502abef2a335d7dd8a6bee0aee2bd0935bef824027e22058f7d0231aaac32bd283c1d21c243284efa116c12cd62f6996d9745090af0fd01bc81e5db7a7a72a02e9aa349f5c3680be7573a9592ac24298c99730b235d74057eb525fa9db4c88c386fb7c0b46f9fdcfe7544e163b8d878bf5637d02513a462b231640c1d7da3924460347c7de8413addc5d90e28213b0861f0e1312ad87102d3ced97628b7a51796d9202d6931f53b7e97f813898c3904c6c6ed792557baf1872893751a1199da549e8d40e6b767b2e617de5efc68ed43f80bc3d31893ed70d476c3b224725c08b418cea498cd805ffdaa309aab6e7fff9be15a6e24be8f3fe1edc8135ad3cfb453f91f8f3b28f00f5e1cdb413c8011b887c467141a4a9c8a75d9cca761c00badd5ef5f2d9c972c9653444c9fb39a9b8c153a38eb10002a870704163cda0c9f3c8794adabf8829b3cfe43c76db4b3e070204c19c300406d3c59112f4fd8f6d49864b41d3bdcda00b91ae3c38cf4715554dc3ad9cf2c05da85f79b5e1467c590cf088a8374f80ecb58ad120b2e010f8e04dc890cc42aa372e47c3af4fbf8b47f9bd6672bcfbabf3d48bcb37128c6e4960c57f23c1c25b7dd1f37782125d0d2f68d963f91dc2821dc5440ab5c8684df689bd36106001a9872c04f1e304a64f1ea42cb4d890c92d8631d3898e7a35d561498f8db69282264fe269eb5ec83b76cb73f81bb42821205e36e90b8b0e3550a48a47dad2042088cbc58567746b617df4891ceea45deb36c71b044e6999ccb3bacd0d7bffce80f839b549c474af9bd63e5d94fb55ce9334faca560233ca30f7cfb7e3500989805b4d31e5225e33ea6fcbc9fb9568707144034a248380cd18a4fe71161659fde2a6677fbcce9e7d655fc95c69900ccf6b85bc59c803cd83c9d617c9fe7cf6206051e944b53d5ac4b5f6b8d149c84d3cd97eb7afd61242757bd95b537da02cd5624e1041188c6d776ec38b7002128126520a7b090be289ed5f9394b615638365d1547bcaf555d5e9b44a663e9a490fd058c2b86c578fcce7b52636197d04831ec0e99ea9b8691a14d30f294c1363cba67e813dd3d39b232d0fac06be2e09c782a23f133762b99d99eb458e24bacda09221812d0625fd6ccf763487058ee3d219f16ec60eb16f4b582d0af287cbf885c5de32d182ae8e433348e49929438fc1b0922feb1a9eb418e1493bdee91f15ff98821d95660b3f00d1f69cad59d6bb045a92b942fed2a7ac1711f1b5a340bb79ed772eeeec1ea605a0ec7763682397a146f0546a02fb8944e603d538b10cade7b1fcc99e4dcb4cb49890d9c73aac43eb1b317844df125648d183828baeef070bcaa0c9ae252c6b0088c8180bfe1944139d50825a6afb3f84f161505c8148211127101eaff10682410887d14fa2a6389f5621357679db21bff9216fffb04e994baacf43bbfca9e792ab32a894ace72cb62870e96989fa31e08cfa5f848eb137862f0ed4704e6320516cea852551328ddc36903642426fd362d4afdce884c67d22a312346c4e2d0ec078b8b41c6884abeea122439a1b8174258e1dcf3cb3bfcc773258204c7ee55f77ca6ae3d17a16cb603fe01ec3c26f6dfc003fd1eeadd595b7e46d10fd9552bca5c465f835e833cd8cd9119f1f5e43f0e7f7205f1667c4d7fb0670c284fd2929360ff5599d098ebc5f8661534d76ddaabbe567c74c0f4ce1d1ae50ba2bfc4295d610ecb0d82a6261a7f601ec8512a5887e008af2e49eeb2ea1948d7def7dcfe123bc65d45b6792ac776b30105aa64b0a6564f9a66f1ddddc84a2c67503c34c9fdbe10833db3ef9fde04b12295e8d7cb26d10e81420ee58da3e655fbf0716af6fdb599a8711486cc59dade18b30575632002d0d21b08ce2cc8a54a215c163f3c06030064ce164320252a30038ac91a9f9f6f328420a1ad9ac21964974470784e5e302d4027f3fbb047bea425d60a13d3982f35ae8c86577f1973318a04e85f6d202695b9c5fa41c35b5b852ef84c6eabdb2de8fba547a0b65c05371103030bca01e04bd2306684ba47b76017b1141fcaa56b2206f78226fbb76b0533a7ce50aa0a4b7371372b3570b09cd800d8850bc681af074c1035450dbfdf530331d46c582056e37b8de5244935908a26898b967ddbfcd28228687c7e84fa51b15c096efe9da672f2593589a82e3e14165839aad3a2b0d866257b6a8c9fd9407653a5edf16e6cda91435c4448b74a810b0bb5a263f6a644b58bd759d466e4d7ff683b1cac40f53b529605baac0f1f942c5cc7cc12d073eb337f3b16d1114d1588f979ff8b0b8a9fa7d36c31f7e68a770686258566d7107d246ac0852ba207e065dc52b50d4a6e8956d1f6f5ab68ca7e04d806cebf24c27a061a0e70337fa2eff7263a4aa71c78dc47ac83c0c81e35bc2affc894bda4d88ce418e8234ca460fea23c17f43675e86d654e6cc89a318bca0aabbe885732619a129a42cafeace52ad24ea837d1b002cad6712569457181220603d7ee974e9716aef6df02646394d0e9dd1e1746bb06590d34edbdbcc921c3010311bef8312cc8095a1f4ed732d83cf6039432968804b89c0952a449263a822da1f05a1a42835b7d24f2e9f2b59e167e9ed8d7e14f4051e9880eff09c6792825be65ea3e21d8d02a24758e60f6c7e6565cb823c867114e915db0c7f4cc47b612a7c3019467cd469a422ca3d7587217b52c0e5653749f9affcf36752131a10e07c9844fb8450ff657aa8eaf021bcce295748e121851660e0dc4909a2719af5c28eb96158dae3557d186b9743dadf29d9f11f5d0dcfb4dede3e2ab2739679a0916ef6888b12b324634fb62e4c91ff6d99a61293a7841c20854dd20d6ceb2b85d01f905854e23020044ea2b96c48ff0559706fdf8a1a9a513d5df18ee4e556328026e9e34ab8a4ea4f365688afd3c346599782ad10e1de844aaf27c873fd7c5c626721ef8b7ffd2dba851c17bd7dfeff96d141b5d1e2058eb2f17f313f9ae321c1c7831ffd39c03a5d6027505796fb09cf775ff79f24e8ad288815bce3ed9aac72df73050ccbcd65699633d4fb70fb67217fdda5b617bd4ed7276a22ce3e2eb6a1935fe82954a6a5590f8cf1c91500da83487b87e30ccecab155ae62145bdff181a31844bab00b615677c78ca3cfe697de5b49c3eb357f52ff3a7f2ec7915128b2d237ccefa11241cc47a81b9a8d2541c8fe2d52e0cf5d135ea1b4b1b8a9d15cd149914a3da61606e033e9d0e4a7b63b2cb13cb9ea370f63e2414d3418a5c7d991b4633b0149d8f03c9c367d03cfec76c5109d53f21596e32da280cd5cf45776a0b87b21b258bbd20d69fd51c1a9558b1dc2138ce33b072a8049a8f2638d28c150f5fb9090cd0e2d31c9812d5480d0009df8454e1417d6f46dbd05ba4a8ad6ca618bdb22a6cc8ab63943c13b2ea5d193b20e6b08227a589122dbe3a73f3ef1cc32fff616e1c095353330c96cc982404527938bf88505d3657ebfc50977be0020b03541e480230514809974e9e69fddb437d174359ce3223a21e390b97878a8fb2201c227f3b6fa370bd3a16acfb7e6b0894bbc720511cad2f1b2ddd28f29f83eb026e011a09b10faa9329ba60f4f1593fd15c3b698d255ce4faf6453bad13cd760de3427a971293a2d5dff7360740304f5474deffc50a4604840053da7652b9b02d6b0e28d6cb789ba636d32f86267196d2614812552594e7dcb0d55c7f3c065f19bfc2883d105f9f2750943abab64249cc002750c21f7f17139b88a18118d396265ab2edaa8e1d15dc3fae0eeab9526a3656d667066afd2513be00e48a1f19810bc20f53fdbe2daf96da6ffa1a4cfd89bac34441a116580f9dc70b1d90b6343722c892b4b34410f5d72c564c7801d46f78d3f810140089ca81e696f9c168de1b09a60966a24563f814100aeedcf627f0420d292ba9dd663ac76f35d77493972809de34afa8fc2cbec1507bfd4d8214150fb7bc4c776d270a7ac59e09a2ba0442132e7feac7eb2f0885f4232c77ced69d98bf587b9e25a03b51fff2032e1623b32a92d8fd82069c99cc1a1eadf51bce00bcec8f13ae581f506ab4cab1a949a4162bc9a368764de7a6e6374b3958f61b422db4c8ab79b3aabf61318650260e09d5197faf7ac774048996e153e153285fe70e22c0ef8de78bcd7f82dce646766eeb0759a9e4e6695edded54aec59e271655a908a67e9a978a6cc7117b7c838f884e1ab20ade1ade3220a5290330da0a30ac5fd6b27c0a20fbdb6a86831467607f421a740222ab535e73c0bbf14d4d5dc94398e7dc07e3a68461621ec44f22a1dffc04f3eeba280588b6bc54d4cfa12ddf74f0eb41d28d71b7e1f40154b650de01f40002f96e89c482024cd807c4dce65f95154862969cc7af25f3e593d3ff75668d23dc884e8873c8868de377ac148524fea10091dc26e256c0fae4a865669cc23b109c1435222a257e8f81d0138390d41fd15e24554484d2bda75ba68fd9ebd5fb0e0a6eb696c1d53f1c2c6d1876ecc4db74a7468c5f1101e616e5f9601200ada7229859cab751c62d3150c969d594ebc26a09aa2b452bab04cc0926ffcff77e0f2728a6df1187a01c19ca1246a6e3fa46fc745f1ccb321ab3510783dd3f9164ae029c8c0374acf4a62099c0cb553840f77ac7e8892630a753baf8b659d33246ee13910d2643c85c3a652e98e9074d305614b27a4bfed9b19b58912c068f131f9c27db076544b0f37fd76a9037f5279f369dcbd7e5ea53fa5ea1b4cebe86d84da003da622a4f51e5b780b50502de2bebb6d08e5783da1e99ea0516eeda99b29b58172d82a6ee6bc082520e3f65fe65153d3b116b6bd4e5a516a973fd01e45013f806b49e863c12caf6dc51bf201335d03da3c7baec7132824a9be95ecacf49bee405c2ec72bbd0674d967d5e247aa1048cd42d3c93abf4e94d99be54b3a686dc65108ad51a31ec40a8feba4f1643319baf8bc0371f0286cc4b8182b1b8ebb5676c07494ecb86ef09692147160fe4453ee58a4fd06fd13a3ea30f0df887e31f61f011c9b2e48b84e9780898bc2cfd18c8dddf03fe98451d43f8785d851a3f16d1101466576200d8b8d94e0111eba9d87f04e429f26bd24562269463991ef5c4f90a1c396159f9e1367471f9c652b7ba1f85a8f39f14676e57f27bc71ba0ee37de16119f8d03f603ee5bcc29aa33ed04c99ae110fc380985e9f78f14034824f7d60d09caa5b032fd1365d586d7d0ef008bf007107ba2851865fe1e85a38a3623b7f60c7944bd39da759d1a9c0904258bbb43668c05073f1c47797c64ba7476105c5a3032e52bd90dca1f89445a24e680b21946b78714d905c686757da1f7bb325f930ead078ca2b4da812d06ae76f795250a9614c84a4de7ebab91698a1d932e9c3914d19e4fe62fbb06dc685ff96f1ee2ab685a1b2dd11bc574814e55bbbb8b4bf118e9abd351095e04b308b0a080b423a0290b2c240b68813c56ec447be417d1660b9ba2c932ec8ae62cbe94a966dc1d97905e336e2fc104e71963aceb2434a0f00f213d112e2fb79dcf5bf9109a5a89171bc77e74f677c1bd91adaadd7bcee37e08085211f4f3830aee88958efb8b9abc5d048c9060e19bab14c71a134359961d047f23e809c640d4bc2f8c1013073ef2bb7d47092229a058d9a8a0e368e438051920b215b9536191b75c45babf5539c2013a15dc0168d950289aac025398df260b5e8266859c2ccf7734b2abc57cab97559e1690e3b46c53efbd701733356fcc024d0527b5d1868e60f847d2850ad78d1b0e14a6c8d0fe88b8fac37a22ecdbc9249f53b74629a75beb4065e8c37d7d6f4642ca9fe336fa422498b32c8bc4f7512a2be4b259a1b59cdecf3130eefee0e2e5df6b2c9bdbf668b75aabab1576d53303db0d44ec971fe3d1321a9350d8fa6896a2cc2e1098698da58eb3a7a35eb4000e4853fa959ba339a30c0ef0c013234b5a28a5b452c624c8f8fb8ea2b42968eb19af260b94cd35e4261a065151badeb645494e07d6861cb5525808b37b42ffef0e2b0adc409b0b654134cff58049bc938132ec45d9ef3a29c44364310129aae112d8301156c8ef325358488ef5d3efbb02b572deba3a29af4933865fd6c1bc490708844e738a7826a8ba820c8951fbcc346c05d6f2272d758aa1492512d270ef0254b7bcbc7663ac5f7df79f7aa876dda879d8641724107d51745986adf8fa0d1c7be3449daa3316310805fc39038801b6bc2839218b89d9599bd4a97059cfb71af65732d4ad5f7f085bc5fabf031d31fea3654a4d01a951e7f5b01886c42e3d21b27b018697ecc90219da4e2862891264fa808c13cd091d0480b09f49c7af9701d675d72e6bb13a542bc0d8778deeddb084d33610d8487f9560985cf4a8de49254372d2f7b5b359e27531954d74588044d27b0699fab23a1f402d1b7fdbc24826ecc129e4234ba397dda35d7bb9ea7a843ed5aa132e4800ac530b5d453734ce0be7f134c2b0b0082a14407e58bf0b9268d6c8f71a4e6e32027dd544bdef3c96160d0fb52d0b455efe22dd5d80e27444882381d80c00381306846ffc5f3061a521b205b976fe30137a433bf9b17472ae316a82e9a56764b1815775de43e5dc3b2a589baa1dff943708db4e6699cbc5fb855c6d65d3797722a085c536b5c3c5a42e386bef06c7b38ea71ab55d213dfb86b5808e4b9567e35aab3464d56470873903ae9e4fa7fb9b93bca027fb8b7f1342d177584ab04bab2f054007f74c13f8c018e6362f609c16447db1630a9f2d4f7adb816214579991be4bd2005a868c888f28244c9734714284120aedc3dbc9ab237f85e2d0831d2d670893351e5c57ae77086d2e0e84ba6b90355a564c2bd7f2975fd8a1e83ccd2f9e02bec6b8c37e598823af1676dbb3390568d8b9720dd95fa5fee9befbae00270e23a91a87e544e39d0678ce6640c5a6158db874fc7999eb07693828e7f6bc17714e56692cb25fea7b2e2dabb545915cdb9dfff5d5b6db877f8c6b431946ef463eff26e15e8f31854d11d3a836eeb99da348301c8f9c050e520f4cb05a84d178678f3712e05b864ea31a516435a747f2565bccdcf0b67daab6049aa9865df90ff6daf8d0aefa01dd5606bfa36a679b0e172066572f74741ffe72fcacfcfedc85bfb61595b539cc6e890251fb014b1e88e762f64420d34542bd3c66de5ee997e1783de8a9b527e0320f07c6e0071967dc2e82aefb43ac97abe08d9384e0f6bd8f70435d65d0804a480a24ef56af6c2a94db8af52b40844d3b5216787e134ac6b9aa1957d9e5f4e0aa122e15140bd00dfc578b008ff196a929dc8699083b3104307d78979e927d164e182a2dac33cc1d55fa5f02fd3bfc82f608a68eeddf64745a9e7ebcc8a733412c711297d9219cfe2c3c4a281566293b6fd5212e051f11c2a414a35e72fbf9b427542543ac075358882948274f15d6301f1b1c86627c77a116d4d9c06caafa3a8727038e8041967a62abc6c4b07e8b52868234e840bdfbc588883cf7963a0eb4b447186c802ada3390a6516ffdb9969ee1ef8f0f922ffeb2a32225adf999709e56fd10734cca02bc98215c779cc2c1cbd097fc18d335fcf4e1c133b1e7aeaa345e7323cc0c97adb0df8d596ae452c79ec2516777d0f5fa170079eec45a62676117ce0b87d58ac85b3bbe61ffaa2dcf252cd9edd9c7d5eb7695e3302818734d295e791a8198161c5422b10688131459328a435a3935768e805b534fc41fe31b6f882e0ed1b3cd7c1cb61c5c7125e291e96297acf09f44375e434da29cb00024729fed15747faf7337d199a12148eb0aceb829e87ef46dd5da65f635b788eddb52f975041cfdb370f2fa6c1d7185325050e76e07f1b7ca7bc0bc3457346e39467540590c92e4a82e8da7e7574481a096f68ad1a1e7c7218ff2f63fd2290d5239feb49e2b3522fe944280b6dc2ef1af9e137a458d956398c7b668dd3cdd6e46dfd05077081d99ff8047a33d91fd12d5260251c55e116c495c623cb5becc11cf1f6bce340a2a4015ab47366084fe7afa4f6ed6afde94640034686b9ee7910443a840dd781825d575f1365f2c47f660c1a09bfcb48f94342708904696ae07027691713af3150866bdd1d28b613bb3fba63052b0fd65ff1cde3a26f85d3f845891c89089552d8fdb9b2042ca080d659f7ff593d9f83ca330624afa999edbfff2c4050631251809b6200b186582198ce248618be9533a3760145f26acfbc7f01f4b22a8bc8111bc30770b490db35c289a89bb0a57bd0ef49623c5f7016aedbb299dbc8aadbe10577093fa4c2c12a107687154203d73ede9041a4e883271481da373d537976419dba316d7cd066a23790e24aa5796a6217d8afc2b27c1cb5521f2896f0f1c82276cb6e2fd185d9d11b6ec8c4b7957787d64dd7fd64e03f01bd8b968590137610fc26595c927377cb7e0473f53a2d8b67458a8e1dab91a38c40e67bb799a95372fc8208b0fcb17b344902b85e74d9e657c4a5ee2815dcc59152baac7ac152d7a0de4a6b22a20fe4b35513b95d694ef98bbda78a01de6075c642351f30bf246621dc199d582026984759c97b15393a0acc5d6d3b04667fd9e5ac67743afddf5aa07ef5e51331df9e4fc770e98199323b08e9c1e523bfad993ad0e4ebf674bb476054e8dcc4f4ca9de4b892da265c18c375bfad551d7b48e9babd153c9f4da59fd0e889e9d0809c27438d70d6dbb442021e70bc4523b1f8fa4cf4f1c83cf41a87b247e24a2efcc3eed818878d18d5af4ff7a406a34b1e267abc9eb73bbad288704039946c740d20368f36b51b905316ec8631c42154ef93719115297b35f5553f8090bea916e568e1abe9ff9d67c00fc50d30089baab588674beebd374ec41e4f52892e8c129dabf6c4452513b2294fbc8e3f01bffd128cd33bde349e7efe92a9b2acfa186941bc9f0f28746a2daa6c1a29f014254a592d0f7b79b4e7fd2c037752f9b7b5f0b6b6b22805bd517ba131ffeffb08adec919bc56f4e842bf99d209ae25b1578a8507e7427f947da702fb1dc013eff4c276eea7e87e3b0fa62ea8c0050bba25b30db28558b4af4f523b2b2435b563fec70d0e7d592a4cdc685656df030ca1ee2dd95a75abbf407f2172194663f318f678a83eaaa08e42c5aff919aee101580a7cd016fa1f2604e196f1d073c2029857904e78cb627a77b188364cc8531a9255179a2f86972d5adaa0d57c6cb3d6324dba2fb08fff2079286041e396ae2dbae93ad2de194c7cdaab18ca1e86b9b4da00c92b839adc172b3a7e2958778d21bd1ae2d19865b9de3c6f621c281424ea494bb0e3116978ef9bfac44077e21123146399626189c1adde7441069e728a64fed893ebf03b9ba7566a82114937e31a14caa89c9e1408d23f20af3e7b60f4187404918ed4347623a032223072ecfd0be0093a0b2244085e38311914e2c4b51b8e4fb1e534e5aaed3c2d23e09d09b519711e0ab303c58be6ae3fceab557c6a48f490a7eda2e99dc4e71fc3d035ed61226fd2b6e1c607254ac45148b05fe1f6b4927c284fdefc4dd5d011d56dbc621d0c765f5510db7aed1c290f3ecc956c0b280d20b9736bce299c89966bf31017fb1075a74a5a55b21b468658e5712e2c4eaaaa0c3c5f28211a121798f8efed38411c3dd26d5d56552f13e56ec646818978f9f81ce61d9cce7847742d9cb0f03c0605a5076b1454099c2a90d975eb63093ee11b0972f1dd12a98d7b6f9d7542df3318329042264375893c8952c49a963c6613aa0a2a7184919c667aaeb4ca3dbe7a16cbcf934e32bbdaa1ccf5f3181decebad10f51becff7d86a213b83c5dd3e952294cec8bb5bb87cf11d7a47da6dfe755cd4d4f775bae27edf884edf868a112190b09a50305c6a6ff212d16c1cc3da81c60a84082471b28114a27bc911ee054772b3ece09c17b74a236f0bfcea3b28067b800037e9ee4926d4e4b106ceb740e3b240274d2a2901f4efa32332051aef11a9fd263eee1b84def9a79133f299a75570ae17d67567962eeb27384f796c3fcd35eff160a32a15526beb0f53c7efb8ed119a9732149e2db128ce2919a5ff7a4d6ac9da5e8680bd8dc4f02d75d280bcce507ba19d19b518d756604fed9656b50ed5c312eb299d5fab45c24b93813bc98b1127581dc7e6ca399b27434360a547b61a7b4d6ee4f500a42a95ae69730a1978b27da01e89e23f06246fec4fdd2f84686dde0d3232d961c52deee6be3e23194dc07ed4f4f1e32df19bf5910d0e6ce55fd5412bf6d2e9f24c77fdf038d689048640a6f31e45b940f585a8d793a5bf7e1844d7edafadfaf9110d1e3baa5b33987de29c2786664670caa4b75390afe5ecbd2727e1ff0db2660d0da53d04bb87fe4fd22a004791bc0d8dc2cb9ca4404c71be948bfdbe8666c5f26db6fa1b4fe9763c91b8ad82b6251e2714fb0a295bad966180ddf19333b1eecc2a87a259e595888052fabe42ffcec38882b078f484bd369fc14d4fd0ad8e779d74e996a80c7546e6a0b2847a75f8d439dca9b43556c59c25546db6206b486f84aef9e4cadf02184c92c8d437c84e405774a31862453cbc764512bfb22ab9b3108dc4fa5a742f4e8a80a2f6b1f06f92d6d888284ef955d6fe80fff1caca9205d55381f701542d26b2a2b50e97fb99b0438f01356573a01bd15ecb89109b904c770644692947ec0a1ac", + "public_inputs_hex": "0x23820b662b29c1eb92181286c78069e2c68d9bb3f3f95e131510bb674adafe902573360a3431f9fb0997d1717493aeb1edcc58f966d0a1587066b4b2f8851e4c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000090331489384274c12b43fb7cd86990be000000000000000000000000000000007b6b44c05ca9dfc4194f954eb77bc8f50bdada746c10c4cbcd2821cccbd15a0e64a76fb9566b3912b9d9c5e6ec89f27305f1a42155a63345c51416db9134e24a473534e1bc7264e47c3d8ceeb0048f3f00f750355551fc21f4f8b099e8aa9d146476b5a3eba3f12ec324353d52c1498e0f0a7cb8a62c14a214735dc01b43a24c55e0f368bb80c5740f91d32c79b109a902adda50f3a68b086921840211e0be05add7ad8933f82d689d427af65040b9e42190c3823163063956218b83ca1ae19999d5986c495dcf68fabffe666e78796909ec0921581b707660aae7e03a29bcf5f6310e32bb1ef1bfa21f265e211ea0dc1d34da8f75be8d59fdd8e87f406948e636b3c1dce3485b81e8db02be6a2b619d04097be17ce35a4c426eea7846e3cf55a6ecca6207bf3e1453d1e46a962e47c704c3f121cbb02760f51ff659d1a9699301fb413fc3021b4ffff5c1d00a7b57162a19c999035857bf3a4db9a179b5166dd723b3db90b27a94e60f886b23dcd1b10beacc821d87be0996601e9ea55375f31f80de63e7631046d945065e07a8821c" + }, + "decryption_aggregator": { + "proof_hex": "0x00000000000000000000000000000000000000000000000274e50c43238015ea0000000000000000000000000000000000000000000000059f17e17ea294782f00000000000000000000000000000000000000000000000c9df3c7d1c1f90d5d0000000000000000000000000000000000000000000000000002ad5a9fbf030c00000000000000000000000000000000000000000000000de213a227f53473e9000000000000000000000000000000000000000000000008fbcde03fda652920000000000000000000000000000000000000000000000000e11c34b9200625f200000000000000000000000000000000000000000000000000018d110cb7a1b20000000000000000000000000000000000000000000000009332ce29e5cb0ec9000000000000000000000000000000000000000000000000fda69bfd2f73cdde000000000000000000000000000000000000000000000001976ff26b1855e86a00000000000000000000000000000000000000000000000000001e966433079700000000000000000000000000000000000000000000000ff21e58564f35f893000000000000000000000000000000000000000000000006a888f7863669e42000000000000000000000000000000000000000000000000279b9affcf271f2bf0000000000000000000000000000000000000000000000000002ae5e7a39e6df27ac299d3526550e7124432b7b9a1228e997ac7e5b1a8bcf79f341054a22273d158994c9d7430c98cdc139b5b19dd89ab4cbcd9e51c3653866d0ee3bf6bf754b128afa7679a3ee03477bcc89d48282db791a17b21c9ac3582658b5dd91d7b87d20d0dd06b89d789a5acce06e0842930486b35e3541d0cb7e29459ebe8c29401209d4963b35dd572cb21a1ed37b60fc510868b4776b94bfe295982a342e2e5dbe0b933598e687fd3aa3964746f600177ebfe703de2c891c0717cc6de4862675791eff43b86c3e360401f844c91e23ad9fbc215e1ed26c5d2bfcce4e828fdc867910644afbbfcd0191e1d1bed4a42e4456da443e5403037855abccaf4fea7791e92274b7e5fbb06146901b5ef9bf21c7021199477d79186812151d90c441ac53ea0445085a26005f443b8bb9767ffe9f2626db8016c3e2fb2303e3e62079abe3e42cf4336e62f409088a23582b0fab5544c8e4d8218a4b24cdca589c38dbb4c4731a29051c26930a5d74a77df2f06bbfeac457fb9f8acd6cff6b7f4650de62cd6216acf35c813087c35074389c393e94ad19cffe7cdfdd45e8636a97abc6089edb08497c82d25a2840bab820f941dc403213f2f1df22ce4b47c0845a3a5dbae20114d0baad277734f5e1d172e3364f30c9300acbf66ab70ad240b92fa513765c0026dc2207778f3d6190a41bcf8c69f9dabc02933b0d679290dded9c6d603749f810fcf1c6d28da75a8cda9921c463871626aee206404c314ef3a029ad5241332a23036bd7fc56a06a444528090c82bffeedacb0cbd2f506a8d7fefe82907ee5f72da8b2311e27987ad01f51a7ffabbe54ab0e281ef2cac78d991c5bcb91a5e9b3038bd7c2b57793de7ea75b84ae06f2c022b281119517cdaf1f6a0bc5494d017b020530b0271760f59fdf0cdc7e044c0432e5947bbc91cf12d477020e590d772904450b143927ceac383338e9a271ab0935a523e8976dd77879d8326668df946425180389af7d344d31016edb5179cc0dcb71c78c0eb8076aa43704690af226f502502d0f9cd0454dccd15ce172f9fd1de22d15ef4d8ede4860cb4abf3a66a6a10b7bd16430bfd4e01885115404059a203393fa43d74a10b5d271d5860163e619075969c54590c6c4ea43253bec367db52dfb47e5fc5d1b4d32b4c6907862986e12c6096cfbeb0dc76711b435d4328b247c13cadb44834ca40b8df0195ec8933d0da081b7fa40a36384bed824503f20db2c49bd1757e3e73fbb14ea500db7e9ed18a60bdfa0eea4bb6bda048a545f6e8bee5259a030f463275265166e5254c6551d0da8977a24824f2e2ce529a72b9803a739f5fae408cac6f51741477ca989b80ec09d7a9a284c8de0ff2feaee8055a4a71fa01090d6538a7b2b596582206e660a3f8fa31d8128b30836559cb3f930d2f7f4922f4196240b994c29c1363c92510e79ef7ee1b0a675603c5eb7ec15d17d62a668bee92c6a97da7f6eed06f0a1fd26a2308abe247506731af691f19b442bc9a58b0bbdfb19d116ccc687a24ff3c427e64faca338475c96e05c8986c5eb26e080a27985920f6ddf2bfd06c53f853f28c8c04257bbaf1d07d4bf5a0abc91ea53726ce5b7d829778e642c8828e0ca6106e24be30f342518f66b1bbc2a6ebb9e0b78c2f692a74dd5e4c08becc1ea5f2b19f5018f6acd7edb9d6d5b356e09a7c8e6d4ae8110418d47d164a113ab6e9fb803f692d6ffbc33f0a03122f83e8097e9de5dab9f5432b7a067bd72b110c0447505b883e02603fe8dbfb31f90df1fde45f99f7279926c24fd909c9bfa62f776f12c99d35fff14d217ec307cc454a60746c6e51cb6e87589ac945c189fc07298b726a5838880b7bee93461a1354ec39ee6f289e880ea0c56d3e299982cc9165249298666c54bdaed4ae49242460ef710de3668a621dcb6d420f4a502092b9fd54724c7720dcad871aa2dbd838d66338807e1c900d735376566b5e3dbc5859b1de62d98b29430ceaf92d0ad7b13be18501f6b3c6ee3d5fd8ae6ca1fb5f0461e2c0b1ea500dc5abf4f9046326a758b190b3c9724be069a666ae6bf824f2cb41370990b313298d3aac27edbf9876b39b9793002f253732e579a2284c852db39f0596f17fa08ef8293aa965d7d528a306dd85cd62c0a0b3b1ff9cbaaec43c1f59467a809ce53db01c2ec74e356c0932d803eeecd2e89e3406a838d761fc1200cbc93b90de0bc4cad5a6990ece0daf51408ec05b81126abddcc78ca3cab92c21c1ca2d6214cd47029334379b17cdfaebf31278c1222859f186d6be68c937dff68bf5b7d0570f5a7c9bb806731bb1a27f9472c7ac1089c020c1259d71e060a61ff6717df044ef06408f0bee60d68beb07a958174dbc522b8f1da88f625378dc78a4331a3212a501b1cab8d65e95595df5ff009fc972b9e68331f3b52a5e6ab57840d6c98132713b7ed6899bbf362d8a3ce1de600c532ba062fe090c147b06e692a84184b0ef0562323083dab4c75f905f4b12456d5ea2a30b7e0f9f550898514011178bd0caca0dd287be62c72f3d7ceb7258527469169036e961c27dbe305b73225b15e013c616966ce84a0ef385b8992e69b4577b94867054d4f1be64b7568d12ba0ac2f6561bd6ba1b002a5741fe0c30e35a5374937263a6bc2ee81a896ae77be14712936b73225f423be009558db37314545bb54d7b72e93fe3a05009e54c334fc3f12d7332c38fe0c0c1a5ea14c23bf0c0161034a9101649551f7eeb61b1e91c6061c72004c85318979426d5115e3d23a76a8e00701cada72ca3a4e1fc09d96a0c9015acc104c51483effe786b91c7201cfa2cf81ad2f83ad6af8ff8f3367acc01418d223baae41fd64d9c5081528c209131a697ee656853836d24d5f266a6581970551531f3e5b1da57eb35e6ec73b98ce38b88cf661f25a0cb2fd3967277b29c928ca5c24004e83fdaf8810d838b2955e04613446fbea306a4beb875a463d118a2d58faf03175b910a088ed0b3f21d1c582d0ae314723f4b9d570cc14bfcbb04c16a1978d784d0f01004d401ef9f6f8cc93903cbcbbaf1d82dc25680d1839bfda230d45ab9178bbd752c543902f383180122901bc672fff31dc749cb2fd4e155421eafc60963b91198077c55af3485011d9dfb40304dbbb40cdea29d243ef045f2b80c8f661f2b9e98b732c3ac435e7fea439855d56b75ff8b3dad072f5191100064d26f2f9bade02f99ae1778c1cbd5b67b21323279a917b6485441cefa9869a008f99bc45d2fdb462320933a3b89947d22c244bddee86c2ac05ed7456e3d3972b2ac1aa7f752ff8ed765561d02d85918d260a7f9f4b6660f559a8dab724f2f212bbc55e76f73293b2febf6449ea68485dd6a3dce9af0983b159a4362bb1efcd2a8f2f95e4f13c9c570f69f61e4a66817cbd536634a16a789a2d4ce32590aa7406320ea737188269f8dc1b3321f3c52867794b38b2444250abf69a656466d0df11a1af729be8a3751830047ec0e363828777ab3cf6a606f66b962ac164c26cdb048de3f0d86cb0c520474e945c194a36aee899e5fc22b70e76d70ef55dea8db70e83015fb98872130bc9a0f88db0975a2b915c7ac77459b722820adb69a12ffb255b573884ac653b62a76a349bdbe5618f43360bedbc2dee105a4731de59681c0b4c5bc3e620d67ac078324e6a70134fc90d994065ed5685f509b03cbea0b09c2084818b486256f8c3fd3dd8dbb31e87cbe853be43db69e5196c0649d9949a181fa798f847250419c22d8354f7296d954ab5a950865357359bfc11746e5b32cd2310eaf6766323dcc76133e773f4573f7e06d3711a6c617379cccd5c5273f17a0687a86ae4843e6745e85d46197434292e99ecd6e94956f7de41adbc71b5e3260d384ac3ab70617b1a0827e050c3206f707478c27717216f269c849d03f4a0fc2d8b4f0a2d1cfd8e91b4eafd322dff635b9c141bd526244d95a791e0ef39afad25683d367dc245a3137e31f2732e9aa85e60713c10e9f1cdf2e7800d6cec807517738d67795cba0e54f3979ee0b6b355b3a9e9b1e0f023cfaf7b503214a9e4d317672a16039f93ec5505f748afd302a8d165e66a5b1b8d2be91c4c2ec4c558e82397a06d0c9ceeabfb63767108cea6a56738132873dbeb99b1ffa05203139bea1a9ea13d709036926b508a343c7f714833ec2d47235ab35885c7c79266c0e74004ebc60d754dbf647d700cda950a2f156e08dce144987cb3ea3cc276c83fea1c0559cee3b97a3cd94fb36a1f1d61bc913acf5323961f569907d01ed05f0aca4b154ee2deb7c3212b6cc19c39575fc57287f812a93bce77235df506ba9d75dc3325d9d4b7c2e1000ab4e5162fd6790d21ad791378d09f6b8d0bb9bb0f4030814c10ab4ccac4de07f03217fbb07c39e404384fb1134d577f66fb14e31655509d4617e88de6ef335a667ef2fa652515fd48b5b9891453858ee0880ffb26bedd349d2d84d40d7bdd5adf773c9ca93f2a04e49bcc49c17553762d3ccf39fcd1dfe2fc2ab7b1e25af6022ca5c4b201ced659841628d77d381075752f1337ca443d247603c594e585804fe8ca91420f60b279da2c7cc7a5bb502bf8a1b00474384e536e14fc2ccae26658c8064c573cd385313ef4da868a6736b0cc6772c1e9a49471a62ecf1f997fbc3511cbb8dfa5b83c1b75a5afd2993a4211f7000d24ce4d39da2d20c7a4d21dc13bd740130e6ac6677ebaa54a00cb9b1895f5907f918898d0f2ab2195b05cd7fce248b7b1356b0c476f4f6238705a75e1075ab34855a64a22603a2d49931302311cfe1fa1558b79dd6361098c827cf8de6ecdd455340565de95f6047c4ae833f47124a4a76f209ed426a39872b55b3d8b02aad35a6eca92bacc87046c41f8ad45f520c090d289111144b5fc3cde94f8ce93502552200b29e1756e1df3d1bbdaa407a21f20fe0c91a6c3496fc2f560474f31cdb3c0126b2ca06ae72629d92bdeaa93235a9c6dd36d5457bd217a0013424710662cd691f7a65363d32955f09763623b3448223c675a12a1f558c292842af84035acdc07e5be8caa341b293bd328232f70b4b1d9c789840db2ded2670791d63042f7402c061ab30dad136b5bc0dce353f75d97f26a372e8ee4627e94276737f0a1416d5d89ea19bf9110eb6f6ef9441016e322a2ab487b72a756356018089d2124897b6136aaeea11412bf15c214030f5c778dce5d38aa2dc924ad5ccf1deba1d1e0c51227e3feeb7615f380c14b8bde53e5b666f0c2102e2a0a54641afb8d90f2fa045b30db868ecc2a97a6738f9c70fe550bcb8470101e571d5539989700d29b2a59c52d9dc1f1700cce3124df00e287dc3d7fc1a0716ca5da98f28041e20375b62f742cc1de4a521d7932002414a663afbacf2a2d8e19b45acf35c11e679b5f6d34fd324c7e0301154b567209c7e52bb9d800df593fa1ba125b26833ba877cb66fccd6107a0f4fe177d7aaf90a56b8863321f5c3c4ecbdd2c1a0b8ce49397e6f9d1a9fc4b2ed0bb2b3b7673ad9b6339ce2e127aa83bb4994f22fcc602a7bd8ef2b00be4f62e24bb04ba69ddb98750e773625c816ea897316d2d063d6bcc9b00759e9fa279c3705a17b2ecdd3adb9af7dcf14b5a21cb1d0c8c20829f902f5cb3cba2d3aa705f6dd70a812ef571e2641d794a52aa575b1eff7ba75e0108cb1455b7e725f6299255022ef41b4b1747c43f596725dea53d6b4e431bfff009ad1af4143c20cf6370301516a8b9172b9133f3f924b146e95d960d95a837142938f647a8ee341ecf52448613b36cd7c7e2d33405c1f931e17fc7fb8622e830209d1830ea8f3ed65c544f1d0851cc7647db121b4f15bdf6e5afad37d4accfa5d67991e9dec26d059a7cbc9c22e51042e13c1bffa7364c9302c502b7680c1c098064122eeaf8d7c986fbe5e12defec49ec61e43e2708445e6393df995a94ddc666f1dd323fd45853638ff61f0f65d3b273d213e7a006098c0c495ccca8fdf649e0e6dba1840982ac6fa367620cf9b0f1e48159ab822da05d248c4d7c748e1067f3ec6340136bb7bb89f2f67223ab3d06ff27ff8a9f382d122c3b8250f2daaf97997cc6215fb62b39269e82b218e8fe4eddc867c358acfb154ca425ab6f89536b56240a69558a24a9f2fe833e2cc0cd6fdd6c7fda67861212bb2aa5b15b7f3604d20973923d92500d69b60d292916aaeda3b2409f038c7ced9f2821d0aa09df325e8ee4deb25695f107a199d52075284bbc500a1d8ff500e2a29f1af08605ed3a4dd9fcfaee896cfd508093ec0795a99961a9d8b7a7ebd429c142f1a52935aadc417a1494e16ccd582a8f606a27cd0d699b98ae46be499a9be50979783a2dfee266702d614bc17334a4fa5cf803235c71f610f627f5651c286c3548390f4864588258a4fc5bc704c9fc04e9a32f29146aba8d2871dc62a779772d5b0e60fbb9c5a3067f0e09550230774342060cdb2f0b888483c433df32452ef6f972fdbe85137cd277dd05dacef6da6ce836069f2a979d656e3123fa1f583b0e96bf24b9fcce569d04a5875ebc5ad8a757b60d1955b9b8d8f2ab846882f7264d4b958a610d286f0de3ad78f3e8ad2d36c9d617fac1844665306220f58a2e078a3a7b9beed30714b1c98ccc97835aa72fb08e04dc73f09b52f608ec9ff811866da284ffeba9b1dc991fcc9c1123bb4ec9b40b22e1203591edd3008336ede805a586af5f55e025e0c5eadd9b1e15030ce5f7b90820aa339f10bc3c697d3f87e8f2328d5a9854509969718a3677c029ff42753b0f1d11cbbb6a8301816ae0e3df81c6845e70495dd1f23514e899893f232e1ee3004212ca190421bceb21670d40f6e24d8ed22a77e08a643eac2c2697b3b4a1b024d04b2e31b4a594b25bbad952af7746c5cacd7c547869157bad1fdd6c6f136c1d96e60b2450748c8038b3f062310ef969ccd09d9b23214c0263a7f514767d51264ef41d32d69cec979817b0fe3a7392e4da263887edbaef9bb602d2a19e4f4f058c30dab386d374b1765c9d0f63fb160c333a612f6b619184df5e80173669b22e8c33565af82730493c4584f6b4253ebbb3c47eff603e26a6f2335d24a180c4044d9915eba47b365bf8009b9ab3af799ac5a92a2a7591851237b12182e052851ca4a9519b6c3ce54392dc00e31213d1987c972c8ea0dd5f191b74c912773b201e900a8fee5f7ab2a88f8f7487b31f4bc93765698d56df2c5a1f381034986add1ad05db3e4d1e4f09fed5ab1e4bbc7c4472041c9cb4972f1332117c2c44a469704ec71515df6ee7073afb4c5bfd91aa1a3c4300d3c7d1d5a760ff17f984d10eb092d51b9f8b619a50fead55d961176e2d127c4b7aafe0cd618eb5e45d2f1192714c17292f9fbcae5109189c56451ec540ae5186160d1dbb923a30ac966cf559829e60bd688a491f036b7496acef7d001c40affc1c12a4a14f91627e95f2068390f644ca843c7c7f885c85de7686b664f037ef0094cf81b7b9d369538267872ab15c1ec9138e8000ff1b5f093470408765a24606539cd61eea3ed83b3e4b409da080691ce3c77e782449f6fc0013f835fb9801f91fc0d909b4c0507cb16b885e619a6bf0facdac14fa8fabad867299b1e0e15987815017e5ed11a2e41206148422c140343c29fc377852181a168f9f8943e2ef69ff35232bdc60c4c2ce9e1008e20c21970bfb9159377cdb8fbeeadc9ae6b397ee5fea1891580598c3a6a69073e05a8c0f4f50c97ceea01f628baad9a9f2c31bbd13571121404a0c47e2ba0e6650583e8c8007859d694f25427a9b11930cc15259f9ed269287beb265de0a2cd5c0599673fdc867e1105b15cf31c7df65da2d668f560a942bbe1e79eb6a0153bee1dccc266a1a93ab21627de45588a6df7a1bd63424fc022134813c81b09d46b091337f7928a5d3c12b844166b15b29f6147d12b226d8d72ee760cdc97ddb6559d009557f41bf25615402879a1123d23c437faf99e87158a6b079235f7dbe70da82af54040a6eaca28ab4887a90a644c8156fc756cc2646924180e33ea18efd3611e50024f90653d78bba33a553272c82d4944b4eeb7e092a0fb811e0e9b57bd761bc388db2ba75b019e1e5356e208934e450437de9c66f044ebcff954dd24fe541ee32319b4cc155fc7c328d539d0d9545719a2baa3520c6f75a3ce5fb920ce0104ce1468873394faa870c861e1b36320b9acb37aa867e63343443d86e30654f92975424db8bd6bad25528cb645b8b13dc5ac11bd59b25da99b002b54d28462ac1ec32e39bd0d42cbeb5d2d886b0eedeae9fd1a2d0bf63896d7881029a6d364f0001267cbe17552d99cd0c6186f834b30a186b1b2336d9b551c487bb1b4c467031871224f6243c5661cf161bc1f30abaa1cb0815a27880b10b3008e06f92d8b6d1c527b4bd1c1d9ae9495f5d2781b9eb915beefd0f646fbbfc70dc6a1e041de74280ced9871585e00e4f4ed6c7ad09ec3cade833f314532383eb59fa436bcd7fd13c1bf25e05097498dd1d8b5c10a7c03b4ec44d950b5678ff3e36f4268873f7b243a5beb6bde7464b980ca4048b894db5be0bf4bee0dd8f035c04c39b8a364162dd0c75e01f65a4853e0dfd4cd85d9f2df213d090c025227b1a604d426da9f420e392217489b4d6961f5e059abdff7e19b2a40c5fdcc3601b08fa591e15625ec1d23b784a1383efedcc10a0f2bff1ed1412d769eb62d43d2b3591e25449b46ec0b81440c61e8469b92bcb3e63cdaf153fb51a3ae7c4899545f64b3f5b8bd0da70b9be9795714c934a47ca512b8de45fd2073b37128e0679554c8a1310ded4009230401de2d429b87afcd8bf213992707db742de262d29ad9b5d919ac8c4aac2c195b1c16e8a75e4bc087c79a18b386e8c4fe7c354b39581b5271910be90c6e6e183cbea1329f34e6d42fdbd9f87479e806eac3a2ba72d1edf68d801e1e234bfe0bc686d2da396aa64f6e45f44c0ab4a98f1f27f98fb6075f4a8dc3ec788aef5913446a33bd535b8e18b788f0d133ea6b614f51b42ff22dd904843ab590f371fa17bcb96cdf86658d2fb52fd18d7387f54476ef9af4c96fecaa2767c7b7ed8d3e1fd053b3d7562a404cac627846b565e56086a595f3e3939fc5820ef8e132d0652c1c4389334c37a151b076eaacb18cdf52546e7f6a570b8dfb051e29ec156fed0ab39db430469900dcf281b81f4b0b229dc65cc1cfb9ad673ae6e2cc52bdb9ab152cc689c163d10e151f16aa5838c4410aa49d241c69a90b7c0a70b1ecb1ad4520d750e60414326413f46f13e486712e1ec52d5615542e3cceb0e2699b89a24b2b3bea775489bb6e930446875984970366632fe619a2746a06014ce0c1f321cc158005d63bedca1d65a9b1415f8e3bc2bd4b631fd581e22568643237c3a7034029a5f8ebbc4baf602065fdd8070b7e111a6c9962b0ed6558b47f87420450980f2c7685c703e7a93cc0f9c9e1d7442f9993d7936a337b4342507e3391b3942246116c8007283d673a4f4c67504f06a21297a6eba41f92e6bc7aa2851919d5cce410ea7e206d399897438f3ce005086d367db59c1da8ff90141fdfb83aa71cae9c0e6cfd3a2ce1082dea0076f3a70e9641c23a25bed51928210379a22a20a44363259903c82576c27417f8892c7d2ad7d9e7ad32d458ae13eec4307925f5ced51200eaf15cb0a83be99cc92ce5452d4684130a463e58a9579276b84a528147f51d23dabb1213e833e9aeac5c3cd28957f090493a01f2d2a4bde724ce48a56218662507aa48f1967215d5715f760e70a0e3a5fe457d81a16edeb2d2fea71a358b9915fc9aa20c2a3079422d7eea48dd2281e606d81944419c14561c8162e2be05721576cdd4598b653cbfdb069ff3d659bc500a985de13975c0f03b751e5c959c2126976e6cb104bd8e800e18949783b4428475ecbc87a0381341ded87dc075f18a1e1c94a43acc261ea8732ecfbc98a75f093a2714c49a93cf5fb63d8e8919b2f317a543766769585be32d9847670abd090d1dca7b02362d09c5a9c3d5bd89e0f12b8f35148f2741b38debfa80d8bfc9b3b3304a7d16a0334d67c15bd9e59b63a5094b80128e75f1c72fd4869cde46c22a41f4e928bf12c07ed7f1179d6901d7eb244ec2b2d265b5c4fa37470ba64e697ce2b512843d2667a50129b34e218c1f8c1796f117c3d604e26f41df33b435c1f2b5a223deb47403f34337ccb3f103d27e18b9cc27b33ed1cff29f5196b89592d1093f112d8ff251caf6fedb445c2d83b50c0cbc652286df8f53d7b692398531189e0b9885f067a48b28f56720f064b09e2302555f53f43aba84c4fc5d295dcc9424bd891955ecf80497e8016c875f3687112bb8ffe8fa8e242fc2f70a4dd5eea97729f7124faa4dad5c3b55add76b938005d37fe85401ac0b0165e58f3c2ed63be8b24091d43fc90bfb9760b1399b321c243f86d81ac6ebd2c6e964a86f2088c9399e310c83df00be01ad1567c63f68ef1396fa0625b6e1aa243560d1a4ca16c9c7f654238255854d680d2a47e39ecf161a5f9739b80822204acd81b0aadb4f4ed5c0ec01ad655997aa94be0350edb3d02201fa3856a2adbcced5a73023b4ac48b107533020841e267bb92ccdd5a0b5442855ad43057c3e50ce71b6207b9eb5e592c3049cbd3899d8b93659152e15a3d305e36a8cf3b8d1d223d5741cb7e3e5409556dd3857dc47f762241f417604ea101cfeba0e4e49430f12fdcdab2d2bfe41e03ca0a6d0097370ac820a85d44643b23063848bff646413831b237fcdbe0dd52f46157559b095ff1e205e13771a3d2000ccb559fabd0b57a7b4250968aa3aab83ed4904066104bc0aa30f674d268ebb01fcd5980db3033877a5b8b8a347179a97ae2980a1e021f9f422b3c73186781a1be5d7b40e9994409acb7c1ecffb33695a4a2ef950adce004671ac200d9c9ec11fdf0e2045b05a11880de00a6f543ec757141569d140848ce721a47520a046720501a1d27ff21224663b017936e1b5f42740ba5b14183c8dfe9205af97bfc40c2df0aeb19d15d1e0eda8357834c30ade0a39e05cda38e4d876b6b92413996d1112e07dca6f16e69a42e18f625ae4878ab71324d6b2bcbcfe94bca2d7cc7658510b2d810c41833fb673074ed5bc02709ec24c5d1a8be0caa6dad7b8e63b8f43b11feee70fc19f71786fdb92b98cbc9acf462b020be1c6f56516eb5b1dbfe5c5232cdac53c4765f5d3fe3c2f23e56bbff3ce4362f55aa40426a62b0792d39ee5370146c50e27924119d7fe95869ea7b3cd20553d2f1b8f534cd244d6db046ae5e303d2d35f245aed74e09d173515b6c8d7b04a58a9f8d7a1fb315e65dd5a1596241bfafe81afb4643b8bdb63448380c5e560a55aca12785f1c1d8890ae344d5f970d28cf753227028b1450a848b3f4ee01c7d2152f4baf3371d0f0672d9dafddef25051770b9c248ce4ccbd7feb035765b83270bd8519dff799a253a19a70f9ff316c6c4523b65e011c281e6c01ff208e9348c3f5042e454602ff194dccb84413625b684264dfb015dabb8ac6455aedeea7db7b17d62a93e2e697233ce7b16657601095fe4ab7e752fceada9f771f780bbef5aca23b18f28930bf8f4da3b87125a23030f1ba1ef7ae5d0494e6d014737262d7cc80f7f9c5ea40a667d690ab5eb080e804e248b500cd1ec41c6195b57e6b2be6f006829df7794c251ead90935f0e00217daf4ce82e6ed33d3253f6cf91d482a466ea04efd6ebeddb929e485d29a7617335f8f4166a6d239d67845875d520b2e09b89f3d4149f0947de8dbcd9e53fc04f163b282d76662d28a2d640c32e75e14b62a8cffcd36fc70dc803d686ccbde0a0e56205b59a863fc6c3a04dc12139765da7f3372bc9b61a20304b6c5247a45149c05ed4dd039f4526fd97d260b2ce48324b4c60b76bd78fa8299ff808782e117aa90d0dd2a7d5d97c3cd4584c46aa2e6e5ed8339d4ca3c4c6ab220b222f37201f6fc11a247203fc11ef0178efd35c4b465eed3e1c1ad6ce0b722958251ec380891a83cb4fa4474328dcaa24a4a2552a96b90c0048d4fa09bf55e37460a6153271c1319c302a279ec6e72dd16f68a19a5148fd66c6ea2168041225c8d174e5e0baf33b60269a6d1eb136cf14c134b6cf48103eec542916aa381526079fe55e30b55423a11b8b409970e30304278d10daa1730951e6553df0d14d1d00b4853f12af7bde8ef3b7271630c34548575581b4472ac89942cb99d5c2ddaff1cb0e7df23ea1eb4e1a01a2565a553672bfe3e4de8ef6e8221f051386bb025f6b7d93a960b10d5bdc36ac410a77909e908ea8f82bd8fd262fc195f431707d7ea9e9917561ae93a944477cfb8d5e858062e03c0dfa36049dd6e08d83b3a6f66bc0cb3724b2d2cf2ccd32c04528207cff392cf3215001248b480cae65bac809a60bbb275f02e837fcf8601e5e49f4bf4e9904965177e3fc78cf54db4bf5fda8ebe3adeff0a0ee52bba99ffef884bf1c679efe5c1fdd82bbfa9118427379bf7319c6e403b3c15212499fe05d579c5bb29a9647cdd504affd929e472c8c0a765b263e20412221a4143e8ecd008005e4172a80864b481fe334eca6dd6345916d61c9dc53e727528a7fe6b5fe07802c8255582348f0f89acd339960485ca37a3e93db8cba2dabe0429460a225d9c7d11980630ac4428a974b457a348bf548a1bb39028ee90e1a72aaf8cc0dcf1b3640cdfad8bab296de26f2c27f2ba59c10a5ad9816c6d135f2f0e30b18b389446ff804d15028276a2c6cacff95ac841df51cae506041c2230d123ae9e84f21d4b8b035f0c767d2ee38a181a6cd888962e19561e41810d25eb530e20982647f2a6c88f8a042914379e4cad105da371a9bd7d018deda79bf0179f09c7c4c22c20c17f2894a3574df4ea32086a99ca101b942eff7d9468299c2e64216b751e6a1ad4647cf14ea164d2afe1dabac100b74f72f674842ba39f41a4e421398d5f96385b6eed0dbdd088ece9a2c4883e1cee362426a69f4f00ff868a6e1ecc411663ff9029247d94409330cfec1da09ce738137ad51659eb54f164c92c2866afcaec6a312524aa3ae724f3821d09879c8b2964f383c4aa5ddd70aa504601ec959d30178335b6c0534894143f89173f1c2178940bf93fc90b29733d51f315cd28ec05cbbf19046ff479fb94699400de6e5a0a343e750ef15defa06587d72eff79f85d1f409725f62338aa1b73b59fd1dc64d547bd554fe0e784aad3345f178209c19e7fb529cd0732c1d31ebf5062c79119f4c5774758a0d3c090404ad5187b5cf6ede0b0c3f73527cbba47e2e133a7238549a59e7fa5f9c52350f0c4b8268b61e4dc217ec409f10cf07ca353ff16e7c9ce15d7e4b4704ec4f5979fd6c1217a56d589d9d94a594722984500ebf99c141180bd471dd2e59c1b5161c894ef25016ffd3f6de21ae544e50608de70afc1f10b21e2befd09f3f729bb417975032233f30a13690359b3743382ea3a61de248a0b44b79e72d6bdfa04727bd8e2181359219df4fa79de9bb68167143b18460063d3263eb948f7f3e4ac55ba1e83e12e8e03b44059d8e7aa97f9b8122ba0f91c805420f2dd299c8f0d40c74a928154029df76d0084dad12b3d11e65527be81fc8c15b50fd3cd13da6eb84708c2a34d2ca2f10e6789c1d9102b61813357d37eb282352af21cb38accb85ceabd719b9717a606345f550071685ce5d7b4e8c49f2fc1562a05ae44786162380948ca988121e0bd48e7918d566279898e28c3456e9f48b7c6659baead75aecaaf48470aa40528c23d9448aed33bcefd7605f2bbf5daf4ef9774c6cd73aeadc18bb451a7b325c37bfe73729f527cd465eb4bd5dc46a094e6e1aceccfd2593198c3d72c66c40950bb4ff6f949f4e2e3650af16d58fbe21282016c4f786f668a4591468e7853126e1d414cd3279c13f814803c68ac17807d86d493d7400ba54bb4820982c5e1072dcad6588b1f80286e384cba4f86c63ac2c381e9a82f138c5777de89601f1e026ba4d12f3970f0c1720a1174e6d30e87f5046717293806db61ec712f2f2af9250eb7571c5c3fc64177f78cd7419798fc1e3c4cf81b5855af937340dc9d2507260707458e85049f268bde1875f7ab5b0450f7c6d2f3a2357aa2f2af75be18ca22e07db0f5cace9cd5c3bcb7d14c3c4229ec33415eda000f200cfad4481a74e000f788fa9d0b6af042338db2b28a086d75d573886f76effb2493a0c1c6079b160508fec14e24aee940397fe51796ba8715c9edef1e64b0276e2e1bf57e9da9561380ae3b40ffc99b38802d763391b64a4d6f06a437da9b37801718960de0299121b661515752c04b38002b3d050dfd5e73bf5cfe05c777fb84b7ee06b59744de1cedd3f90abb9038e67e34502199b94711d7991b84f6fe6d97415a66252da87e167df5b14c7fce6bff4fde042c64283b7fc884405dd880a9ec579d40882f20bc0bbb22bc01826b565f74898cce22f8dcf8a33ede13f9804b5d346746074b619014de9aced457d5733f04b24e3283e1aff240a5c99b1f8cee22c886fb5df77b20257b9abd7bd2f337b78294e966df5f644251e97c6484692cbce4a066876cd0ba19828ea5e5f25f0e4dd5c434d40514a86ee67443b99f5b92f48a046044e797a9120fee14c2536100e99c34111357a8561ff2275749e362a783dede851482cccf11ef8c9cd95200ef80fddf456a96c065ecd15f98a62fe1db7863607f503abe39", + "public_inputs_hex": "0x1486143d0564ca941cac449924e8ee74c1b4433cf73a6b4920737c223ba6b1e113d3a6e1ee0c2390b252eea2ac285eb7ab99ff1ac03a2a446a00a036dfa953780000000000000000000000000000000090331489384274c12b43fb7cd86990be000000000000000000000000000000007b6b44c05ca9dfc4194f954eb77bc8f51e9919742ce609e9e07b6a91eec28f3d0f8cc4fe94c8f02f425b601258f389400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000505f1a42155a63345c51416db9134e24a473534e1bc7264e47c3d8ceeb0048f3f00f750355551fc21f4f8b099e8aa9d146476b5a3eba3f12ec324353d52c1498e0f0a7cb8a62c14a214735dc01b43a24c55e0f368bb80c5740f91d32c79b109a902adda50f3a68b086921840211e0be05add7ad8933f82d689d427af65040b9e42190c3823163063956218b83ca1ae19999d5986c495dcf68fabffe666e78796909ec0921581b707660aae7e03a29bcf5f6310e32bb1ef1bfa21f265e211ea0dc1d34da8f75be8d59fdd8e87f406948e636b3c1dce3485b81e8db02be6a2b619d04097be17ce35a4c426eea7846e3cf55a6ecca6207bf3e1453d1e46a962e47c704c3f121cbb02760f51ff659d1a9699301fb413fc3021b4ffff5c1d00a7b57162a19c999035857bf3a4db9a179b5166dd723b3db90b27a94e60f886b23dcd1b1000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + } + }, + "test_exit_code": { + "crisp": 0, + "folded_export": 0, + "interfold_contracts": 0 + } +} diff --git a/circuits/benchmarks/results_secure_micro/integration_summary.json b/circuits/benchmarks/results_secure_micro/integration_summary.json new file mode 100644 index 0000000000..ace99603b9 --- /dev/null +++ b/circuits/benchmarks/results_secure_micro/integration_summary.json @@ -0,0 +1,250 @@ +{ + "integration_test": "test_trbfv_actor", + "benchmark_config": { + "mode": "secure", + "bfv_preset_subdir": "secure-8192", + "bfv_preset": "SecureThreshold8192", + "lambda": 50, + "proof_aggregation_enabled": true, + "multithread_concurrent_jobs": 13, + "committee_h": 5, + "committee_n": 9, + "committee_t": 4, + "nodes_spawned": 16, + "network_model": "in_process_bus", + "testmode_harness": true + }, + "proof_aggregation_enabled": true, + "dkg_fold_attestation_verifier": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", + "multithread": { + "rayon_threads": 13, + "max_simultaneous_rayon_tasks": 13, + "cores_available": 14 + }, + "operation_timings": [ + { + "name": "CalculateDecryptionKey", + "avg_seconds": 0.041923838, + "runs": 9, + "total_seconds": 0.377314543 + }, + { + "name": "CalculateDecryptionShare", + "avg_seconds": 0.169763022, + "runs": 9, + "total_seconds": 1.527867206 + }, + { + "name": "CalculateThresholdDecryption", + "avg_seconds": 0.188830667, + "runs": 1, + "total_seconds": 0.188830667 + }, + { + "name": "GenEsiSss", + "avg_seconds": 0.517591101, + "runs": 9, + "total_seconds": 4.658319917 + }, + { + "name": "GenPkShareAndSkSss", + "avg_seconds": 0.519758712, + "runs": 9, + "total_seconds": 4.677828416 + }, + { + "name": "NodeDkgFold/c2ab_fold", + "avg_seconds": 28.564699092, + "runs": 9, + "total_seconds": 257.082291836 + }, + { + "name": "NodeDkgFold/c3a_fold", + "avg_seconds": 665.751029958, + "runs": 9, + "total_seconds": 5991.759269624 + }, + { + "name": "NodeDkgFold/c3ab_fold", + "avg_seconds": 13.677920254, + "runs": 9, + "total_seconds": 123.101282292 + }, + { + "name": "NodeDkgFold/c3b_fold", + "avg_seconds": 627.044043615, + "runs": 9, + "total_seconds": 5643.39639254 + }, + { + "name": "NodeDkgFold/c4ab_fold", + "avg_seconds": 12.85089624, + "runs": 9, + "total_seconds": 115.658066167 + }, + { + "name": "NodeDkgFold/node_fold", + "avg_seconds": 29.335869893, + "runs": 9, + "total_seconds": 264.022829042 + }, + { + "name": "ZkDecryptedSharesAggregation", + "avg_seconds": 5.934307209, + "runs": 1, + "total_seconds": 5.934307209 + }, + { + "name": "ZkDecryptionAggregation", + "avg_seconds": 97.371421875, + "runs": 1, + "total_seconds": 97.371421875 + }, + { + "name": "ZkDkgAggregation", + "avg_seconds": 5.380075417, + "runs": 1, + "total_seconds": 5.380075417 + }, + { + "name": "ZkDkgShareDecryption", + "avg_seconds": 76.340271504, + "runs": 18, + "total_seconds": 1374.124887084 + }, + { + "name": "ZkNodeDkgFold", + "avg_seconds": 904.325249439, + "runs": 9, + "total_seconds": 8138.927244957 + }, + { + "name": "ZkNodesFoldStep", + "avg_seconds": 4.391163124, + "runs": 5, + "total_seconds": 21.955815624 + }, + { + "name": "ZkPkAggregation", + "avg_seconds": 31.237663333, + "runs": 1, + "total_seconds": 31.237663333 + }, + { + "name": "ZkPkBfv", + "avg_seconds": 9.470989805, + "runs": 9, + "total_seconds": 85.238908249 + }, + { + "name": "ZkPkGeneration", + "avg_seconds": 87.110795134, + "runs": 9, + "total_seconds": 783.997156209 + }, + { + "name": "ZkShareComputation", + "avg_seconds": 94.84146134, + "runs": 18, + "total_seconds": 1707.146304126 + }, + { + "name": "ZkShareEncryption", + "avg_seconds": 105.638965935, + "runs": 432, + "total_seconds": 45636.033284039 + }, + { + "name": "ZkThresholdShareDecryption", + "avg_seconds": 187.468467925, + "runs": 9, + "total_seconds": 1687.216211332 + }, + { + "name": "ZkVerifyShareDecryptionProofs", + "avg_seconds": 0.583209972, + "runs": 9, + "total_seconds": 5.248889749 + }, + { + "name": "ZkVerifyShareProofs", + "avg_seconds": 2.362957439, + "runs": 11, + "total_seconds": 25.992531833 + } + ], + "operation_timings_total_seconds": 72012.254993286, + "operation_timings_metric": "tracked_job_wall", + "phase_timings": [ + { + "label": "Starting trbfv actor test", + "seconds": 0e-9, + "metric": "wall_clock" + }, + { + "label": "Setup completed", + "seconds": 2.393101875, + "metric": "wall_clock" + }, + { + "label": "Committee Setup Completed", + "seconds": 16.092655959, + "metric": "wall_clock" + }, + { + "label": "Committee Finalization Complete", + "seconds": 0.001054875, + "metric": "wall_clock" + }, + { + "label": "Aggregator P2: PkAggregation pending -> PublicKeyAggregated (wall)", + "seconds": 406.98499, + "metric": "wall_clock" + }, + { + "label": "ThresholdShares -> PublicKeyAggregated", + "seconds": 5251.559821667, + "metric": "wall_clock" + }, + { + "label": "E3Request -> PublicKeyAggregated", + "seconds": 5252.065132542, + "metric": "wall_clock" + }, + { + "label": "Application CT Gen", + "seconds": 0.288574125, + "metric": "wall_clock" + }, + { + "label": "Running FHE Application", + "seconds": 0.001384625, + "metric": "wall_clock" + }, + { + "label": "Aggregator P4: Aggregation pending -> PlaintextAggregated (wall)", + "seconds": 103.520856, + "metric": "wall_clock" + }, + { + "label": "Ciphertext published -> PlaintextAggregated", + "seconds": 334.1983295, + "metric": "wall_clock" + }, + { + "label": "Entire Test", + "seconds": 5605.042426708, + "metric": "wall_clock" + } + ], + "folded_artifacts": { + "dkg_aggregator": { + "proof_hex": "0x00000000000000000000000000000000000000000000000954c24ebda205a1890000000000000000000000000000000000000000000000047891ca0a612eea510000000000000000000000000000000000000000000000032cd6988555d9375700000000000000000000000000000000000000000000000000020a71945a61890000000000000000000000000000000000000000000000013f378b0dc552f37000000000000000000000000000000000000000000000000578d3973b8a5ff2e600000000000000000000000000000000000000000000000e6230cb8ef3f2874b00000000000000000000000000000000000000000000000000005fb56392be3b0000000000000000000000000000000000000000000000037d2ab4998b6b2f2b00000000000000000000000000000000000000000000000753c7e1e41947b7e100000000000000000000000000000000000000000000000c913f223f3cc93109000000000000000000000000000000000000000000000000000015ad2d9cb057000000000000000000000000000000000000000000000009bf71e3c026d6750600000000000000000000000000000000000000000000000a7b65e0bdbb03a37a00000000000000000000000000000000000000000000000bcfd70795f55b571d0000000000000000000000000000000000000000000000000000fd6d2df268130479d738c4ee38577d65b445b420ec265f9c9d743e868525978879a50fb2676526d4c60047e4b4b5b366ab2903818a6e23f01a9a19dcf15005c869f763f0d3a718603f91d677484afd67bdf49862b7a0d8a7885eb94dbf0fdce2a0f2e8e3fea707a3d0275aef9b17f71236978cb2d218add2a58fe2bb5d47f0c77792d4b03de917df3288ea86583d86a6c706ccd356ff13ac09352c7fdb9ee1b7eb870144a5a0032279b0f26b994415350699f0584f685cdcc4fa9c6012523e64b359b3a0a315180949863db19097494da78086af7ab28a4f30051af93bd8b457341fa4e3a4b82c51f9ca9639322c8b2cc935c697a662cfd7e1bdc24b820869db5c02d4224bba03b871237ad99247a84e3f4ae2aee3397f2ac4b1e67d13d9992d2355b35d473e1caf903eb3681d0612cf7b84fe084821333d5a7a5ede332609f37c05f346a5972c7b427180c02d447856d1a00e12b702cc7808b995b49ae9d0886881532077771470d6d7b36709f696e70fd45260daad0dd7c05f60714838cac65febcf5920b40afaae846ac976914cd712ea5525e57ab68c7fb495ca0b6565a2461aa6987ff00d6f129c8f0052cc9f3593a4e650c2aaad3f7438876d40ca5a2f2744e3d6a2b121cabbd1a2de7dd994ecf1515a7542cddc8021886e53e71214c4c49fe0019b931df5198d8f3c27deb3cb9278452f7fd8be2bd3ab23e32357aaab0dcc639d43a50e87245b3eb0b4d17c3d94e4489c1116440347973ac5d1b3ffdbdb5bda4bd4a317115e9b5ddaced77884f0873a639ea45c628ca552fdd96aa39716fbe01ff66e29d5f35b4dfe51961e9cf22346931a9a9fb30c45c41d94c687ccfd9c19163b1e20f390f8e062e1c738b0a3a4bba05672aa3048f39921e74b5f07d1d7185e2dd321115ac66fa437573482a2818e18ef87eee118edc795d190b7633af3b407a46a1e34a9856ce1037c5bebafea03332cc8eb47d8a50ad8130bb7fd81013c311297244810ea5dc9183c9d13c47a8e666863d80c64c05baa20c9c227903425e2c9e52988b98d1c607835da46b657ab73038f9fc26deb30d37c2a6de109bf1b03140519d9c12414a86a64ca1ddb999e61db596798fa182d101a32a40344bbe759dc070fb4c4faf6cad2d5f53df2abcbdf4485058be55470c36a2cd6ebaefeeebef439001882b6f4b8d3a0a9b74dc3ade61be5f3aebd4230cd36e7d7b6a5e9374b03b411c376a48ffa289b5cd0382fb39c1183f2c227c5ca891ff7ecf504b566914e030a1db94582c46e2f1ff6ff257c3e9416096b49a61fa2c435cb5248abaa6b758b0457295b1f05f279ed93cd004f1b5c814f5930f61d95d0164061c4310d3c08681ba02bc39592fb7636ffc11815de6e56fe6d757674658670d7e92f80acd7681e212be07766aac444e30079100453253785ed9f3bc2e264c0705bcf655f4a18dd2224af0f6d6cf1ae1a9950e751a011863db40254bf6a2ef87d564a245d51e5f11c2542d9e1797e8faeb7d4ad847f94985829650ad4927c98f2e5cbfe53c50f1120b905eb3f2a192d3ddbbdad5be7bf6e139bdd9eba0b72f063103ddcf3169daa28f9993f0052a2714b0108368bb1bb68b3b69bb7c43711e3ede8fec1fbf54e440bdf8f48b3c5ab0d5816ec621766b5bad522677ed2dcd4e30b34b0f5e8bf56b006d4b03918e8137a4b84714d55cd3a9f9157c07609d443a9b3a10447e76c4cf31831a1e30b388e8086e8aeb6251fc2eaca53ffb56b036b3ec9c675ed816d877911f7396949a5a45782d14ae2f8fb0d4496cb76be92f3123c4494fcb88bfa88782cd0b15b923c1063ce41d0be55059cca8900361437b919b821beb588f752c49e1a3306d750f05ea5eb43fd147d7754169f9cbb137cbe051f26964070cb8248c1152ee00b158348359946dd6a052b56b812cde74bdcdfb0d8706bba44093ea007225560f11fae351928424dab17684a8146db2a1870dd9e0766422fc8dee7c930181fd2a5f180a80981e004f48600a546163bc214e0dab04eccd47acde05e2f08265430e92984148699b8ad94d8b237249e845e61ecca660b38a9ecbd28b4a57f13f9063635b386d27e21a6890bdf58841ce614dd0b019ee4a95c35ed5e5841261823380c9dd2307bfbfca69b30afc09ef68afd3bc4bb6826fc673ede3921013f21fef391da29cfc93e74b4eda833bd820e760a9dcf30baf8348ee2efe2c801b8233826304bff681879221e2762b80640e5e70b065d879d4892aacb8040e3b94a0fe3fc8764545d4cd3a06ca5eb919e773b8ccf6e77544b77b08b5c2c3e6c52c3215ff7fdd83fbcdce4d1c48035f505f9918ff2c91d74ff3dac351affc1e09cc81dd589e2cf4edc0a0a30c660bc592e296037dfe165f9c9ef958d9510ed93931206760b3f2dbfac735bbcd4fea1596e6ece49bb58eca25fcaf2193db71c1bfbc407dd9482cc4e4353dcc6af030062ec7306a92e143da95f847998e0baaf690eed0cd3c79f560db4ebfbde0c969eaf9990785b8f105742a8ef523fea869992e1790b932637305e08eea241cb84740016d04d1181e1ee258e0dceef1373f4ee9b492692c9dd0c0aca9f9c1169ebd6cc3ea15e1dcdf9afaf00d9ef34da272beb108f1b6934a6e6cd0162853152259ea61f7152d01006ee9d4cdb7f39c4f468095375295e5d021cfc1cb744b26cb49fbe2de5889e996502439a7918a7853ab85986872be94b747ea2ff3b4fe25c9742fae8556c4d881b339205fb8d92e8ee31873d3303a87e48f886ad41a6ad621508757282b3490e8d8fe2da22844614359cfee3c623eb476c158647f316928f903ad86c67e6d04b905a788caef1a4066c7a3e36ac24db37b6d796c23d77e7a4a80e7fbe5312e3290b80a3097a65fc13f955b2edef0b72616a71073e5ab28ae16343cac84591281313d71eb8b676d324b4cf3bf7e02a5454fc081168e6070b9ef6683990545b34069c5bfd3079efff96f4d8503a6c25ccb480b918e97636b68e2152daafd94bbfded22e615f3f0fb3ab18ec022d832cb930987ec9bd0c8d90705b362dc20123a6edf2267359b7692061ed7390e7722f714ee99dbd02816dec267537222a4efe2260684077840542ad5608d226c458214961234d6b9df88ac960bb145da06ee967330c2f941441dde2860de102b56003dc5dd86bc2691a6136f98dffbd6a6bde83059e71dad335abfd47ee7f265715242692b67dd1ca15da007b880531fee6d551fbf63978d744445e7c047001d27e2c5a20ffe2e0412e14eabd0cac98df969d10364918cfb0c14b09db809991c80710dd08b4dca4bb4752983e00ac7d80929ba4261dc45433f657d06c79d8d3411f1e33e768ffbe9f120620405857b4d2009236536d8ba4f960c30633b60f9807b2255cf915b29b6d204ad6bda65eb98a76584d048729a2aa6950bd5497f31834812f9f03764a12918634d150dadfef911bdb6b9345b654bf5077c65a37df70cb2409c682dbdc44f157e14e9a538e1724609bcdf9f19223a2d2ad5441a3249d5cad021befff0c472ff884d903a8e263c0d6ec25e771df369378b77e2c7c49bd9ed32eac8025ed81eec539e40a29167412ce07768f524ab397345d6b583d7491181b2061fec9445e09787f5ba53674471a06613034d8e3e977c6aaf1921300143bb6263c15dbfebe951fc8fe5d5fee441e1a61259e24b9ba8539738eae9dca67a93226aad08653953fa4671e46be63098b3a7af0569ebcb58754c1c2ce89291112c6238de55e26acb61dd66c6e141d63045cc092931d884495b05f01742e6d0da04708b1a336f53b269a0ed09920fb119177d4a57e46865d739292e02d1963b72b222140070855347cef1811823f94e295526e5debb4b8a9b8e6ff071b0c3da6f3c50a82030a7b71a6188c761bfae5555dd8fcc8052494099fedc0dac2e2149621a618ee11b2de3283ff999e1e7b75f0dc41c3cab1f82982fd25d4c5219285335e3a08b5d32f53149e2c627d124203d708f4bf689c0bfd205c23bd7287864f43fc5505f5db8722397932ede80faa4502abef2a335d7dd8a6bee0aee2bd0935bef824027e22058f7d0231aaac32bd283c1d21c243284efa116c12cd62f6996d9745090af0fd01bc81e5db7a7a72a02e9aa349f5c3680be7573a9592ac24298c99730b235d74057eb525fa9db4c88c386fb7c0b46f9fdcfe7544e163b8d878bf5637d02513a462b231640c1d7da3924460347c7de8413addc5d90e28213b0861f0e1312ad87102d3ced97628b7a51796d9202d6931f53b7e97f813898c3904c6c6ed792557baf1872893751a1199da549e8d40e6b767b2e617de5efc68ed43f80bc3d31893ed70d476c3b224725c08b418cea498cd805ffdaa309aab6e7fff9be15a6e24be8f3fe1edc8135ad3cfb453f91f8f3b28f00f5e1cdb413c8011b887c467141a4a9c8a75d9cca761c00badd5ef5f2d9c972c9653444c9fb39a9b8c153a38eb10002a870704163cda0c9f3c8794adabf8829b3cfe43c76db4b3e070204c19c300406d3c59112f4fd8f6d49864b41d3bdcda00b91ae3c38cf4715554dc3ad9cf2c05da85f79b5e1467c590cf088a8374f80ecb58ad120b2e010f8e04dc890cc42aa372e47c3af4fbf8b47f9bd6672bcfbabf3d48bcb37128c6e4960c57f23c1c25b7dd1f37782125d0d2f68d963f91dc2821dc5440ab5c8684df689bd36106001a9872c04f1e304a64f1ea42cb4d890c92d8631d3898e7a35d561498f8db69282264fe269eb5ec83b76cb73f81bb42821205e36e90b8b0e3550a48a47dad2042088cbc58567746b617df4891ceea45deb36c71b044e6999ccb3bacd0d7bffce80f839b549c474af9bd63e5d94fb55ce9334faca560233ca30f7cfb7e3500989805b4d31e5225e33ea6fcbc9fb9568707144034a248380cd18a4fe71161659fde2a6677fbcce9e7d655fc95c69900ccf6b85bc59c803cd83c9d617c9fe7cf6206051e944b53d5ac4b5f6b8d149c84d3cd97eb7afd61242757bd95b537da02cd5624e1041188c6d776ec38b7002128126520a7b090be289ed5f9394b615638365d1547bcaf555d5e9b44a663e9a490fd058c2b86c578fcce7b52636197d04831ec0e99ea9b8691a14d30f294c1363cba67e813dd3d39b232d0fac06be2e09c782a23f133762b99d99eb458e24bacda09221812d0625fd6ccf763487058ee3d219f16ec60eb16f4b582d0af287cbf885c5de32d182ae8e433348e49929438fc1b0922feb1a9eb418e1493bdee91f15ff98821d95660b3f00d1f69cad59d6bb045a92b942fed2a7ac1711f1b5a340bb79ed772eeeec1ea605a0ec7763682397a146f0546a02fb8944e603d538b10cade7b1fcc99e4dcb4cb49890d9c73aac43eb1b317844df125648d183828baeef070bcaa0c9ae252c6b0088c8180bfe1944139d50825a6afb3f84f161505c8148211127101eaff10682410887d14fa2a6389f5621357679db21bff9216fffb04e994baacf43bbfca9e792ab32a894ace72cb62870e96989fa31e08cfa5f848eb137862f0ed4704e6320516cea852551328ddc36903642426fd362d4afdce884c67d22a312346c4e2d0ec078b8b41c6884abeea122439a1b8174258e1dcf3cb3bfcc773258204c7ee55f77ca6ae3d17a16cb603fe01ec3c26f6dfc003fd1eeadd595b7e46d10fd9552bca5c465f835e833cd8cd9119f1f5e43f0e7f7205f1667c4d7fb0670c284fd2929360ff5599d098ebc5f8661534d76ddaabbe567c74c0f4ce1d1ae50ba2bfc4295d610ecb0d82a6261a7f601ec8512a5887e008af2e49eeb2ea1948d7def7dcfe123bc65d45b6792ac776b30105aa64b0a6564f9a66f1ddddc84a2c67503c34c9fdbe10833db3ef9fde04b12295e8d7cb26d10e81420ee58da3e655fbf0716af6fdb599a8711486cc59dade18b30575632002d0d21b08ce2cc8a54a215c163f3c06030064ce164320252a30038ac91a9f9f6f328420a1ad9ac21964974470784e5e302d4027f3fbb047bea425d60a13d3982f35ae8c86577f1973318a04e85f6d202695b9c5fa41c35b5b852ef84c6eabdb2de8fba547a0b65c05371103030bca01e04bd2306684ba47b76017b1141fcaa56b2206f78226fbb76b0533a7ce50aa0a4b7371372b3570b09cd800d8850bc681af074c1035450dbfdf530331d46c582056e37b8de5244935908a26898b967ddbfcd28228687c7e84fa51b15c096efe9da672f2593589a82e3e14165839aad3a2b0d866257b6a8c9fd9407653a5edf16e6cda91435c4448b74a810b0bb5a263f6a644b58bd759d466e4d7ff683b1cac40f53b529605baac0f1f942c5cc7cc12d073eb337f3b16d1114d1588f979ff8b0b8a9fa7d36c31f7e68a770686258566d7107d246ac0852ba207e065dc52b50d4a6e8956d1f6f5ab68ca7e04d806cebf24c27a061a0e70337fa2eff7263a4aa71c78dc47ac83c0c81e35bc2affc894bda4d88ce418e8234ca460fea23c17f43675e86d654e6cc89a318bca0aabbe885732619a129a42cafeace52ad24ea837d1b002cad6712569457181220603d7ee974e9716aef6df02646394d0e9dd1e1746bb06590d34edbdbcc921c3010311bef8312cc8095a1f4ed732d83cf6039432968804b89c0952a449263a822da1f05a1a42835b7d24f2e9f2b59e167e9ed8d7e14f4051e9880eff09c6792825be65ea3e21d8d02a24758e60f6c7e6565cb823c867114e915db0c7f4cc47b612a7c3019467cd469a422ca3d7587217b52c0e5653749f9affcf36752131a10e07c9844fb8450ff657aa8eaf021bcce295748e121851660e0dc4909a2719af5c28eb96158dae3557d186b9743dadf29d9f11f5d0dcfb4dede3e2ab2739679a0916ef6888b12b324634fb62e4c91ff6d99a61293a7841c20854dd20d6ceb2b85d01f905854e23020044ea2b96c48ff0559706fdf8a1a9a513d5df18ee4e556328026e9e34ab8a4ea4f365688afd3c346599782ad10e1de844aaf27c873fd7c5c626721ef8b7ffd2dba851c17bd7dfeff96d141b5d1e2058eb2f17f313f9ae321c1c7831ffd39c03a5d6027505796fb09cf775ff79f24e8ad288815bce3ed9aac72df73050ccbcd65699633d4fb70fb67217fdda5b617bd4ed7276a22ce3e2eb6a1935fe82954a6a5590f8cf1c91500da83487b87e30ccecab155ae62145bdff181a31844bab00b615677c78ca3cfe697de5b49c3eb357f52ff3a7f2ec7915128b2d237ccefa11241cc47a81b9a8d2541c8fe2d52e0cf5d135ea1b4b1b8a9d15cd149914a3da61606e033e9d0e4a7b63b2cb13cb9ea370f63e2414d3418a5c7d991b4633b0149d8f03c9c367d03cfec76c5109d53f21596e32da280cd5cf45776a0b87b21b258bbd20d69fd51c1a9558b1dc2138ce33b072a8049a8f2638d28c150f5fb9090cd0e2d31c9812d5480d0009df8454e1417d6f46dbd05ba4a8ad6ca618bdb22a6cc8ab63943c13b2ea5d193b20e6b08227a589122dbe3a73f3ef1cc32fff616e1c095353330c96cc982404527938bf88505d3657ebfc50977be0020b03541e480230514809974e9e69fddb437d174359ce3223a21e390b97878a8fb2201c227f3b6fa370bd3a16acfb7e6b0894bbc720511cad2f1b2ddd28f29f83eb026e011a09b10faa9329ba60f4f1593fd15c3b698d255ce4faf6453bad13cd760de3427a971293a2d5dff7360740304f5474deffc50a4604840053da7652b9b02d6b0e28d6cb789ba636d32f86267196d2614812552594e7dcb0d55c7f3c065f19bfc2883d105f9f2750943abab64249cc002750c21f7f17139b88a18118d396265ab2edaa8e1d15dc3fae0eeab9526a3656d667066afd2513be00e48a1f19810bc20f53fdbe2daf96da6ffa1a4cfd89bac34441a116580f9dc70b1d90b6343722c892b4b34410f5d72c564c7801d46f78d3f810140089ca81e696f9c168de1b09a60966a24563f814100aeedcf627f0420d292ba9dd663ac76f35d77493972809de34afa8fc2cbec1507bfd4d8214150fb7bc4c776d270a7ac59e09a2ba0442132e7feac7eb2f0885f4232c77ced69d98bf587b9e25a03b51fff2032e1623b32a92d8fd82069c99cc1a1eadf51bce00bcec8f13ae581f506ab4cab1a949a4162bc9a368764de7a6e6374b3958f61b422db4c8ab79b3aabf61318650260e09d5197faf7ac774048996e153e153285fe70e22c0ef8de78bcd7f82dce646766eeb0759a9e4e6695edded54aec59e271655a908a67e9a978a6cc7117b7c838f884e1ab20ade1ade3220a5290330da0a30ac5fd6b27c0a20fbdb6a86831467607f421a740222ab535e73c0bbf14d4d5dc94398e7dc07e3a68461621ec44f22a1dffc04f3eeba280588b6bc54d4cfa12ddf74f0eb41d28d71b7e1f40154b650de01f40002f96e89c482024cd807c4dce65f95154862969cc7af25f3e593d3ff75668d23dc884e8873c8868de377ac148524fea10091dc26e256c0fae4a865669cc23b109c1435222a257e8f81d0138390d41fd15e24554484d2bda75ba68fd9ebd5fb0e0a6eb696c1d53f1c2c6d1876ecc4db74a7468c5f1101e616e5f9601200ada7229859cab751c62d3150c969d594ebc26a09aa2b452bab04cc0926ffcff77e0f2728a6df1187a01c19ca1246a6e3fa46fc745f1ccb321ab3510783dd3f9164ae029c8c0374acf4a62099c0cb553840f77ac7e8892630a753baf8b659d33246ee13910d2643c85c3a652e98e9074d305614b27a4bfed9b19b58912c068f131f9c27db076544b0f37fd76a9037f5279f369dcbd7e5ea53fa5ea1b4cebe86d84da003da622a4f51e5b780b50502de2bebb6d08e5783da1e99ea0516eeda99b29b58172d82a6ee6bc082520e3f65fe65153d3b116b6bd4e5a516a973fd01e45013f806b49e863c12caf6dc51bf201335d03da3c7baec7132824a9be95ecacf49bee405c2ec72bbd0674d967d5e247aa1048cd42d3c93abf4e94d99be54b3a686dc65108ad51a31ec40a8feba4f1643319baf8bc0371f0286cc4b8182b1b8ebb5676c07494ecb86ef09692147160fe4453ee58a4fd06fd13a3ea30f0df887e31f61f011c9b2e48b84e9780898bc2cfd18c8dddf03fe98451d43f8785d851a3f16d1101466576200d8b8d94e0111eba9d87f04e429f26bd24562269463991ef5c4f90a1c396159f9e1367471f9c652b7ba1f85a8f39f14676e57f27bc71ba0ee37de16119f8d03f603ee5bcc29aa33ed04c99ae110fc380985e9f78f14034824f7d60d09caa5b032fd1365d586d7d0ef008bf007107ba2851865fe1e85a38a3623b7f60c7944bd39da759d1a9c0904258bbb43668c05073f1c47797c64ba7476105c5a3032e52bd90dca1f89445a24e680b21946b78714d905c686757da1f7bb325f930ead078ca2b4da812d06ae76f795250a9614c84a4de7ebab91698a1d932e9c3914d19e4fe62fbb06dc685ff96f1ee2ab685a1b2dd11bc574814e55bbbb8b4bf118e9abd351095e04b308b0a080b423a0290b2c240b68813c56ec447be417d1660b9ba2c932ec8ae62cbe94a966dc1d97905e336e2fc104e71963aceb2434a0f00f213d112e2fb79dcf5bf9109a5a89171bc77e74f677c1bd91adaadd7bcee37e08085211f4f3830aee88958efb8b9abc5d048c9060e19bab14c71a134359961d047f23e809c640d4bc2f8c1013073ef2bb7d47092229a058d9a8a0e368e438051920b215b9536191b75c45babf5539c2013a15dc0168d950289aac025398df260b5e8266859c2ccf7734b2abc57cab97559e1690e3b46c53efbd701733356fcc024d0527b5d1868e60f847d2850ad78d1b0e14a6c8d0fe88b8fac37a22ecdbc9249f53b74629a75beb4065e8c37d7d6f4642ca9fe336fa422498b32c8bc4f7512a2be4b259a1b59cdecf3130eefee0e2e5df6b2c9bdbf668b75aabab1576d53303db0d44ec971fe3d1321a9350d8fa6896a2cc2e1098698da58eb3a7a35eb4000e4853fa959ba339a30c0ef0c013234b5a28a5b452c624c8f8fb8ea2b42968eb19af260b94cd35e4261a065151badeb645494e07d6861cb5525808b37b42ffef0e2b0adc409b0b654134cff58049bc938132ec45d9ef3a29c44364310129aae112d8301156c8ef325358488ef5d3efbb02b572deba3a29af4933865fd6c1bc490708844e738a7826a8ba820c8951fbcc346c05d6f2272d758aa1492512d270ef0254b7bcbc7663ac5f7df79f7aa876dda879d8641724107d51745986adf8fa0d1c7be3449daa3316310805fc39038801b6bc2839218b89d9599bd4a97059cfb71af65732d4ad5f7f085bc5fabf031d31fea3654a4d01a951e7f5b01886c42e3d21b27b018697ecc90219da4e2862891264fa808c13cd091d0480b09f49c7af9701d675d72e6bb13a542bc0d8778deeddb084d33610d8487f9560985cf4a8de49254372d2f7b5b359e27531954d74588044d27b0699fab23a1f402d1b7fdbc24826ecc129e4234ba397dda35d7bb9ea7a843ed5aa132e4800ac530b5d453734ce0be7f134c2b0b0082a14407e58bf0b9268d6c8f71a4e6e32027dd544bdef3c96160d0fb52d0b455efe22dd5d80e27444882381d80c00381306846ffc5f3061a521b205b976fe30137a433bf9b17472ae316a82e9a56764b1815775de43e5dc3b2a589baa1dff943708db4e6699cbc5fb855c6d65d3797722a085c536b5c3c5a42e386bef06c7b38ea71ab55d213dfb86b5808e4b9567e35aab3464d56470873903ae9e4fa7fb9b93bca027fb8b7f1342d177584ab04bab2f054007f74c13f8c018e6362f609c16447db1630a9f2d4f7adb816214579991be4bd2005a868c888f28244c9734714284120aedc3dbc9ab237f85e2d0831d2d670893351e5c57ae77086d2e0e84ba6b90355a564c2bd7f2975fd8a1e83ccd2f9e02bec6b8c37e598823af1676dbb3390568d8b9720dd95fa5fee9befbae00270e23a91a87e544e39d0678ce6640c5a6158db874fc7999eb07693828e7f6bc17714e56692cb25fea7b2e2dabb545915cdb9dfff5d5b6db877f8c6b431946ef463eff26e15e8f31854d11d3a836eeb99da348301c8f9c050e520f4cb05a84d178678f3712e05b864ea31a516435a747f2565bccdcf0b67daab6049aa9865df90ff6daf8d0aefa01dd5606bfa36a679b0e172066572f74741ffe72fcacfcfedc85bfb61595b539cc6e890251fb014b1e88e762f64420d34542bd3c66de5ee997e1783de8a9b527e0320f07c6e0071967dc2e82aefb43ac97abe08d9384e0f6bd8f70435d65d0804a480a24ef56af6c2a94db8af52b40844d3b5216787e134ac6b9aa1957d9e5f4e0aa122e15140bd00dfc578b008ff196a929dc8699083b3104307d78979e927d164e182a2dac33cc1d55fa5f02fd3bfc82f608a68eeddf64745a9e7ebcc8a733412c711297d9219cfe2c3c4a281566293b6fd5212e051f11c2a414a35e72fbf9b427542543ac075358882948274f15d6301f1b1c86627c77a116d4d9c06caafa3a8727038e8041967a62abc6c4b07e8b52868234e840bdfbc588883cf7963a0eb4b447186c802ada3390a6516ffdb9969ee1ef8f0f922ffeb2a32225adf999709e56fd10734cca02bc98215c779cc2c1cbd097fc18d335fcf4e1c133b1e7aeaa345e7323cc0c97adb0df8d596ae452c79ec2516777d0f5fa170079eec45a62676117ce0b87d58ac85b3bbe61ffaa2dcf252cd9edd9c7d5eb7695e3302818734d295e791a8198161c5422b10688131459328a435a3935768e805b534fc41fe31b6f882e0ed1b3cd7c1cb61c5c7125e291e96297acf09f44375e434da29cb00024729fed15747faf7337d199a12148eb0aceb829e87ef46dd5da65f635b788eddb52f975041cfdb370f2fa6c1d7185325050e76e07f1b7ca7bc0bc3457346e39467540590c92e4a82e8da7e7574481a096f68ad1a1e7c7218ff2f63fd2290d5239feb49e2b3522fe944280b6dc2ef1af9e137a458d956398c7b668dd3cdd6e46dfd05077081d99ff8047a33d91fd12d5260251c55e116c495c623cb5becc11cf1f6bce340a2a4015ab47366084fe7afa4f6ed6afde94640034686b9ee7910443a840dd781825d575f1365f2c47f660c1a09bfcb48f94342708904696ae07027691713af3150866bdd1d28b613bb3fba63052b0fd65ff1cde3a26f85d3f845891c89089552d8fdb9b2042ca080d659f7ff593d9f83ca330624afa999edbfff2c4050631251809b6200b186582198ce248618be9533a3760145f26acfbc7f01f4b22a8bc8111bc30770b490db35c289a89bb0a57bd0ef49623c5f7016aedbb299dbc8aadbe10577093fa4c2c12a107687154203d73ede9041a4e883271481da373d537976419dba316d7cd066a23790e24aa5796a6217d8afc2b27c1cb5521f2896f0f1c82276cb6e2fd185d9d11b6ec8c4b7957787d64dd7fd64e03f01bd8b968590137610fc26595c927377cb7e0473f53a2d8b67458a8e1dab91a38c40e67bb799a95372fc8208b0fcb17b344902b85e74d9e657c4a5ee2815dcc59152baac7ac152d7a0de4a6b22a20fe4b35513b95d694ef98bbda78a01de6075c642351f30bf246621dc199d582026984759c97b15393a0acc5d6d3b04667fd9e5ac67743afddf5aa07ef5e51331df9e4fc770e98199323b08e9c1e523bfad993ad0e4ebf674bb476054e8dcc4f4ca9de4b892da265c18c375bfad551d7b48e9babd153c9f4da59fd0e889e9d0809c27438d70d6dbb442021e70bc4523b1f8fa4cf4f1c83cf41a87b247e24a2efcc3eed818878d18d5af4ff7a406a34b1e267abc9eb73bbad288704039946c740d20368f36b51b905316ec8631c42154ef93719115297b35f5553f8090bea916e568e1abe9ff9d67c00fc50d30089baab588674beebd374ec41e4f52892e8c129dabf6c4452513b2294fbc8e3f01bffd128cd33bde349e7efe92a9b2acfa186941bc9f0f28746a2daa6c1a29f014254a592d0f7b79b4e7fd2c037752f9b7b5f0b6b6b22805bd517ba131ffeffb08adec919bc56f4e842bf99d209ae25b1578a8507e7427f947da702fb1dc013eff4c276eea7e87e3b0fa62ea8c0050bba25b30db28558b4af4f523b2b2435b563fec70d0e7d592a4cdc685656df030ca1ee2dd95a75abbf407f2172194663f318f678a83eaaa08e42c5aff919aee101580a7cd016fa1f2604e196f1d073c2029857904e78cb627a77b188364cc8531a9255179a2f86972d5adaa0d57c6cb3d6324dba2fb08fff2079286041e396ae2dbae93ad2de194c7cdaab18ca1e86b9b4da00c92b839adc172b3a7e2958778d21bd1ae2d19865b9de3c6f621c281424ea494bb0e3116978ef9bfac44077e21123146399626189c1adde7441069e728a64fed893ebf03b9ba7566a82114937e31a14caa89c9e1408d23f20af3e7b60f4187404918ed4347623a032223072ecfd0be0093a0b2244085e38311914e2c4b51b8e4fb1e534e5aaed3c2d23e09d09b519711e0ab303c58be6ae3fceab557c6a48f490a7eda2e99dc4e71fc3d035ed61226fd2b6e1c607254ac45148b05fe1f6b4927c284fdefc4dd5d011d56dbc621d0c765f5510db7aed1c290f3ecc956c0b280d20b9736bce299c89966bf31017fb1075a74a5a55b21b468658e5712e2c4eaaaa0c3c5f28211a121798f8efed38411c3dd26d5d56552f13e56ec646818978f9f81ce61d9cce7847742d9cb0f03c0605a5076b1454099c2a90d975eb63093ee11b0972f1dd12a98d7b6f9d7542df3318329042264375893c8952c49a963c6613aa0a2a7184919c667aaeb4ca3dbe7a16cbcf934e32bbdaa1ccf5f3181decebad10f51becff7d86a213b83c5dd3e952294cec8bb5bb87cf11d7a47da6dfe755cd4d4f775bae27edf884edf868a112190b09a50305c6a6ff212d16c1cc3da81c60a84082471b28114a27bc911ee054772b3ece09c17b74a236f0bfcea3b28067b800037e9ee4926d4e4b106ceb740e3b240274d2a2901f4efa32332051aef11a9fd263eee1b84def9a79133f299a75570ae17d67567962eeb27384f796c3fcd35eff160a32a15526beb0f53c7efb8ed119a9732149e2db128ce2919a5ff7a4d6ac9da5e8680bd8dc4f02d75d280bcce507ba19d19b518d756604fed9656b50ed5c312eb299d5fab45c24b93813bc98b1127581dc7e6ca399b27434360a547b61a7b4d6ee4f500a42a95ae69730a1978b27da01e89e23f06246fec4fdd2f84686dde0d3232d961c52deee6be3e23194dc07ed4f4f1e32df19bf5910d0e6ce55fd5412bf6d2e9f24c77fdf038d689048640a6f31e45b940f585a8d793a5bf7e1844d7edafadfaf9110d1e3baa5b33987de29c2786664670caa4b75390afe5ecbd2727e1ff0db2660d0da53d04bb87fe4fd22a004791bc0d8dc2cb9ca4404c71be948bfdbe8666c5f26db6fa1b4fe9763c91b8ad82b6251e2714fb0a295bad966180ddf19333b1eecc2a87a259e595888052fabe42ffcec38882b078f484bd369fc14d4fd0ad8e779d74e996a80c7546e6a0b2847a75f8d439dca9b43556c59c25546db6206b486f84aef9e4cadf02184c92c8d437c84e405774a31862453cbc764512bfb22ab9b3108dc4fa5a742f4e8a80a2f6b1f06f92d6d888284ef955d6fe80fff1caca9205d55381f701542d26b2a2b50e97fb99b0438f01356573a01bd15ecb89109b904c770644692947ec0a1ac", + "public_inputs_hex": "0x23820b662b29c1eb92181286c78069e2c68d9bb3f3f95e131510bb674adafe902573360a3431f9fb0997d1717493aeb1edcc58f966d0a1587066b4b2f8851e4c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000090331489384274c12b43fb7cd86990be000000000000000000000000000000007b6b44c05ca9dfc4194f954eb77bc8f50bdada746c10c4cbcd2821cccbd15a0e64a76fb9566b3912b9d9c5e6ec89f27305f1a42155a63345c51416db9134e24a473534e1bc7264e47c3d8ceeb0048f3f00f750355551fc21f4f8b099e8aa9d146476b5a3eba3f12ec324353d52c1498e0f0a7cb8a62c14a214735dc01b43a24c55e0f368bb80c5740f91d32c79b109a902adda50f3a68b086921840211e0be05add7ad8933f82d689d427af65040b9e42190c3823163063956218b83ca1ae19999d5986c495dcf68fabffe666e78796909ec0921581b707660aae7e03a29bcf5f6310e32bb1ef1bfa21f265e211ea0dc1d34da8f75be8d59fdd8e87f406948e636b3c1dce3485b81e8db02be6a2b619d04097be17ce35a4c426eea7846e3cf55a6ecca6207bf3e1453d1e46a962e47c704c3f121cbb02760f51ff659d1a9699301fb413fc3021b4ffff5c1d00a7b57162a19c999035857bf3a4db9a179b5166dd723b3db90b27a94e60f886b23dcd1b10beacc821d87be0996601e9ea55375f31f80de63e7631046d945065e07a8821c" + }, + "decryption_aggregator": { + "proof_hex": "0x00000000000000000000000000000000000000000000000274e50c43238015ea0000000000000000000000000000000000000000000000059f17e17ea294782f00000000000000000000000000000000000000000000000c9df3c7d1c1f90d5d0000000000000000000000000000000000000000000000000002ad5a9fbf030c00000000000000000000000000000000000000000000000de213a227f53473e9000000000000000000000000000000000000000000000008fbcde03fda652920000000000000000000000000000000000000000000000000e11c34b9200625f200000000000000000000000000000000000000000000000000018d110cb7a1b20000000000000000000000000000000000000000000000009332ce29e5cb0ec9000000000000000000000000000000000000000000000000fda69bfd2f73cdde000000000000000000000000000000000000000000000001976ff26b1855e86a00000000000000000000000000000000000000000000000000001e966433079700000000000000000000000000000000000000000000000ff21e58564f35f893000000000000000000000000000000000000000000000006a888f7863669e42000000000000000000000000000000000000000000000000279b9affcf271f2bf0000000000000000000000000000000000000000000000000002ae5e7a39e6df27ac299d3526550e7124432b7b9a1228e997ac7e5b1a8bcf79f341054a22273d158994c9d7430c98cdc139b5b19dd89ab4cbcd9e51c3653866d0ee3bf6bf754b128afa7679a3ee03477bcc89d48282db791a17b21c9ac3582658b5dd91d7b87d20d0dd06b89d789a5acce06e0842930486b35e3541d0cb7e29459ebe8c29401209d4963b35dd572cb21a1ed37b60fc510868b4776b94bfe295982a342e2e5dbe0b933598e687fd3aa3964746f600177ebfe703de2c891c0717cc6de4862675791eff43b86c3e360401f844c91e23ad9fbc215e1ed26c5d2bfcce4e828fdc867910644afbbfcd0191e1d1bed4a42e4456da443e5403037855abccaf4fea7791e92274b7e5fbb06146901b5ef9bf21c7021199477d79186812151d90c441ac53ea0445085a26005f443b8bb9767ffe9f2626db8016c3e2fb2303e3e62079abe3e42cf4336e62f409088a23582b0fab5544c8e4d8218a4b24cdca589c38dbb4c4731a29051c26930a5d74a77df2f06bbfeac457fb9f8acd6cff6b7f4650de62cd6216acf35c813087c35074389c393e94ad19cffe7cdfdd45e8636a97abc6089edb08497c82d25a2840bab820f941dc403213f2f1df22ce4b47c0845a3a5dbae20114d0baad277734f5e1d172e3364f30c9300acbf66ab70ad240b92fa513765c0026dc2207778f3d6190a41bcf8c69f9dabc02933b0d679290dded9c6d603749f810fcf1c6d28da75a8cda9921c463871626aee206404c314ef3a029ad5241332a23036bd7fc56a06a444528090c82bffeedacb0cbd2f506a8d7fefe82907ee5f72da8b2311e27987ad01f51a7ffabbe54ab0e281ef2cac78d991c5bcb91a5e9b3038bd7c2b57793de7ea75b84ae06f2c022b281119517cdaf1f6a0bc5494d017b020530b0271760f59fdf0cdc7e044c0432e5947bbc91cf12d477020e590d772904450b143927ceac383338e9a271ab0935a523e8976dd77879d8326668df946425180389af7d344d31016edb5179cc0dcb71c78c0eb8076aa43704690af226f502502d0f9cd0454dccd15ce172f9fd1de22d15ef4d8ede4860cb4abf3a66a6a10b7bd16430bfd4e01885115404059a203393fa43d74a10b5d271d5860163e619075969c54590c6c4ea43253bec367db52dfb47e5fc5d1b4d32b4c6907862986e12c6096cfbeb0dc76711b435d4328b247c13cadb44834ca40b8df0195ec8933d0da081b7fa40a36384bed824503f20db2c49bd1757e3e73fbb14ea500db7e9ed18a60bdfa0eea4bb6bda048a545f6e8bee5259a030f463275265166e5254c6551d0da8977a24824f2e2ce529a72b9803a739f5fae408cac6f51741477ca989b80ec09d7a9a284c8de0ff2feaee8055a4a71fa01090d6538a7b2b596582206e660a3f8fa31d8128b30836559cb3f930d2f7f4922f4196240b994c29c1363c92510e79ef7ee1b0a675603c5eb7ec15d17d62a668bee92c6a97da7f6eed06f0a1fd26a2308abe247506731af691f19b442bc9a58b0bbdfb19d116ccc687a24ff3c427e64faca338475c96e05c8986c5eb26e080a27985920f6ddf2bfd06c53f853f28c8c04257bbaf1d07d4bf5a0abc91ea53726ce5b7d829778e642c8828e0ca6106e24be30f342518f66b1bbc2a6ebb9e0b78c2f692a74dd5e4c08becc1ea5f2b19f5018f6acd7edb9d6d5b356e09a7c8e6d4ae8110418d47d164a113ab6e9fb803f692d6ffbc33f0a03122f83e8097e9de5dab9f5432b7a067bd72b110c0447505b883e02603fe8dbfb31f90df1fde45f99f7279926c24fd909c9bfa62f776f12c99d35fff14d217ec307cc454a60746c6e51cb6e87589ac945c189fc07298b726a5838880b7bee93461a1354ec39ee6f289e880ea0c56d3e299982cc9165249298666c54bdaed4ae49242460ef710de3668a621dcb6d420f4a502092b9fd54724c7720dcad871aa2dbd838d66338807e1c900d735376566b5e3dbc5859b1de62d98b29430ceaf92d0ad7b13be18501f6b3c6ee3d5fd8ae6ca1fb5f0461e2c0b1ea500dc5abf4f9046326a758b190b3c9724be069a666ae6bf824f2cb41370990b313298d3aac27edbf9876b39b9793002f253732e579a2284c852db39f0596f17fa08ef8293aa965d7d528a306dd85cd62c0a0b3b1ff9cbaaec43c1f59467a809ce53db01c2ec74e356c0932d803eeecd2e89e3406a838d761fc1200cbc93b90de0bc4cad5a6990ece0daf51408ec05b81126abddcc78ca3cab92c21c1ca2d6214cd47029334379b17cdfaebf31278c1222859f186d6be68c937dff68bf5b7d0570f5a7c9bb806731bb1a27f9472c7ac1089c020c1259d71e060a61ff6717df044ef06408f0bee60d68beb07a958174dbc522b8f1da88f625378dc78a4331a3212a501b1cab8d65e95595df5ff009fc972b9e68331f3b52a5e6ab57840d6c98132713b7ed6899bbf362d8a3ce1de600c532ba062fe090c147b06e692a84184b0ef0562323083dab4c75f905f4b12456d5ea2a30b7e0f9f550898514011178bd0caca0dd287be62c72f3d7ceb7258527469169036e961c27dbe305b73225b15e013c616966ce84a0ef385b8992e69b4577b94867054d4f1be64b7568d12ba0ac2f6561bd6ba1b002a5741fe0c30e35a5374937263a6bc2ee81a896ae77be14712936b73225f423be009558db37314545bb54d7b72e93fe3a05009e54c334fc3f12d7332c38fe0c0c1a5ea14c23bf0c0161034a9101649551f7eeb61b1e91c6061c72004c85318979426d5115e3d23a76a8e00701cada72ca3a4e1fc09d96a0c9015acc104c51483effe786b91c7201cfa2cf81ad2f83ad6af8ff8f3367acc01418d223baae41fd64d9c5081528c209131a697ee656853836d24d5f266a6581970551531f3e5b1da57eb35e6ec73b98ce38b88cf661f25a0cb2fd3967277b29c928ca5c24004e83fdaf8810d838b2955e04613446fbea306a4beb875a463d118a2d58faf03175b910a088ed0b3f21d1c582d0ae314723f4b9d570cc14bfcbb04c16a1978d784d0f01004d401ef9f6f8cc93903cbcbbaf1d82dc25680d1839bfda230d45ab9178bbd752c543902f383180122901bc672fff31dc749cb2fd4e155421eafc60963b91198077c55af3485011d9dfb40304dbbb40cdea29d243ef045f2b80c8f661f2b9e98b732c3ac435e7fea439855d56b75ff8b3dad072f5191100064d26f2f9bade02f99ae1778c1cbd5b67b21323279a917b6485441cefa9869a008f99bc45d2fdb462320933a3b89947d22c244bddee86c2ac05ed7456e3d3972b2ac1aa7f752ff8ed765561d02d85918d260a7f9f4b6660f559a8dab724f2f212bbc55e76f73293b2febf6449ea68485dd6a3dce9af0983b159a4362bb1efcd2a8f2f95e4f13c9c570f69f61e4a66817cbd536634a16a789a2d4ce32590aa7406320ea737188269f8dc1b3321f3c52867794b38b2444250abf69a656466d0df11a1af729be8a3751830047ec0e363828777ab3cf6a606f66b962ac164c26cdb048de3f0d86cb0c520474e945c194a36aee899e5fc22b70e76d70ef55dea8db70e83015fb98872130bc9a0f88db0975a2b915c7ac77459b722820adb69a12ffb255b573884ac653b62a76a349bdbe5618f43360bedbc2dee105a4731de59681c0b4c5bc3e620d67ac078324e6a70134fc90d994065ed5685f509b03cbea0b09c2084818b486256f8c3fd3dd8dbb31e87cbe853be43db69e5196c0649d9949a181fa798f847250419c22d8354f7296d954ab5a950865357359bfc11746e5b32cd2310eaf6766323dcc76133e773f4573f7e06d3711a6c617379cccd5c5273f17a0687a86ae4843e6745e85d46197434292e99ecd6e94956f7de41adbc71b5e3260d384ac3ab70617b1a0827e050c3206f707478c27717216f269c849d03f4a0fc2d8b4f0a2d1cfd8e91b4eafd322dff635b9c141bd526244d95a791e0ef39afad25683d367dc245a3137e31f2732e9aa85e60713c10e9f1cdf2e7800d6cec807517738d67795cba0e54f3979ee0b6b355b3a9e9b1e0f023cfaf7b503214a9e4d317672a16039f93ec5505f748afd302a8d165e66a5b1b8d2be91c4c2ec4c558e82397a06d0c9ceeabfb63767108cea6a56738132873dbeb99b1ffa05203139bea1a9ea13d709036926b508a343c7f714833ec2d47235ab35885c7c79266c0e74004ebc60d754dbf647d700cda950a2f156e08dce144987cb3ea3cc276c83fea1c0559cee3b97a3cd94fb36a1f1d61bc913acf5323961f569907d01ed05f0aca4b154ee2deb7c3212b6cc19c39575fc57287f812a93bce77235df506ba9d75dc3325d9d4b7c2e1000ab4e5162fd6790d21ad791378d09f6b8d0bb9bb0f4030814c10ab4ccac4de07f03217fbb07c39e404384fb1134d577f66fb14e31655509d4617e88de6ef335a667ef2fa652515fd48b5b9891453858ee0880ffb26bedd349d2d84d40d7bdd5adf773c9ca93f2a04e49bcc49c17553762d3ccf39fcd1dfe2fc2ab7b1e25af6022ca5c4b201ced659841628d77d381075752f1337ca443d247603c594e585804fe8ca91420f60b279da2c7cc7a5bb502bf8a1b00474384e536e14fc2ccae26658c8064c573cd385313ef4da868a6736b0cc6772c1e9a49471a62ecf1f997fbc3511cbb8dfa5b83c1b75a5afd2993a4211f7000d24ce4d39da2d20c7a4d21dc13bd740130e6ac6677ebaa54a00cb9b1895f5907f918898d0f2ab2195b05cd7fce248b7b1356b0c476f4f6238705a75e1075ab34855a64a22603a2d49931302311cfe1fa1558b79dd6361098c827cf8de6ecdd455340565de95f6047c4ae833f47124a4a76f209ed426a39872b55b3d8b02aad35a6eca92bacc87046c41f8ad45f520c090d289111144b5fc3cde94f8ce93502552200b29e1756e1df3d1bbdaa407a21f20fe0c91a6c3496fc2f560474f31cdb3c0126b2ca06ae72629d92bdeaa93235a9c6dd36d5457bd217a0013424710662cd691f7a65363d32955f09763623b3448223c675a12a1f558c292842af84035acdc07e5be8caa341b293bd328232f70b4b1d9c789840db2ded2670791d63042f7402c061ab30dad136b5bc0dce353f75d97f26a372e8ee4627e94276737f0a1416d5d89ea19bf9110eb6f6ef9441016e322a2ab487b72a756356018089d2124897b6136aaeea11412bf15c214030f5c778dce5d38aa2dc924ad5ccf1deba1d1e0c51227e3feeb7615f380c14b8bde53e5b666f0c2102e2a0a54641afb8d90f2fa045b30db868ecc2a97a6738f9c70fe550bcb8470101e571d5539989700d29b2a59c52d9dc1f1700cce3124df00e287dc3d7fc1a0716ca5da98f28041e20375b62f742cc1de4a521d7932002414a663afbacf2a2d8e19b45acf35c11e679b5f6d34fd324c7e0301154b567209c7e52bb9d800df593fa1ba125b26833ba877cb66fccd6107a0f4fe177d7aaf90a56b8863321f5c3c4ecbdd2c1a0b8ce49397e6f9d1a9fc4b2ed0bb2b3b7673ad9b6339ce2e127aa83bb4994f22fcc602a7bd8ef2b00be4f62e24bb04ba69ddb98750e773625c816ea897316d2d063d6bcc9b00759e9fa279c3705a17b2ecdd3adb9af7dcf14b5a21cb1d0c8c20829f902f5cb3cba2d3aa705f6dd70a812ef571e2641d794a52aa575b1eff7ba75e0108cb1455b7e725f6299255022ef41b4b1747c43f596725dea53d6b4e431bfff009ad1af4143c20cf6370301516a8b9172b9133f3f924b146e95d960d95a837142938f647a8ee341ecf52448613b36cd7c7e2d33405c1f931e17fc7fb8622e830209d1830ea8f3ed65c544f1d0851cc7647db121b4f15bdf6e5afad37d4accfa5d67991e9dec26d059a7cbc9c22e51042e13c1bffa7364c9302c502b7680c1c098064122eeaf8d7c986fbe5e12defec49ec61e43e2708445e6393df995a94ddc666f1dd323fd45853638ff61f0f65d3b273d213e7a006098c0c495ccca8fdf649e0e6dba1840982ac6fa367620cf9b0f1e48159ab822da05d248c4d7c748e1067f3ec6340136bb7bb89f2f67223ab3d06ff27ff8a9f382d122c3b8250f2daaf97997cc6215fb62b39269e82b218e8fe4eddc867c358acfb154ca425ab6f89536b56240a69558a24a9f2fe833e2cc0cd6fdd6c7fda67861212bb2aa5b15b7f3604d20973923d92500d69b60d292916aaeda3b2409f038c7ced9f2821d0aa09df325e8ee4deb25695f107a199d52075284bbc500a1d8ff500e2a29f1af08605ed3a4dd9fcfaee896cfd508093ec0795a99961a9d8b7a7ebd429c142f1a52935aadc417a1494e16ccd582a8f606a27cd0d699b98ae46be499a9be50979783a2dfee266702d614bc17334a4fa5cf803235c71f610f627f5651c286c3548390f4864588258a4fc5bc704c9fc04e9a32f29146aba8d2871dc62a779772d5b0e60fbb9c5a3067f0e09550230774342060cdb2f0b888483c433df32452ef6f972fdbe85137cd277dd05dacef6da6ce836069f2a979d656e3123fa1f583b0e96bf24b9fcce569d04a5875ebc5ad8a757b60d1955b9b8d8f2ab846882f7264d4b958a610d286f0de3ad78f3e8ad2d36c9d617fac1844665306220f58a2e078a3a7b9beed30714b1c98ccc97835aa72fb08e04dc73f09b52f608ec9ff811866da284ffeba9b1dc991fcc9c1123bb4ec9b40b22e1203591edd3008336ede805a586af5f55e025e0c5eadd9b1e15030ce5f7b90820aa339f10bc3c697d3f87e8f2328d5a9854509969718a3677c029ff42753b0f1d11cbbb6a8301816ae0e3df81c6845e70495dd1f23514e899893f232e1ee3004212ca190421bceb21670d40f6e24d8ed22a77e08a643eac2c2697b3b4a1b024d04b2e31b4a594b25bbad952af7746c5cacd7c547869157bad1fdd6c6f136c1d96e60b2450748c8038b3f062310ef969ccd09d9b23214c0263a7f514767d51264ef41d32d69cec979817b0fe3a7392e4da263887edbaef9bb602d2a19e4f4f058c30dab386d374b1765c9d0f63fb160c333a612f6b619184df5e80173669b22e8c33565af82730493c4584f6b4253ebbb3c47eff603e26a6f2335d24a180c4044d9915eba47b365bf8009b9ab3af799ac5a92a2a7591851237b12182e052851ca4a9519b6c3ce54392dc00e31213d1987c972c8ea0dd5f191b74c912773b201e900a8fee5f7ab2a88f8f7487b31f4bc93765698d56df2c5a1f381034986add1ad05db3e4d1e4f09fed5ab1e4bbc7c4472041c9cb4972f1332117c2c44a469704ec71515df6ee7073afb4c5bfd91aa1a3c4300d3c7d1d5a760ff17f984d10eb092d51b9f8b619a50fead55d961176e2d127c4b7aafe0cd618eb5e45d2f1192714c17292f9fbcae5109189c56451ec540ae5186160d1dbb923a30ac966cf559829e60bd688a491f036b7496acef7d001c40affc1c12a4a14f91627e95f2068390f644ca843c7c7f885c85de7686b664f037ef0094cf81b7b9d369538267872ab15c1ec9138e8000ff1b5f093470408765a24606539cd61eea3ed83b3e4b409da080691ce3c77e782449f6fc0013f835fb9801f91fc0d909b4c0507cb16b885e619a6bf0facdac14fa8fabad867299b1e0e15987815017e5ed11a2e41206148422c140343c29fc377852181a168f9f8943e2ef69ff35232bdc60c4c2ce9e1008e20c21970bfb9159377cdb8fbeeadc9ae6b397ee5fea1891580598c3a6a69073e05a8c0f4f50c97ceea01f628baad9a9f2c31bbd13571121404a0c47e2ba0e6650583e8c8007859d694f25427a9b11930cc15259f9ed269287beb265de0a2cd5c0599673fdc867e1105b15cf31c7df65da2d668f560a942bbe1e79eb6a0153bee1dccc266a1a93ab21627de45588a6df7a1bd63424fc022134813c81b09d46b091337f7928a5d3c12b844166b15b29f6147d12b226d8d72ee760cdc97ddb6559d009557f41bf25615402879a1123d23c437faf99e87158a6b079235f7dbe70da82af54040a6eaca28ab4887a90a644c8156fc756cc2646924180e33ea18efd3611e50024f90653d78bba33a553272c82d4944b4eeb7e092a0fb811e0e9b57bd761bc388db2ba75b019e1e5356e208934e450437de9c66f044ebcff954dd24fe541ee32319b4cc155fc7c328d539d0d9545719a2baa3520c6f75a3ce5fb920ce0104ce1468873394faa870c861e1b36320b9acb37aa867e63343443d86e30654f92975424db8bd6bad25528cb645b8b13dc5ac11bd59b25da99b002b54d28462ac1ec32e39bd0d42cbeb5d2d886b0eedeae9fd1a2d0bf63896d7881029a6d364f0001267cbe17552d99cd0c6186f834b30a186b1b2336d9b551c487bb1b4c467031871224f6243c5661cf161bc1f30abaa1cb0815a27880b10b3008e06f92d8b6d1c527b4bd1c1d9ae9495f5d2781b9eb915beefd0f646fbbfc70dc6a1e041de74280ced9871585e00e4f4ed6c7ad09ec3cade833f314532383eb59fa436bcd7fd13c1bf25e05097498dd1d8b5c10a7c03b4ec44d950b5678ff3e36f4268873f7b243a5beb6bde7464b980ca4048b894db5be0bf4bee0dd8f035c04c39b8a364162dd0c75e01f65a4853e0dfd4cd85d9f2df213d090c025227b1a604d426da9f420e392217489b4d6961f5e059abdff7e19b2a40c5fdcc3601b08fa591e15625ec1d23b784a1383efedcc10a0f2bff1ed1412d769eb62d43d2b3591e25449b46ec0b81440c61e8469b92bcb3e63cdaf153fb51a3ae7c4899545f64b3f5b8bd0da70b9be9795714c934a47ca512b8de45fd2073b37128e0679554c8a1310ded4009230401de2d429b87afcd8bf213992707db742de262d29ad9b5d919ac8c4aac2c195b1c16e8a75e4bc087c79a18b386e8c4fe7c354b39581b5271910be90c6e6e183cbea1329f34e6d42fdbd9f87479e806eac3a2ba72d1edf68d801e1e234bfe0bc686d2da396aa64f6e45f44c0ab4a98f1f27f98fb6075f4a8dc3ec788aef5913446a33bd535b8e18b788f0d133ea6b614f51b42ff22dd904843ab590f371fa17bcb96cdf86658d2fb52fd18d7387f54476ef9af4c96fecaa2767c7b7ed8d3e1fd053b3d7562a404cac627846b565e56086a595f3e3939fc5820ef8e132d0652c1c4389334c37a151b076eaacb18cdf52546e7f6a570b8dfb051e29ec156fed0ab39db430469900dcf281b81f4b0b229dc65cc1cfb9ad673ae6e2cc52bdb9ab152cc689c163d10e151f16aa5838c4410aa49d241c69a90b7c0a70b1ecb1ad4520d750e60414326413f46f13e486712e1ec52d5615542e3cceb0e2699b89a24b2b3bea775489bb6e930446875984970366632fe619a2746a06014ce0c1f321cc158005d63bedca1d65a9b1415f8e3bc2bd4b631fd581e22568643237c3a7034029a5f8ebbc4baf602065fdd8070b7e111a6c9962b0ed6558b47f87420450980f2c7685c703e7a93cc0f9c9e1d7442f9993d7936a337b4342507e3391b3942246116c8007283d673a4f4c67504f06a21297a6eba41f92e6bc7aa2851919d5cce410ea7e206d399897438f3ce005086d367db59c1da8ff90141fdfb83aa71cae9c0e6cfd3a2ce1082dea0076f3a70e9641c23a25bed51928210379a22a20a44363259903c82576c27417f8892c7d2ad7d9e7ad32d458ae13eec4307925f5ced51200eaf15cb0a83be99cc92ce5452d4684130a463e58a9579276b84a528147f51d23dabb1213e833e9aeac5c3cd28957f090493a01f2d2a4bde724ce48a56218662507aa48f1967215d5715f760e70a0e3a5fe457d81a16edeb2d2fea71a358b9915fc9aa20c2a3079422d7eea48dd2281e606d81944419c14561c8162e2be05721576cdd4598b653cbfdb069ff3d659bc500a985de13975c0f03b751e5c959c2126976e6cb104bd8e800e18949783b4428475ecbc87a0381341ded87dc075f18a1e1c94a43acc261ea8732ecfbc98a75f093a2714c49a93cf5fb63d8e8919b2f317a543766769585be32d9847670abd090d1dca7b02362d09c5a9c3d5bd89e0f12b8f35148f2741b38debfa80d8bfc9b3b3304a7d16a0334d67c15bd9e59b63a5094b80128e75f1c72fd4869cde46c22a41f4e928bf12c07ed7f1179d6901d7eb244ec2b2d265b5c4fa37470ba64e697ce2b512843d2667a50129b34e218c1f8c1796f117c3d604e26f41df33b435c1f2b5a223deb47403f34337ccb3f103d27e18b9cc27b33ed1cff29f5196b89592d1093f112d8ff251caf6fedb445c2d83b50c0cbc652286df8f53d7b692398531189e0b9885f067a48b28f56720f064b09e2302555f53f43aba84c4fc5d295dcc9424bd891955ecf80497e8016c875f3687112bb8ffe8fa8e242fc2f70a4dd5eea97729f7124faa4dad5c3b55add76b938005d37fe85401ac0b0165e58f3c2ed63be8b24091d43fc90bfb9760b1399b321c243f86d81ac6ebd2c6e964a86f2088c9399e310c83df00be01ad1567c63f68ef1396fa0625b6e1aa243560d1a4ca16c9c7f654238255854d680d2a47e39ecf161a5f9739b80822204acd81b0aadb4f4ed5c0ec01ad655997aa94be0350edb3d02201fa3856a2adbcced5a73023b4ac48b107533020841e267bb92ccdd5a0b5442855ad43057c3e50ce71b6207b9eb5e592c3049cbd3899d8b93659152e15a3d305e36a8cf3b8d1d223d5741cb7e3e5409556dd3857dc47f762241f417604ea101cfeba0e4e49430f12fdcdab2d2bfe41e03ca0a6d0097370ac820a85d44643b23063848bff646413831b237fcdbe0dd52f46157559b095ff1e205e13771a3d2000ccb559fabd0b57a7b4250968aa3aab83ed4904066104bc0aa30f674d268ebb01fcd5980db3033877a5b8b8a347179a97ae2980a1e021f9f422b3c73186781a1be5d7b40e9994409acb7c1ecffb33695a4a2ef950adce004671ac200d9c9ec11fdf0e2045b05a11880de00a6f543ec757141569d140848ce721a47520a046720501a1d27ff21224663b017936e1b5f42740ba5b14183c8dfe9205af97bfc40c2df0aeb19d15d1e0eda8357834c30ade0a39e05cda38e4d876b6b92413996d1112e07dca6f16e69a42e18f625ae4878ab71324d6b2bcbcfe94bca2d7cc7658510b2d810c41833fb673074ed5bc02709ec24c5d1a8be0caa6dad7b8e63b8f43b11feee70fc19f71786fdb92b98cbc9acf462b020be1c6f56516eb5b1dbfe5c5232cdac53c4765f5d3fe3c2f23e56bbff3ce4362f55aa40426a62b0792d39ee5370146c50e27924119d7fe95869ea7b3cd20553d2f1b8f534cd244d6db046ae5e303d2d35f245aed74e09d173515b6c8d7b04a58a9f8d7a1fb315e65dd5a1596241bfafe81afb4643b8bdb63448380c5e560a55aca12785f1c1d8890ae344d5f970d28cf753227028b1450a848b3f4ee01c7d2152f4baf3371d0f0672d9dafddef25051770b9c248ce4ccbd7feb035765b83270bd8519dff799a253a19a70f9ff316c6c4523b65e011c281e6c01ff208e9348c3f5042e454602ff194dccb84413625b684264dfb015dabb8ac6455aedeea7db7b17d62a93e2e697233ce7b16657601095fe4ab7e752fceada9f771f780bbef5aca23b18f28930bf8f4da3b87125a23030f1ba1ef7ae5d0494e6d014737262d7cc80f7f9c5ea40a667d690ab5eb080e804e248b500cd1ec41c6195b57e6b2be6f006829df7794c251ead90935f0e00217daf4ce82e6ed33d3253f6cf91d482a466ea04efd6ebeddb929e485d29a7617335f8f4166a6d239d67845875d520b2e09b89f3d4149f0947de8dbcd9e53fc04f163b282d76662d28a2d640c32e75e14b62a8cffcd36fc70dc803d686ccbde0a0e56205b59a863fc6c3a04dc12139765da7f3372bc9b61a20304b6c5247a45149c05ed4dd039f4526fd97d260b2ce48324b4c60b76bd78fa8299ff808782e117aa90d0dd2a7d5d97c3cd4584c46aa2e6e5ed8339d4ca3c4c6ab220b222f37201f6fc11a247203fc11ef0178efd35c4b465eed3e1c1ad6ce0b722958251ec380891a83cb4fa4474328dcaa24a4a2552a96b90c0048d4fa09bf55e37460a6153271c1319c302a279ec6e72dd16f68a19a5148fd66c6ea2168041225c8d174e5e0baf33b60269a6d1eb136cf14c134b6cf48103eec542916aa381526079fe55e30b55423a11b8b409970e30304278d10daa1730951e6553df0d14d1d00b4853f12af7bde8ef3b7271630c34548575581b4472ac89942cb99d5c2ddaff1cb0e7df23ea1eb4e1a01a2565a553672bfe3e4de8ef6e8221f051386bb025f6b7d93a960b10d5bdc36ac410a77909e908ea8f82bd8fd262fc195f431707d7ea9e9917561ae93a944477cfb8d5e858062e03c0dfa36049dd6e08d83b3a6f66bc0cb3724b2d2cf2ccd32c04528207cff392cf3215001248b480cae65bac809a60bbb275f02e837fcf8601e5e49f4bf4e9904965177e3fc78cf54db4bf5fda8ebe3adeff0a0ee52bba99ffef884bf1c679efe5c1fdd82bbfa9118427379bf7319c6e403b3c15212499fe05d579c5bb29a9647cdd504affd929e472c8c0a765b263e20412221a4143e8ecd008005e4172a80864b481fe334eca6dd6345916d61c9dc53e727528a7fe6b5fe07802c8255582348f0f89acd339960485ca37a3e93db8cba2dabe0429460a225d9c7d11980630ac4428a974b457a348bf548a1bb39028ee90e1a72aaf8cc0dcf1b3640cdfad8bab296de26f2c27f2ba59c10a5ad9816c6d135f2f0e30b18b389446ff804d15028276a2c6cacff95ac841df51cae506041c2230d123ae9e84f21d4b8b035f0c767d2ee38a181a6cd888962e19561e41810d25eb530e20982647f2a6c88f8a042914379e4cad105da371a9bd7d018deda79bf0179f09c7c4c22c20c17f2894a3574df4ea32086a99ca101b942eff7d9468299c2e64216b751e6a1ad4647cf14ea164d2afe1dabac100b74f72f674842ba39f41a4e421398d5f96385b6eed0dbdd088ece9a2c4883e1cee362426a69f4f00ff868a6e1ecc411663ff9029247d94409330cfec1da09ce738137ad51659eb54f164c92c2866afcaec6a312524aa3ae724f3821d09879c8b2964f383c4aa5ddd70aa504601ec959d30178335b6c0534894143f89173f1c2178940bf93fc90b29733d51f315cd28ec05cbbf19046ff479fb94699400de6e5a0a343e750ef15defa06587d72eff79f85d1f409725f62338aa1b73b59fd1dc64d547bd554fe0e784aad3345f178209c19e7fb529cd0732c1d31ebf5062c79119f4c5774758a0d3c090404ad5187b5cf6ede0b0c3f73527cbba47e2e133a7238549a59e7fa5f9c52350f0c4b8268b61e4dc217ec409f10cf07ca353ff16e7c9ce15d7e4b4704ec4f5979fd6c1217a56d589d9d94a594722984500ebf99c141180bd471dd2e59c1b5161c894ef25016ffd3f6de21ae544e50608de70afc1f10b21e2befd09f3f729bb417975032233f30a13690359b3743382ea3a61de248a0b44b79e72d6bdfa04727bd8e2181359219df4fa79de9bb68167143b18460063d3263eb948f7f3e4ac55ba1e83e12e8e03b44059d8e7aa97f9b8122ba0f91c805420f2dd299c8f0d40c74a928154029df76d0084dad12b3d11e65527be81fc8c15b50fd3cd13da6eb84708c2a34d2ca2f10e6789c1d9102b61813357d37eb282352af21cb38accb85ceabd719b9717a606345f550071685ce5d7b4e8c49f2fc1562a05ae44786162380948ca988121e0bd48e7918d566279898e28c3456e9f48b7c6659baead75aecaaf48470aa40528c23d9448aed33bcefd7605f2bbf5daf4ef9774c6cd73aeadc18bb451a7b325c37bfe73729f527cd465eb4bd5dc46a094e6e1aceccfd2593198c3d72c66c40950bb4ff6f949f4e2e3650af16d58fbe21282016c4f786f668a4591468e7853126e1d414cd3279c13f814803c68ac17807d86d493d7400ba54bb4820982c5e1072dcad6588b1f80286e384cba4f86c63ac2c381e9a82f138c5777de89601f1e026ba4d12f3970f0c1720a1174e6d30e87f5046717293806db61ec712f2f2af9250eb7571c5c3fc64177f78cd7419798fc1e3c4cf81b5855af937340dc9d2507260707458e85049f268bde1875f7ab5b0450f7c6d2f3a2357aa2f2af75be18ca22e07db0f5cace9cd5c3bcb7d14c3c4229ec33415eda000f200cfad4481a74e000f788fa9d0b6af042338db2b28a086d75d573886f76effb2493a0c1c6079b160508fec14e24aee940397fe51796ba8715c9edef1e64b0276e2e1bf57e9da9561380ae3b40ffc99b38802d763391b64a4d6f06a437da9b37801718960de0299121b661515752c04b38002b3d050dfd5e73bf5cfe05c777fb84b7ee06b59744de1cedd3f90abb9038e67e34502199b94711d7991b84f6fe6d97415a66252da87e167df5b14c7fce6bff4fde042c64283b7fc884405dd880a9ec579d40882f20bc0bbb22bc01826b565f74898cce22f8dcf8a33ede13f9804b5d346746074b619014de9aced457d5733f04b24e3283e1aff240a5c99b1f8cee22c886fb5df77b20257b9abd7bd2f337b78294e966df5f644251e97c6484692cbce4a066876cd0ba19828ea5e5f25f0e4dd5c434d40514a86ee67443b99f5b92f48a046044e797a9120fee14c2536100e99c34111357a8561ff2275749e362a783dede851482cccf11ef8c9cd95200ef80fddf456a96c065ecd15f98a62fe1db7863607f503abe39", + "public_inputs_hex": "0x1486143d0564ca941cac449924e8ee74c1b4433cf73a6b4920737c223ba6b1e113d3a6e1ee0c2390b252eea2ac285eb7ab99ff1ac03a2a446a00a036dfa953780000000000000000000000000000000090331489384274c12b43fb7cd86990be000000000000000000000000000000007b6b44c05ca9dfc4194f954eb77bc8f51e9919742ce609e9e07b6a91eec28f3d0f8cc4fe94c8f02f425b601258f389400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000505f1a42155a63345c51416db9134e24a473534e1bc7264e47c3d8ceeb0048f3f00f750355551fc21f4f8b099e8aa9d146476b5a3eba3f12ec324353d52c1498e0f0a7cb8a62c14a214735dc01b43a24c55e0f368bb80c5740f91d32c79b109a902adda50f3a68b086921840211e0be05add7ad8933f82d689d427af65040b9e42190c3823163063956218b83ca1ae19999d5986c495dcf68fabffe666e78796909ec0921581b707660aae7e03a29bcf5f6310e32bb1ef1bfa21f265e211ea0dc1d34da8f75be8d59fdd8e87f406948e636b3c1dce3485b81e8db02be6a2b619d04097be17ce35a4c426eea7846e3cf55a6ecca6207bf3e1453d1e46a962e47c704c3f121cbb02760f51ff659d1a9699301fb413fc3021b4ffff5c1d00a7b57162a19c999035857bf3a4db9a179b5166dd723b3db90b27a94e60f886b23dcd1b1000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + } +} diff --git a/circuits/benchmarks/results_secure_micro/report.md b/circuits/benchmarks/results_secure_micro/report.md new file mode 100644 index 0000000000..0e28bbe383 --- /dev/null +++ b/circuits/benchmarks/results_secure_micro/report.md @@ -0,0 +1,216 @@ +# Interfold ZK Circuit Benchmarks + +**Generated:** 2026-06-13 18:13:56 UTC + +**Git Branch:** `lock/fhers` +**Git Commit:** `c2dfd37d36d9e85b45b03db03bc7267964a4a6da` + +**Committee Size:** `H=5`, `N=9`, `T=4` + +## Run configuration + +Settings for this benchmark run (integration test + Nargo circuit benches on the same host). + +### Integration test (`test_trbfv_actor`) + +| Setting | Value | +| ----------------------------------------------------- | -------------------------------------------- | +| Benchmark mode | `secure` | +| BFV preset (artifacts) | `secure-8192` | +| BFV preset (enum) | `SecureThreshold8192` | +| λ (smudging / error) | 50 | +| Nodes spawned (builder) | 16 | +| Network model | `in_process_bus` | +| Testmode harness | true | +| `proof_aggregation_enabled` | true | +| `BENCHMARK_MULTITHREAD_JOBS` (max concurrent ZK jobs) | 13 | +| Rayon worker threads | 13 | +| CPU cores (host) | 14 | +| `dkg_fold_attestation_verifier` (EIP-712) | `0x7969c5eD335650692Bc04293B07F5BF2e7A673C0` | +| Verbose logging (`run_benchmarks.sh --verbose`) | true | + +### Hardware & software (Nargo / Barretenberg host) + +| | | +| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **CPU** | Apple M4 Pro | +| **CPU cores** | 14 | +| **RAM** | 48.00 GB | +| **OS** | Darwin | +| **Architecture** | arm64 | +| **Nargo** | nargo version = 1.0.0-beta.16 noirc version = 1.0.0-beta.16+2d46fca7203545cbbfb31a0d0328de6c10a8db95 (git version hash: 2d46fca7203545cbbfb31a0d0328de6c10a8db95, is dirty: false) | +| **Barretenberg** | 3.0.0-nightly.20260102 | + +--- + +## Audit status + +On-chain verify gas: **complete** (CRISP Π_user + Interfold Π_DKG / Π_dec replay). + +--- + +## Measurement methodology + +| Metric kind | Source | Meaning | Do **not** use for | +| -------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | +| **wall_clock** | `test_trbfv_actor` phase timers / HLC event span | End-to-end wait in the in-process test harness | Production WAN latency; per-node deployment cost | +| **isolated_nargo** | `benchmark_circuit.sh` per circuit | Single `bb prove` on oracle witness, one circuit at a time | Full protocol pipeline (different witness path) | +| **tracked_job_wall** | `MultithreadReport` per `ComputeRequest` | Wall time of each job on the shared Rayon pool (≤ `BENCHMARK_MULTITHREAD_JOBS` concurrent) | End-to-end time — **sums exceed wall clock** when jobs overlap | + +**Harness limits (integration):** all ciphernodes share one process and bus +(`network_model: in_process_bus`); sortition registers extra nodes; `testmode_*` enabled; proof +aggregation always enabled. Compare runs only with the same `benchmark_mode`, committee, +`BENCHMARK_MULTITHREAD_JOBS`, commit, and hardware. + +--- + +## Protocol Summary + +### Circuit Benchmarks (isolated Nargo + Barretenberg) + +Single-circuit `bb prove` on the benchmark oracle witness (not the integration actor pipeline). + +| Circuit | Constraints | Prove (s) | Verify (ms) | Proof (KB) | +| -------------------- | ----------- | --------- | ----------- | ---------- | +| C0 | 287764 | 1.52 | 27.60 | 15.88 | +| C1 | 2223151 | 8.98 | 25.48 | 15.88 | +| C2a | 4283826 | 17.92 | 24.61 | 15.88 | +| C2b | 5726479 | 19.99 | 25.41 | 15.88 | +| C3a | 3475239 | 10.76 | 25.48 | 15.88 | +| C3b | 3475239 | 10.76 | 25.48 | 15.88 | +| C4a | 2418310 | 9.17 | 24.96 | 15.88 | +| C4b | 2418310 | 9.17 | 24.96 | 15.88 | +| C5 | 1426408 | 5.27 | 25.33 | 15.88 | +| user_data_encryption | 1688676 | 5.71 | 24.50 | 15.88 | +| C6 | 2977263 | 10.14 | 25.04 | 15.88 | +| C7 | 191104 | 0.81 | 25.24 | 15.88 | + +### Artifacts + +| Artifact | Proof size | Public input size | Verify gas | Calldata gas | Total gas | +| -------- | ---------- | ----------------- | ---------- | ------------ | --------- | +| Π_DKG | 10.69 KB | 0.66 KB | 3136910 | 178440 | 3315350 | +| Π_user | 15.88 KB | 0.12 KB | 2972965 | 193180 | 3166145 | +| Π_dec | 10.69 KB | 3.75 KB | 3658366 | 190656 | 3849022 | + +### Role / Phase / Activity + +| Role | Phase | Activity | Metric | Duration | Proof size | Bandwidth | +| --------------- | ----- | ----------------------------------------- | -------------- | --------- | ---------- | --------- | +| Each ciphernode | P1 | one-time DKG participation (test harness) | wall_clock | 5251.56 s | 127.00 KB | 130.06 KB | +| Aggregator | P2 | C5 + Π_DKG fold (aggregator span) | wall_clock | 406.98 s | 10.69 KB | 11.34 KB | +| User | P3 | per user input | isolated_nargo | 10.96 s | 15.88 KB | 16.00 KB | +| Each ciphernode | P4 | per computation output (C6) | isolated_nargo | 10.14 s | 15.88 KB | 16.00 KB | +| Aggregator | P4 | C7 + Π_dec fold (full publish→aggregate) | wall_clock | 334.20 s | 10.69 KB | 14.44 KB | +| Aggregator | P4 | C7 + fold only (pending→plaintext span) | wall_clock | 103.52 s | 10.69 KB | 14.44 KB | + +_P2 **tracked_job_wall** sum (ZkDkgAggregation + ZkPkAggregation, parallelizable): **36.62 s** — not +comparable to P2 wall_clock row above._ + +## Integration test (`test_trbfv_actor`) + +### End-to-end phase timings (integration test) + +| Phase | Metric | Duration (s) | +| ------------------------------------------------------------------ | ------------ | ------------ | +| Starting trbfv actor test | `wall_clock` | 0.00 | +| Setup completed | `wall_clock` | 2.39 | +| Committee Setup Completed | `wall_clock` | 16.09 | +| Committee Finalization Complete | `wall_clock` | 0.00 | +| Aggregator P2: PkAggregation pending -> PublicKeyAggregated (wall) | `wall_clock` | 406.98 | +| ThresholdShares -> PublicKeyAggregated | `wall_clock` | 5251.56 | +| E3Request -> PublicKeyAggregated | `wall_clock` | 5252.07 | +| Application CT Gen | `wall_clock` | 0.29 | +| Running FHE Application | `wall_clock` | 0.00 | +| Aggregator P4: Aggregation pending -> PlaintextAggregated (wall) | `wall_clock` | 103.52 | +| Ciphertext published -> PlaintextAggregated | `wall_clock` | 334.20 | +| Entire Test | `wall_clock` | 5605.04 | + +### Multithread job timings (`tracked_job_wall`) + +| Name | Avg (s) | Runs | Total (s) | +| ----------------------------- | ------- | ---- | --------- | +| CalculateDecryptionKey | 0.04 | 9 | 0.38 | +| CalculateDecryptionShare | 0.17 | 9 | 1.53 | +| CalculateThresholdDecryption | 0.19 | 1 | 0.19 | +| GenEsiSss | 0.52 | 9 | 4.66 | +| GenPkShareAndSkSss | 0.52 | 9 | 4.68 | +| NodeDkgFold/c2ab_fold | 28.56 | 9 | 257.08 | +| NodeDkgFold/c3a_fold | 665.75 | 9 | 5991.76 | +| NodeDkgFold/c3ab_fold | 13.68 | 9 | 123.10 | +| NodeDkgFold/c3b_fold | 627.04 | 9 | 5643.40 | +| NodeDkgFold/c4ab_fold | 12.85 | 9 | 115.66 | +| NodeDkgFold/node_fold | 29.34 | 9 | 264.02 | +| ZkDecryptedSharesAggregation | 5.93 | 1 | 5.93 | +| ZkDecryptionAggregation | 97.37 | 1 | 97.37 | +| ZkDkgAggregation | 5.38 | 1 | 5.38 | +| ZkDkgShareDecryption | 76.34 | 18 | 1374.12 | +| ZkNodeDkgFold | 904.33 | 9 | 8138.93 | +| ZkNodesFoldStep | 4.39 | 5 | 21.96 | +| ZkPkAggregation | 31.24 | 1 | 31.24 | +| ZkPkBfv | 9.47 | 9 | 85.24 | +| ZkPkGeneration | 87.11 | 9 | 784.00 | +| ZkShareComputation | 94.84 | 18 | 1707.15 | +| ZkShareEncryption | 105.64 | 432 | 45636.03 | +| ZkThresholdShareDecryption | 187.47 | 9 | 1687.22 | +| ZkVerifyShareDecryptionProofs | 0.58 | 9 | 5.25 | +| ZkVerifyShareProofs | 2.36 | 11 | 25.99 | + +Sum of tracked job wall time: **72012.25 s** — **not** end-to-end latency (jobs run in parallel up +to `BENCHMARK_MULTITHREAD_JOBS`). + +### NodeDkgFold sub-steps (`tracked_job_wall`, per fold prove) + +| Step | Avg (s) | Runs | Total (s) | +| --------- | ------- | ---- | --------- | +| c2ab_fold | 28.56 | 9 | 257.08 | +| c3a_fold | 665.75 | 9 | 5991.76 | +| c3ab_fold | 13.68 | 9 | 123.10 | +| c3b_fold | 627.04 | 9 | 5643.40 | +| c4ab_fold | 12.85 | 9 | 115.66 | +| node_fold | 29.34 | 9 | 264.02 | + +### Aggregation jobs (`tracked_job_wall`) + +| Operation | Avg (s) | Runs | Total (s) | +| ---------------------------- | ------- | ---- | --------- | +| ZkDecryptedSharesAggregation | 5.93 | 1 | 5.93 | +| ZkDecryptionAggregation | 97.37 | 1 | 97.37 | +| ZkDkgAggregation | 5.38 | 1 | 5.38 | +| ZkNodeDkgFold | 904.33 | 9 | 8138.93 | +| ZkPkAggregation | 31.24 | 1 | 31.24 | + +Sum of aggregation job tracked time: **8278.85 s** (parallel CPU work; not P1/P2 wall clock). + +### Folded on-chain artifacts (exported for Π_DKG / Π_dec gas) + +| Artifact | Proof (bytes) | Public inputs (bytes) | +| --------------------- | ------------- | --------------------- | +| dkg_aggregator | 10944 | 672 | +| decryption_aggregator | 10944 | 3840 | + +## Raw circuit benchmark JSON (Nargo) + +Source files for the **Circuit Benchmarks** table. Persist this directory with +`crisp_verify_gas.json` (and optional `integration_summary.json`) to regenerate the report without +re-running the integration test. + +| File | +| ----------------------------------------------------- | +| `config_default.json` | +| `dkg_e_sm_share_computation_default.json` | +| `dkg_pk_default.json` | +| `dkg_share_decryption_default.json` | +| `dkg_share_encryption_default.json` | +| `dkg_sk_share_computation_default.json` | +| `threshold_decrypted_shares_aggregation_default.json` | +| `threshold_pk_aggregation_default.json` | +| `threshold_pk_generation_default.json` | +| `threshold_share_decryption_default.json` | +| `threshold_user_data_encryption_ct0_default.json` | +| `threshold_user_data_encryption_ct1_default.json` | + +## Notes + +- All nodes are executed on the same machine in this benchmark run, so inter-node network latency is + effectively 0. diff --git a/circuits/lib/src/configs/insecure/threshold.nr b/circuits/lib/src/configs/insecure/threshold.nr index c6741c0e36..8163f6e1a9 100644 --- a/circuits/lib/src/configs/insecure/threshold.nr +++ b/circuits/lib/src/configs/insecure/threshold.nr @@ -1067,7 +1067,7 @@ pub global PK_GENERATION_BIT_PK: u32 = 35; pub global PK_GENERATION_EEK_BOUND: Field = 20; pub global PK_GENERATION_SK_BOUND: Field = 1; -pub global PK_GENERATION_E_SM_BOUND: Field = 206867200; +pub global PK_GENERATION_E_SM_BOUND: Field = 252051456; pub global PK_GENERATION_R1_BOUNDS: [Field; L] = [256, 256]; pub global PK_GENERATION_R2_BOUNDS: [Field; L] = [34359701504, 34359615488]; diff --git a/circuits/lib/src/configs/secure/threshold.nr b/circuits/lib/src/configs/secure/threshold.nr index 544d9b3def..93cece8081 100644 --- a/circuits/lib/src/configs/secure/threshold.nr +++ b/circuits/lib/src/configs/secure/threshold.nr @@ -24627,7 +24627,7 @@ pub global PK_GENERATION_BIT_PK: u32 = 57; pub global PK_GENERATION_EEK_BOUND: Field = 20; pub global PK_GENERATION_SK_BOUND: Field = 1; -pub global PK_GENERATION_E_SM_BOUND: Field = 8307674973655728550649657019768766464000000; +pub global PK_GENERATION_E_SM_BOUND: Field = 8307674973655731686596149550392541184000000; pub global PK_GENERATION_R1_BOUNDS: [Field; L] = [4096, 4096, 4096]; pub global PK_GENERATION_R2_BOUNDS: [Field; L] = [72057594049265664, 72057594048610304, 72057594047397888]; diff --git a/crates/aggregator/src/actors/threshold_plaintext_aggregator.rs b/crates/aggregator/src/actors/threshold_plaintext_aggregator.rs index 497d7b6628..4b90e906c0 100644 --- a/crates/aggregator/src/actors/threshold_plaintext_aggregator.rs +++ b/crates/aggregator/src/actors/threshold_plaintext_aggregator.rs @@ -18,8 +18,8 @@ use anyhow::{anyhow, bail, ensure, Result}; use e3_data::Persistable; use e3_events::{ prelude::*, trap, AggregationProofPending, AggregationProofSigned, BusHandle, - CommitteeMemberExpelled, ComputeRequest, ComputeRequestError, ComputeResponse, - ComputeResponseKind, CorrelationId, DecryptedSharesAggregationProofRequest, + CommitteeMemberExpelled, ComputeRequest, ComputeRequestError, ComputeRequestErrorKind, + ComputeResponse, ComputeResponseKind, CorrelationId, DecryptedSharesAggregationProofRequest, DecryptionAggregationRequest, DecryptionshareCreated, Die, E3Failed, E3Stage, E3id, EType, EventContext, FailureReason, InterfoldEvent, InterfoldEventData, PlaintextAggregated, Proof, Sequenced, ShareVerificationComplete, ShareVerificationDispatched, SignedProofPayload, @@ -642,6 +642,27 @@ impl ThresholdPlaintextAggregator { return Ok(()); } + // Surface the structured threshold-BFV failure when present so the implicated party and + // failure mode are visible in logs. Slashing/accusation stays driven by the C6 proof + // verification path; this is diagnostics only. + if let ComputeRequestErrorKind::TrBFV(trbfv_err) = msg.get_err() { + let failure = trbfv_err.failure(); + match &failure.threshold { + Some(threshold) => warn!( + e3_id = %self.e3_id, + kind = ?threshold.kind, + party_id = ?threshold.party_id, + "threshold decryption failed with structured error: {}", + threshold.message, + ), + None => warn!( + e3_id = %self.e3_id, + "threshold decryption failed: {}", + failure.message, + ), + } + } + self.fail_decryption_round(ec) } @@ -1173,7 +1194,7 @@ mod tests { aggregator.handle_compute_request_error(TypedEvent::new( ComputeRequestError::new( ComputeRequestErrorKind::TrBFV(e3_trbfv::TrBFVError::CalculateThresholdDecryption( - "boom".to_string(), + "boom".into(), )), request, ), diff --git a/crates/evm/src/domain/interfold_events.rs b/crates/evm/src/domain/interfold_events.rs index 898f69f003..ee74b6f823 100644 --- a/crates/evm/src/domain/interfold_events.rs +++ b/crates/evm/src/domain/interfold_events.rs @@ -54,14 +54,18 @@ impl E3RequestedWithChainId { let params_arc = BfvParamSet::from(params_preset).build_arc(); let params_bytes = encode_bfv_params(¶ms_arc); - let lambda = params_preset.metadata().lambda; + // Lambda is secure or insecure depending on the preset's security tier. + let lambda = params_preset + .lambda() + .map_err(|e| anyhow::anyhow!("Failed to build lambda for preset: {}", e))?; + let lambda_value = lambda.value(); let error_size = match calculate_error_size(params_arc, threshold_n, threshold_m, lambda) { Ok(size) => { let size_bytes = size.to_bytes_be(); info!( "Calculated error_size for E3 (threshold_n={}, threshold_m={}, lambda={}): {} bytes", - threshold_n, threshold_m, lambda, size_bytes.len() + threshold_n, threshold_m, lambda_value, size_bytes.len() ); ArcBytes::from_bytes(&size_bytes) } diff --git a/crates/fhe-params/src/lib.rs b/crates/fhe-params/src/lib.rs index 7d06629ebf..bd6bdf2d76 100644 --- a/crates/fhe-params/src/lib.rs +++ b/crates/fhe-params/src/lib.rs @@ -22,6 +22,6 @@ pub use crp::{create_deterministic_crp_from_default_seed, create_deterministic_c #[cfg(feature = "abi-encoding")] pub use encoding::{decode_bfv_params, decode_bfv_params_arc, encode_bfv_params, EncodingError}; pub use presets::{ - default_param_set, BfvParamSet, BfvPreset, ParameterType, PresetError, PresetMetadata, - PresetSearchDefaults, SecurityTier, DEFAULT_BFV_PRESET, + default_param_set, BfvParamSet, BfvPreset, LambdaConfig, ParameterType, PresetError, + PresetMetadata, PresetSearchDefaults, SecurityTier, DEFAULT_BFV_PRESET, }; diff --git a/crates/fhe-params/src/presets.rs b/crates/fhe-params/src/presets.rs index 4a533c43dc..105dbd1361 100644 --- a/crates/fhe-params/src/presets.rs +++ b/crates/fhe-params/src/presets.rs @@ -22,6 +22,7 @@ use std::sync::Arc; use thiserror::Error as ThisError; use fhe::bfv::BfvParameters; +use fhe::trbfv::Lambda; /// BFV preset configurations for PVSS (Public Verifiable Secret Sharing) /// @@ -208,6 +209,36 @@ pub enum PresetError { UnknownPreset(String), #[error("Preset does not define a Threshold (trBFV) / DKG (BFV) pair: {0}")] MissingPair(&'static str), + #[error("Preset lambda is not secure: {0}")] + InsecureLambda(String), +} + +/// Serializable smudging security level, mirroring [`fhe::trbfv::Lambda`]. +/// +/// `Lambda` is a foreign type without `serde` support, so requests that travel over the +/// wire (e.g. `GenPkShareAndSkSssRequest`) carry this instead and reconstruct the real +/// [`Lambda`] at the point of use via [`LambdaConfig::into_lambda`]. The `Secure`/`Insecure` +/// distinction is preserved across (de)serialization so the security tier chosen upstream +/// from a [`BfvPreset`] is faithfully enforced downstream. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum LambdaConfig { + /// Production security level; validated against `MIN_SECURE_LAMBDA` by [`Lambda::secure`]. + Secure(usize), + /// Deliberately weak level for fast testing; see [`Lambda::insecure`]. + Insecure(usize), +} + +impl LambdaConfig { + /// Reconstruct the real [`Lambda`]. `Secure` variants fail if the value is below the + /// library's secure minimum. + pub fn into_lambda(self) -> Result { + match self { + LambdaConfig::Secure(lambda) => { + Lambda::secure(lambda).map_err(|e| PresetError::InsecureLambda(e.to_string())) + } + LambdaConfig::Insecure(lambda) => Ok(Lambda::insecure(lambda)), + } + } } /// A complete BFV parameter set definition @@ -386,6 +417,28 @@ impl BfvPreset { self.metadata().security } + /// Returns the serializable smudging security level for this preset. + /// + /// Maps the preset's [`SecurityTier`] onto [`LambdaConfig`]: secure presets become + /// `LambdaConfig::Secure`, insecure presets `LambdaConfig::Insecure`. Use this when the + /// level must cross a serialization boundary; otherwise prefer [`BfvPreset::lambda`]. + pub fn lambda_config(&self) -> LambdaConfig { + let meta = self.metadata(); + match meta.security { + SecurityTier::SECURE => LambdaConfig::Secure(meta.lambda), + SecurityTier::INSECURE => LambdaConfig::Insecure(meta.lambda), + } + } + + /// Builds the smudging security level ([`Lambda`]) for this preset. + /// + /// Secure presets validate that lambda meets the library's secure minimum; insecure + /// presets opt into a deliberately weak lambda for fast testing. Mirrors the preset's + /// [`SecurityTier`]. + pub fn lambda(&self) -> Result { + self.lambda_config().into_lambda() + } + /// Returns the base directory name for circuit artifacts (e.g. `"insecure-512"`, `"secure-8192"`). /// Threshold and DKG presets at the same degree share the same compiled circuits. pub fn artifacts_dir(&self) -> String { diff --git a/crates/fhe-params/src/search/bfv.rs b/crates/fhe-params/src/search/bfv.rs index 8cc66be28e..ecf54c6817 100644 --- a/crates/fhe-params/src/search/bfv.rs +++ b/crates/fhe-params/src/search/bfv.rs @@ -194,15 +194,12 @@ pub fn finalize_bfv_candidate( * BigUint::from(bfv_search_config.b_chi)) * &two_pow_lambda; - // B_fresh ≤ B_Enc + d B B_chi+ d B B_chi n - let term_d_b_chi = BigUint::from(d) - * BigUint::from(bfv_search_config.b) - * BigUint::from(bfv_search_config.b_chi); + // B_fresh ≤ B_Enc + d B B_chi n+ d B B_chi n let term_d_b_b_chi_n = BigUint::from(d) * BigUint::from(bfv_search_config.b) * BigUint::from(bfv_search_config.b_chi) * BigUint::from(bfv_search_config.n); - let b_fresh = &benc_min + &term_d_b_chi + &term_d_b_b_chi_n; + let b_fresh = &benc_min + &term_d_b_b_chi_n + &term_d_b_b_chi_n; // B_C = z (B_fresh + r_k(q)) let b_c = BigUint::from(bfv_search_config.z) * (&b_fresh + BigUint::from(rkq)); diff --git a/crates/keyshare/src/actors/threshold_keyshare.rs b/crates/keyshare/src/actors/threshold_keyshare.rs index 9df9b0fd3d..757b938930 100644 --- a/crates/keyshare/src/actors/threshold_keyshare.rs +++ b/crates/keyshare/src/actors/threshold_keyshare.rs @@ -696,7 +696,7 @@ impl ThresholdKeyshare { TrBFVRequest::GenPkShareAndSkSss(GenPkShareAndSkSssRequest { trbfv_config, crp, - lambda: defaults.lambda as usize, + lambda: threshold_preset.lambda_config(), num_ciphertexts: defaults.z as usize, }), CorrelationId::new(), diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 8db3d8cf1c..1adafa3e61 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -54,7 +54,7 @@ use e3_trbfv::helpers::try_poly_from_sensitive_bytes; use e3_trbfv::helpers::try_poly_ntt_from_bytes; use e3_trbfv::helpers::try_poly_pb_from_bytes; use e3_trbfv::shares::SharedSecret; -use e3_trbfv::{TrBFVError, TrBFVRequest, TrBFVResponse}; +use e3_trbfv::{TrBFVError, TrBFVFailure, TrBFVRequest, TrBFVResponse}; use e3_utils::SharedRng; use e3_utils::MAILBOX_LIMIT; use e3_zk_helpers::circuits::dkg::pk::circuit::{PkCircuit, PkCircuitData}; @@ -270,16 +270,18 @@ async fn handle_compute_request_event( ComputeRequestKind::TrBFV(ref trbfv_req) => { let msg = format!("Pool error: {pool_err}"); ComputeRequestErrorKind::TrBFV(match trbfv_req { - TrBFVRequest::GenPkShareAndSkSss(_) => TrBFVError::GenPkShareAndSkSss(msg), - TrBFVRequest::GenEsiSss(_) => TrBFVError::GenEsiSss(msg), + TrBFVRequest::GenPkShareAndSkSss(_) => { + TrBFVError::GenPkShareAndSkSss(msg.into()) + } + TrBFVRequest::GenEsiSss(_) => TrBFVError::GenEsiSss(msg.into()), TrBFVRequest::CalculateDecryptionKey(_) => { - TrBFVError::CalculateDecryptionKey(msg) + TrBFVError::CalculateDecryptionKey(msg.into()) } TrBFVRequest::CalculateDecryptionShare(_) => { - TrBFVError::CalculateDecryptionShare(msg) + TrBFVError::CalculateDecryptionShare(msg.into()) } TrBFVRequest::CalculateThresholdDecryption(_) => { - TrBFVError::CalculateThresholdDecryption(msg) + TrBFVError::CalculateThresholdDecryption(msg.into()) } }) } @@ -558,7 +560,9 @@ fn handle_trbfv_request( request.e3_id, )), Err(e) => Err(ComputeRequestError::new( - ComputeRequestErrorKind::TrBFV(TrBFVError::GenPkShareAndSkSss(e.to_string())), + ComputeRequestErrorKind::TrBFV(TrBFVError::GenPkShareAndSkSss( + TrBFVFailure::from_error(&e), + )), request, )), } @@ -572,7 +576,9 @@ fn handle_trbfv_request( request.e3_id, )), Err(e) => Err(ComputeRequestError::new( - ComputeRequestErrorKind::TrBFV(TrBFVError::GenEsiSss(e.to_string())), + ComputeRequestErrorKind::TrBFV(TrBFVError::GenEsiSss( + TrBFVFailure::from_error(&e), + )), request, )), } @@ -590,7 +596,7 @@ fn handle_trbfv_request( error!("Error calculating decryption key: {}", e); Err(ComputeRequestError::new( ComputeRequestErrorKind::TrBFV(TrBFVError::CalculateDecryptionKey( - e.to_string(), + TrBFVFailure::from_error(&e), )), request, )) @@ -608,7 +614,7 @@ fn handle_trbfv_request( )), Err(e) => Err(ComputeRequestError::new( ComputeRequestErrorKind::TrBFV(TrBFVError::CalculateDecryptionShare( - e.to_string(), + TrBFVFailure::from_error(&e), )), request, )), @@ -625,7 +631,7 @@ fn handle_trbfv_request( )), Err(e) => Err(ComputeRequestError::new( ComputeRequestErrorKind::TrBFV(TrBFVError::CalculateThresholdDecryption( - e.to_string(), + TrBFVFailure::from_error(&e), )), request, )), diff --git a/crates/support/Cargo.lock b/crates/support/Cargo.lock index c9f2d987df..3cfe739ccb 100644 --- a/crates/support/Cargo.lock +++ b/crates/support/Cargo.lock @@ -221,6 +221,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -295,12 +304,12 @@ dependencies = [ "either", "k256", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "secp256k1", "serde", "serde_json", "serde_with", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -336,7 +345,7 @@ dependencies = [ "futures", "futures-util", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -377,7 +386,7 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -400,7 +409,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -422,7 +431,7 @@ dependencies = [ "serde", "serde_with", "sha2", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -474,7 +483,7 @@ dependencies = [ "http 1.4.0", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -501,7 +510,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -530,10 +539,10 @@ dependencies = [ "alloy-signer", "alloy-signer-local", "k256", - "rand 0.8.5", + "rand 0.8.6", "serde_json", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "url", ] @@ -599,7 +608,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -704,7 +713,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -730,7 +739,7 @@ dependencies = [ "either", "elliptic-curve 0.13.8", "k256", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -745,8 +754,8 @@ dependencies = [ "alloy-signer", "async-trait", "k256", - "rand 0.8.5", - "thiserror 2.0.17", + "rand 0.8.6", + "thiserror 2.0.18", ] [[package]] @@ -838,7 +847,7 @@ dependencies = [ "parking_lot", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tower", "tracing", @@ -934,7 +943,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -945,7 +954,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1322,7 +1331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1332,7 +1341,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1342,7 +1351,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -2132,7 +2141,7 @@ dependencies = [ "maybe-async", "reqwest", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2192,7 +2201,7 @@ dependencies = [ "sha2", "siwe", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tokio", "tokio-tungstenite", @@ -2237,9 +2246,9 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] @@ -2333,7 +2342,7 @@ dependencies = [ "semver 1.0.27", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2376,9 +2385,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -2386,9 +2395,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -2398,9 +2407,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck", "proc-macro2", @@ -2429,7 +2438,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2878,7 +2887,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2950,13 +2959,30 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "e3-bfv-client" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" +dependencies = [ + "anyhow", + "e3-fhe-params", + "e3-polynomial", + "e3-zk-helpers", + "fhe", + "fhe-traits", + "rand 0.9.2", + "thiserror 1.0.69", +] + [[package]] name = "e3-compute-provider" -version = "0.1.5" -source = "git+https://github.com/gnosisguild/enclave?rev=632766e4ed1ceeccdeb023a56f16413a33be6f46#632766e4ed1ceeccdeb023a56f16413a33be6f46" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" dependencies = [ "ark-bn254 0.4.0", "ark-ff 0.4.2", + "e3-bfv-client", + "e3-fhe-params", "hex", "lean-imt", "light-poseidon", @@ -2970,16 +2996,57 @@ dependencies = [ [[package]] name = "e3-fhe-params" -version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=dbdaaee1db9d868adacc03f75bb2ac85024359ae#dbdaaee1db9d868adacc03f75bb2ac85024359ae" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" dependencies = [ "alloy-dyn-abi", "alloy-primitives", + "anyhow", + "clap", "fhe", "num-bigint", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "serde", "thiserror 1.0.69", ] +[[package]] +name = "e3-parity-matrix" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" +dependencies = [ + "num-bigint", + "num-traits", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "e3-polynomial" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" +dependencies = [ + "fhe-math", + "num-bigint", + "num-traits", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "e3-safe" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "hex", + "sha3", + "taceo-poseidon2", +] + [[package]] name = "e3-support-app" version = "0.1.0" @@ -3017,7 +3084,7 @@ dependencies = [ "fhe-util", "log", "methods", - "rand 0.8.5", + "rand 0.9.2", "risc0-ethereum-contracts 3.0.0", "risc0-zkvm", "serde", @@ -3047,6 +3114,36 @@ dependencies = [ "fhe-traits", ] +[[package]] +name = "e3-zk-helpers" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" +dependencies = [ + "anyhow", + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "clap", + "e3-fhe-params", + "e3-parity-matrix", + "e3-polynomial", + "e3-safe", + "fhe", + "fhe-math", + "fhe-traits", + "hex", + "itertools 0.14.0", + "ndarray", + "num-bigint", + "num-integer", + "num-traits", + "rand 0.9.2", + "rayon", + "serde", + "serde_json", + "thiserror 1.0.69", + "toml", +] + [[package]] name = "ecdsa" version = "0.14.8" @@ -3214,6 +3311,26 @@ dependencies = [ "log", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -3227,7 +3344,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3313,69 +3430,73 @@ dependencies = [ [[package]] name = "fhe" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#7692b954ce887ee78f0b6baae36447ca1aa74708" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs#c9c052e11dc5d0b6864767c4e867c9fd779253a7" dependencies = [ + "bincode", "doc-comment", "fhe-math", "fhe-traits", "fhe-util", - "itertools 0.12.1", + "itertools 0.14.0", "ndarray", "num-bigint", "num-traits", - "prost 0.12.6", + "prost 0.14.4", "prost-build", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.2", + "rand_chacha 0.9.0", "rand_distr", "rayon", "serde", - "thiserror 1.0.69", + "thiserror 2.0.18", "zeroize", "zeroize_derive", ] [[package]] name = "fhe-math" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#7692b954ce887ee78f0b6baae36447ca1aa74708" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs#c9c052e11dc5d0b6864767c4e867c9fd779253a7" dependencies = [ "ethnum", "fhe-traits", "fhe-util", - "itertools 0.12.1", + "itertools 0.14.0", "ndarray", "num-bigint", - "num-bigint-dig", + "num-bigint-dig 0.9.1", "num-traits", - "prost 0.12.6", + "prost 0.14.4", "prost-build", - "rand 0.8.5", - "rand_chacha 0.3.1", + "pulp", + "rand 0.9.2", + "rand_chacha 0.9.0", + "serde", "sha2", - "thiserror 1.0.69", + "tfhe-ntt", + "thiserror 2.0.18", "zeroize", ] [[package]] name = "fhe-traits" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#7692b954ce887ee78f0b6baae36447ca1aa74708" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs#c9c052e11dc5d0b6864767c4e867c9fd779253a7" dependencies = [ - "rand 0.8.5", + "rand 0.9.2", ] [[package]] name = "fhe-util" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#7692b954ce887ee78f0b6baae36447ca1aa74708" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs#c9c052e11dc5d0b6864767c4e867c9fd779253a7" dependencies = [ - "itertools 0.12.1", - "num-bigint-dig", + "num-bigint-dig 0.9.1", "num-traits", "prime_factorization", - "rand 0.8.5", + "rand 0.8.6", + "rand 0.9.2", "rand_distr", "rayon", ] @@ -3393,7 +3514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand 0.8.6", "rustc-hex", "static_assertions", ] @@ -3404,6 +3525,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.5" @@ -4264,15 +4391,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -4394,7 +4512,7 @@ dependencies = [ "ena", "itertools 0.11.0", "lalrpop-util", - "petgraph", + "petgraph 0.6.5", "pico-args", "regex", "regex-syntax 0.8.8", @@ -4741,15 +4859,18 @@ dependencies = [ [[package]] name = "ndarray" -version = "0.15.6" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" dependencies = [ "matrixmultiply", "num-complex", "num-integer", "num-traits", + "portable-atomic", + "portable-atomic-util", "rawpointer", + "serde", ] [[package]] @@ -4796,7 +4917,8 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", - "rand 0.8.5", + "rand 0.8.6", + "serde", ] [[package]] @@ -4810,18 +4932,34 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", - "serde", + "rand 0.8.6", "smallvec", "zeroize", ] +[[package]] +name = "num-bigint-dig" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f9a86e097b0d187ad0e65667c2f58b9254671e86e7dbb78036b16692eae099" +dependencies = [ + "libm", + "num-integer", + "num-iter", + "num-traits", + "once_cell", + "rand 0.9.2", + "serde", + "smallvec", +] + [[package]] name = "num-complex" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ + "bytemuck", "num-traits", ] @@ -5114,7 +5252,18 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset", + "fixedbitset 0.4.2", + "indexmap 2.12.1", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset 0.5.7", + "hashbrown 0.15.5", "indexmap 2.12.1", ] @@ -5302,7 +5451,7 @@ checksum = "bb24cb4f70d64221509ab3dca82ad2ec24e1d7f3fa3e7cb9eed4ced578683287" dependencies = [ "itertools 0.10.5", "num", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -5389,39 +5538,37 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.6" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", - "prost-derive 0.12.6", + "prost-derive 0.13.5", ] [[package]] name = "prost" -version = "0.13.5" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", - "prost-derive 0.13.5", + "prost-derive 0.14.4", ] [[package]] name = "prost-build" -version = "0.12.6" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ - "bytes", "heck", - "itertools 0.12.1", + "itertools 0.14.0", "log", "multimap", - "once_cell", - "petgraph", + "petgraph 0.8.3", "prettyplease", - "prost 0.12.6", + "prost 0.14.4", "prost-types", "regex", "syn 2.0.111", @@ -5430,12 +5577,12 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.6" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.111", @@ -5443,9 +5590,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.5" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools 0.14.0", @@ -5456,13 +5603,36 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.6" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" dependencies = [ - "prost 0.12.6", + "prost 0.14.4", +] + +[[package]] +name = "pulp" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e205bb30d5b916c55e584c22201771bcf2bad9aabd5d4127f38387140c38632" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", ] +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40e24eee682d89fb193496edf918a7f407d30175b2e785fe057e4392dfd182e0" + [[package]] name = "quick-error" version = "1.2.3" @@ -5483,7 +5653,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.35", "socket2 0.6.1", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -5504,7 +5674,7 @@ dependencies = [ "rustls 0.23.35", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -5547,9 +5717,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -5609,12 +5779,12 @@ dependencies = [ [[package]] name = "rand_distr" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.9.2", ] [[package]] @@ -5626,6 +5796,15 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -5652,6 +5831,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -5680,7 +5865,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -5851,7 +6036,7 @@ dependencies = [ "risc0-zkp", "risc0-zkvm", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -5978,7 +6163,7 @@ dependencies = [ "anyhow", "cfg-if", "risc0-zkvm", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -5997,7 +6182,7 @@ dependencies = [ "risc0-aggregation", "risc0-zkvm", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -6160,7 +6345,7 @@ checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" dependencies = [ "const-oid", "digest 0.10.7", - "num-bigint-dig", + "num-bigint-dig 0.8.6", "num-integer", "num-traits", "pkcs1", @@ -6192,7 +6377,7 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.5", + "rand 0.8.6", "rand 0.9.2", "rlp", "ruint-macro", @@ -6253,7 +6438,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6386,7 +6571,7 @@ dependencies = [ "sha2", "strum", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "toml", "yaml-rust2", ] @@ -6485,7 +6670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", - "rand 0.8.5", + "rand 0.8.6", "secp256k1-sys", "serde", ] @@ -6792,7 +6977,7 @@ dependencies = [ "http 1.4.0", "iri-string", "k256", - "rand 0.8.5", + "rand 0.8.6", "serde", "sha3", "thiserror 1.0.69", @@ -7002,6 +7187,19 @@ dependencies = [ "libc", ] +[[package]] +name = "taceo-poseidon2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac59d3df4c827b3496bff929aebd6440997a5c2e946f46ff4fdd76f318447581" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", + "num-bigint", + "num-traits", +] + [[package]] name = "tap" version = "1.0.1" @@ -7018,7 +7216,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7032,6 +7230,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "tfhe-ntt" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10650c743ade46b166c698c8349902ed5986784a7db418c51f810bea25f09e3d" +dependencies = [ + "aligned-vec", + "bytemuck", + "pulp", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -7043,11 +7252,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -7063,9 +7272,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -7461,7 +7670,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.8.5", + "rand 0.8.6", "sha1", "thiserror 1.0.69", "utf-8", @@ -7792,7 +8001,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -8149,9 +8358,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", diff --git a/crates/support/Cargo.toml b/crates/support/Cargo.toml index 247b353c5c..4f1ea01f26 100644 --- a/crates/support/Cargo.toml +++ b/crates/support/Cargo.toml @@ -13,7 +13,7 @@ e3-support-app = { path = "./app" } e3-support-host = { path = "./host" } e3-user-program = { path = "./program" } e3-support-types = { path = "./types" } -e3-fhe-params = { git = "https://github.com/gnosisguild/enclave", rev = "dbdaaee1db9d868adacc03f75bb2ac85024359ae" } +e3-fhe-params = { git = "https://github.com/gnosisguild/interfold", tag = "v0.2.0" } methods = { path = "./methods" } alloy-primitives = { version = "1.3", default-features = false, features = [ @@ -26,7 +26,7 @@ alloy-signer-local = { version = "1.0" } anyhow = { version = "=1.0.98" } actix-web = "=4.11.0" bincode = { version = "=1.3.3" } -bytemuck = { version = "=1.23.1" } +bytemuck = { version = "=1.25.0" } env_logger = "=0.11.8" hex = { version = "=0.4.3" } log = { version = "=0.4.27" } @@ -38,12 +38,16 @@ risc0-zkvm = { version = "=3.0.3" } risc0-zkp = { version = "=3.0.2", default-features = false } serde = { version = "1.0", features = ["derive", "std"] } serde_json = "=1.0.141" +# NOTE: untagged on purpose. e3-compute-provider/e3-fhe-params at interfold +# v0.2.0 declare fhe.rs without a tag. A tag here would be a different Cargo +# source than theirs, yielding two incompatible copies of `fhe`. Keep this bare +# so it unifies with them; the v0.2.2 commit is pinned via Cargo.lock. fhe = { package = "fhe", git = "https://github.com/gnosisguild/fhe.rs" } fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs" } fhe-util = { git = "https://github.com/gnosisguild/fhe.rs" } -e3-compute-provider = { git = "https://github.com/gnosisguild/enclave", rev = "632766e4ed1ceeccdeb023a56f16413a33be6f46" } +e3-compute-provider = { git = "https://github.com/gnosisguild/interfold", tag = "v0.2.0" } tokio = { version = "=1.46.1", features = ["full"] } -rand = { version = "=0.8.5" } +rand = { version = "=0.9.2" } tracing-subscriber = { version = "=0.3.19", features = ["env-filter"] } boundless-market = { version = "=1.1.0" } url = { version = "=2.5.4" } diff --git a/crates/support/host/Cargo.toml b/crates/support/host/Cargo.toml index 382aa80d83..14fa9e9313 100644 --- a/crates/support/host/Cargo.toml +++ b/crates/support/host/Cargo.toml @@ -16,7 +16,7 @@ methods = { workspace = true } risc0-ethereum-contracts = { workspace = true } risc0-zkvm = { workspace = true } tokio = { workspace = true } -e3-compute-provider = { git = "https://github.com/gnosisguild/enclave", rev = "632766e4ed1ceeccdeb023a56f16413a33be6f46" } +e3-compute-provider = { git = "https://github.com/gnosisguild/interfold", tag = "v0.2.0" } fhe = { workspace = true } fhe-traits = { workspace = true } fhe-util = { workspace = true } diff --git a/crates/support/host/src/bin/profile_risc0.rs b/crates/support/host/src/bin/profile_risc0.rs index a16cb73273..2c3725b282 100644 --- a/crates/support/host/src/bin/profile_risc0.rs +++ b/crates/support/host/src/bin/profile_risc0.rs @@ -9,7 +9,7 @@ use e3_fhe_params::{build_bfv_params_from_set_arc, encode_bfv_params, BfvPreset} use e3_support_host::run_risc0_compute; use fhe::bfv::{Encoding, Plaintext, PublicKey, SecretKey}; use fhe_traits::{FheEncoder, FheEncrypter, Serialize}; -use rand::thread_rng; +use rand::rng; fn main() { println!("Starting RISC0 profiling with mock ciphertexts..."); @@ -20,16 +20,16 @@ fn main() { // SECURE_THRESHOLD_BFV_8192 | SECURE_DKG_BFV_8192 let param_set: BfvPreset = match std::env::var("BFV_PRESET").ok().as_deref() { Some("INSECURE_DKG_512") => BfvPreset::InsecureDkg512, - Some("SECURE_THRESHOLD_BFV_8192") => BfvPreset::SecureThresholdBfv8192, + Some("SECURE_THRESHOLD_BFV_8192") => BfvPreset::SecureThreshold8192, Some("SECURE_DKG_8192") => BfvPreset::SecureDkg8192, Some(other) => { eprintln!( - "Warning: unknown BFV_PRESET={}, using default InsecureThresholdBfv512", + "Warning: unknown BFV_PRESET={}, using default InsecureThreshold512", other ); - BfvPreset::InsecureThresholdBfv512 + BfvPreset::InsecureThreshold512 } - None => BfvPreset::InsecureThresholdBfv512, + None => BfvPreset::InsecureThreshold512, }; println!("Using BFV preset: {:?}", param_set); @@ -42,7 +42,7 @@ fn main() { ); // Generate keys - let mut rng = thread_rng(); + let mut rng = rng(); let secret_key = SecretKey::random(¶ms, &mut rng); let public_key = PublicKey::new(&secret_key, &mut rng); diff --git a/crates/support/methods/guest/Cargo.lock b/crates/support/methods/guest/Cargo.lock index d6f85fc924..50e6a4092f 100644 --- a/crates/support/methods/guest/Cargo.lock +++ b/crates/support/methods/guest/Cargo.lock @@ -23,6 +23,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -164,6 +173,56 @@ dependencies = [ "serde", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + [[package]] name = "anyhow" version = "1.0.98" @@ -699,9 +758,9 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] @@ -754,15 +813,61 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "clap" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + [[package]] name = "cobs" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "const-hex" version = "1.17.0" @@ -971,13 +1076,30 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "e3-bfv-client" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" +dependencies = [ + "anyhow", + "e3-fhe-params", + "e3-polynomial", + "e3-zk-helpers", + "fhe", + "fhe-traits", + "rand 0.9.2", + "thiserror 1.0.69", +] + [[package]] name = "e3-compute-provider" -version = "0.1.5" -source = "git+https://github.com/gnosisguild/enclave?rev=632766e4ed1ceeccdeb023a56f16413a33be6f46#632766e4ed1ceeccdeb023a56f16413a33be6f46" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" dependencies = [ "ark-bn254 0.4.0", "ark-ff 0.4.2", + "e3-bfv-client", + "e3-fhe-params", "hex", "lean-imt", "light-poseidon", @@ -991,16 +1113,57 @@ dependencies = [ [[package]] name = "e3-fhe-params" -version = "0.1.7" -source = "git+https://github.com/gnosisguild/enclave?rev=dbdaaee1db9d868adacc03f75bb2ac85024359ae#dbdaaee1db9d868adacc03f75bb2ac85024359ae" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" dependencies = [ "alloy-dyn-abi", "alloy-primitives", + "anyhow", + "clap", "fhe", "num-bigint", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "e3-parity-matrix" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" +dependencies = [ + "num-bigint", + "num-traits", + "serde", "thiserror 1.0.69", ] +[[package]] +name = "e3-polynomial" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" +dependencies = [ + "fhe-math", + "num-bigint", + "num-traits", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "e3-safe" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "hex", + "sha3", + "taceo-poseidon2", +] + [[package]] name = "e3-user-program" version = "0.1.0" @@ -1011,6 +1174,36 @@ dependencies = [ "fhe-traits", ] +[[package]] +name = "e3-zk-helpers" +version = "0.2.0" +source = "git+https://github.com/gnosisguild/interfold?tag=v0.2.0#6d70e718d31141ad3a049ed82cea0e82bcf08681" +dependencies = [ + "anyhow", + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "clap", + "e3-fhe-params", + "e3-parity-matrix", + "e3-polynomial", + "e3-safe", + "fhe", + "fhe-math", + "fhe-traits", + "hex", + "itertools 0.14.0", + "ndarray", + "num-bigint", + "num-integer", + "num-traits", + "rand 0.9.2", + "rayon", + "serde", + "serde_json", + "thiserror 1.0.69", + "toml", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -1100,6 +1293,26 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1162,69 +1375,73 @@ dependencies = [ [[package]] name = "fhe" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#7692b954ce887ee78f0b6baae36447ca1aa74708" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs#c9c052e11dc5d0b6864767c4e867c9fd779253a7" dependencies = [ + "bincode", "doc-comment", "fhe-math", "fhe-traits", "fhe-util", - "itertools 0.12.1", + "itertools 0.14.0", "ndarray", "num-bigint", "num-traits", "prost", "prost-build", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.2", + "rand_chacha 0.9.0", "rand_distr", "rayon", "serde", - "thiserror 1.0.69", + "thiserror 2.0.18", "zeroize", "zeroize_derive", ] [[package]] name = "fhe-math" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#7692b954ce887ee78f0b6baae36447ca1aa74708" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs#c9c052e11dc5d0b6864767c4e867c9fd779253a7" dependencies = [ "ethnum", "fhe-traits", "fhe-util", - "itertools 0.12.1", + "itertools 0.14.0", "ndarray", "num-bigint", "num-bigint-dig", "num-traits", "prost", "prost-build", - "rand 0.8.5", - "rand_chacha 0.3.1", + "pulp", + "rand 0.9.2", + "rand_chacha 0.9.0", + "serde", "sha2", - "thiserror 1.0.69", + "tfhe-ntt", + "thiserror 2.0.18", "zeroize", ] [[package]] name = "fhe-traits" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#7692b954ce887ee78f0b6baae36447ca1aa74708" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs#c9c052e11dc5d0b6864767c4e867c9fd779253a7" dependencies = [ - "rand 0.8.5", + "rand 0.9.2", ] [[package]] name = "fhe-util" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#7692b954ce887ee78f0b6baae36447ca1aa74708" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs#c9c052e11dc5d0b6864767c4e867c9fd779253a7" dependencies = [ - "itertools 0.12.1", "num-bigint-dig", "num-traits", "prime_factorization", "rand 0.8.5", + "rand 0.9.2", "rand_distr", "rayon", ] @@ -1249,9 +1466,9 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "fnv" @@ -1446,6 +1663,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -1457,18 +1680,18 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] @@ -1636,15 +1859,18 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "ndarray" -version = "0.15.6" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" dependencies = [ "matrixmultiply", "num-complex", "num-integer", "num-traits", + "portable-atomic", + "portable-atomic-util", "rawpointer", + "serde", ] [[package]] @@ -1676,20 +1902,21 @@ dependencies = [ "num-integer", "num-traits", "rand 0.8.5", + "serde", ] [[package]] name = "num-bigint-dig" -version = "0.8.6" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +checksum = "a7f9a86e097b0d187ad0e65667c2f58b9254671e86e7dbb78036b16692eae099" dependencies = [ - "lazy_static", "libm", "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "once_cell", + "rand 0.9.2", "serde", "smallvec", ] @@ -1700,6 +1927,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ + "bytemuck", "num-traits", ] @@ -1780,6 +2008,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -1826,11 +2060,12 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.5" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", + "hashbrown 0.15.5", "indexmap", ] @@ -1850,6 +2085,21 @@ dependencies = [ "spki", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + [[package]] name = "postcard" version = "1.1.3" @@ -1909,7 +2159,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.7", ] [[package]] @@ -1964,9 +2214,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.6" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", "prost-derive", @@ -1974,16 +2224,14 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.12.6" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ - "bytes", "heck", - "itertools 0.12.1", + "itertools 0.13.0", "log", "multimap", - "once_cell", "petgraph", "prettyplease", "prost", @@ -1995,12 +2243,12 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.6" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.111", @@ -2008,13 +2256,36 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.6" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" dependencies = [ "prost", ] +[[package]] +name = "pulp" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e205bb30d5b916c55e584c22201771bcf2bad9aabd5d4127f38387140c38632" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40e24eee682d89fb193496edf918a7f407d30175b2e785fe057e4392dfd182e0" + [[package]] name = "quick-error" version = "1.2.3" @@ -2105,12 +2376,12 @@ dependencies = [ [[package]] name = "rand_distr" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.9.2", ] [[package]] @@ -2122,6 +2393,15 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -2148,6 +2428,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + [[package]] name = "regex" version = "1.12.2" @@ -2565,15 +2851,23 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", "ryu", "serde", - "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", ] [[package]] @@ -2661,6 +2955,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -2701,6 +3001,19 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "taceo-poseidon2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac59d3df4c827b3496bff929aebd6440997a5c2e946f46ff4fdd76f318447581" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", + "num-bigint", + "num-traits", +] + [[package]] name = "tap" version = "1.0.1" @@ -2720,6 +3033,17 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "tfhe-ntt" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10650c743ade46b166c698c8349902ed5986784a7db418c51f810bea25f09e3d" +dependencies = [ + "aligned-vec", + "bytemuck", + "pulp", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2731,11 +3055,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -2751,9 +3075,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -2769,6 +3093,27 @@ dependencies = [ "crunchy", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "0.7.3" @@ -2778,6 +3123,20 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + [[package]] name = "toml_edit" version = "0.23.7" @@ -2785,7 +3144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 0.7.3", "toml_parser", "winnow", ] @@ -2799,6 +3158,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tracing" version = "0.1.41" @@ -2883,6 +3248,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "valuable" version = "0.1.1" @@ -2989,9 +3360,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", diff --git a/crates/support/methods/guest/Cargo.toml b/crates/support/methods/guest/Cargo.toml index e0e7fffce5..201418993b 100644 --- a/crates/support/methods/guest/Cargo.toml +++ b/crates/support/methods/guest/Cargo.toml @@ -13,7 +13,7 @@ path = "src/bin/program.rs" [dependencies] risc0-zkvm = { version = "=3.0.3", default-features = false, features = ['std'] } -e3-compute-provider = { git = "https://github.com/gnosisguild/enclave", rev = "632766e4ed1ceeccdeb023a56f16413a33be6f46" } +e3-compute-provider = { git = "https://github.com/gnosisguild/interfold", tag = "v0.2.0" } e3-user-program = { path = "../../program" } bincode = "=1.3.3" anyhow = "=1.0.98" diff --git a/crates/support/program/Cargo.toml b/crates/support/program/Cargo.toml index 04f8bd6915..96c4538823 100644 --- a/crates/support/program/Cargo.toml +++ b/crates/support/program/Cargo.toml @@ -6,5 +6,5 @@ edition = "2024" [dependencies] fhe = { workspace = true } fhe-traits = { workspace = true } -e3-compute-provider = { git = "https://github.com/gnosisguild/enclave", rev = "632766e4ed1ceeccdeb023a56f16413a33be6f46" } -e3-fhe-params = { git = "https://github.com/gnosisguild/enclave", rev = "dbdaaee1db9d868adacc03f75bb2ac85024359ae", features = ["abi-encoding"] } +e3-compute-provider = { git = "https://github.com/gnosisguild/interfold", tag = "v0.2.0" } +e3-fhe-params = { git = "https://github.com/gnosisguild/interfold", tag = "v0.2.0", features = ["abi-encoding"] } diff --git a/crates/test-helpers/src/usecase_helpers.rs b/crates/test-helpers/src/usecase_helpers.rs index 53040684f4..c9b9942dfa 100644 --- a/crates/test-helpers/src/usecase_helpers.rs +++ b/crates/test-helpers/src/usecase_helpers.rs @@ -8,7 +8,7 @@ use std::{collections::HashMap, sync::Arc}; use anyhow::{Context, Result}; use e3_crypto::{Cipher, SensitiveBytes}; use e3_events::ThresholdShare; -use e3_fhe_params::{BfvParamSet, BfvPreset}; +use e3_fhe_params::{BfvParamSet, BfvPreset, LambdaConfig}; use e3_trbfv::{ calculate_decryption_key::{ calculate_decryption_key, CalculateDecryptionKeyRequest, CalculateDecryptionKeyResponse, @@ -75,7 +75,7 @@ pub fn generate_shares_hash_map( GenPkShareAndSkSssRequest { trbfv_config: trbfv_config.clone(), crp: ArcBytes::from_bytes(&crp.to_bytes()), - lambda: 40, + lambda: LambdaConfig::Insecure(40), num_ciphertexts: 1, }, ) diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index 1182b2d4fc..ec296b25f2 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -1371,7 +1371,8 @@ async fn test_trbfv_actor() -> Result<()> { let nodes_spawned = participant_count + 1; // +1 non-registered observer collector // Statistical security parameter λ used for smudging bound / error_size. // Comes from the selected BFV preset metadata to avoid mixing parameter families. - let lambda = benchmark_params.lambda; + // Lambda is secure or insecure depending on the selected preset's security tier. + let lambda = benchmark_params.bfv_preset.lambda()?; let seed = create_seed_from_u64(123); let error_size = ArcBytes::from_bytes(&BigUint::to_bytes_be(&calculate_error_size( @@ -1662,10 +1663,17 @@ async fn test_trbfv_actor() -> Result<()> { _ => None, }) .collect(); - assert_eq!( - dkg_parties.len(), - threshold_n, - "node 0: expected one DKGRecursiveAggregationComplete per committee member (N={threshold_n}), got parties {dkg_parties:?}" + // Unlike KeyshareCreated (cheap, gossiped early — all N arrive before aggregation), the + // per-node recursive fold proof (DKGRecursiveAggregationComplete) is expensive and late. + // `PublicKeyAggregated` fires once the aggregator selects and aggregates the honest set, so + // only the H honest folds are guaranteed to have reached node 0 by this barrier; the extra + // N-H members' folds race against it (and may land afterward). Assert the guaranteed floor + // and that every observed fold party is a committee member, not the racy `== N`. + assert!( + dkg_parties.len() >= committee_h + && dkg_parties.len() <= threshold_n + && dkg_parties.iter().all(|p| (*p as usize) < threshold_n), + "node 0: expected DKGRecursiveAggregationComplete from {committee_h}..={threshold_n} committee members before PublicKeyAggregated (only the H honest folds are guaranteed by this barrier), got parties {dkg_parties:?}" ); assert_eq!( ks_parties.len(), diff --git a/crates/trbfv/src/calculate_decryption_key.rs b/crates/trbfv/src/calculate_decryption_key.rs index 2e2104d3ab..8bf8ef6c76 100644 --- a/crates/trbfv/src/calculate_decryption_key.rs +++ b/crates/trbfv/src/calculate_decryption_key.rs @@ -115,7 +115,7 @@ pub fn calculate_decryption_key( let params = req.trbfv_config.params(); let threshold = req.trbfv_config.threshold() as usize; let num_ciphernodes = req.trbfv_config.num_parties() as usize; - let share_manager = ShareManager::new(num_ciphernodes, threshold, params.clone()); + let share_manager = ShareManager::new(num_ciphernodes, threshold, params.clone())?; info!("Calculating sk_poly_sum..."); let sk_poly_sum = @@ -126,7 +126,7 @@ pub fn calculate_decryption_key( .esi_sss_collected .into_iter() .map(|shares| -> Result<_> { - let share_manager = ShareManager::new(num_ciphernodes, threshold, params.clone()); + let share_manager = ShareManager::new(num_ciphernodes, threshold, params.clone())?; share_manager .aggregate_collected_shares(&shares.to_array_data()) .context("Failed to aggregate es_sss") diff --git a/crates/trbfv/src/calculate_decryption_share.rs b/crates/trbfv/src/calculate_decryption_share.rs index a8e252539c..f6e256a046 100644 --- a/crates/trbfv/src/calculate_decryption_share.rs +++ b/crates/trbfv/src/calculate_decryption_share.rs @@ -118,7 +118,7 @@ pub fn calculate_decryption_share( .into_iter() .enumerate() .map(|(index, ciphertext)| { - let share_manager = ShareManager::new(num_ciphernodes, threshold, params.clone()); + let share_manager = ShareManager::new(num_ciphernodes, threshold, params.clone())?; info!("Create decryption share for ct index {}...", index); // Currently there is a single smudging noise polynomial shared across all // ciphertexts. When multiple per-ciphertext noises are supported, the diff --git a/crates/trbfv/src/calculate_threshold_decryption.rs b/crates/trbfv/src/calculate_threshold_decryption.rs index e527f9d0b5..8d8c214aa8 100644 --- a/crates/trbfv/src/calculate_threshold_decryption.rs +++ b/crates/trbfv/src/calculate_threshold_decryption.rs @@ -148,7 +148,7 @@ pub fn calculate_threshold_decryption( index ); - let share_manager = ShareManager::new(num_ciphernodes, threshold, params.clone()); + let share_manager = ShareManager::new(num_ciphernodes, threshold, params.clone())?; let Some(threshold_shares) = d_share_polys.get(index) else { bail!("Poly not found for index {}", index) }; diff --git a/crates/trbfv/src/gen_esi_sss.rs b/crates/trbfv/src/gen_esi_sss.rs index bae8979461..b214b39420 100644 --- a/crates/trbfv/src/gen_esi_sss.rs +++ b/crates/trbfv/src/gen_esi_sss.rs @@ -84,7 +84,7 @@ pub fn gen_esi_sss( info!("gen_esi_sss:mapping..."); let e_sm_poly = try_poly_pb_from_bytes(&e_sm_raw, ¶ms)?; - let mut share_manager = ShareManager::new(num_ciphernodes, threshold, params.clone()); + let mut share_manager = ShareManager::new(num_ciphernodes, threshold, params.clone())?; info!("gen_esi_sss:generate_secret_shares_from_poly..."); diff --git a/crates/trbfv/src/gen_pk_share_and_sk_sss.rs b/crates/trbfv/src/gen_pk_share_and_sk_sss.rs index 07d48a8eac..33273652e4 100644 --- a/crates/trbfv/src/gen_pk_share_and_sk_sss.rs +++ b/crates/trbfv/src/gen_pk_share_and_sk_sss.rs @@ -12,6 +12,7 @@ use crate::{ }; use anyhow::Result; use e3_crypto::{Cipher, SensitiveBytes}; +use e3_fhe_params::LambdaConfig; use e3_utils::utility_types::ArcBytes; use fhe::{ bfv::SecretKey, @@ -30,8 +31,9 @@ pub struct GenPkShareAndSkSssRequest { pub trbfv_config: TrBFVConfig, /// Crp pub crp: ArcBytes, - /// Statistical security parameter λ for smudging noise generation. - pub lambda: usize, + /// Statistical security level λ for smudging noise generation. Carries the + /// secure/insecure tier chosen from the preset so it survives serialization. + pub lambda: LambdaConfig, /// Number of ciphertexts (z) for smudging noise generation. pub num_ciphertexts: usize, } @@ -39,7 +41,7 @@ pub struct GenPkShareAndSkSssRequest { struct InnerRequest { pub trbfv_config: TrBFVConfig, pub crp: CommonRandomPoly, - pub lambda: usize, + pub lambda: LambdaConfig, pub num_ciphertexts: usize, } @@ -131,8 +133,9 @@ pub fn gen_pk_share_and_sk_sss( // Generate smudging noise let trbfv = TRBFV::new(num_ciphernodes as usize, threshold as usize, params.clone())?; let share_manager_for_esm = - ShareManager::new(num_ciphernodes as usize, threshold as usize, params.clone()); - let esi_coeffs = trbfv.generate_smudging_error(req.num_ciphertexts, req.lambda, rng)?; + ShareManager::new(num_ciphernodes as usize, threshold as usize, params.clone())?; + let lambda = req.lambda.into_lambda()?; + let esi_coeffs = trbfv.generate_smudging_error(req.num_ciphertexts, lambda, rng)?; let e_sm_rns = share_manager_for_esm.bigints_to_poly(&esi_coeffs)?; let e_sm_raw = ArcBytes::from_bytes(&e_sm_rns.deref().to_bytes()); @@ -140,7 +143,7 @@ pub fn gen_pk_share_and_sk_sss( let eek_raw = ArcBytes::from_bytes(&eek.to_bytes()); let mut share_manager = - ShareManager::new(num_ciphernodes as usize, threshold as usize, params.clone()); + ShareManager::new(num_ciphernodes as usize, threshold as usize, params.clone())?; let sk_poly = share_manager.coeffs_to_poly_level0(sk_share.coeffs.clone().as_ref())?; let sk_raw = ArcBytes::from_bytes(&sk_poly.to_bytes()); diff --git a/crates/trbfv/src/helpers.rs b/crates/trbfv/src/helpers.rs index f20c669f8a..7813934489 100644 --- a/crates/trbfv/src/helpers.rs +++ b/crates/trbfv/src/helpers.rs @@ -10,7 +10,7 @@ use e3_crypto::{Cipher, SensitiveBytes}; use fhe::mbfv::PublicKeyShare; use fhe::{ bfv::{self, BfvParameters, SecretKey}, - trbfv::{SmudgingBoundCalculator, SmudgingBoundCalculatorConfig}, + trbfv::{Lambda, SmudgingBoundCalculator, SmudgingBoundCalculatorConfig}, }; use fhe_math::rq::{Ntt, Poly, PowerBasis, RepresentationTag}; use fhe_traits::DeserializeWithContext; @@ -77,7 +77,7 @@ pub fn calculate_error_size( params: Arc, n: usize, num_ciphertexts: usize, - lambda: usize, + lambda: Lambda, ) -> Result { let config = SmudgingBoundCalculatorConfig::new(params, n, num_ciphertexts, lambda); let calculator = SmudgingBoundCalculator::new(config); diff --git a/crates/trbfv/src/trbfv_request.rs b/crates/trbfv/src/trbfv_request.rs index bfed92350e..3e436c38b1 100644 --- a/crates/trbfv/src/trbfv_request.rs +++ b/crates/trbfv/src/trbfv_request.rs @@ -44,11 +44,24 @@ pub enum TrBFVResponse { #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum TrBFVError { - GenEsiSss(String), - GenPkShareAndSkSss(String), - CalculateDecryptionKey(String), - CalculateDecryptionShare(String), - CalculateThresholdDecryption(String), + GenEsiSss(TrBFVFailure), + GenPkShareAndSkSss(TrBFVFailure), + CalculateDecryptionKey(TrBFVFailure), + CalculateDecryptionShare(TrBFVFailure), + CalculateThresholdDecryption(TrBFVFailure), +} + +impl TrBFVError { + /// The structured failure payload, regardless of which operation failed. + pub fn failure(&self) -> &TrBFVFailure { + match self { + TrBFVError::GenEsiSss(f) + | TrBFVError::GenPkShareAndSkSss(f) + | TrBFVError::CalculateDecryptionKey(f) + | TrBFVError::CalculateDecryptionShare(f) + | TrBFVError::CalculateThresholdDecryption(f) => f, + } + } } impl std::error::Error for TrBFVError { @@ -60,13 +73,165 @@ impl std::error::Error for TrBFVError { impl fmt::Display for TrBFVError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TrBFVError::GenEsiSss(_) => write!(f, "GenEsiSss"), - TrBFVError::GenPkShareAndSkSss(_) => write!(f, "GenPkShareAndSkSss"), - TrBFVError::CalculateDecryptionKey(_) => write!(f, "CalculateDecryptionKey"), - TrBFVError::CalculateDecryptionShare(_) => write!(f, "CalculateDecryptionShare"), - TrBFVError::CalculateThresholdDecryption(_) => { - write!(f, "CalculateThresholdDecryption") + TrBFVError::GenEsiSss(d) => write!(f, "GenEsiSss: {d}"), + TrBFVError::GenPkShareAndSkSss(d) => write!(f, "GenPkShareAndSkSss: {d}"), + TrBFVError::CalculateDecryptionKey(d) => write!(f, "CalculateDecryptionKey: {d}"), + TrBFVError::CalculateDecryptionShare(d) => write!(f, "CalculateDecryptionShare: {d}"), + TrBFVError::CalculateThresholdDecryption(d) => { + write!(f, "CalculateThresholdDecryption: {d}") } } } } + +/// Payload carried by every [`TrBFVError`] variant. +/// +/// Always holds the human-readable `message`. When the underlying failure is a structured +/// threshold-BFV error from fhe.rs (`fhe::Error::Threshold(..)`), `threshold` is populated so +/// callers can react to a specific failure mode — including the implicated party — instead of +/// parsing `message`. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct TrBFVFailure { + pub message: String, + pub threshold: Option, +} + +impl TrBFVFailure { + /// Build from an `anyhow` error, extracting a [`ThresholdFailure`] if a + /// `fhe::Error::Threshold(..)` is anywhere in the error chain. + pub fn from_error(err: &anyhow::Error) -> Self { + Self { + message: err.to_string(), + threshold: ThresholdFailure::from_anyhow(err), + } + } +} + +impl From for TrBFVFailure { + fn from(message: String) -> Self { + Self { + message, + threshold: None, + } + } +} + +impl From<&str> for TrBFVFailure { + fn from(message: &str) -> Self { + Self::from(message.to_string()) + } +} + +impl fmt::Display for TrBFVFailure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.threshold { + Some(t) => write!(f, "{} ({t})", self.message), + None => write!(f, "{}", self.message), + } + } +} + +/// Structured detail extracted from a fhe.rs `ThresholdError`. +/// +/// `party_id` identifies the implicated party when the underlying variant carries one. Note +/// the index space depends on the operation: for decryption-share reconstruction +/// (`decrypt_from_shares`) it is the 1-based Shamir party id; for share aggregation +/// (`aggregate_collected_shares`) it is the 0-based index into the collected-shares vector, +/// i.e. collection order. Callers attributing blame must map it accordingly. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ThresholdFailure { + pub kind: ThresholdFailureKind, + pub party_id: Option, + pub message: String, +} + +/// Mirror of fhe.rs `ThresholdError` variants, kept matchable across the wire. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ThresholdFailureKind { + InvalidPartyId, + DuplicatePartyId, + InvalidThreshold, + ShareCountMismatch, + MalformedShares, + NonInvertibleShares, + InsecureLambda, + SmudgingBoundInfeasible, + PartyCountExceedsModulus, +} + +impl ThresholdFailure { + /// Find a `fhe::Error::Threshold(..)` anywhere in an `anyhow` error chain. + pub fn from_anyhow(err: &anyhow::Error) -> Option { + err.chain() + .find_map(|e| e.downcast_ref::()) + .and_then(Self::from_fhe_error) + } + + /// Extract from a `fhe::Error`, returning `None` for non-threshold errors. + pub fn from_fhe_error(err: &fhe::Error) -> Option { + match err { + fhe::Error::Threshold(t) => Some(Self::from_threshold(t)), + _ => None, + } + } + + fn from_threshold(t: &fhe::ThresholdError) -> Self { + use fhe::ThresholdError as TE; + use ThresholdFailureKind as K; + // Exhaustive (ThresholdError is not #[non_exhaustive]) so new fhe.rs variants force an + // explicit decision here rather than silently degrading to an unattributed failure. + let (kind, party_id) = match t { + TE::InvalidPartyId { party_id, .. } => (K::InvalidPartyId, Some(*party_id)), + TE::DuplicatePartyId { party_id } => (K::DuplicatePartyId, Some(*party_id)), + TE::MalformedShares { party_id, .. } => (K::MalformedShares, Some(*party_id)), + TE::InvalidThreshold { .. } => (K::InvalidThreshold, None), + TE::ShareCountMismatch { .. } => (K::ShareCountMismatch, None), + TE::NonInvertibleShares => (K::NonInvertibleShares, None), + TE::InsecureLambda { .. } => (K::InsecureLambda, None), + TE::SmudgingBoundInfeasible { .. } => (K::SmudgingBoundInfeasible, None), + TE::PartyCountExceedsModulus { .. } => (K::PartyCountExceedsModulus, None), + }; + Self { + kind, + party_id, + message: t.to_string(), + } + } +} + +impl fmt::Display for ThresholdFailure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.party_id { + Some(party_id) => write!(f, "{:?} [party {party_id}]: {}", self.kind, self.message), + None => write!(f, "{:?}: {}", self.kind, self.message), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn extracts_malformed_shares_party_id_from_anyhow_chain() { + // A malformed-shares error wrapped with context (as the handlers do) must still be + // recognized and attributed to the offending party. + let fhe_err = fhe::Error::malformed_shares(2, "bad shape".to_string()); + let wrapped = anyhow::Error::new(fhe_err).context("Failed to aggregate es_sss"); + + let failure = TrBFVFailure::from_error(&wrapped); + let threshold = failure + .threshold + .expect("threshold detail should be extracted"); + assert_eq!(threshold.kind, ThresholdFailureKind::MalformedShares); + assert_eq!(threshold.party_id, Some(2)); + } + + #[test] + fn non_threshold_errors_have_no_structured_detail() { + let err = anyhow::anyhow!("some unrelated failure"); + let failure = TrBFVFailure::from_error(&err); + assert!(failure.threshold.is_none()); + assert_eq!(failure.message, "some unrelated failure"); + } +} diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 88e224a2d6..2600a88a3c 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -322,7 +322,6 @@ fn main() -> Result<()> { committee, dkg_input_type, sd.z, - sd.lambda, )?; let circuit = ShareEncryptionCircuit; diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs index 623dff8b1b..c6aa208c7f 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -157,7 +157,10 @@ impl Computation for Bounds { .search_defaults() .ok_or_else(|| CircuitsErrors::Sample("missing search defaults".to_string()))?; let num_ciphertexts = defaults.z; - let lambda = defaults.lambda; + // Lambda is secure or insecure depending on the preset's security tier. + let lambda = preset + .lambda() + .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; // Use the same committee size as C1 (pk_generation) so smudging bounds and // bit widths match PK_GENERATION_BIT_E_SM / SHARE_COMPUTATION_E_SM_BIT_SECRET. @@ -165,7 +168,7 @@ impl Computation for Bounds { threshold_params, data.n_parties as usize, num_ciphertexts as usize, - lambda as usize, + lambda, ); let e_sm_calculator = SmudgingBoundCalculator::new(e_sm_config); diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs index fe80db362a..a1c7a8bff5 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs @@ -32,15 +32,14 @@ impl ShareComputationCircuitData { let (threshold_params, _) = build_pair_for_preset(preset).map_err(|e| { CircuitsErrors::Sample(format!("Failed to build pair for preset: {:?}", e)) })?; - let sd = preset - .search_defaults() - .ok_or_else(|| CircuitsErrors::Sample("Preset has no search defaults".into()))?; let mut rng = rand::rng(); let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()) .map_err(|e| CircuitsErrors::Sample(format!("Failed to create TRBFV: {:?}", e)))?; let mut share_manager = - ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); + ShareManager::new(committee.n, committee.threshold, threshold_params.clone()).map_err( + |e| CircuitsErrors::Sample(format!("Failed to create ShareManager: {:?}", e)), + )?; let parity_matrix = compute_parity_matrix(threshold_params.moduli(), committee.n, committee.threshold) @@ -86,8 +85,11 @@ impl ShareComputationCircuitData { (secret_crt, secret_sss) } DkgInputType::SmudgingNoise => { + let lambda = preset + .lambda() + .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let esi_coeffs = trbfv - .generate_smudging_error(committee.n, sd.lambda as usize, &mut rng) + .generate_smudging_error(committee.n, lambda, &mut rng) .map_err(|e| { CircuitsErrors::Sample(format!( "Failed to generate smudging error: {:?}", diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs index 084de37f17..f937c9cd57 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs @@ -42,7 +42,13 @@ impl ShareDecryptionCircuitData { let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()) .map_err(|e| CircuitsErrors::Sample(format!("Failed to create TRBFV: {:?}", e)))?; let mut share_manager = - ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); + ShareManager::new(committee.n, committee.threshold, threshold_params.clone()).map_err( + |e| CircuitsErrors::Sample(format!("Failed to create ShareManager: {:?}", e)), + )?; + // Lambda is secure or insecure depending on the preset's security tier. + let lambda = preset + .lambda() + .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let mut honest_ciphertexts: Vec>> = Vec::new(); let num_honest = committee.h; @@ -81,7 +87,7 @@ impl ShareDecryptionCircuitData { } DkgInputType::SmudgingNoise => { let esi_coeffs = trbfv - .generate_smudging_error(sd.z as usize, sd.lambda as usize, &mut rng) + .generate_smudging_error(sd.z as usize, lambda, &mut rng) .map_err(|e| { CircuitsErrors::Sample(format!( "Failed to generate smudging error: {:?}", diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs index e2b7d13aea..cab5533dce 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs @@ -198,7 +198,6 @@ mod tests { committee.clone(), DkgInputType::SecretKey, sd.z, - sd.lambda, ) .unwrap(); let artifacts = ShareEncryptionCircuit @@ -221,7 +220,6 @@ mod tests { committee.clone(), DkgInputType::SecretKey, sd.z, - sd.lambda, ) .unwrap(); diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs index c37a205dc6..19553c5258 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs @@ -597,7 +597,6 @@ mod tests { committee, DkgInputType::SecretKey, sd.z, - sd.lambda, ) .unwrap(); @@ -620,7 +619,6 @@ mod tests { committee, DkgInputType::SecretKey, sd.z, - sd.lambda, ) .unwrap(); let constants = Configs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); @@ -645,7 +643,6 @@ mod tests { committee, DkgInputType::SecretKey, sd.z, - sd.lambda, ) .unwrap(); let inputs = Inputs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs index 568024a977..5d9f63838c 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs @@ -26,7 +26,6 @@ impl ShareEncryptionCircuitData { committee: CiphernodesCommittee, dkg_input_type: DkgInputType, num_ciphertexts: u128, // z in the search defaults - lambda: u32, ) -> Result { let (threshold_params, dkg_params) = build_pair_for_preset(preset).map_err(|e| { CircuitsErrors::Sample(format!("Failed to build pair for preset: {:?}", e)) @@ -34,13 +33,20 @@ impl ShareEncryptionCircuitData { let mut rng = rand::rng(); + // Lambda is secure or insecure depending on the preset's security tier. + let lambda = preset + .lambda() + .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + let dkg_secret_key = SecretKey::random(&dkg_params, &mut rng); let dkg_public_key = PublicKey::new(&dkg_secret_key, &mut rng); let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()) .map_err(|e| CircuitsErrors::Sample(format!("Failed to create TRBFV: {:?}", e)))?; let mut share_manager = - ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); + ShareManager::new(committee.n, committee.threshold, threshold_params.clone()).map_err( + |e| CircuitsErrors::Sample(format!("Failed to create ShareManager: {:?}", e)), + )?; let share_row = match dkg_input_type { DkgInputType::SecretKey => { @@ -65,7 +71,7 @@ impl ShareEncryptionCircuitData { } DkgInputType::SmudgingNoise => { let esi_coeffs = trbfv - .generate_smudging_error(num_ciphertexts as usize, lambda as usize, &mut rng) + .generate_smudging_error(num_ciphertexts as usize, lambda, &mut rng) .map_err(|e| { CircuitsErrors::Sample(format!( "Failed to generate smudging error: {:?}", @@ -126,7 +132,6 @@ mod tests { committee.clone(), DkgInputType::SecretKey, sd.z, - sd.lambda, ) .unwrap(); @@ -161,7 +166,6 @@ mod tests { committee, DkgInputType::SmudgingNoise, sd.z, - sd.lambda, ) .unwrap(); diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/sample.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/sample.rs index c935bba54c..bcc27e36d9 100644 --- a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/sample.rs +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/sample.rs @@ -52,6 +52,10 @@ impl DecryptedSharesAggregationCircuitData { let sd = preset .search_defaults() .ok_or_else(|| CircuitsErrors::Sample("Preset has no search defaults".into()))?; + // Lambda is secure or insecure depending on the preset's security tier. + let lambda = preset + .lambda() + .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let num_parties = committee.n; let threshold = committee.threshold; @@ -77,8 +81,14 @@ impl DecryptedSharesAggregationCircuitData { )) })?; - let mut share_manager = - ShareManager::new(num_parties, threshold, threshold_params.clone()); + let mut share_manager = ShareManager::new( + num_parties, + threshold, + threshold_params.clone(), + ) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to create ShareManager: {:?}", e)) + })?; let sk_poly = share_manager .coeffs_to_poly_level0(sk_share.coeffs.as_ref()) .map_err(|e| { @@ -95,7 +105,7 @@ impl DecryptedSharesAggregationCircuitData { })?; let esi_coeffs = trbfv - .generate_smudging_error(sd.z as usize, sd.lambda as usize, &mut rng) + .generate_smudging_error(sd.z as usize, lambda, &mut rng) .map_err(|e| { CircuitsErrors::Sample(format!( "Failed to generate smudging error: {:?}", @@ -173,7 +183,10 @@ impl DecryptedSharesAggregationCircuitData { // Aggregate collected shares to get sk_poly_sum and es_poly_sum per party for party in parties.iter_mut() { - let share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()); + let share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to create ShareManager: {:?}", e)) + })?; party.sk_poly_sum = share_manager .aggregate_collected_shares(&party.sk_sss_collected) .map_err(|e| { @@ -221,7 +234,10 @@ impl DecryptedSharesAggregationCircuitData { let mut d_share_polys: Vec> = Vec::with_capacity(honest_parties); for party in parties.iter().take(honest_parties) { - let share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()); + let share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to create ShareManager: {:?}", e)) + })?; // For a single ciphertext, es_poly_sum is one Poly per party let d_share = share_manager .decryption_share( @@ -238,7 +254,10 @@ impl DecryptedSharesAggregationCircuitData { let reconstructing_parties: Vec = (1..=honest_parties).collect(); // Threshold decrypt to obtain message (verify) - let share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()); + let share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to create ShareManager: {:?}", e)) + })?; let plaintext = share_manager .decrypt_from_shares( d_share_polys.clone(), diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs index 85f319814e..d2b148a6a1 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs @@ -190,11 +190,15 @@ impl Computation for Bounds { .search_defaults() .ok_or_else(|| CircuitsErrors::Other("missing search defaults".to_string()))?; + // Lambda is secure or insecure depending on the preset's security tier. + let lambda = preset + .lambda() + .map_err(|e| CircuitsErrors::Other(e.to_string()))?; let smudging_config = SmudgingBoundCalculatorConfig::new( threshold_params.clone(), committee_n, sd.z as usize, - preset.metadata().lambda, + lambda, ); let smudging_calculator = SmudgingBoundCalculator::new(smudging_config); let e_sm_bound = smudging_calculator.calculate_sm_bound().map_err(|e| { diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs index 118f1807df..bf5e50ba52 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs @@ -42,22 +42,22 @@ impl PkGenerationCircuitData { let num_parties = committee.n; let threshold = committee.threshold; - let preset_metadata = preset.metadata(); let defaults = preset .search_defaults() .ok_or_else(|| CircuitsErrors::Sample("missing search defaults".to_string()))?; let num_ciphertexts = defaults.z; + // Lambda is secure or insecure depending on the preset's security tier. + let lambda = preset + .lambda() + .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let trbfv = TRBFV::new(num_parties, threshold, threshold_params.clone())?; - let share_manager = ShareManager::new(num_parties, threshold, threshold_params); + let share_manager = ShareManager::new(num_parties, threshold, threshold_params)?; // Generate smudging error coefficients - let esi_coeffs = trbfv.generate_smudging_error( - num_ciphertexts as usize, - preset_metadata.lambda, - &mut rng, - )?; + let esi_coeffs = + trbfv.generate_smudging_error(num_ciphertexts as usize, lambda, &mut rng)?; // Convert to polynomial in RNS representation // bigints_to_poly returns Zeroizing, we need to clone the inner Poly diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs index 60c9603c6d..016af8bf24 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs @@ -42,7 +42,10 @@ impl ShareDecryptionCircuitData { let num_parties = committee.n; let threshold = committee.threshold; let num_ciphertexts = sd.z as usize; - let lambda = preset.metadata().lambda; + // Lambda is secure or insecure depending on the preset's security tier. + let lambda = preset + .lambda() + .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; // Create TRBFV instance for share generation let trbfv = TRBFV::new(num_parties, threshold, threshold_params.clone()) @@ -82,7 +85,10 @@ impl ShareDecryptionCircuitData { // - Party 0 collects shares from other parties (including themselves) // - When party 0 computes a decryption share, they aggregate all collected shares - let mut share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()); + let mut share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to create ShareManager: {:?}", e)) + })?; // Generate shares for each party's secret key // In reality, each party would do this independently diff --git a/crates/zk-prover/tests/common/node_fold_witness.rs b/crates/zk-prover/tests/common/node_fold_witness.rs index 54087c92ca..77aff34319 100644 --- a/crates/zk-prover/tests/common/node_fold_witness.rs +++ b/crates/zk-prover/tests/common/node_fold_witness.rs @@ -62,19 +62,23 @@ pub fn pk_generation_sample_with_esi( let num_parties = committee.n; let threshold = committee.threshold; - let preset_metadata = preset.metadata(); let defaults = preset .search_defaults() .ok_or_else(|| CircuitsErrors::Sample("missing search defaults".to_string()))?; let num_ciphertexts = defaults.z; + // Lambda is secure or insecure depending on the preset's security tier. + let lambda = preset + .lambda() + .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let trbfv = TRBFV::new(num_parties, threshold, threshold_params.clone()) .map_err(|e| CircuitsErrors::Sample(format!("Failed to create TRBFV: {:?}", e)))?; - let share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()); + let share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()) + .map_err(|e| CircuitsErrors::Sample(format!("Failed to create ShareManager: {:?}", e)))?; let esi_coeffs: Vec = trbfv - .generate_smudging_error(num_ciphertexts as usize, preset_metadata.lambda, &mut rng) + .generate_smudging_error(num_ciphertexts as usize, lambda, &mut rng) .map_err(|e| { CircuitsErrors::Sample(format!("Failed to generate smudging error: {:?}", e)) })?; @@ -113,7 +117,9 @@ pub fn share_computation_sk_from_pk( .map_err(CircuitsErrors::Sample)?; let mut share_manager = - ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); + ShareManager::new(committee.n, committee.threshold, threshold_params.clone()).map_err( + |e| CircuitsErrors::Sample(format!("Failed to create ShareManager: {:?}", e)), + )?; let sk_poly = share_manager .coeffs_to_poly_level0(secret_key.coeffs.clone().as_ref()) @@ -154,7 +160,9 @@ pub fn share_computation_esm_from_esi( .map_err(CircuitsErrors::Sample)?; let mut share_manager = - ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); + ShareManager::new(committee.n, committee.threshold, threshold_params.clone()).map_err( + |e| CircuitsErrors::Sample(format!("Failed to create ShareManager: {:?}", e)), + )?; let esi_poly = share_manager .bigints_to_poly(esi_coeffs) diff --git a/crates/zk-prover/tests/fold_accumulators_e2e_tests.rs b/crates/zk-prover/tests/fold_accumulators_e2e_tests.rs index 47b9a443c4..32b1773289 100644 --- a/crates/zk-prover/tests/fold_accumulators_e2e_tests.rs +++ b/crates/zk-prover/tests/fold_accumulators_e2e_tests.rs @@ -319,7 +319,6 @@ async fn setup_c3_fold_with_inner_share_encryption() -> Option<( committee.clone(), DkgInputType::SecretKey, sd.z, - sd.lambda, ) .ok()?; let sample_b = ShareEncryptionCircuitData::generate_sample( @@ -327,7 +326,6 @@ async fn setup_c3_fold_with_inner_share_encryption() -> Option<( committee, DkgInputType::SecretKey, sd.z, - sd.lambda, ) .ok()?; let prover = ZkProver::new(&backend); diff --git a/crates/zk-prover/tests/local_e2e_tests.rs b/crates/zk-prover/tests/local_e2e_tests.rs index 6027aa5e1a..83e5289e5b 100644 --- a/crates/zk-prover/tests/local_e2e_tests.rs +++ b/crates/zk-prover/tests/local_e2e_tests.rs @@ -160,7 +160,6 @@ async fn setup_share_encryption_e_sm_test() -> Option<( committee, DkgInputType::SmudgingNoise, sd.z, - sd.lambda, ) .ok()?; let prover = ZkProver::new(&backend); @@ -201,7 +200,6 @@ async fn setup_share_encryption_sk_test() -> Option<( committee, DkgInputType::SecretKey, sd.z, - sd.lambda, ) .ok()?; let prover = ZkProver::new(&backend); diff --git a/examples/CRISP/Cargo.lock b/examples/CRISP/Cargo.lock index fac9910624..815911abe1 100644 --- a/examples/CRISP/Cargo.lock +++ b/examples/CRISP/Cargo.lock @@ -832,7 +832,7 @@ dependencies = [ "alloy-rlp", "alloy-serde", "alloy-sol-types", - "itertools 0.14.0", + "itertools 0.13.0", "serde", "serde_json", "serde_with", @@ -1136,7 +1136,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1147,7 +1147,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2784,7 +2784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2852,8 +2852,8 @@ dependencies = [ [[package]] name = "fhe" -version = "0.2.0" -source = "git+https://github.com/gnosisguild/fhe.rs#a92478b39625f4c18b91a5033928b35d8a0090cf" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "bincode", "doc-comment", @@ -2878,8 +2878,8 @@ dependencies = [ [[package]] name = "fhe-math" -version = "0.2.0" -source = "git+https://github.com/gnosisguild/fhe.rs#a92478b39625f4c18b91a5033928b35d8a0090cf" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "ethnum", "fhe-traits", @@ -2903,16 +2903,16 @@ dependencies = [ [[package]] name = "fhe-traits" -version = "0.1.1" -source = "git+https://github.com/gnosisguild/fhe.rs#a92478b39625f4c18b91a5033928b35d8a0090cf" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "rand 0.9.2", ] [[package]] name = "fhe-util" -version = "0.1.1" -source = "git+https://github.com/gnosisguild/fhe.rs#a92478b39625f4c18b91a5033928b35d8a0090cf" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "num-bigint-dig", "num-traits", @@ -4655,7 +4655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", - "itertools 0.14.0", + "itertools 0.13.0", "log", "multimap", "petgraph", @@ -4674,7 +4674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.13.0", "proc-macro2 1.0.106", "quote 1.0.44", "syn 2.0.116", @@ -5207,7 +5207,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5658,7 +5658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5820,7 +5820,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/examples/CRISP/Cargo.toml b/examples/CRISP/Cargo.toml index 03aae2092f..9164078a26 100644 --- a/examples/CRISP/Cargo.toml +++ b/examples/CRISP/Cargo.toml @@ -47,10 +47,10 @@ log = { version = "=0.4.27" } reqwest = { version = "=0.12.22", features = ["json"] } serde = { version = "=1.0.228", features = ["derive", "std"] } serde_json = "=1.0.141" -fhe = { git = "https://github.com/gnosisguild/fhe.rs" } -fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs" } -fhe-math = { git = "https://github.com/gnosisguild/fhe.rs" } -fhe-util = { git = "https://github.com/gnosisguild/fhe.rs" } +fhe = { git = "https://github.com/gnosisguild/fhe.rs", tag = "v0.2.2" } +fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", tag = "v0.2.2" } +fhe-math = { git = "https://github.com/gnosisguild/fhe.rs", tag = "v0.2.2" } +fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", tag = "v0.2.2" } tokio = { version = "=1.46.1", features = ["full"] } rand = { version = "0.9" } tracing-subscriber = { version = "=0.3.19", features = ["env-filter"] } diff --git a/examples/CRISP/interfold.config.yaml b/examples/CRISP/interfold.config.yaml index 7c2b28a473..3a11c747e9 100644 --- a/examples/CRISP/interfold.config.yaml +++ b/examples/CRISP/interfold.config.yaml @@ -5,23 +5,26 @@ chains: rpc_url: ws://localhost:8545 contracts: e3_program: - address: "0xc351628EB244ec633d5f21fBD6621e1a683B1181" - deploy_block: 40 + address: "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650" + deploy_block: 38 interfold: - address: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" - deploy_block: 16 + address: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + deploy_block: 14 ciphernode_registry: address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" deploy_block: 8 bonding_registry: address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" deploy_block: 9 + bonding_registry: + address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" + deploy_block: 10 slashing_manager: - address: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" - deploy_block: 7 + address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + deploy_block: 8 fee_token: address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" - deploy_block: 5 + deploy_block: 4 program: dev: true # risc0: diff --git a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json index bb4cc4e3f0..fd0a8a2207 100644 --- a/examples/CRISP/packages/crisp-contracts/deployed_contracts.json +++ b/examples/CRISP/packages/crisp-contracts/deployed_contracts.json @@ -1,32 +1,39 @@ { "localhost": { "PoseidonT3": { - "blockNumber": 4, + "blockNumber": 3, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 5, + "blockNumber": 4, "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, + "InterfoldToken": { + "constructorArgs": { + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + }, + "blockNumber": 5, + "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" + }, "InterfoldTicketToken": { "constructorArgs": { "baseToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 6, - "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" + "blockNumber": 7, + "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, "SlashingManager": { "constructorArgs": { "initialDelay": "172800", "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 7, - "address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" + "blockNumber": 8, + "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "CiphernodeRegistryOwnable": { "constructorArgs": { @@ -40,8 +47,8 @@ "proxyAdminAddress": "0x61c36a8d610163660E21a8b7359e1Cac0C9133e1", "implementationAddress": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, - "blockNumber": 8, - "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + "blockNumber": 9, + "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, "BondingRegistry": { "constructorArgs": { @@ -73,8 +80,8 @@ "claimSource": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "bondingRegistry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, - "blockNumber": 12, - "address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" + "blockNumber": 10, + "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, "Interfold": { "constructorArgs": { @@ -93,8 +100,8 @@ "proxyAdminAddress": "0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08", "implementationAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" }, - "blockNumber": 16, - "address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + "blockNumber": 14, + "address": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" }, "E3RefundManager": { "constructorArgs": { @@ -109,46 +116,50 @@ "proxyAdminAddress": "0x524F04724632eED237cbA3c37272e018b3A7967e", "implementationAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508" }, - "blockNumber": 18, - "address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" + "blockNumber": 16, + "address": "0x9A676e781A523b5d0C0e43731313A708CB607508" }, "MockComputeProvider": { - "blockNumber": 32, - "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" + "blockNumber": 30, + "address": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570" }, "MockDecryptionVerifier": { - "blockNumber": 33, - "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" + "blockNumber": 31, + "address": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D" }, "MockPkVerifier": { - "blockNumber": 34, - "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" + "blockNumber": 32, + "address": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029" }, "MockE3Program": { - "blockNumber": 35, - "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" + "blockNumber": 33, + "address": "0x1291Be112d480055DaFd8a610b7d1e203891C274" }, "MockRISC0Verifier": { - "address": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", - "blockNumber": 39 + "address": "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1", + "blockNumber": 37 + }, + "HonkVerifier": { + "address": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", + "blockNumber": 38 }, "HonkVerifier": { "address": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", "blockNumber": 40 }, "CRISPProgram": { - "address": "0xc351628EB244ec633d5f21fBD6621e1a683B1181", - "blockNumber": 40, + "address": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", + "blockNumber": 38, "constructorArgs": { - "interfold": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - "verifierAddress": "0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3", - "honkVerifierAddress": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", + "interfold": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "verifierAddress": "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1", + "honkVerifierAddress": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", "imageId": "0x0ad904cfaec1eeefa9b89a11020086ec51454423db1fee3b1ab614fff97368d6" } }, "MockVotingToken": { - "address": "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc", - "blockNumber": 42 + "address": "0xFD471836031dc5108809D173A067e8486B9047A3", + "blockNumber": 40 } } } \ No newline at end of file diff --git a/packages/interfold-contracts/test/fixtures/bfv_vk_binding/folded_artifacts.json b/packages/interfold-contracts/test/fixtures/bfv_vk_binding/folded_artifacts.json index 4362b9ed57..eb13788444 100644 --- a/packages/interfold-contracts/test/fixtures/bfv_vk_binding/folded_artifacts.json +++ b/packages/interfold-contracts/test/fixtures/bfv_vk_binding/folded_artifacts.json @@ -1,10 +1,10 @@ { "dkg_aggregator": { - "proof_hex": "0x00000000000000000000000000000000000000000000000d40b640292037485500000000000000000000000000000000000000000000000e013a55061005862600000000000000000000000000000000000000000000000df66475045cf2145500000000000000000000000000000000000000000000000000006b402cd405e80000000000000000000000000000000000000000000000088dac7863b055694300000000000000000000000000000000000000000000000712fb7da233c478dd000000000000000000000000000000000000000000000003cf964c70e07b8765000000000000000000000000000000000000000000000000000038ac24735e980000000000000000000000000000000000000000000000064a66aa4d3608d2760000000000000000000000000000000000000000000000098fcfbe919172471b00000000000000000000000000000000000000000000000290ed9dd97921e215000000000000000000000000000000000000000000000000000302c7ebbb9138000000000000000000000000000000000000000000000004012de0ed8142cc580000000000000000000000000000000000000000000000087216e554f3ebfad8000000000000000000000000000000000000000000000003baa67f4d46f478d70000000000000000000000000000000000000000000000000001d50ea7c7c287165b7fede784cb79cadf6580912b2988fdeb42ce54e7f391bf679ab9a66ece7821eab249944824928fd0c02d5646c093a7cc8b002698234c635954b0eb13edd0079d8707715ffced9b59ead2d528fc6e9b0ecffec6728b8530f803d3d0cafc7a043f574f9b17558991193e7548639d892743044078e9b0c5a0597a413bcfcb662fe795a27f2c1b77b011fdc4eb28e4183f5862815d85ebaec975e8f9e07ef18e0afea638c43868fa1e25fa97a99eeb6d0437270d3e994bed93e1820008a99a7e27b4edc391188be914d9d9259d25828b5f4e971460619ae6708932b2cf0e602410899e95a26ea70acc3ac7ea8363c2d73149c06e14256363bf0e19751231e8ca1751822545b6a5994393b7a1d33419ef3af71f250e6aab81194b76a5c7de943b231b103523d9d9b07e0b8f9d4e9db56a08adabbf9b17d9c1e0945a3941379ce015c0fae30c484973db6068c9681f3d57d6ebd5a820169f6cc40e6e761c7d05dc09e051a03df59398cfb89e32b5f2adfa0e344a33e660fff996b08c89ceaf4f1a11c0530117285014e4b70f37e7f66dbee9061b6d1490044a1db6478ab1c83bc629c523f14f226267f1035b6b0ae5f7dd80875f6816d8121fafa448908b708625184955bcbae4cdce272bddb97dcf930b9b27a7d9fb866ed7a3bd86e883488e49014a2a0fbba849da7992682d55da80ee20d9d4b2c025867a7590e56d934557470b038824f651dce95aac46d204582a7fb23e3959539d525567952549f574fed80a0d06549577332ba9666fba17ec302303c4864bfe30a81ef5c0b5e7b5b929e91d4cdf3c912da2aa921c0a317c80283621b9342647a6e1e0d6d6371815803f42034cddf41d88ef437097c0cad537128ba24646d6ee133b0c347cdf22033413ee2e7b545f475e7e33ceb8d1273dcef965264c7101d1fcd72d8c6d80250ee190e516c20d3fd17ba136f1dcbf7f19b1538fa9476ce8a90527e41f8fac711649571a1a10fc31232a3e6fa123ca98694e2b5ba6cbef46915b98782f24070c1965c0900bcbfbadfcde3bbf41a763dcfc1b7ba366bd2ceb94456e642f244efc2033e7bb12f955d116cf1c177c29e80d544e0cccc36f5ab38418466b94191f0387c267ea2427805f1c283fc3b6abd9144308a78fccd63e0a3c6725756efe35ecc626c81309771693eefb56d05f71ea85cd632f17428e1b09c0fae64a3dd6126006462d9809beee4db34fe70bb3c4897ad267428750547cde8edcfee3a1ae1584588ab8e71b396e34821866574d4a120cb1300142d1bfc4ec060a2cf216399237c805164a11ec60e2dbad9cfec202e139a9a37ec628d1997562537c47a6e1c69b36ab12521cfa79c60ea974914538bd4b327171e2754d2fa4a1aba05cdea506ddeb8b0bc80d78d4ffa01927f4305743a2f3f3f1dad7cb96652482fa914ad41acbb2abce5e2e0e826c01a4c6adfccadb2088c9f41d04c2b68a793fca0efe33704cc6900f2416085fa06bce76dce6e76311ca08d5dd3226d78140d209eb32aef0d2fcf951ab0748f907fa2aab94dab8f052177f047f764caef711a635a188d82ead0b45e5a82745a02e9622dc0156f5569246c9142a879cbd5aea7cb5e6b26e88cf44b8ea4929807da246855d55d9e68039f3e4b7274683e67007b1ada2cf02b4a51e0c73a208d43d23f6ac91256cff142f9e6437c076b75efcb7d578a363475ca2b5772ffd1a1d9fb8353f4af4d0ae15a2f9d8320c2a1ce7a5ba4cfb9e885320cc5c11a49415a335cb2ae9e28346ae2d697c54c2afdb1bc813569566972df5d93d551570722f3379a24283d123d57a240382b10b5be5e33a4e49201d85200f6637cf593cd103cd4f8691b0fcff9b1f9a740279b56046f264b121aa0467d0640f481171d43f0468368a2e55a0d9a1cd5c035bf09c799b5931d285be5197796e0cd594ccee8b1838548852640231d0555f4218b9672e4d01d50e52840f0f18a48347036fd11d2692b21503337f42b31ae1aa00881af1ba79a4c4422fa20632a00395363851fd0e2c67f59c285d1eab34d4575a76ebc77d433681712e196684a0acaf2fddc4401b0bffbc1b8590c88682ee0cefe9a3c5dda489d01a2ae0183613b5bd9a1cc3e0297b9311850ff676074f55e28e05bc2c551d35ae3c4e95fe802ccb3ca84d3f231de83444928b37de079b260aa497ced448587529431fdfd7d395f70e57baa6b7283413219283c15b14be525706433e162e7f30aa177b7078d6da2ee21fa67adb15375bf599c207e38cce91d7dc0766d9224bcc32d863ced1ebc3420250c8b0142d4d32263331acb3b04381c1402381cd86f5a76cbf436e38e80432e516b4c86d2320a1aede18540f87051948680ee477668d8907a767be0bf4a1abd0af570af80ace408a697a201104e11e9167b153de258e305f0423979dc37be9e8be4d01511b0e89eb67264a8e65676a124682a356242f003d408808b34ebe19c752eacfaf140fa44e370451401924a02f97fb647fdbf9d76a6bfcd8a5f058bf9e179511d700f29320a7a6c2aa45ceb9b49e3ef68ac8863664e1d40b4f3805c0cf94ee859d12e0e9940ab596361cc766ea553dc2a6b924c7024aba394ecd8797f99ee41d7629cac872ffdc4d14225337da53af492a42d04a4892be7f3103ec1cd701296b842fc852842ea02c8868bff79d7bb5b8be42205124523f619dcc17505bdf7ba15903bb0cd9f3a5df2bf5b9a02f252385920c78387cc9182768a4adbb2e74de3bca2cf084826f4709c1b11d287de4814eb4e83198a587ec88db94f3cdae7e3169cf2328a74c89d801f5cb61de8c859f7805d8b7e2520ac13392e59784bda06b4bae1d22fc67dab449c57e4d857fdba747abde5e7da7a24ba2bcaa0ea10a69f4e4430c7d6c09fe679c525600f3ce2e1c4060cb28983b4832333d01e058eb4aaf7f7f12bdf214205aa6dfe8b4c6b5b61262b401a054de68bdf874d45992e358c4e37815a1ce0645cc90b1cad6f45e2565f6c90088960aa98069d9c4b7bff92fdb958111e534100bbc41390f4555362ce7ab9825eb22fbd4d9ea4db0bd3795318b12cd2de1c96d38d0baa60761771d3def691a0043a38dca228adb6736d9ac55e34c56044db1574ce259d7c6744cb87af4cf9fffa8fff02e3b4d75791f6d5755231db30e905f55b313af101d2065b895b085c69cd65d1523e27a35cec132932bf0937416b4bdb3f357117644e2351937ca6d05e5a9e840d5441c36f09aee5e0dc9618a231856bc58b7020d0cf923040d45535c8f7d9c670ee85c5266f61f55031acf4e12edc91db549bff6b8f3f650bda9d77627b3af9de1f9d5de6db8e998e87a8f672c2caad5095fec179a1cb7ddabc27a9a75f33327fb01e1242c779b918e227b0a23b5b1503b12a6729f9f870dde75836c097762b08f6615ea824eb2acbccbde070a2f64b56862599eade7ae5468a6312d27b79f6c6389295957d3df159bbaac760b8f0fd8722e85e9b9659aeec9b577439cf36618c680877d3870870f70d4d4251ba7bfc4fca6eb847eaa71f5144544854fd6c399fc2d18b5a5c05405bd2f179e0a889578e0c297db2845932bab911e76468fe4559ab0d6221900fd1434af038c262ad5104cc0cfc6d02b1d8f45374a84a9acee52382810369069e1db871bf7fd1e313c099d46bbbf8a3c4a3efcff8fef4915523311935bc3f166564a783cf1fa30130605b57e2803c7e3ac5aa373dec14b3166e6ab7eb4cc8fbed26b702651512052687a086447f5ddde111e16e5dbe1588719ba88ff3684266e92b1cbd00488161d933bd2a4077cbc73ed7a7f0f1d6d01caea02ead4928d6aed867d17c523a41059fa178a3ae010ea37c1261bda560576335bc48dbe1ca12c65003f5cc24e0b2082d8d67c62d8377bedab3fc4b4445dbb336fa0c043afc11477698ffc4465512d05d2bcd5f030bd1b84c7a10f6c5be35933f28bbe513ab3f18a43c740bf811d2dfea2e21a0b3865b720418ab4c64b5d6625c6dc156b428f8e7a25119a37da8d27b1133ca9c0f8b66a66e604e2a89b7f70e50f11335e40cee8aa649ec8be12b905bf8c5739e2425205ad8ace017aa76756a2da6eaf35a47cd1bd7c6540c3cc6d1407d2345892f5e448bf45af7377d229ac597ffff7ff43e0c39cbb5a97731b5b298f63adcad80ff024cc4dadb60ce5ccbc03378be66ba7ab7332d39149adfc2a0a9af606c4784cae59eeeaea3a96ced73a1afe40fcb6513e12a9f0239f19197d26ecc525c7858b3ad57349926e4fb4fc745085b39d5205928027f7e5585f195421bd609e595ba99bb70de80abc57d275a5149f283cfdf462aba7bcdc98d02bbf2644c7f498f3683dd927694e1a02b9827f76a645da15bbafce0d9b0294a2a8f3282f545d03147683a5ee21fb76398d2c66dd450182a1a1055bab5241ba157da11a8ff6cacefbed1f2ec7f6f3a5ef29c9003ec1f3a395e4fd17d552f644342f3d2713dbbbd0f81a7980f6b720e647ce0619dd78435170599c44f52877366b466f2051dd759ae7fd27d1c9941a2328fa859f62a4101561d4df222da9f20249531101855c4063f695155e6eae20e182d92843b3e208f5d950caffa49754668809862278fb0b6bb20bb911fca9f314edfe6049c679587320349065c0431d981302c41e888c3662240d7810ee0e9006ce4b725061666873a7f36e193e986933149db6261a52a383a620448302a5e8062a0e99e37e720564425bf514bbc74257c0151811ea75f770e26d75919fa3f3850bbcc9af993fba980871885dad8014bd61de782ebbeae1be2dba9c6c5faaae73761d0a23ddf30a3726a76e29a392cf311fb5cd18f7405b78f060da2f7667ccd91af53a7f6008404b1b1d292215a365741dc8bb220db90cb27a42ce95278a660fb5c02174e8fe4b872146592b4b1a6abf6dad0d23f9d408ca96df0b12024bba35f2f956942d1aa68daed91d2d9b7ba5b811b42d2d5997044e48dc3c6b7d7e84538fd19d1d6c6f07a3f6386e5d07b7554c6330d00993fe72cc27fdc482bde73aee8aad8a0c1a674fe9c80dc07f8142ed8747ffda15779aa83fa9cbead07b5ca8f34afa08f006defb94fbe26492f107574badfc081317a22554ad10ca7a09223b3a9c8440fe7eb8b3381989c7db31f9f1f1c6d0b305d3be09042a5f7212de5d680e2d91058d8df4de8107950e85d30735fca633d4278578f6535baca22e67d9e5a1dafae38c21918dcb8bdaa04896c176030cc934255e8a84baf084d8f826c9abc4b936b1920d518e214404443003bd108180d3de236e4e31063fbbe63c15fd875e3e8c51e1c0daa7329603851b07ef6416e6fd790f19b2370ce15cec25bb2e58cb9229a4eb68872ad054bbc092fe8347201518f4280eac24ec6eabcf3210f3892001e86abdbb7b012e15c1e539ef3a2a8d41a7a1112fc5e20a89fe3faf913be190acad0da906ecf689d2a414e1eeba10873b110d14cc312bcde9f4332a857b9e9bc12b0e5d1bf92803f516b51d47ee269af04ab11c7053ed6cea41d55c88d460a83eafc560484d1cdbcc3c98382ddbc9de7952e224cd829081f01b29e12af540a810162208c51a9bf9f812489259e47881ea9d871f2f77540287e3a52de99b651590547d45c89fc26e339abf13263863755f732b112416cd80f64c3b7f6fcad8b1f9874e93ed7375f004139a9fb9e6bfa2078f661c11f3f73c844fb43525e0c1fc428b2b89aea4a65c36fef3ad29e3e5ebd4983f1cf2e58ccd40d5f1bcea898e59b3e9ae31d5d16ff3c454d8ad9938f54b0bc35f2f169dc6a400cc793bbde44fd0dcf3c957fdf8605b1850140a0b48bd0766e7421dcdad2ae3f917d125a0fb0f843f6b62d0f046d37911259e3e9a3745138e7ca629ee29e287c714e3837294849a47c42f20e6ac74b07940e7d42ba6e7711764672bf5e0ced486385881661cdf7fc0bad18b573785eda1dffd8cea52e1706c02be09486b233f886885c4a20ce9c592cf44fa1b389af571ad7c95e72f8bff07d43e176bdfa018c7f722c9c860c70246325b6922b4ff045828e0b553fb4bebf827601fcee22dac4cf38fb87b6fb13e316b9d007be667f7882456e806ca49479028062c6960159f49bde5e5211dd8fd000713489cac20b1b9bdfad44fc9292edf11b4186796d5c2455297c9bf3aefdf41b169dced5398cdfaf6c71b1422c8374ba40912d19a6b6765ca3457101caa863d21911edf345b42ce8610b4ea4bb1184943d51c6d8f5ffd5a8ee22a980b1ba379be11be1b35dc366d4f646c9e995dda08bfb52457854d4ef3628598fda31c9c71b513fc50b0f52c5607d16d4464ab57fad4ed022289af86c813747386ec1b19bb900cdb368c862a851541b3480d9d445783ea0419adb37416d3fdf66906112952bcf5ce14dc0afe874d3a7d9f3eed3a1dc9fb0a881b3d22c1b9537c0d54448b252e02aa873d7cce22a8075eff0cdf83b1977f1c335852560b8c49ef9a3ae1d38798b17e81e77d2809bd55cb7e27556ab8d27d2c7507f1e664680ce460bd6e8b19dca256ade4d65eba1d75200307a97785a9fc20c84d83608551ae6a6d5af2172b04a9560e47fd90987bb14890ab3fe786607c17ed5213a37d661dafe7de8f107363355c1de7ffc6f622ce398ba845f127233b2c120c8386b86e4f49ec6ec694bdaa95b8141681393d1998db9da81e082a18422adbc9ba48606955f943c98ae633f846c1a86a226f5b12bb59eaf43f432e3be6110df3d8bd26a0ff66f09a3c63a69309bdc3cabf2ae71e7a8b70ba7676b8a1a70dfd6de933dfae3fab2d67c3282d8f3c5067b477e46c80372778cf190710803b2e951f3369ebaea902d4559485ddcaa647d17d4083e017af5390ca90b32066472bc8fa47822c331caea9d551c96fc99eb9c15dacd032dd11896eeff9de47e88a0a9a74583ea6e0f2364c38120a9b4ccb6bf1e776a41e9d79ca097d0fb95160bb093ff7978d2ac276d93a1cd8e35e8e65fda7d5d1f1aa0eeddcc20b3777247bf811174b750b8b36c0de073e3393fe0a8cd71d9367bdf9b431b9aa898cd1015435101ec5802f0d608f8b6e73730eaaf9237bbbe9cc2e627a1e3d6bb012913333410697506a77dbf86f258fe7626738c00770f395ee096f01d5d6c61ff310684ecb16e972eb20234f959deae2bd77a43bf0d2e77a4eca3a27dfd1fd3a6d26d3dc4406dcf993aac3f0dd29bd64107f4479eec6def086d0d13e3ef04be1522be6afa0166311e191055e0b47317ebdb1c715560ac3573a0a484380cd1aa884017331a32ec9542cd657a6db0a573bd14989837ba91ce99f7da2bcd5595d2883cfcd5dc82add6e730ba39e41aa4182aab9251b6124f88e5042521d47a9f0e74adf5d0af0219514b2b36879cec67d23d7856ef85d93b0aaeac5a4e0a40ad0e70c848b737117a083dd9a5056c402a8aa4df6e636bdde0afc2108815ac3c865eabc49b088a60889d3c65f322d68ac11b26a5046d9a7fe3d229fca7614e2dfd6f38466834fa909370753af10e464a8fc648e03249895b862d524f8af9f68b37de6125e6e75c30ce4eb722604dac1b1b4d7564c11d5e4735be16cbfa53ab4d0db0def3373ed690899de270812ce3b89359eb92962b32467246846ad9f55ea695d1b18e2735fc7210aee7a94555795d267f76802117cf04507137b03fea99f0e760f9ed5518a752cd5efce3c1d40b3513af08d8c24e0ffe15d9688c12e781bb8bdcf837f5811f30a91fecab860171942d310a512ff8099ca5429a40cdd18c2008b353b3b70e74924cb1183c9de09b0629eab79557b9812e5570ad31cb1aa66c4cc89845500de602a1ab9a697d09da65c89faef7840004d4cac64c321f7ef83ccdbae89eb6bc4471d70d7698d0e5e435bb5d2f7db6061bb080e1722acd4d3ce37f25816a36cce60293fa06fcb41ec2cdcdea76d0148ee6419ab7c56ef432ebf0b3cd767ac5ef0e8294be4b435c739565dde491b7eeef6a2dafcb9345d4fa07d8c6afd28afbe44fd2651777f1a5f3a78efe4e0709870a58e5e4f33e0e3743e3d671d0c6e1e313f961ca9cff93e4f8a2278448ca0d7b41fd17855319a24b59f1f3c4fe3212e97c0eb11dccc2026f1d50380c36b70334ade800d04a6448ab539c21fccf068b642085505bac178f565cce415d7a074390bd16c37b9a28049e8d112ed1a3c32ac30d15f1272bda3091e1dc8b51b3b613af57f290703c505b36fcecc5a066ef2abff4c1f2b4475402aad821572534988b8c05b3f097aae8e82f9fdd316a5a7016b859b6202c541f40ac7edbda0b6b1acdf65b761b91a66ddd29417c8aeedfc816203b9032c35e6a0276854b679e1ddc47b41a21b6d71399a61fcf51f097c08f92cc12c291fd2dc48ccb0de0be133b3fda6c6a9bc04ece8a8ad02229fe99ad035a28189d82f35fd133455d9066b102b6748b9d4c2bd8eeb6e371e1ffbdf4b1a6c29a3a0af020e5447d7bd790db03e89fdb9d262547862e6377a32ac02e0b91d2ad44b992f1d7db08caa3656fdbc5f430448877092ede59bd105068b6d8a8bd142a70894ab2b56cfd0292fea152bcbb1adbb117ef968dd60c1004511229e2180c98abe417d11cdb19d8b157a54ff930a2466a77ca6ddbc6f2398f4e493e6b7c097e6744a9915147beb465870e736dbd8ade86a5e04a3db47f24ba6da3f5654560976c5b17108da7ecba21da62f07243db9ecc7a29fb0eb2d28cac24ef4dde738262eb344fb1f5a778138d3239d82f1471d79ee588cae6334c52bedafbb4bcebe375af3054409b9dd53eedad4de84465c599e3a1eda867e61cf569a91af0a8e2565cc51077803e8ce6de0303602e178620ebcf84f3db7a24812d0a91941d231c354efbb7e0019e71ff8d261ff37e057cca2d17ac2e5349433957d92482a268fec90364e0615020792754f75f88e1751288ed857ab891e46a9cac9359c0cf5cfef219d8ab0541f2914a2a6aa019aa28621273067f5902de9d27e6c1ef74bd7f804723359edd022cee65b9c003bbcc30b6f6356d6ecad3965e1ad4f6000b85e7f7e32dfc1bc6b189d93272340775e676389912e49f9af4a10084d8a75135d9adfa17355b90e921b85717e5f723f1b3e8bb9a715c4b4117ec1b26d98f049bb0f542f3afde7fbf724984c7fa83dfcb6776413479e3b15379d5a60abb3db4d713d711ea444882fac1c273f60643cf18588dbf67a37c04ca5e4aeb4a5181ee696e48f33a1b1b8cb231686d6b58eabc9155b6f7bc8b337db2e6646cd305e0f95fd127432f2f67e2af026534963befe4d398caaba921a4985a51a714de7df682f049be356ea06a0062f02ba1a5ff63a970e15746b52bced960e42fb975d1f0f245904f307a07721416e0deb72a9420179cf205754ae147ac0c1d83483d7ff9db4f5dd528b409be937e929dc59c6cc9c2507616c1f1b74d6c1837a325055f480d05403e240d9ac68229f27d3a7cd095d405e8a241b818fd860461e32a183842be658cadd13e7253b2f0323bda80349145a1337244b58a19beb039480ef9b5c8869dc1778e06fda4485542954c7673c0dd74ed185afd0ed95f3b2878758b138241d848bd63acf9114065f2e38db9edc0503bb0b8ee56c651216441b7880053ff22c93412eb6627b5146ff164934d8690c715f3ee9f3a860be67a8349f7edd84d1704cbfc07a784b106dfe29d070ad43834c309a93d749b52d2ae801841ea7e5783c16ba497a8060617c2a2815f8e1c7a96a419ecb11e1559121be988da7a6c27bc26e65e165e56162a1fe142512dca4b4e59a540c6fecc77b6a7381910399d96d6899824253ea7a106f9c10a475b11f52fac6ad0ab92131cb7648ad3531ac236f8eeb42daada27871af6b06d02e6fdbed16a2ab65c1cb726c47497f235fc1f4aa4c6bd712150c6fb7ee81098cb22f4320849d701bb99aad74afa8ef395d7b105eb62bb55a9ec69baf15c30abb195977557b71b92ddd465370d9a5a4b75ce32fb6595177329fda5ab42df123f412d24f70a495a3c86c1731ef55c38b14352ef2579b6fe98fb4da693fe7c10452aea04254fd8833605ca54874b751da26650517ea0821a196ac7b66e10bb604d8ad90129bd940461a0c2060f4799e9bd70d159594a704d171dff7587df5161036e9669db09c1cfa469ea776b520c34e383b814c8df6fdbb0780fbc272e5e8032be0857f253f2253b5aa9445a0ce0e517d3599d810dc9733ee4828d091282300917a658e9a574d6d20743c899c8885d8cc7b23ae994960341872283a9d4da5081a1ecee62447a303627a1969e9ae73b2a1a7688d8541416f49f96c975d9cbf06df0ce599d84803e8b126fa6fb9696fc9099fb4341ee10c6011506f796e5f52286a516f77ecbaeb27c9fc0cb693fa24e06cac55b985d62ba53561233d1ad53a2d543782a2f4eb70bb7ce83b05a9e062077857b2c7749e3dc1007e845d15f8be24eb493d1148b440e56c0841f8e2d3c6a442c8f0175e07b2c9cca522a7e3dde3105ca00de87c294517b084a1780f9630dd2f114d36f149c02dc92693c5f58df80d052c005ace253ed194b58606b5cd08ea81e853f09d4ccf57d9fae5aadab68a00d6a792c01cebce5c049bcad0170884dbbe0121ac65a43340a0547a124f13d3186a6406fecf35e4e2b64fc1782ddbba140ca62ca9f42c59a607425dbc365378095e4f7019e382f9195ac59abd6857740c46f847a82d50943fd2cc60564283bb20452f1bc58eb3f4859f77acdf67a7be589907845303cd316cda7f8c37de528d1a6e7fb3e3aded9188bd2379fbfee5e0a5d13456ceafbf17f81064c5e3d7511700feff703430e2c652d235f3938cbba7bb05499f7de8c3ae607468d69e443756245462ae438bebb5529578130a6e20ae725bfd8d11858d6a60404f709d6080950583427472332603fd017fafc7c088c316e336c7044c79731a335b2020948c341cf08823d04902110e4f9ea8abd1f46bb50a0afd1e4462899e69bd6ff5d289fa13a052c61f50ae765b9271f5b109929a59d27720683335829e7e3e05e01f8066213f99cf2b030903f5ff09e48bdbb5d916158df9e50f99727076a645fc515e0c174fe49fb24e33c79c49be6fc3774520812b72c5b31cacbbc295e35d1813da752020ca170e31fc3f8c34a4e62a4edf4959ac76c316f54f30ae118bbee4d1c8c01c65e82dec54f4702d8754f7c8c05c58b066f1fa3492406da04e163fcc17affc1f1da2b6753203348de9d462598624dd2ccd4078b699b51bd4e6f28a57ab86011157f4acbd8f16ad42211481ee64e4c83b92d989f94e8bebf7db23304fe512e12002529ed325dcff472c2c80c5b189141053804b55f8a7696657b34cce04b7371fec396ef59641d03d83aa12cc633eadf7281f591e70c1479d480c448f1f2e372839151cd1a94da661d1f15104e2c22cc1292adbf0bca846fe2d4103a7b0b32522ce9997385b48cf38fee7766c8a8936a7adf660a6dcd2153bb5da514fe7b5b0144abcb0142860d4db639824d35745387e8a8e95ae5b0beb30a13fae28b7c48022e2583b07d03788848ef1f114f04834785e6304e2b062c352cb5d4d12cb331421f00f710a669aba85299882a43506c74b318f518ea9f0e14ff3b79511669b4a21ade3543dbd159a5e9514e42d02ff723c6717c8e0d1dc96b55ea6e4972aa98d2fce3c1907bc15c676937cbf32f478c66160dcdbb1fc4077d713c1f0ad4aa47214f5588a06d6991250f5d93342e2d84c50fb310d8834187b9e83a5e3e230a1b31353704f758a730bd6446aa9d1419ad87abd6326cb6db4db5fc7e0943b111b4b23fa577ce023391f673542e44ff057655b5bba09f3b5486cba48d90d7a89a8800cc4f250d9ed664fba635986fa2ddbce7063c11d5534abfa1918e527be32c77f1d506d3b821e512bed75c3831880b3315137536487ecef20c2b64e0f7b1064810a5a0f74d19b57d8409effc46d5acd083fa4f1d861c57e634f50750ad0f574981b32aacd0f657d61d28bdce7d06b4cebdcb8580a289e992df85f2ebdefcbfcef16e104ab6be6c95cb6e32dd0e6711b49186ec15cf3bed20a8c84d775031f79a02b145b9c08303b069ba0cd302c60da6cd74bcb04024f29855f2d34c1f6aedfb50ac040f7ad915bba39dea3ed4ab1042cb994c214740267adf97a299f35e597b000ffe05aff6d762e31a3cd90378c7c075982797e869c57e4dfe7702fbc5f74341b40a9287c35b2fa1f735de08204bc3ef84bd3df1c66b28f38300108ca8bdcf72c3ed1d7905044f754fea08756d150f6ac88bbab31228e40e0eba859fc68c8e8118b71069af14fb780bc05991194886472d0a44bba06024e5754763fa1e261941ee35cc2775bd35a66c65bebf7c5e31f15b9d5a7da70e7af7b977e057c8fde371dc6046aa71c1c14e2de124421be04c8111172efc4ef9dd4acc571c10ac5076c0a249d87a01a2e8888d6994aedd31ae0966558438d2f3ba0de1d29a427b14487127be3c66f1d3b1cbb945544767d4e95d56972fe5d7ba34b03a4f92ea92783932d128c1c4d0d056abc8f38c50eec5ed87798a594c50e6747bc9f308d9ab9baaa052f112758734b371e6dbcc2dc8aa2e60bcad9d001dce3d02de7b408c977c30b0fd3c6d530597a9c5f2b646a2b0f6f37e837ee8df3ce4d643e9048cc75bb6f6f2c76506e23f035f3f75d43d1a1b626f0f241151fee627294fac95006070eb967150fbce7e1411849762d1a55e4530301bd14b0976102f04b901b65d450ad4325289bc1c0b27140ec24f729a3950b242fb4aaa303cf377a8c4c0ef81407dcf3112b28ddd33f3dff292eaaf6ef3b1ae0743ebeae5c5fbb7780effcddebcd31209215c7b6bb9f5a53b98086afdfd7a20d791a37f8eb10fdfa552df4c019a929516b19b4fe75d6da7838e2afd82feb5c5cd6f46633b6d607eb9c08b032eab501565c2659d526e90a56a650b568bac4d8848bb16379677dc85e9faf844210ec753cae13762ab84e11541047f1536dbd56fd71c6fbc0575c1a2abc646db9b8268ffba416a5a26909f67da15e1772ea1bf516fe534c779afc8da5484b3caa5f0660381926fdb69d5d7e284f884c0a639c09a13356e417673c80cf5ecb6af911c7d58c9a1869705d7086dc7e897e1658403e7ec186d036d7e08f02ddc2be124b33ca9ab905c84e0f126e1651af373d3f02500262eceb24081a8d297ea3543cd9dce1f570233e05b9e8f747717545fc56b11bc2b6f6480802277fbd402bac8fcdbe2e715b206abcd7b45ec5b4afcb43da465d114785782efab654df1d44d8edcae8224b1407610ea653938dec4ac4bf3be2f5f78924a7132e9ce06f70e63f39cda0d37c1d06c1b21358eab23523f5393f9f0fea21dc073d27c99eaf5b22e2067186965e6e1e9a9fd6a6ec83802c06e8ede9abd8980a1cd8ba99e40cbccbe7337513b3e6c81df310405a333252c912859253613a95c74fa76a361b8553e661b51697e1f6910d6a7b89595b193ff7ed1ddb2edc081e3be310f357643b69f069c9109451e2ae3017ae984a8ff7467a7d9eb78b2c57a9485426fb5fc7bc9c356720288c21c8430ab70bd587e8a66af28e589b2d52a29b4d3e3608844d437c0ecacea2fb54837a0b5311cd793005cf078a8dbefdeceb82037176b5b0f714419b179066fc42e85e19dbd2cb19c94e9b33dde9624447c9aa6703d5a32ece2d36959b0658edac44dd0b7cfad9772bd459d3f9584775c68eaa3753bd3f0b62aab4364b122577484a41070e78095d771b31324ef881729d127e022f1725d3aa3be809fdcc84e29bffc2201ca5fc4ba88c7292dd57b294440ac99ef5bba25b677ad94aa490cea9a1bf1b00fb2751cf6492cc61e9bd683fe4173eae9c04317398446b11ceb90d83054f4a1970de0a0ccac63218601afcfdf9a69f49fbb6f0ffea39e15d515d33eb369a6f0edc03905488fba74cb5d3c428cd5347b6d9b0d3d02a7fdaa0bfca6cc97b164403cb6267ecadaae9efc05ab3992a2d58830fc5fd32bba5afc4500d21464925b50844113b0ad71d0781c62154ed6e9367915fa70ea40c8e061f1045473d4af5160f5180b4acd4cc9f68735ee48bbed6c4e572b3a77c9b9c82cf85624263c74cc80453074af8b24b850eabee47badd6bee6e5d3873ea26312eca837552ec8d3c042d9bc2c72c9f5956c9b845e3ba5b4901503f5dcc4cac00ec530cfc0036ef92be11db406234f53d6ca59aa9dbcf35d38a79da93b6ca7699e1dde4b2e1a37949051efc64042770efaa8d6420f52712533bee9e928a52b365025fe30d9f78375db21f5dbbddc49127312827bfb00018ac0e2d5786f59d588e7a47f0c52470080cf309859432f45648ac3766443cc411877e0c97667fc1b035ae0891c9de98bbf4e01971c3031b317d0382cfafd11bcaf151825bc8a1823edf5f9e59ffe612fec75f1dfba34c8b1e6ee61b09b85644b958d8104131775438292349be8a08b6937fb81089089fa26c5ea6ccae00ffd749e739852c949702ef72442605f3e73feb09161487ebd701dd0c58b7ea24dd0c18293b1cd43f0a9c42011cf71d3be8f57084390d70475bb7b72bdfa2c703ac5e7f1a80f56e0c0f817e4f07db8585b5aa84b43c22b359b6b6e8d0646dbc3d528b6fa19a3f802c9e16f3ad10b04c4b39458bc1710afe1ea5173379a6887702810b0b972218a3c47e836ccc261073d0ab07592cca1668ea695ef8d4a62eb1c7bd122bd0dadb5bc95313851cfe097945ff086cc532", - "public_inputs_hex": "0x25832c071d6d99ad42c7b885e48b10100726dc0de03c0a779b282c519ccf538a25fc312a5f0009edd084811b739bcbd529987df1cea36f5a245153866625739600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000007ec37a8a654c78ed3a8e7c4d6e933fa600000000000000000000000000000000520f0c28dc3d0e79e73f3f0ea8d8dcc02208484402633db0cfcb2af11d3b66ff07cd7b6b12c3a35e9581183e399d53f7195e6ee0539776806e913a0259520d29c75b0f8496a9cf04f16a674bf1ea4e640d0a0b42b415ea987c680bb2fb8d9891c2b7934374e865271df6f818d1b089c32f3529d2257b7810036b3d044fc8c3ce879ccbe2a182cadf83c8ffc73f3bdf9c2d81dfbd8a3f5f3a78af2e6f5ec2028880040866451dd20e92f58bc0790494862cc39c01748d1ffb677d7a2df15e2aa5674d56359e30c82c375055a1a837a49b" + "proof_hex": "0x000000000000000000000000000000000000000000000001e75e6e6bf316ec7900000000000000000000000000000000000000000000000e5e66538093519ec700000000000000000000000000000000000000000000000ea1d99185bf57a67100000000000000000000000000000000000000000000000000027a81f6b520c100000000000000000000000000000000000000000000000300a975ba6f13dbee000000000000000000000000000000000000000000000004a477a61f80a9e6d100000000000000000000000000000000000000000000000fd219fd84fb4defd70000000000000000000000000000000000000000000000000002fef0aaa80f5700000000000000000000000000000000000000000000000587f4bf0b7f2c5edb00000000000000000000000000000000000000000000000e89f1f317a49cf14f00000000000000000000000000000000000000000000000176869939c2c359c0000000000000000000000000000000000000000000000000000129747f04f15f000000000000000000000000000000000000000000000002d178d9b11de53f8f0000000000000000000000000000000000000000000000009044587fc21f92c1000000000000000000000000000000000000000000000009fdf61347211a781000000000000000000000000000000000000000000000000000008cdfd42e77de1980c43e1a2bdb19cf0131ff4fad06d6f11d7fd3279bd66410f2388c088f70a31e30bb64c3895db6a3301e9be27e79c07a3027013b0e68eb9f15025acf14e1972ecf23db1d3f80e4db8d3a8ce5e61a085775421a4ef5a8e770f626dc0fce3db71d0f0ff62f59bef95993de4ff0fc655fba80d47d6dd49ac6a99222e6b74f833800c35d744198e39965e71b95e55f6b34b670494a4279fb653f46cdce9e7d9e910a06fdd4d860032388619541f32f14d021660e8fa5497b849a703d63a58e930a195f63db46d4043c35c1fc90f21d2251f27e90874b191252eea17c8946d69d060959a597a665f3f225822e7cd537ed3be57beb9ea1ca04fc1baf6135541631ab1500bf70062fecf66545666e5f092e35c6655aa6eaf8d8bf20727575f7fc6c2c26c51b65b1a2161c60550ff91579866608e5f84a22bd9d525db9c3c0910d5a9d257d8b9fb3278bf7e3983384d4569ef101962e973fdc50af677e61249f7659180a6357f6d55ff9c3d0c3ded3bbcb37b1c49d08a76bba3d8ade40e097310dc2d2162b769f4d49f4a18af470a46bbe04ee29c054b4d644f034bed1b330b7f8624916ab19c29d424d52039e2f724aece13e882b30e012c588d515212a73f8539ef1167250177895980aea7471392e3ba028ec0715fae1cf2539f351dcf30b5d48790b4c6789f819bad95e55d1789e239dbd28d7cdcc56ad0bc1359ba6868a569d5d0bee03d62aec658dfd96444f35a5254fb150e5273acc5c643f0c6232b71c27d7109e197faa332c23d8b7c3f590a4a9972c5dc7f072121d62db7e63389c59c03726436b338944ea9825f719c9763ef1c8633e4ba2ee18556f162d735ae3c56d9e09e8ac442f6d947881166eef26a7b6fa538cbd84f3eebe8dbb6853e7399ec4520677fd7801b63bd7fa27430251fa14ea07fc97371a20021362a971be8ff709462c2e2be304a8baf3c6f57cecfb7e2dcb804656a16b3533262110bfd6bb8983cb16f23b3025152a5a67b3ba9d5f073fde73e67ff05ee11a29924d74412cf1555709498cba2023bf4d40d0fdbc85670c51082e006b173761c4183d75815fb12cda2b696fad8923295ae21e8958d220435e0a1b7bed0da50cee047f64c599848aab17beebb6cc9e802e65596603823a70658891f4d5da2d768e5acba89a796f3b8b3034f1a2c01add7ccb7fed20cc7068b76c2738cdc9f4c0cb7b752dba7393173a28f16aafb2f8423dc1c661fe8bfb5339cc45482f1df073b995b7e97f2a6c2a5824adc1dc1cfcdfeb2a64daef86e37da39bc9c8197716f12be4903f3228fbaab02bd7959a5ef295739062e15917703d8fd32f6a2a7c7667561282eb80fdb3fb6b2b123ef2586fd658281d49dca1b320e2b10851631c9b11f42550bf7a0bf737c3005918962545897986fb73a7cfacd6960fdef24f2a08d0d9909425f1b401e0572f6c2d7f7f1b16621e22948e190ffada5611905e724e14031cf80a38fd2431500edd1c857e0d04bfe118ba0e6620899d7e77a2d87d2a74adcca704a1b2c0186526acfe031abc4da8b5acfa7d50b2c69790ebf6c47f70cc3f43c6984704592a4e28316ef07835a8b598db038d52417ec5f81bf7f1b3697f5f1bc45e95a34112300f0b77c2213b2a827c30939cfd9282d84dcaa386c25bee8d7da7ca98b02968b31e1c714facf0d484cf017398d694cc979513571261b7df8c6b2c32a28820004f27df14770753cc82ad82fc23820185c486444867a8080bb57075d9c3cf32e23d1d643fda915e0ad53332c302f5e2804bdc2581874b0104eedc116923256237f41adb04fe2fb7c053b4716f2bbfada9b87cc1b4c22af9b77248e0a26bb622c3521f0e4244c66a7af714d4f840273335d1aa8d2e3159cddfd166e3d567c9268b472dc2d46705f03557130da85c7d69b53de20961e3850b6e8fb8591beaa86d0fd10b765c64e3a18d3a3171538b4a236aa5a0598d8e0678617a8cb90c62c8f941010680b196b8fefd31a1948ffda6faa57093c0e5bdb15ab793644f278ceb0ff43f12090bdafbc4674113de9a445ce1aaa725587a8e3d5aaa1ba49ff4930ec7447e0dbc9bfbdceafd180cfa38214128df7f02b436865f4ab2e5dc449557d5386c301affca4aeac69ec4f8167d8db4ad57d0e4ea4b4e60ac2f62187edfef5efbc7b2009d2829cf84a14526960e56b022d48ede9b144f6440242904da9cacb7f8db180bbed0140d8a53ef6cebbf37bb856bf73bd8fa5317e59044f72aa111b65483550994ca20ca1b0b1236fc724c0863806e5f7382ad322d66b21a8e1756f01ad84504a8c28f257e89a6369320f29a2369a69ea93a8a5afc6781483cbf31c33091a400513cca8308b55905d0cbdb38a453c8db8833cdba8c6ae2ff057fcd60e43a561ddfc683a046f7584dc4e2861eb9fcce302bf211e9693589c6e77a11cccedefb2dd16cf71ac62163775ddb1c3832a7b7ec669461968961ceccb928d387ee35e10a040c3f1cb63e84e1d359e36fe76052c8c44d4c7448e50cdd148dccfd5868a009d8b8e6fb0ca9f7e41ff668cde4ebe82df366177918e950849d8107e9a338c822b015efb43ba64949f8bcb267a544cd2e2485d66cc1525ee4e54f0d3dd0b3221e77b6ca1ad4a95d905c06145fd1a9d2821708e1ae3000e868b4f4707f38af1404d399bb89c3f4d4c4a5730271048b5d96072f147d106ac16cc13f20367e67b8142a20c858c21ee4e5e081ea05813323e4b76020518dd83c4e3eb040aac1ba862a4870e2f07eb53071ef39daa2cd981381a6adc2fe595944b16b8f3f487a870f2dfb23a1114ebbd6a5de16cc1f0fb09af8d174afba1902c520a90117ecb45afc04f60d3d3105d013af7696668a02aa56e31ac9cc3670265bb8a0d2103cfc3d072cc2af914aa524cdbf165a4f3190afc28b5810774e6c6b77669f24c3b35fc3f4119e0006c49411d8ae9918d6c3ff4207bea8185e836c24ba66c4f6c6caea6d341386396dde428fe86014248ef438f5ab688a18dbb82b516e360a706f83e414f119573fe6cc28a0d6d52ec6c133bdfcecfd4e547d43816e6fa064b6d7e93f532825ab2a81e8db45f8efb64ae9972a7cca6a163df9f2033de5a2b79240304f782d2dd93fa04491da05424e25dc8c6cbefcbffae80b6c8e792c0e5f43edc39dd7df23f9d0f14541bafae611a59f5c8aa26e03380b723934162c19a459608d98b347299086598038e2a91c73b7e0782ce39331f6d8512fbe07a7fc7b08c9b98f35082cbe354c1fb739964ac30f61d4b1f71d08a8b6a196574934238a87028427dfec27fc4436566de283beb42dbdc55e87188162e72844fcba35e452d654f06d80840a0f19ab6ecb4bab013f2e50d3069b47664158b0c63fe98f22dda6ce21cc87ad039f328fbc56d4394b0c8d0c42d6acd596912931ed363a6054458ae54789198e2d3bbc4b536f974d0b8bd865e9909e2e5bd297d45b64da2dfd9ae5c9a3a687c208e2a4ff1134488d967ddbc64b3107a9b23e7b7ca83402be13743a67d66c9a5e18418af5a327d3d8ddcd5e15421465c4b889bfa0e31c1126a743aa2f515d3c8b2a2f5598ad35c7bde623ae2bd70be128018bab38332186f3c8ea5b0ed790046421e604996d3d77c0257ce4598a66ffa6c39c755cc330dd0f827298ff230a11502223b2d5017042fb406f1cd386e642521d9f761c3c6d99b16cd03b8253982d331303a9d35dd122d9f49cc010e33c00eb51cf199abdcaf56f8648edea566a574d2e69611d85210c46ad4702245353f3616f899c74847da4456bd3fc09b84d396e234d15ca8b10caf34411fa4d9b16c44c0ae469bdeef548b7ad3a26c772810cf225a4af0997e2f4c9145a0b0e91ab7a1cc519dd271a6c3b58f2bc45c24a790bcb0bebca54fbef6125eb192cb92b1fa3c6bcb0a61c73d1efbd57d138b402953e5100ae1c1e7d107647636bee7a9309e1b1eb66e1cc1faf8ab5e8f77ddc06121a3f0244f4f4304b06453fdc75fa41db3e1b15bc1b04256ae0896b00f3268f67ff511f363f7632cadbbbac7997eb2722ddfc9d59e21c8d842b5251c4d3d76aed8fc50042272b8de59329dff728a309ff3d13bccc8913f2a664e2ba6fd8bdc0d0cbf42d453ece719b816784dc9aebe3e3e9053b6c22d14aabbec84bd9fd670e1ccbe7055be7145f3995da1a2187f9f16cbcd1b3d757624bedbf93346352a131c1840f187b4964ab692aad4d6107b75b2d22cdf9efeb5536c24a0ce84b4a57d30b42262d404e0f3178cbc291d51a96ba7ee2f7e14025059d8ff407aac7d03758f4c4260114dda8a818c704dfd406e98d804128cab1c04f35bb681799ee4a30b656ec202fb62bccc933a1416ad7b85aad49eb3180167ca52842b89ba82ac83b8d6554950ac7cf180f14c8bf1fb3b4362be359524d47bfac31b78affb5dc5c6d3d3026ef181ea0ec299ebdf35bf046ad6e9c45b6f18a4010de317e26295723ba5743fd700d3ad9a07c060be8eaf1c903412063aece07c05331fd2dfa95ef1060d2f601fd08bf69a7b0a6f2a4527320ca4f4969abce4099492c007166abe37419d20826cc1f0a166adb3e36c8f5a8e072a4aff8ca9bec1181beaa2b87e9a0f70f1c18c2db10c751f37041d1362319dcfb04fc5793083903e75cb4b510b777e81fc4ae7e3d27d853dc7a2f500f08e29ec406bd29da17b3c2477fc65aa413c13ae19d66f9b120edca4a240d2c8848d9fe4e33f695538043af26e4f6f3f19fed0f13f01ec1f103eb9cd40eafecb1d2fa1baab9e43ae7d150426827416f64a93865dc1f97b0610eb497f1d8364fdf8cb9a23478d77d66aa4a120752fa843f0c7dd4b37ed3d3d8117864f2c913d928bee9f700024ce3e597abd468289f4ba5acf754817f751a831e80aa16566b4f413037a9b7fd9fdd21400a23091807b764e3010226586b9ab929c21f256d595a95444a4e09cc4e7a5ff59d047ea54260df1d4a7a603a3a18a108984ca89e76f6ffc7369eb3215e572e865e2c05ca1c108dd3e592371a7f7bb626a0dc75a6b3d61c907c9d3f35895b219ba5d50f573e739942737cacfcaef3f70fea9682b9104dae31342cfafc5832621000bc409403687fef64c904c7d4e9a3249c19a2b07c46eb1d0667837580a380a5bf8d4501792b51fdbd8e215e44e307251e8b962cc1c1cbe05e792db1e0d4870bceacd545709283af3a9b5c3e917c690e4cff620d02eda82401c1349026a6a9809c49f899d86499d1c7900523454b7623569eeca508db51e54938b1ff954bd5abfd4b2ab5c7f67e7a11adb60119a07f0bfdb09da284b1450ba5b230fff60c7f4be779eb8d3cde7c0580fb5205f4802e0b8d987dfa249b777f42cd3d213f5945759a31c79211426f0bae9a138c9b90892c8f6912ac582724062ee8b6192acb157553f51f6317fd6ebb50af0e51419e6f25c2fbeb3e2814a21016c8489f37fe016d82b563409565692bedab6449f7095c24e77e51ee36c1d4daf80c9ef363a2f9b3673a0b3da7ae46993667adfabb47d80d6d7a3d15e84d0c42cffbdf6a2e75dc2815f6d5e0a1f64aa7d3dd22a838b11f1f6293522395fc29b60109bbadcf3de1d3a461d75131580ed56675b7648064e12626c930ca131799f18d1a050b8f2bf5e84366b8ae90d4dc77f15e98a84a3a290b13dde5201fa4ba401b2959be9d60793da5cafc8c11cf3e891aea323b5bf15d0e24a049c82597ac0781bab9dd41edfb58268a690625911be4f01c7cb94d934c1b25176d969dfe9b34d6fed67f9ae47d57d833d9af528dbaaf91285ddc45eaf9122dbe16539cf24a0c3dacc44ec98f8d88ab7adcb74bfee1a543c88f4e2e62d022541213eaeef891e4debbe5ea26f90f3474e55099d4b7f5ee86eef7772989fe1d1c25d9420a2e0d332e755af7b835a05e47a493edbd291f92126cf8f1855f5c1a244c568838828c4d494aaf5b88e13acbd8c3d7c4be49b0b3fd8b7ab4c9fb630e8afa9e4bf719bf5632d8910b7fb79b4cbba77f5810c1e6fe7401b5b41b91c61f9a5940657a8bcbafcf34afc0e9a434569a4f1919307a325dd86c8c10e1aa2a133fd94a58aac7c3be622a6f994261eac893484f9aea9826feaf71c375f073520cc58a69a35ceb6dbb2c1c65df95947118cd406728a12eb3854ee662074247b417b5a11b5daf6791ed17986906542a20f94515f08f218a8ab502e1b11e0aac770081d293b47933c806076012139832a8e64423c19a1074e9e0de79531196f2ea0bfcc8bc150670f2be8274e8ae922f2bbfad37eebc9077f98aa4ee75a28f4d7905a64017a72c409e12e9cf6ada08ac13b2dba318d32bd083c672a6c614d0bf9822bf7306835cc4c6de854e0d688c9f9cbb2e1232afd03be44541fa60eaac5c8a1f63971da1ccfa9936179c5019c8ff81a58b59edf5999d04508ed13c89645c6613ab155d18e2619002fd0a72004b09d224cb4148f271591cb2a4426ed904fb2d2212ad06ad516d91c6bf5327f0eb369bce8c8b21e7565d02cea64b2d7f3c34c91a5a0c816b61884489c8b3276e5561350da01cb1a119a6731003f2b17e86c1b30bdc6dc3b26d9151f5431e081c634ea1a9b614d7ae882a25d795af76b912eb841c150e88557ca4b44af991b82f7de755d4194ffd7451cdce97a08e72519e2bda060743c8fdba876bba64d622d3a327b26d7b2afa91a01a50d1d01d185360b8ae0170c51968c9f474009653bd419140cad4617e717a1bfa931648040b94e327da062e27394520550f3306ef2e382620771b6f8fbeee6cd62843a3250439de314019c4b41cc44de9007f07a2e5080ab05ac265f887744c70327792f6049a1fcccb27da87d4ab427b9b4c85af0927600f93fdc236d2289da94542315d39b7bdb768060b03b1d14a9cdc60b1557c6fd8b6bac4542231bca105e635f3281e7c62cc2b1f48c7159380fd0d6216d9554aba836c201b849f2a605040e9121314be4b866d1a2fae8c523bbdff12f569e21fce88ffa46cb9ced93c840921992dea2efd19460757f5c2f64cd24c7715a82de62612724ae70c8ca5d3cc85b63cc9c7fc299ce7233846d0922dcc43f9add1a83bbef63484e773b158ee55a5d30f4578266b1dfa2373becaaff6cf6b7170b4c2085fc791eff1de8917f17c9431bf4cc8ee95f61713c630086899abd3d7f20e0e8268815fd7983d5f3537e1c3c468c2c230cd39f0139087845c94cb7b81f812f650f9380f4cb21477b3a38b803134e9a22c24f67d29dfa788f5121f5f0d9eb5525984578f241189170113154c11b2254cab4bc5341007ddf7059c3331775cfb74f91f6191831e9cb87307dd294081121ffada8aaa073cc372846273c03b391c56748ddf263f87d4a60fc4b995ae8cb2b20221305b1aa0cc91d7da4aab51b807dde0675cc29c484c3c02c3456307b7eeb667a831d523c695ef210c609ba4cba27c99da6e51dd302b8fdae82d6d9f6a9aa396a6c3e30795648859df738869d97752561a1d88841995749366464fdfcbd661586ceadb144bb1217b3c7c2f44ecbe837472b2664dc8332fef90f9510cc2487d88b5927c2433456419d301db11a097d3a25997a3d2eeefbef2a7a8db4f2c7245b8485e932c50919631c4ce6b5ce255fb3a5cc858c3e3544c506117a619f707bc19c3efba1d95b287af15f587081deebabcdfb4c4f908b3b8c8693691b096df4f6dc52a41305543367023d60fd114cc4bf0affd9b5d9ffa07ae31d8c7f25cc412930046350c203cf0ae151b4ca11593001a15458bbce8485d4482fce3d2cc3403c04c77aa2f20209eaf549ebd9cc1a981171ababf3ec2ba1ed8e65ca6551c7788d52d515a0b089e0b1524de80ab0fd091d1214e4d5bdba3565a7b0efcc62d8e0fa9f404dd21f4dec3ff782e6ab2b5ae3ee011ee9d11a0d1a78b1e210366fd5b3f8edc769c2db8fe731d318984acb0ac6f4623ee3da9efdc6f9ff6ff3ba48567db397df4ea289db1c6510d93632c53679ff55101b789440208421573fd0c444df98007f3492ae39f895998fbf498f649ed257cda5f907d6ed197e2fff53a5419db41ac81a50d483775cecb08ae4c0bf98c73fced128605c54cdc366204ea89b06d2334a7ac22c92bd95e7c976ce697aa0c4f2dd1c14f78a292df8a922e8fef5e70f3a71e6e12a3ecba8a005ff5b5f0907cde6addd618f87dc3af2282c7fd224c4b81489e5c21e04a303f14fd115821b8d2c9c0c87ddef37c2f8b75a38706ace5b753a14cd404d0070d8090b5b1a0c558531a7626b40b0793f8ba8222bf81c038a3873d56881446ccea637d62a1649fb1ef247d0a342eb4a80b80e50d9b3bde954cb36da862292fc562e6ea29b4e4540670f6091f8e208f3654dc8775fe7cb2d0396c9098912f3841a820514ab2888d1336989d54d731f343bc117e3e5ea9c44273500bdf4c1ade9f6df986799bdea8d7f971c6914e70d406ca623d827a38c06e8b3824e9792853e50587a4b6d3dd71d34debc7e16ca04ca6b341b156b7393f409ded9f6f2c15cdf3390434653ccc1001e4122760cedec3a26aef2447d1ea5ee1af1035e28e1c6bdfeb3b32736479f04b19a4a1145dc0c458661d5b7d1318bb173d15385635180271dc91dd237698a29c921bd0894c593b7baa8ae6a7acd3611b5d0f4412852d476bb32f265ad24625d3e0682a8287d30a28e80d927243ebd4d31f233fc390176f79482c591fb0dc511b571e2c5f3542d52f32262ce0dd0226d4a0289de5c40550e19c3a7ae98cb29bc08cf3b6839097693797d5ca83fe66d684f44f9e9044232f95842896713cf8d85fadad0cd8292644b1a544e5a1313668f27af4f17cd600b28b913f5decc071740c52d7743edc53cda78277d9aff4e5ee8155c0854b4f135d1f7ac80785ddf791093fd7f722f19b2adcfd954f7aa838f2fe64c22214ce19c24064dc86e1c55333c3c32439bc8e8a71e8124af4078a7e9e50762bd0f4331327baf15995e2559ba95f44067b3e51a2038379b66ca898763030856451c29b2ea529401bcaf41b1b223ef9984d1c50793dfbb0744b871ac7385b1bed63140508a63c8806d470c88868cd900442157edcab4575b1a4a4841dc59e05610a17881163a53193b4f46038ec16d3a2acbfb22a828e1a66624123169cb5a837c2b2011ad3085e87c50e9c2090861347696909b69c5e58b7815ddcd0643ba8878b1e8f036cee3022f1da9114688c17a2ebf421eaafd7361287c4c521181d54cba83b3817308c53918ee0943f330dbbc80cf4cc32b9ba6b6a4c062a8f43b4376f1d14b52559c7ad92155bd042d3c5eebfb3507f296f6d43c94c22498614015eff3695f70e0ff18ee349bdf38f952f695bf514c1df01bf656937c556ac772dd985c440922409ee81e639296fdc8c12260c32447264782affadcc5bf4a79dd22811caab34269b381426ce376540fc588cf3619c022e7eadc42b5922677c22c2ff713ca3cf107db447a08cf0613c5bc706a050c83c06471958fa6469a56d77e3f45e8d3c331a535823cee65ac7c8c4afdb19949f24181d7c7f21e1aa4799980622831538c8151cc5566f52a7b13833b80415eac513ec1c379d80deb19bd856e201a18dfebc2eee4f168918883e7340b94437016cfa000c73edfdcb415138dd4f2e7d1f1b5500564a9eac0cf699202924402233d3cf17fb0cc71c2d36a5756e6c9a66a4d0e82e54d53a42d0f6c6e68b4fc6201138af40afdafdb86dd19c3743f5c1bd756b3a0129e5d2912421adc8599fd2153517102b90b3e61e6057536869bc28db874754062177ae022f1f1b0d740d39449cd7180f6e9e5a1ae0f570ab236b4296d4306c1a3fd3f59866db6bb3853c890f8993b31fadd42ad1c44c9c054f18a41db4facf2a7a8a5a2fc146bd14040dcd9dee96424fcff3a3d942093bb298b8a23f9721ee12abfb3f889e0bf1fdb61379cf4b45319e071b6e2a6d6c37be28099addceef0a0b65e63b0f17706ad47fb2cc73a40de4dfe018a4e3fa93c7b873a2456c35610a1272f488091e34387fd1db5b9e5b3fa673dd169652af9eafd7070bd4e353ce961b0b0f58259760aba424f2216f090ca085bfcce32299bce70cf9b1c4167f08ec2842f0022eeb47eccd16484a32e08299e9f7f6688c3d21d74780c284d98af5d22077f2b2d8792de1960e21ed61e1af6df5948ffb2f92d82cd8d00efae054c6730f75d06e2287eba7416dcefe8bddc2fc68b222de614f5642a8a1787b4bf652411071c6e00b058da7aedceb2923578667f49be6451e2f68336c6a63a3f1c8dbbf2126794ee71fd02d449d1b0febcdfd42713447a8023cd91866df9eb83cb0c84b2ea1de622cb3cf144613d6f7a002d159653c9f96d02de3d21bc23192c9f78f911f2c41152c4066d401b95984d314f961f38b6743f440a3f04550d4d4e150b7110d1b91be8944aa03a26fa598db4a5a0f69e75b533a2207c46bc39c351c9ba7f223fda2324aea7101eafad809321f8415dbab2aff593cb49291b7e8945641d8f425cb352ec3a15786cdc3597cd032670839a4c551ab6574edb82c6e76dcba9333268c043a2982fa0d4317237502c90a1200ee11e53feb3637ec36f551f122e1e50083ae2b39be1cd219b0d78c6958e6b5cfd88996fd748a14e99a6d2fc2962bc114690032e6d81fa9d2ac6aed38b94d92af501ff32c9b3ab5aee0b0e76d84d5cd235088e4efd4d536a88ce0b512f98777f9b0198e44e865876669cd498d3a55ab0dd10534d18582c409a543c9c9f87a00a79e62382382765eba9bef2b1c5e91be0031fd9f3b1fb2d76b661d1d0f7b9a0cd08ad31881809c625913a6eb18fe23f50f119e9782aecc36fee9ac6f56a3b3fe0c5240297a01afc7cb0f0675af297a790755a4ccbcd4f1866e1f561b5f03e8a9448e57528caf7148b9725cac6831ca982740bc1864a832bf1c7df354412f5e31780a951386ff364ea1f01ce880fc974c176e1209efff827a6e99ff93a5626451cbf51c4bc89b2f14e5aa376ad61e9fc22f7fd42b0f4378cd0326b21031805568ac8fd7d64ccf4e872e7cdff021f48f7d162205911801d4a6ac2cd45882dc7e02c465034d976c358a781079831f87a889242de0921ca6e9ec53f785ec9b695b3cd0bb080a6109a2d5158e9d9573bf42ab2c6f3fb93baeb4cb45d84d16ff83ebcf879cacef6a2c47f6eddd47f08cb9649b0b1ab3c4334de9a157252c42ed94d64ce65ab4666fdd06e256489ccef93a9fe5041380b0aba4610c03a7cad4f8d205c8c7477cf65e79edf520e85f0752dc4f9427bc42b9359c1a8498f935dc47fbc843d4fff6eaa3d0eb3a361edfe1e31a10502f073561801dae02a117682fcbd044310886caf0f756bee1388d1c260d1e0dd320cc47a959354d31a31e6f8aa49c74a388f2e6515dfb55b712cfc9e54397d24918b47a2576194194a6efcdc203bb3e67524336a62e288fb251ba07d348954ef92fea075d7e50785d56376bcff1157cc85e2670076c58a9d2ce39cb1be88abbc00413d4ccd164c11e57b2f8c0e122cebd75ad03376dc7da3355b0ccb87dc125a31c3176bfdd507b2fe2eab4a020b0bb85a2e75a40b46f7e25b4b7df5dd0a92ccd01bb041eb5abfce247954c34fa5a4b5cbec62d0cd0860db0f1a6cbf9d5f106e202c3c76759bd8ed2d8b45dee6b1470428729ea03d0dac9a65e9da2048c7f99ce04c3329559fa0290eac1a9a42e1bae853424cfd3fada637d50067aa58d1d4d943015b03c2309ccaca5a209459c4bf2c301e4274b1e3045453a918d8a2e214db618dff718f0e8287927480d5a3ae118be697a50cff71b45211e65eacf0357d8d8084a5a7663313d67403bd7ecb9140c76fbaf2387e480f89302b47bcc31d1e6d7005b0b9e5c5d626e9a6619c4ce5587637b45c44a486f7c62e075adc727eb01cb2213bcb92edf2e81be9f2a49ad512fea35fd981727f3140cc114f24f28e9ce950f6e1d41030ca825b20c02bafa07da5283d31726260659ea0575a470e9f3862612594ae7da751efe342fbf919a5ac503522de63fd4cdb644d7560d435f237f7c22eabffdcda551aacaa33bfbfd7b264b0de538421e87c5b6112d4c10fa837a670872ab3fd883ca6be74ec4a41888ea76efccbda23a48f3876c20007bb5e8fd6608bcf1f22036ccf763f19291926e1ae21061c1381f42bb82af11bc3bb8f34f1d25c566966289b286af824e626be44fa609fb9f8e5c280b11ab25da9eae47547d1134ba4a20c37fa9f9c9119b3f0946765dc30ae229db3828469d37c4af2a3d43255889f4da75691e9009aed63ec762f684754cc16909e6f55c5269ff4ed7626917c8a264883124b54e4c661f42400179867e83256dd6c8b5c70a6eecf33f69fd2e3c04920a72f5f2249b63e1e08881e8273da02e502582e13dbe9d0b5d79b1b226a8698609fc8716a362b03401118b9ffd1a8d4151e25d3aab6864751a3f82db0affa91866f5748099a7a04ecf6c582401622c3aee7b56e9926ed231141c26f1034408199b216c95816741706ac3a241e10725fb30888123fbcd7609ae4cc233041e412d88d21b96c2c899f9d3464d3be3ada75bfa0a939522cb1222f6fd2d201250460ce0f1a7fb9074a0f6fa3b172e262e1724003855416d8485c2d780f07f1195cae1b14cd3e8b70a23cf4667c9913433b4f2ca4488685c0d83f58f24902023ce9091da8bd491e977134e6d24ba3e0d62afba9b9f0c9596ce5e207a75f02e05a488621b6b1b2f82eab998582f6da1b507d69d16de20539f5ea50ca1e89bf3043c3d2bb158ea3be35187e000b92298549448cd4d5b896af4c10ff7c292a9b6234888e4059dd68e03f9d2f6ba50284c55a63594c727efccb615a7dc0336d3641caec9c366f569ff0f2cecb46452264a800c5963aafcc862f4ef840e4839dc1c25c64913bb18e4943f1bca953e7bc4ca7238ffb61f87ccb9b4f411cca65172d82a9b37ff2faf9238ab9a8cd548f01a5c06a2f0c852b173ed8bb2f16e4e49e7491a0841bc464caab75ddb9afac16a3c4c9979a16067072e41ec38ac45384fc7db175ce8432e258234b709460386d667b25e97f32010e41cec2cab1c959c68d23b09d014d1421ec2b3fecd14fa25e9c288f5e508fa5606c89a8678bf87d7b7c938002486328b9b29b627d0f6ebf8a77cb2e8a8038986c07da3a8b718c3fb80602705be98a904bb2f54859cc16434cde0b7e8a6de36b5ec8a6ea22f10f227be5b060f1dcb994fad2f44350307e50d5481ce1a3cde7147806383acb56cf1cb59bd25244acc1d62e14a82954b17b519a1cb03b5d4103beb62feb96e13350c9c7fa5bb07bd3a2c8ea44417d8a935a9e6099406c7b36215c35285a0614326bf40fccab901a9a0fcb2319ffbade7f8d15ced7f3f51f979809c892ac9f6bfd4c9a2a1a5aa04a128248d3cb749415d418a4384da9bebaeca135d0f28ba169e4947a16ca1f52b82b51b5263c99d50552714a9a2fea1a5d0bd680d2fe441f8abe121960053680300e3f56615a750fc1d8e8bbdc1a74feb42de4f686920a55b33c9d37364c1261f9637c6e7ca508cd210914189000700c7299658e9f007bacaa37980f64bd6cb249cc42f44e8f86a0afca5ea0f982453b72208aa9fbb69df5b2d91e1cbcefebc21027e3be6e9bafc8efa9000169e7fdc38fe27e534e786e5b363fd2fb3c706ca24bb1c43e8e4d331239503d6c0820a75a673526b2b4c8e33d810f0cd432caeac2961cae418d33d3f5fc24a83452e897af28d3d2cb3e94d177fd1eccfd030ebc1186974863f6fbeed1d5b16ff8eb4fff18629f25349caeda0f588bee9bf6342c42f5b264f5b6c882fa61c8e9d80c4fd68019c6220c9c04a4fb50fda44c1294f0417e5c37beb768112252becc0cb87f4f7b3668e7acf882e119b83517186944b6402d290f7c84aca9901c8c2e39bf76cdc6e6dc58affb9a31cc2eda3b04ffb86700a2cffaa7330da0604b73af6be18bb8c028958709e814773086372e6f300e1a31df307488514ba7a83b1776aadac1d20c306b12776e8050edc6d21e3cd6e23910a90de67c3d41d621aa294394bfad19c0cade227c268ab93bd9c69fea9d554c4253a34614ea3eb7565f89a7b76c6294edc4359819409b436da27f11206bd097b26c2e4d4e0b551afde56514b47c71407c1b1faf7470958c3cd29ddc98d4d57782f6fafc9c9b673e30ba8387fc5d09b2d6af4fb50260aad9cd5efbc5232f9c79b30612b7f03f037f16a8a5b587aa1556ceacd8c1e3cb7a936a7dc132341ecb5d0067578f8637ba3fdb938deedfe74d89f87891021566578a7f2c238794d73d0571a77b344f2989ebe251e7e1ff9029bd686e1608af6d7c5b275671f67d2083830134ab23a331c5f5d8208748b39f75da694aa518406fcb20273227668c7e598121a8f82dfb5d84f876fba9a258d1a1817968395ee75da396f59abbd3bc8d3143a11a434282eeac18c25ed67af3b2733e69375ecc8b0e604e168f2f6bdf41a4c9f0ba4b7b622754e332144a32a7678575beeffc8cd293964509b5d9bc054a0792e2d967ff97e72a34fcde286456d9a94e248963c6fd50338647d8996d23f27298118e03f44699f28d7ff31f31c5cc651b47df9ec3b9b869ac0bc4f66bef4e5b14c0e186b0227bba2e618527749c1c041b9e4b5c39abb3a77e9328fcbc0cb025d4225f3bb62ce3504f467d07fc5ece3b1ba8a5ad7682a503bc7a6502f37f4f0fe13106355c8bda71d12797aaca34008c3a006fdd066b64da246671b4d0f37152bd4", + "public_inputs_hex": "0x25832c071d6d99ad42c7b885e48b10100726dc0de03c0a779b282c519ccf538a25fc312a5f0009edd084811b739bcbd529987df1cea36f5a24515386662573960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000c946233fabee579beb961008103a4c8d00000000000000000000000000000000026a297ba8fb72294cced25b49dae7c92208484402633db0cfcb2af11d3b66ff07cd7b6b12c3a35e9581183e399d53f72b942ad53baa7c6e1a7616e4f0aa2eedf7d67eacc63696345525988225847479273fc58215abae6a73503410e8209a542c92bbea2ba8e6fb129c971def4ca8452ddcd2c1e0b08c268d942b52a49e7597873ccad9df1b02ea1ab854e7111eb2dd188f793a89424a83a3e746a72d4cdfd5702a7944deb0fa2ee04f84489f4af3c22f5ffac9352fe35d950a4ce548798309f5d896dd11f304f93d83cdfe284b37a6" }, "decryption_aggregator": { - "proof_hex": "0x00000000000000000000000000000000000000000000000987adb4e5f7f3bde4000000000000000000000000000000000000000000000005ed378603dc5d82b100000000000000000000000000000000000000000000000a42fd21b16d083942000000000000000000000000000000000000000000000000000094a09fbdc32f00000000000000000000000000000000000000000000000255c07c89c05d8fa0000000000000000000000000000000000000000000000003af09308293d20718000000000000000000000000000000000000000000000007d947bb153b660457000000000000000000000000000000000000000000000000000222011da6fc88000000000000000000000000000000000000000000000007205f5f8f0fb2b10900000000000000000000000000000000000000000000000d600ae130c582aeea0000000000000000000000000000000000000000000000067e0fe450a682f2fa000000000000000000000000000000000000000000000000000031d4ffa4172e00000000000000000000000000000000000000000000000783b4771b3064cfe900000000000000000000000000000000000000000000000bbff8345d387212a300000000000000000000000000000000000000000000000ebbe0d5f8dec02c6c00000000000000000000000000000000000000000000000000014e6eb98612aa01ec19d83d482649c0940fb8146c6999d65c8686684592d5d5208aca9869eaca13973e9570bc8399530a1c135f547d32460f4b83cd330227ca818217f45e06672bd62e28e76bdf130767205a1757b84e2032baf1f39912a88c28845b6b88592c305211427894c22e10d8b134f28d77610246dfcf641560e33ce08d551764c7111c8b24ecfdef12d3806fa419d342fc550079772985cc25cbac908d16d4999adb0ce7010e598efdb7a4ee94186dfd1b659d16e74ebd1edf874a6ab72b7084835e2774f06ba5c953ffa51c58bf88aace20a6e85f086b962a403eda2293f27ed9fc12a42258c030971841ff6be72c5b691807f3fd7a347f41521f379baa51c4410e04f5d9ecb7dd6da287ba0c4a94c7cd0830dad532f110cb376d56e3b19ddbe99906820707c23159cb64fd6601babc62871c34c81d83962973c6a6bf113debc1b016744cb4fb1304dd416f0bdc283797f0fcb354d00afb6430910cb84915d9fb430b6736964c0a095fc5cf78670da5be0bc0999076d4d91deb91199296bb5736690edc71f7a3c22526e633f430ff0fde859777d093618372f32148a6ab4aa2888428a33637ed2000dcc33f2f8d16942ff6eb8f698bdd836f80d55293b95e7a99211a5a302b0d8af93f7b3c6c172943ed24c95684aacb9e05b5e94452595574d2d1089e8b01965628b761223708dc74d5a9d0b5b553b1e26ee36ecc131419cc79c41f2679c5df7dceecf959d8ce01d067b83bb6b2728eeec8c5688777cbffbb563812cc1611ee9a5fa8db99c324d518c74d039c72d4807bf95eca4654d90a89b88610440e1cbea0ce1162f6664535ebcb901ce0d9b027cf2a1dafc575910c45dcdb1b777cb1aacd1963dde0dd73ede742e6f68a87d8099cb5395ba1b96076512cdf007ce7f1c2a07000779477c344a41ed27621a8e04b59c1e9a343b0a5241cb068240439abd43685f0d47bdcd80f84e6b2f7fd615a26e58a5d229ad304877914ac12820eba8fc2b8ce0530e6f7160328020d64b4b0a97ad669d9b1e89a85fbd2d1147d3219a422ca5a031b11b25f0ac984be5e161d6a86c2f18225fdba0e422b6c20c15f87172a4cf9a0bff8312203c532f55c7bf3b6363abbf1db4963749592dd1162f0be6f6bb5e9397b782828bf28f13ee6fa783f6842d4210379bd259bb06904808dda1eb88f992e17612753c200d61e1fa4bb883c8f89cb3d4c8d50bc5e8f2d411aef522f83adc808749e5188f3f94738b6cd966e3f227ef06b9d2170597e27354de22efc34361db2337b4977759a456a2456643e0b9abee14dd0351a231518d3312e2c485d0294ccbab0767dc0ea2d482e35e43b928abfd80e22426cc61b0990991c297fab36bea3d6a6e5121edf800de01b5568b670aec0e155d871647a03ca79072efc7981c49afd26a60e3a3788be8916edfeb28a85e43667c48f82da2ed4200bcda4c9f1a6daafa636741ac2da644e6c091d7fbd1afcd426b6c4aa380958f3d019b802f57524cdbfc04d385326d7354da80d9cdef27d3e850dd109fa225349708faa85a46788f81812db14e7d6e3c6c1705b90feabe3bd7d9ab0023408b9fa716c28d9c5e105178f5199bcdfd0d7d6ca6254b834a0cb97c72df2c04d1f6eb5464ec07fd582c6c5dc63e38be5e9a25d1decb1f70a1d96dbba28e127d10003e1fbd3dbdd69e0510d25d4239efd048448b3e1aa1ff0829f8031c67e951d26a2d7e19534b8228a85de9f8be03cd431b0d709f4fc552de727c023909620442a19ebf0350ff6d20427a659ddf78b469d4715de3accfd32965a06df4442610e238ca3c86af918e56dbf54d0d2902339819a612c36f92139a386a62b805f00ba13f93755bb7ea2752515c1e829f3299ccc3642c85076d23947383e2bb86743cd1259204db0d6b58e4bfa4ff740136009e559dc53f8db6dc79f48cfdab3118bea277c9b3102ff22396cf2f2e5b1148088ff8c6873f2bfffa22e5ec6a8f065684d269fa15d4242e598a4a8547536067058882b9bed9165770bcc6a0d0d1dd14dea28360c24cc5a2bc444ca3b2d11b130edfbb84657766aad5098991915a30a19ef1ffae1405c50e81ee28a7427c6deddeec547f2c1284085f37d470523496334660707643d838f0a7776da97e8d5fd6056e2f1028e8d6edb2aa632d70f9fc2edfa274c4c389c0b38211cc6fdbed398394b25a2c8d2f27fd503e070a80941f7f4ca0e4ac64e75715ff0a32bd82e8427e053e4ab684fb36b52dc1f461a5639c735bc2a7db02e3d65828f6a57dec91a2d66ef814da83aff62e0e9af86f321a9e747412c93fa14b231cd2e8239a3afdcf73700887256e03235324fe52ca81c53af91fb26a9353e95bb3c64d914fdde38f20739ecabef1414de4376871f2b156aaf9f8a231508ed88cc5ded4876520b2f24c7d3cbb3def31567cfe59af381afc7a2558e0b6544920d1df558a90ae224abf2830c34dba6d2acec7124276c1585f5aa0dc20288240cba9c6e33a450b528b69658a5e44fc0040dc0686fb21c1a98b5493ec101dacfa48eaa3b4f94af550cf11c12a039a781668d98911b37b18024faa63c4c0d00427633d6353982b01eac200cf41f52d13c810604a1d2be337b87566867d609d87e2bd85f185c75cae95e040740c3f1d14e755f58ef75569b73a32a0888b50dfd0d9bc436dad4aecbf13faf42b2563e753cd192e1e44137a294924cc0fcd72f3039b9fd750b948d0672b8cb8db8c9813e406e4347123fa73c5723ce32f92809d4c3ce2b8082bd89b2477314f2df6decf8cce655acf23677ff9f0e0f9c56fb023f704619b5586e9f5adc1ba2cc93ff33cf0d5f67365b5ede94cca1d289f75d20dcae4403a891838ab0bc57b6514253a974d134d0df60a01c90cd32ae1bcde8156b2cd206edaab9617449960b0ada4e78464bd8f335024e2f76b8a81d1bc28312dae65ce554a26afc518452cc9a5ad6466cd6bd6ac68f51e458c9a50f3a0c5c102c54f9958859eca24310d792f7832cb491565dc08f07a50648b03a02e8071b03eb63a207b1dab3978b69916c106c8326bd50931fab5651fa3ef8203570db961f1f02db4e02e89dd192586588c41411208317f3c306fb9332d983935d344d0110e59c46b088a47bb92d79c87c523f1793f90744125aa283fee3f6117aa87fa823030b6ce1a1d2a5d6755ffc721a0a136ebd208d95f34410c314aa865954bb83098c00b938ee1affd83f40ce57717b5082887338868e5c9422e4bf7fc1857af6083e2e945c543ff8bd3995dffdd5188fbded020c0687fd8da2aa7ef45952b6b9084beea4b251a6ee016ebf5fcb3cca3e96bfc742ab35817fd7282333357a38a010ed8f90f0e8a511e0909296d99545825ae9d1cec7223b36fb85f12bd25a53df0247e3676d29508009322287c2ee3d29e7e4e542a48dfdc950af5035f257eddd13c58ba58ec7fcefe7643644d6160c93fe5df679c2727d3c71e17ce37cd89cc9066dbeb34bea5f22d72ff8652582ca63bd52fe6f74b401446fd606a6b9af4d7a27547054912b101ce8723ad1f6d1f6a7512ef31973465a58a5349d3a3504d088057e40eeb8c2d7fa3fb66730e9768dd008950113787fff06c297938d8e62db230fd0cb14fbe1661f0a51c74301482237bb51f298a0693005c82877c4f827d0b01f07109324bcdee8663f14478265b167a07b508f1975c2c4358d5159402bfbf02b88f14d69954e18a3f7a8ad4bce3d5c5090adfe821e9abc674618191ea50e4b29ad959b5612d728b13f16b3dacf420b4c33cfa74ead666017258f5e6209de0b21ccd0b2a630d74724adc86e7139809bb45b11e8a26ba6921d8d4e08e5b7215a265ca6806ec10495132eca643d375a96f0cb2d729dd25828c199728af83083c90bcec7383b70df0618975159063464eb8c1043cbc82568a090cc4535262df76f269d2c2af4377465d29fbec0d3e44b5b8e9d6358a403726f8dac0345dca084b50691dc8a1178611a2556c3422e52372fb4358d88a86902982551eb9da886eaac0488e3e4e59bbc43ae08e4cf0e79d1e1e2bcf1c26611bc880df4cc88bbb5d89e01fa3f3c586e6e9f5e73f28b25fe95c18e025f3650d7af98f4c44d979cd74ea90f9152d4c31e0581648aab438fda289abe66b8f8248b45e2c53762edb16286442dd380fbf70ef1a66ba55cbe10340ce2e97c6ed356c105b7b70d4d282fa0b08b0525ee02118f1c47c16c265c519aba3e53dc6bceb9fa44e09028337866e8f429229f9e2108a5ef6ab1179c6cae4a1b8fa8fd28d141aa7d73f522db5f823cbab0155ac17aaa428399b6713f5b1f4544f59060be4e24e292a962918f926b12e4d326921ce206f28e9d6b25582e1cda7c45a2316ec6faa24baac50df12a1fbe7188258f96ec5db61da6d2dbae2799154a8e9b37679e5ec1e9b53fa50dc56c42d53322272ebfae69c26e475cbb8dbd5b362dfa9806515e285d8de724a3aecf10c203016db67ada7055801363d45e2b8c271e20545ae707439cff7fdcd84bb7b000b82d3ccf9874d39155d1b69cea14902c8bdc4f1ba7cc29187dd1fb87c56ba68df8008473db80657888115bb42de35bbfa7dd5d58079418d776722bc8d49cbe65a104bcb9b0e9c3710c9f9125d1a8dba6a39129d869384bb0f3fa7026476541549c0bb13b5305f0deee708278b40603c86c87d3d0913c0d7d040f3a0292cbd3f1ac18ac5d0e9ae4081a6d00b0111ad769394c7739399aead4cd7a460be9f4c234f51a8880bb5fd88def16a8816a30b4ea2ca30337f4fcba6aefbcf0bf841ab5b20208c9785c48ee6c6464b0e347b29268361c07453ca42fe14ad61e72b484312188029c4211184a7e59516f73f3adfe1c8bf465f1b246ba4b284648629a0f5cc0590cb8158e7ed1f4cbbd72050d04626560012251c0362476131f27e04dc8d62c9115fa79bdfdcb1534d8ec5cf83b9972c7f31a7f82e96694bd8f061accaf63248a137d89b3acba69e060286da68218868ed9d88f0b1d4f8479775a8d952726e8670c2a562315b76f076a558136be65159d968d33e353198423948c032f4e0aa0d60fbe63e7880348c1d052e02026d1e8d16227713d906d60ca0485eb3d20fd778e01fdeda5f294a03c49629de13e48873396c6d596c4a82c91a6416111fcb8379917e7f210c179a210440e42a50adc3d3591788c2a7be64f861cc546cc054cdc6a15a003b2adbf6fea10dd259477b3beb808aeaf953fde6b99ae7255ec2be91a980b2f9ed5dfa4696711e650688d562abff4be6ec16dea6b36b2a6ee2f2a578b8609e0bffd2bbb2f8f5a743b55507e0e47193af4f1978ed153b2a2093851a43a4b16fd1444082712c372d928c943170f451834630c40ab6d41d1dd601f97fa43a50648b3d01a7db07cac6a683417a1dfb2d710d1984f703e9b6346036f78fbdbdf0b737d94ee509a2170ab6cbd7371389c6e734ce3ac0af0e7446b58217810dda51c03eab1b07dff7856144c1b30702e57fece38c690b7a0bc23af9b034a99eaf61d73a33d4e5ac4d393f406ffea8b0aa995d582a140dbad5b0b99600fce88362b158d1a1879f02f07332d971ad0ae3692a8c61de7456edf33303a36ffaa0134c70ae4f05d894e4a415b1297132b9455bf940921a8236c582b8ce2fba038a5b98a244eae77377810f668bf685d8cd3e8e5ef2f44fc45e319594165bfde57e22bf0213c80d8459bc13c913ee92ee02ab4439f0ea27e876b15d66e0fd2d12cf1830111cc9835393ef58d7ec1974dc30db64bff1cd98e1d9f7e773f44068cee5c67281a83b948f419e187494113e32e221eeacd9a67429ba9c2a931927122668c06992d8b776c3f6259a6ba9eab8957b8340078dfbce002bec4e4cbe6451f74f8d126088b18fd65009fc64bc223dc16d0591d0c5a6f085eadf072ab002804983f238d2a2595616dca5a09a1c1cbfc87cc4af532057518d2885219bde38710f368d9dc07e6aede1da3deca14a7fbb5f1f1292c6a11077fcbb15e0c0a049084252eba8e2c33ce421d37e7986f86e6b18c1135ba95486218f26d961d7877fa449253058f0a592a27254d7c7d01698b85b57a478b94830921a2c94f831846bb38d5c151d42a89f8a13e6462df64b4c55e1864afd0c1d6303ab066d3b2b7da1b801705e9dc186b1ab2fd62328d46d17daa0177a4589a162daa1b01bbea02fa2332219d5629283b89b170dc081d5d94846c966ae430768bd3340779628d33f8bfc7c70ec7de087a5ec32b0b50e5c8e3c71cea77c00d64d964a59902f536dbf6effcf8e6f62e136a001aa16046a8a84ab1e5dace849b0c19cad1389a48c17b61a898896c4b972ceabaa87cb8ed850760c27b357b10fb5f0cd415bc24993084e905c53ad1abe507d734b58ce1841dbd1626bda546ff7b236184d24be658701ed93ede7c83df6a068d48d5286d6113de65e26e31d2eba4a6ba53fdb2e13ee9b30b36c8ca5b28b90f9a5bd292d365e037fa54ec2722a008b0ebe3f418ac9dd7e623d4f417b7e6c6213c6cbd6b6b825accc918a0fd279e9274744088cd2a7a04b4de1501971a2d8e123b45ccc643948dd02f7d076494284c857bfa7e6535746768758b132e4e6aef14c220607c4dd929a32fc1a8ff7ab86652dddb25941330b576feb58086e6e92f23d02cce7c05c8eb211bac021917733b18cabb0058c3203271fa89027a8ee43920efce36b221115afe2ea3acefa050a4cc91ad573a73f4b967159617cce2c4fd2bb366d6b2bbb5334f800810769eeb9fee335b7525299c9298326e04ef0df5af1a34b4f6ce3289e480c55342f167667c1a4c431cfc72236eec4d87a2d69c263c13e05c0693e6e8b3551d90299bed01829233a35c69669f8818dafb8a206027a30385ede0ecf18df90d4d92d16b5d80b82b7a5500c59a7d4ea1a2c16b047e60701d7992e950675a71240fdd6ff91eb509f5a39a20674ba5c411a0ba53b3473bd803134675da21d443fa2c3bb8c5e01fa3fcab64403410f5b860dfd8267199a52a183aca4b265de9aca19b520ee6855497223d443e4ec8edc21bdcdcfc7dfbef2c28f866d113698da8c7a00a755016e6448fac410a460ad08cd4338183ff00f4100d0f2cf928452e13c621ce4bb42b5416863758a28b3a339768f9c0837867217f2afb8393a2535a60c37441dcf0a34e5350938204b71dc6f6beb9f7d20ede917612fa175145d986a7cb5a79fb1857643f33b475fe4f78cb67c4bf23dacfccd77b300d930a85039e9223bf8ab887a4b6fd6dab6e08fa21103aae7e357f84c5a8382373a566882eb882db182aea4896b289efee7c4f2dbfbf0b5cb34c078513023026d7e167c7d23fbe1f06d2c7d6cdb35a560776fc32368172eeb52c06a69cb0b9008e1974f8ceb08312a24effafa9bdc929bdcc2b1720a1abf3392e03948962262a8b5ba87690be615f3963991776b9277f9dfc9bd70fd4e4439fcf2522781e1322ec3850b41844f7814165adf6460f419d3443a0f475a65b2521dd4189ae25cb2d239c179b48ac67225294d53360305249476c2bc432722a46537a87c9db0d5611406c6e8056aa43cbafc6aa655b9bf09e6418a111f39af9bac0d433303d90341cd4ecdfac812a37dfa87113ac195b133930ce95aafda2fd69b574dba16251ac24f8b91d7a2f9c0b08d7c7e1f2516c3e8b1700d84d79a02ec51203b42a56bee6172b19a9f44043443879fa4addcdf80ec12c125ba6a6d6ffa61d5cfb1f2aed5811ca7bd161b2c47d667e3381032d36d45899d3e28f61e31807709be6e331b779062b846c04856208873d7d5f06fada63f0cd74fff984abf8116dae8ffa214ef41fd8427a3125e3920a055259214c2c787b16b779bb3c5113245bdd0f05d9237c175c7fb05feec1534533f361b4923799506b19762afb1e6da73d250a2a062b37263dff74f97fb78328b360f951ff7157322f3fbee45013d25822fbdf218dfdac203e8c3ad3efc7145f6fc8dbd5048e5d0fb1325d5f55ba6a18e3bdf7fd199fdf22d01714a6d892d0bd6822a70ca3a6eff923989f90a115a27a2888e6b01deda6024e86697d916e45b0cb7fb4532d3dd72d4cf2c6e69652f1963364cfa54490a3105a5a6d8b78b58d96044c8dedb23d90ef78bd2feeed8fe795710ca4a4b882ee1c2f3ca91b7f0d7b08c14466061d36915e54b39dc3ca263088d5174dc5940d411ebec5f2684a7f58e9f8ea7f59e3cce71eb6eef697c7408b6e9e5287a5eeb5aa245cc9891529dcc72a0ef044e0ba35d820554e7a97a22b4ed0f88e9bcf3232b30ad148cf4576883069f1a135e306e855574c879d246eb6a617a8d9c4580d14a103663bb9b8e3f9804750f9e5fba5ca9fbab59b5413fd3d40813eeeb2e34154e52bcc6b738a03f19039633168d2b59930f4c02e9043d4edd4dfd8d878777a35202452537d88b6cf7baeda8e5c6e1b23b677604d2cb85affb8df573a0a598bd20c18a6a7cc1786dfeff162ff44fc9431fbd9abc24f088917e64874b0096be181322cf02a63af628f04ef516f7cb724a9a35516d61c96277f0cc15d0b182228b0c71cf44cb9a3ea931b9e6addb9233e2bc471073797e7ecbfcd8579bb7b0d28c8410bb7146ee398663ce27c538a615bee997d2f71ce6026876d03c2e8e0b71ed67d0fdefafd4b06b5b655e0cfa6fb2412fb56da25e5ebf8680fd9dd62cbffa8e44a2c6413eaa5abb7bc68d9b9f8be03e4450d64f62131ed3000fe79f88912063f401146e2613d9198d6016196e69285ac9bed6ab4be2a2784eff94d55678bc5498c182fb91d5e0ab365aec19505cc814c4af5a0bdac19ac72768d10df32e694de0724f0572a14568a1d9355968d15e32c3f7ad44441bb17ccd570c47a09b1ab46c0233af8e34d41efb4c7198e5088c97760f57d783362b94b8fc421c6ff2eb3ebad1ed2ac7511556abe5261f991fb1b9c88ef44e8988e522eb84ffe680d128aa6560f0424641d8caaeaf04c200b67ac903f6f10ce8f17da0b3ecdd1a7fdc8094b82163f7155b8ff4082026a0783c4f9400c740d110a2d0841fe5d8f4d1f8c858c08051fa256b26399626b7b2760bd7240cdcd6f631af28bfa939e0c470a2d36cd2c1c43355810a346d44f6d182b676b82fa4c3f67c9b31d4bffaba4284c4643ea220202d6a6fc7297f85a403f5e06c0cc5b951c0307a022bb05920a711f9290c0372b3080998a4c6f9d2e080a6f8a1a68c2fca31dc0f4261b20afcc1efdfdda88ea07c033515be762c3414d2b33bd71fd2b4e04a2a50a82c0c9661f20832549220519f0a9447115f541eb9f0e9805a507e979f6360aad42bc004427c601572fe15f09960a2a8d7c9ddd7ec6ac56ca55ba09f3ad299059e78357437a5e2f2d3012222cc7cf4461422484becf2bb7975e8fabfedccdd8d352c4af57367a95a5ba35f704aa0939286266d9b7ed4db582a346a2d6ac8f50f57f72d813c380616f0202f021ab05c2de28a42ffb9c38e18a59c60b45ddc9656131239d6bb695593429b89a237861828bf85d73b3f21f46646bdab705b94c3ba83b5bcdc203944e2ec3066f2422ba38d2e24ae9bc5288f3188fb47c63129cbf72edd6acf5136608976715c31193a6d0e51fce9df805738437ee6e24778cf60d56eb02dd9010120403b9cd7c26dcb5dd54355d6ffeb9cf56f26e9e27dc248880a074bd43a2db819019298b0c21dad0e7601c3e2c37671b657a55cff16482dd636d7d1cce2a62b7bdeaa6d2a20b1a4a473aed9939c53801c816cfbb96107afc7c91dab29acbd15b2c15c1ee530da08de8a41dff2efb2dbb0ad65368abec946efdb840e52fd9224a7ac749adf128c7182d4d49072bce90990ae3363920f644cafb988c04e7778b6a2d783540b20e6ba849a24e2cee5badc3d9f87bb08c42df5237bb6511447b519dd001b9b74e1b830244bbeb508512443adc2cf24df2d8bced7ccb616f41a5f0ab835e693c2604d42e60585241fd5b5b0cd7416d31ce9c795f9e4d91d5548f9cdb5012b2c2151360c0004ffa0e1fc3a86eedb19c4551143069618d8b752b51c3ced1da1a9adc2589f4c79a8c917efde3948a5f1da4b6325d6ff7be04877a5fa0af07339129330c486d306b139377720c3e46ef640214d752fbe99be0df92666eb455461c951c18696ff47983e13c5e8e854c7579cc0942ecc67e1d44437b7d578c2f03e6b9fd0f795c65d8ea5afa5bd4db2806b3836f1c79d8ec6f2a456b85eb2f8101b76cf92f85596214fd06b74991fcd21a78a30c4d09799e3a3e92102bed6167f848413910f60f919455f945be3a0ba52e639fe62123d0b31d6b2a0e99691f42d7f7de8928ac7541bd85e698ff43cfa2f7a62481f74f182c3f7006b996943ece228e462e0b6daa7ea37b654ea51687de1c67e57caf994ab65fd55ed20e5d42712a07867328b0baaa572613c68e80dddbb64f9c9e04ffeecd0d8f399f86275f0dbaa968cf0d623c1b792b7619397d70c7724ae40a9d494eafa4d262baade9bccf9a0e14991d0108e154ecc03b179d0178525fa035e854e7bbd74696f426dd580322b6e2bf265c9a5286a5c12875859d816d842a11447239f36c61c7b3a22d697384b91b351efd8b5b8f70b568c069b46e3ff51b60921b2d8e69425b8b51e41974094110c1113ae0bc2b13cbad58589169eb5206c7e0f7fcce4e5158bf64ca3f47ebfc60c81d9f777a020d6789ec71e49e71726722c3921ff07017ffdc93d4949552fef0582496acea2fb767932e2cb43009e45fe9a175ea419a784dde46625504c935ac7f04d8dd62ce096d9df45cbb084e9cbdd782c16c595e9976a83abb9479bcfb326a1fd9ca61d8dbc31c1ac4366c460f7697267e568ccb666fac304a35dd0cea6e2b18c9b3ef6ab01fb02c43244cdabdc0670b658f2349dfef788ff7f3fd8bcff8742c77f8496f8056be9ecafe87e7991458b4a7fd7e9b7723d598ceb43b335bf80a0a3bb5369dc655e704605d802022478ae8fadab73a965376984c163eb0442611244a96313b749816e31483e71c811ed28680ca3c960585c3beb73348a2828f6c1e3de18e2c7ffb2373d7ce07f6af30ad8b5bf7543a67b217ab5508fdc9dae47229007060728ae3ed418a50c27e8d4d3fb38a16e2d198707b0c49f0b0f0981c0c144c50a4d320caf2dbc63f0939a34c9620772ec1dc579564da414a5488f7bf1d24fda2eb29d0329bcaa90d7b370d70359bfce785aa0389b9b64a1d805aeecb2d0668f74723cd50cbf272b439ef58fcf5b9627c3476636244133618cfb18ee67f1d14e3b6780bf49040c8e3713b5da7b4c765ef0f44a4ba2607c6bffe961b804c1a7f983fc86278302a961de07bcab2b1d8c87f42d3e4ec1e26017dcff5a78dd107db774fe31e3dbbb44b2c056fcb2edaba797ef7ffc745491f538168e3cec98f1432cdbccf2735422706e8dd16816491309885e98f6bf001b3e8a8aae5a2e81a16ae509aa335273b37e951bd871959fcb6c812cfef30aa2049b5c3450d9d6b6b09686de1ad299c8130472428f40ebc22c49e21314eb25d6386f95674d34738e72cf0a030170b1ec04af0d3abfb76ede04b388ed2d3f2f977be26b98b714d497f2c6efc0037d324eb08f1f4a2974565b8746dda11ca2944301494a800909bbee806963e92178b4f8d290c517a9751adabe3671dbdf156a49a0920d5111fe7d0640dcb304f7042bf5344eb960e361cd05b802ac2e19ea9439e2b77f5fb09baf20203d23c9d38f1a53a57d924af7d4a8af8a5ec4eee064fed2d5b58a3c43d281a97117c0e30563d429f943d3e3a7d5ba47f7e40af412f7c117ba802403f7258816220d421dda84c76f16e7ca7827a1f85b9d8a79da43c867fcff91594e7dfa847a619a1674e05f8278446c73f7a96fe8138f3ea452dd92fb39b3f7dc52d170e98e5029b951e8e9ad6730f08fc023bcf6fc1425a5a08fd9da207d1b34cdd591e750611313989d33d1bef87d8269e329a8e8f8dff7145bd0942aa674cd84f3d4e5fcb27fd22cd699118671d582bf55fa4e03bd30ae1ce1c90736a1607880446695fae0a3186ce85230bb095847c374632954949d54b6200d61bc3b85b0b95053279961c8c4fdf0ad9df1563eaa7dbdf189c793bd9721391e4c4aacd20aadfb172e15a2efaf8dd0face72b1495f7d5a8bdfa475cfaa7249f23842a937f0a23e68eda9c0a297ea8da1484178e9f20c89cf63a2ddb18610968f0b242ccd4713b4ba492a824ff0532fce504017a5a19ce0ffd959608d883d709623210f1edd71893ae96052c0244f5408f977814136ac5eb32633b453c8126ddb79cb45e659c7b89f427031ff0eae342074abb87887716659092e809ee3843892f7e5902f24b0c2327f7281862cf758082ffe45f7983ca3114ab354faf722e540a2ed3bbad4e37223accc928e0cfd1e75ffa47e5cdb82f7e3cacbc94e75b7ffe926f668d51213993fe39400cbed495ab13350c2028153d9b8f67707fa2028269aa356919b439b4e653e55021c5abb24ace804cc33edac36ee037ff72393c5a29f8d3495a8bb5b1135ebe8e2966a4472202c2f9c802e5fc0ba01e551964c5e92d8e4bdea64b63c593dd52f81fe02a5b8b4ed1fbb9300579ac90fce48304909ffa28a5dbaaf3fcce95501dd003ae91d84f05d0d665c8392a314220eeb1fbf83b6906409d6b4b8814700673c211ba063507bbc57161e68a01e20517006f22ee4bc198c0d9e8a146d26c9603f123533f284db9506d19fe33769f11888e29578d90c0820b5a556ec97a6264910f2d3b13fac13a54292ee27a0fe0e4aa79c44844508a6ba2f337af57e5e344d5dc17c40f8c5c385435407e5636d79490945f0ac6a5f8a13acd61c10d0bf446e1e81cf595d1e46b9cbc6f9eb5d7aa295b62c0f54a52f461c8e626c7649f81bb8f232931fa4058aa75ea5ed549f857dac0b49adaa14577039f02b3243119b05d53ac233b99872d3675a8bf8228877a5a14187cbb910b5423e3a809e31dc82e92ecc80d9a298c94fe9859fef768021fae718e8627b182643a5aece4a823ab14f5c9d0264d6e19c2bdd24d928e2b67e4cd485d77c4eb922d42b0a58db95fdba75728a225ab01c812bb1b17123d6dfcede7860a0d8daea2cbe20569f6f52a524c1860a41a05b5ca27d6b195f07699ee1fe12586119f46314243b6d1e0159676450fe60428a94f172cbc56a36ca5101edfb59ea4c57aa926f082af225764fed67f471d62174ea6046eee3c5f30acc47fc2c11eca36324fb64012e18797b91ba360d7649a15c7069c3b142369923f4ecec193715a094d1ec51166dd88ae415269d9c145250d27f9abf8631ac89fbb45a880c4f573ba095073c7dc7db9815eef4f3fce7bb019f55b969aa0cb3ec360b14705926937ec2bbe3bdc37a55b0a780d53226085bf132f11b6087e33ffb5c18d6219ab19ccc5f9084466d2998670ab2e3cfe7e6448122ceef08ccd7cf0df77b65021a717e56aa59953d78e697eab50d960763a74250ab727d201408dfd574dc10fb918dde0a24fa61be0fe3dca2133d2f5271e23c704c8b56adc72302cde25ab56afe4fc3933241a887524b266c9f800911ef41245284a6c5723f9f4e24802203fdf45224dce5ee3f08185b5bbb4ed881133ed81f317e8a44153c581a1e28869b48e63dd7dc2775d4275d84dd6472704d5333a129410f07b10ef581d8126cde0120a1f14f4e4b61f0d81f0d88364baeff0dc7dc5bc2345fb3fbf3a59012ac3ac1f89badb0730c83d1a19f3511bc6f37d7b13815d3309df10fdf80e914c251813aae350b7116dc63d71659e5497bfb8d4010bcd3ceb11c403112c24e7dd07b5bf70d840151ec0aa6cb17b28512140a8bc133786fc601dd3025e0b6852f1533f24bc7ac931076703cc8c9bec73d17740259f66054d352ceea965f5f992ce0ec353136d496c4b00eb9a577bd96267fea080e76023725228ffc6fc256598331478f366740c1bb8acea00b4c866fbc355fdcf66e6085e691c29e033f95a7ccc66e8bde9975a26db8a3cd918d1c4668b26c3aee106fd6c532d97d6fddb106d6a6e5ae72721bf655d17f117930eb975ae18f93713178ac3500be30edad0bc671bfe377a0459f66270817516ad141658fa900858d38f3171991cf90409230996b193b80e09f6dcc3f8593866ac2e7aed13e2d047f42b9e95462ce6b49ddaf821af5e20b7b0bd48a193a8ee2acd3fb28e4e43a3e947aea85915248bfe8c192530676aae57fb5c16ad9fa02e276d390caf4680ee7eaf831cd30a2f8eeb51f179ba6684883477f96aa4db9411f26ff7b660924d5aa2431399ba3d21a71b3a496e0707d66ec53847b777e56e467151967f155f15471853158f467e11872267467f0878f9b474b25ea29aa42f55358f4c334d6955e16ad67ff7fe612f3931ec6fb062b7bde517bbdbdce100682542e66f47a57e1ddbf32735942ed22eb95440bee718132a5af75299c75cf4f8c22858730bbc75b105c127ecc0b9a814e378689916333fb0fde87908f9ede468c9f730e562372fd874b02769ea2ea729e84bebcddf16f69b9a674cc8f49284e964f698e4c299bdd6d6d5c398aff63f2c3004719b2fb970165aee0f9b90e776c15282d3f876e52ac75fad455656577215db9c9e287abe3a1442f31aae7b13136cfa88fb144a6557523a6b1238d90ad62e9c2203925990843c7621c69ad975d03b4490e9b76682b8e04ac4ecca5dfbca0005184a25d7c3bd6028dac0c4e9037df0e21639b600fb596c8a639ea0d91084", - "public_inputs_hex": "0x1a207628cc6936816ccb62a7b56fdbbf8e975293b677c988644e018fc402e4411dfdc7cb6265f100524012f038ee1f205bf8789b70b46c57463dedb4998f7ac8000000000000000000000000000000007ec37a8a654c78ed3a8e7c4d6e933fa600000000000000000000000000000000520f0c28dc3d0e79e73f3f0ea8d8dcc01806572c19ee2f3532e970d617919b3aa8cdc582782dfad0699badc631f7512800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002195e6ee0539776806e913a0259520d29c75b0f8496a9cf04f16a674bf1ea4e640d0a0b42b415ea987c680bb2fb8d9891c2b7934374e865271df6f818d1b089c32f3529d2257b7810036b3d044fc8c3ce879ccbe2a182cadf83c8ffc73f3bdf9c2d81dfbd8a3f5f3a78af2e6f5ec2028880040866451dd20e92f58bc0790494860000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "proof_hex": "0x0000000000000000000000000000000000000000000000046462eded1bbde4bf000000000000000000000000000000000000000000000009f2fdc2ae91c95310000000000000000000000000000000000000000000000000b2ea26392102492c00000000000000000000000000000000000000000000000000004afc85764e79000000000000000000000000000000000000000000000004c469c087756779e800000000000000000000000000000000000000000000000d3dc12dc70ceba4e500000000000000000000000000000000000000000000000d09dfd159e66bd2360000000000000000000000000000000000000000000000000001d0742afdbf00000000000000000000000000000000000000000000000009104c8f5d91fa2bf500000000000000000000000000000000000000000000000836b1660e0992fc3c0000000000000000000000000000000000000000000000031380efa2b7ff3ce80000000000000000000000000000000000000000000000000001d837d5ff0a5500000000000000000000000000000000000000000000000db4b2385e73fbe68b00000000000000000000000000000000000000000000000e4f7388c73406d61700000000000000000000000000000000000000000000000e8623e231e09b7f3a000000000000000000000000000000000000000000000000000132c5f9251f0e1f3f0033a84e44de526080bdfdef3d2c4e9be76761bf24b2d0e31ac487ab12b12f3b1b559ce6d9ce7c97b5f31aae0e6529acf2d329858733b8c0215b3e3314c51eb174b690f7d4f832df6574dd9e4bfdd41cc47539b0301c66a88984cc0347a312b53b6e93390fa5cff0fbb34c9a55b3bfa61ab7841f31779fec5644f07e5f9c17d0eb1194403db98035cef3a0d47e70ddedfbfb47bd33c44fc0f7f4921d50180f10ba9e083af9917dde07ffbe45a53e65983092dc0f35e1c84f92b7189595390810dddf63209c3f94b856eae91571feb451a665cd1a1035b990c887ecc43cba2433686433d56359e92f6bacf0501d6d7662ce00d33aebf14dbe64164c036caf16519af50842a2f1478375a011705e1b6e3cc8d1427b463a370002e3a70896ad27996d20e8ff12e70916e72471a5dbaef7f378d7232905aba8cecc91e8c077c72a272b8af0c6bc297b6b8563e218a17a1d510b271bf12dd8d3aa90e56d6519a32d29635466a7bd4dfdf51438ccceb51d486058596bd2e201d320c9b63fe239251273008b48d56b8a01c916ad52aace95cfaaae3232df4a22af016a830f00b2280dc4090249ebc6c4f4977b3d2f3e92603ca1e86d0c65fb91f757747e5b3c64f229c98b47e1adbdae651e8c93d1b5e22b26a27a4004b26a4cc6dece078c1e1f420e928f4810f467c371466a5ad780e27de4b7cc7d34c4bcf9bc4d1a8d69382b5c26a661451205b6074193728e248ef7585707509e09eae306e78710f1fb3288251b20d0edf70c5a978f32b92a869fe73975d955f5281b23392981a6f19a2f0f58271d29171ed9a120fb70186dd4f920f56f767149d663c103ad123bb86de7d5ab1678aaa63cc7e6039049df417f336220a6115e3e860bf85aa96a44d22587562527980f69c45463860ee9df44482e0f52c5a10107aa1bd1fa8a3ffa215868740307471504d1532b017118223f73cec88bdb2514519f0eb31f1c14aa42fccc0d541f5b3ea28bd0f5a00d90defbeeb8e25bdb73835f623fa6bfee1e5ea3032e649608c59db0e1d6d963d552f4b4633c5767b0e8bddd6f350c179ed032448ecb129c15f26bb9e1ca1f3529eef918c8ff9788b597b6befad98ef7b67aa0eb9134d5b4210870b8989b0adbbeabe3dfebe6ea30bd7d60659f4e39e266184b8243691b2e0552aae18e9cd627f7c5ec8693c5fa0d609eded0c8d047ec9ea6c224418278250112560148af7398eef1f9dd48561fa9c0441801dbdd69c8bd46e5c4af99ffe4109f7ed54f9cb7f83e617e6b2f93b922bce7e1c07151e676a7ece630dd747bcc142aeccbc157a93950eed8401606e1b16e66571b7f8ef663ccf4e93fb82995212bc9e169ea1c0594356b171e607e54fc02180a1059edf5acb8983064734fd9f8209e394bb72f81f207edcfaf9f2ed0b8c0689b85c47181abcfc8d36e08b0c5ae05832a231dcb4f6da7f1ff2ac341a08b210acdf2bf48f315d5920175f3d5cec908b4961c1e62cb178fd865c31734ac843d19fe58175f36c3bd84dd20e8069f6b0a624f48a0e47210130d1e6697959e2fc351c28cdc4228fe9fe37fd5abaa897e269391d032b12bcccdfe6c5b4157526f2325f20b13b8a81f8ee1492e0008cc770dc6aa7a1be35998418d9d54e3eeb7bc87d1ae47d20d47645c4973519428a79b15d9121c942e03cc0ccd93c2158ec1e33c2b59bb09173e7b7c9662f2baba474b01d7183e0570580d82094361ca63a614ebb95d875ff403af22cabd79cc4e8f400424df7b0904cb8d32decc3e907e6a0b511bec4ce2fe9331afdf13b26b4b010b0b3f755f1a444cc372cbbb860872fa0493f8c492a7f23867ff93dda7b637a2e90efee7b19c99a3476e8f399e1c68944d4bac5b988ae883dac4f406ec18abb7ac0c45dca453ba41f5b9c28dcca7905816fc9600d2298b31a000bda8fd06731f6f26b119a02a7fc2a4ae33307e9f2798f2502ba08f0cba86fa82624386a8a7cf1a1bcc4583eb73ca4ac0b5285cef38d0a4a6302218e5ff0184f9c9908eca82c01a26e711272a4d2dc6c57ad49857ecb1b4adab7807061cb4f5edfdde5930058ee71e8bc31fedd59323de5af8e5713c7ceb21d48c3f73611979e7e2712684589c4711f24d5a2b83af9233bea1cf2268234eac399129feda79cc48c5cd77735d9b000d2d0a40be4f80ee8003228f36bd9602fff33d890bec95c8c23e00e7cae127c41075162afa6c2b18a81f299e7eae0f590c18989adf321dfd19a846924be23a7918c08a3dd27cd71b39d3e3562973ca37074ec000f69aa3824710bc344bd5ea292ae856b14a9a14635879ccbf87f082eddf2dbd240b9fa4702f5fc5d88ee72b2a096808a9862cbf8ae30240058d22dd69272b346467520339fda95a07f6a8403325be8b2eb1744fef4514ad7158c52488019b3eafa4d8cf7df694c3bf47c3a59c1a1fc4a36060bc9af14ebf2eb79d07cfda2528c398b145317c2cc84de6137d44221226dbd233ab0c7c0263ddef6811487072f0e218b94a6c72fc7e39bc27141401b11d54d55aba111509422d31d6cb92dea31534ace379c0427c6c3a92d4f90f02251ec156fd36045aa90adf78403f49e8479b8ca9778326da1425427815fbd6274bf80015317e3ecdca8795d60e025b58a56dc77f5a4b9acb453d3e78deee5c14a5cd11d5d16c49fef05d37c81548bcc270ef9f21a79b449a30d540ee0b9a8307e991ea9b46f57eb69d7c1ebe87a8fdf4f8048b4f41d4808162f8e0f7cfb3e11a5c3cdec6eecb39b1351814f82418b9170740fff3f5681d48fce7310e527b6a03d5ae1282295d1a77b314268efd8cac07fc028290a1c8657e45227f4b6ee63a24de15adf0cd89fa38336b41277d5fbb3dd19e281b815b2c0d2e9c3e5511797e00e3f664895d68974f56c39c9889994ab59f8cab6b8a8ecb2d703646a2236aa91198ae08f77f02dfc879c800f7c84e398ed8a43f20f2aea496b004cc58fcf4780b4966b97a4be732ee4358cbdfa657c322cd78549ee3780e0350374ea4a3d8900f6418d683ec071a1273be730e362daf287de8c335727321143ba40ee0115c241f9ecad9f72e9925e3de109f1e566341752dd39b45d512d2388dacf23b281ce3108c6659ba45864d7bb6836042b9097f20f3f2a053046045dc613ef501a2eea81281834f5e8f9d7140f709b79d53fd264556d70139ed47d25666027aa69c58e42b34757fdfd0b9d122191696b4b4bf4eca578e24fd9b844b34d73c31ac3ddfa801ff012ca13ccd6b0f5e561e0e6430b4be2b0b2d374ea129e470491ed7c3b13b183e35a8ce4cd1853425fe864158971415c7a65f11dfe69d5074f950809a122a28063e5d27dab57ae4f0b3ae9fc361028adf438677dfb5dfb988f8b8b6ae57a3076e5ea1a0b2f974df0ffbfe0a060e3637c3127776fa81d9b01ea0d657dabda901d6807a490c87e46e4e394192ec98b42ed04f334b5a1c0f5057e4a4c557c01a1043c947cc968219522c1abe82b20d6a4db9c2b388a24b71cced988a4b883a481d25e3810be1b850a40508bc3fa632b2e5fcff0a7b1a49340915865bd52f501f1f78f68122727eb366d619ffc67d08d4d38cf0e91ea66920699c3a2dcd37656724c44f6274e12fa976dcaa3d6cb603e2b323f8dcacbcfda2d2371a78596fd7e42e52e2ba095e30b7021a480d05dad2f2838df1fbbaf38e0df2af304c8b1175b80cb447d4e3ba6d26637a624eec49a5617a8380020addb7879bc212fe1c600425147bf057f7596b5f2c938d61049a8cd1889ab5c40ab9386a13aa7022d00ad42d0e1b1338ff64fa766858440e350fa32f5fc613ccd8b46c95090843de78343271006a68bf23479c2223f60d11d64a98c2865fd34f92e687eb8c2b7a28deca830818a2208adc5a09a52dcfdc04f6e0e53bbc232b046d60528ffb794238b3e64b5417d6dfa423205c8a1ab17a90537a19ef9e5931c33a757771a62c7288865270fa1762f450463e4256850c78446525c13e0f55928414615620e1c41fc5f2a62bbe2ec7351b1d969bf8399ed677fcdd79a6689cb8c08682aa486ed19431e2fea30326c3b9cb74eb998fdd93bd366ca54253b2ac0e213d65275b7e15be6f55b87e5326ae1e2391bfc71e63cb96fe8badbb8eea59b1bfbbc89d9fa9c76214c968a9ef10f4697708d257e2caa80176fce9c9dd0bf7f08db787efff22e8c50a2b3d851c03bc1a26a9f96c6c7519fa1f9f69e89d21f3a846cfe8c7825b32cd3b1df30dba1d5dbd1e3883130ed44f9f1708164d7c037f7110fc42a2569005b099cd6c03561737abb36b4e6751c47f50a1749e53575fb2d60336dfb156eea527c39e6044fd0fa4c2d07af7ebb89646540fb42fce47fe70faaf75eeef6741a570a90a39e2212e25594c3141f72b1caabdbcdd33ae4e941fe07d0a9fd16698c6856b74619a810c0cfff04663e7bee110fefa623a4db43fad9bd5d72829e65d9a71427a0d5c2f0bc57df006430e7edbc07970858f1f006b42a6b8e510300fc604821abe5d9ed612879f61da683c655452ab2d76bf7345c63299b02b7ccaf83dcb03552c5048020354ed28001f40fb66a7fc55ff272aa8de1844409e56a6c2a3fcb89c94218f082674c00541fe4443178d227abef44644d4abfd5e9303fff2e68bf3e1d140235910cd28a2f0ac9cd54303522a68c80e618d937536c1d26897c8f85b4acf4aa1060c9d7cd154657431320a619ea0a603fd86a52edf39d34cbfd2327b95c4d02dac2df4af3fda5d0fb1411f7499e9228e69f7baba0aeed7b1e31f6d17aad11c2c4a1fda0b5b8b20f3f9963100e0dcd1ab2346b12f602bc30a2bb453e615a7c7df7a1e209bafa91194c0e3da621aea9637db24336c649d82aed522c6ebdc9e3079df264481411d0846d3f82043e90df085bb974d2ff8c5094b56b0721e622a3ece1e2c72afee9cce381b1ab0a042d7a2a0d32bfe34d76e43ae24cfa00802038ce3482e328423d2b01a87447801dd0fc1e2c33563bf35c6bf7ee27f8ea7808e26da630559777a16193d3cbaa060b686fdf5756e395ca3c942d5415ea321930d9bc4581da056043fe7c53176c8fcd66835b9ef69bd815564d1bc7fc71c1373bfc03d480b7aacb1b71a1d5cb97773bd8420ecedb4abeac3762884e73eb9701d5e192c1e2798ea30398aa7c6aa711720294d914419284a52a9794048ad73895e21c73f2e27ee0aeb6cbfc1ed89266847420a154aa825df70f24eb1b1f0b958c046771a911baa6453cba72219ac18b8e31e8b660828ed849b2cd1b642f2494e7430d440552ccde3ff0ea6860a58505c5d2c029cbb4f5fa598fe4ac011751790e0e606322c13d130dfeb16f00ef3a180f8e4886c9a3f367a36a4b2fa215a60511d77a4d1ab2e1d16ee35174f5333bacc3170eb79641811ab8d999ca78bd6aeb778c04bfe4b022405d81d43d828fa1363837759b1d688f9bc423f18067c1e63e7ddcae77b252d284dd9ebdebff77e6f0f9e799a7461a0f4899e54c7d34d148cb21cc3d29c76292c218095c7386baa2c5304e29fd4c70bd82e6062cf160ebb110230f444c2d71f3187f7f4932fda50d850dc56b3ef554e7745311c76955f8eb116e15d3f72d81a267864acfa25775724ef1f370daa11bc9983df1f6d897ed8f040899ecaad740b34dfb34c45c325bd21c38b8634103c639653053c1a72bf63e3a074b437a978169d1d7cf13831621c414bd3d03f307e7bf3c2bc654afdd857c43c0e1e47c4e726f6703cf414f9bef59c90a72eb596013447cc3cf7a8576cecd1674d8607f22e0cf5d470df13e216be6c2e9e8ea0ff0a179c00cac95375a14e02787cacb54a901fd68c2ae7eb0ee85c442aae3d8304039a8209c8090040c83dc3e287ce5187731d9b8e516f91b73a1475c4c4077544dbd325ee6ccfa3aabd1144352afdbd457c2f39054b92189663deb023ab48081a80b8e28841188838be3981e7e93840ac35199412daa396d472a6a2cd403a92a8a80995a1092e99f0dab79a2c024258bff1009161d190a6eff111d46fd2e4fb3528475d89ed0f4acbd2a83ed488cd0d14fb2864e012328fa9fa8234ef040bd7e64c94d8e5dd435f85564aed36b3c59bf50b0611681a82df36ab4b44b009b20fe02e8689ea64df791546cd3db0588d08074c241768008e3feedafd677164b7e19c9dfb2a1f5b4c91abe6b2716dda9027a8cc020340435acab05014bc0b5fba39be8a5dfe4751ec0cd65a54e3acaf559fc45310001c7116a956f02d2c919493c1972c2a8c37c5c7018371de046603f943861d069b44e45d5c59aa7b8e14620ef592b42e43dcd8022db7fd3d652bc292fdebed066f0db566282ce6fd05432d2bf063cc05c2372a442e4eb1ae95d943750660e129ab4a81c1e699cb974b471bb885af4c42f8be4be4a44f6c16cf33b22bfacc7122532e5f78e3da9ca7b8da40ad3651c7a3cde3c028a39f76ee378a5df8e7122d117b0ada3274420f4169996ff8ad7f0644294b964bd02e43b573e027e56c31f30281ded22c23f5318715d0f675c4c697a2c257f22112738f30ba3d9abfab6ffa1b183c5a511100513b515950e7995ccf0f082cb5b0d62b57269e2c7cf3e4c0eb0bffb7398d6bcbc8c3a742ce3bfb2975d365c391409d9590552733692a1a62b7138cea6bdb50758166ea8db309d0ad52dd680140c79d82fe429ceced64c6c214103a90d27fdc490bf99777ffd713275db4329c3cc42f5d631a29af86d0e449a5298a95d8f509c980f7b11abc90efb5b6ec019ba2abe05ff97971e3cc4494a8d11c320855d17be84acbdabcc29634e10451cc55f7b18e63385dee746c70927b271c9d654c07f30212ba3d0b34ab1c7d81484353e7303ba8739122ea0b4d054c5905b3b6fc0e3482c9d1f92261d1caf129228350cf179238a409ae49a4887645050da8ffcf50fa4ef02b22e5cd98fee5625348f8dd72c4806e6a6028da2514e05314cfd201047def07dbac92fc3f31197a0f94659a6264cd08c7b7b1b73f5cc19d074a8c82be071ccd4ad401a5907a3816735668a55c2c2254cc94d9fd42d2990419d4ce2b03bae535d55557127ecd66370f5a555fa5b3d3255b0e3e18040e7c9718c174a2ecd7916c33c950e946764b5a2490252a6c8b4e8a3f4e4200160e12472f73ca99a627089fc5affdb380da4506c458ac642d6a8f151f60b09261abddb2092edcbd085881db6d5b96013108dabdc80a3b38fa8d2e638be26c78465bbeaf2c06f7a360a2cc343d6d9dc2db8f6424302e9718b8884ec3d80e99407770f64a15777f2da7f4aab9408e3ae2f3414bbffd74a290cd14195ef7fff6b31d737bc22f2a1491ff76b75114b2105c8ced13258e504a7b36c5102321daa348581a551729aa4fd8e5c8a411c89f29eb17ccbbdb02141008bbdf46c7274a2c719595fb7a2f5854c0f6543b85ec7221d1e15b8e867c80848016f25e72617f27e537cd721c05b0e96a7801ee3fc43a534ff819962b1e8282ed7fe5437df8026f12931affc92a45a488eb039c2285d69ebb9cc721d0c0481ade8e27986cc2424bd63f09ea13281fa330a68c69d21ea8262d617a8784c5c3130f9fe2638a28d3cdda46fa9c3f110d509bb1dfefd5f72c7af0c2d6cf3036ea02de515f6876005bbac8d8c51d6300ac7fc8e451e423e4ba7c25eb016814dbbe5a9847c8d72a519dee86749635c52fc29948f9db22787989d3d2630dabd1b4a74528011a8a2df437b4fa220098850a3e4753fadb485e012a8cc00da2cd5919c51598f8ff98d82ff813d03bedaedf2fea1e5e0c489ac9b0400e0d949e92115373e6350c76f49c345b79f2ff56bcd001b956017661a2f6b6b167f5f12b4894a32157334c60f29d0521166d13d15de9302c9b0c0bb5c7880f6acbf2acb78d97a66d12e898b7ec0d8c7322bf62e51aec263770b749a733e1842af368cb129d636ca2c23890cd74c44f69a427e6dcd095119e080b300e82de5fc8b08396e97d027b8e781f0cbd63489c5d496949b3daee08a8d6b94c056c4b29bdbefdfb87f715725d48e2aa2c8ecb2e72e4a6329b861c11a1733f664320f80bb5e0624c847534380dd10bf3b9d7b0b3f3cbc00ed6d8d72ea19cc0831d47f5142d7699110a13d154190e29363598288d9e87582621ec8518aa844817a33cf7385a4867a624e6d35d19f08689cb4694d9eb4068a5a89cee14fe3cfd331d9edeba664f921f4e2af1b182eff2ffca37f65408d3d1b42eb324193f27aea86433f3a6aaa1f0ccc589243d364a6d284678652a5727709a417c212fda27231f6e20f52417f284a5641a6d80362cf3dd402b81e6dda971168329fc14aa355a2874208110adb7169be2c53ef263bdf59336ba78618087e36be1f3c9277257150ffe847f6d86895f1197dcaa651987c33d02f5ba5b482311f817d2752f96ec1b41838e338b2af7008a0d5949ff9f18dc7be79ef5739211659f266d621462e12c1be7bc4b4934fdb8699f993d2caf7b1c570cf25b802c98a25d69b994073b68b01ae79b6a69eb211a914afceb059e78d2a636d304a48f6b05bc32993f1eb3e3e642813c85af8b5f1de0888cb13c53fdc5d5d479c70ccbbe61744c1239002bf3e62cebcaeb5e7303b3e12a1f30fd0db795b804875570e7fed3230bd8052871b4aea05a90ad7b63f4113cef0be4dda60693a503b2378172a58c4624a2eb2d5737e779f841a33c28eb444b62d3d23520278eb740181fda57f544f3aee7e30252680255206ff7d0a4a519afb865069d8a05da1132d795895891fa94f8f29904f4324c26aceac3104c97d39759801a293e22e65a381aa86de3b50159ca9fca09cf8f2106c1ecba895d547e1dc71ed0d9e8db14b9db90888777e29ef793b337179e7d5a6852b64d326b7fff80c4434942d3585902b1c534450562c6ffd93e8c0d157d7f9386ead62dbe90615399b0c8fb87e5fca00721a2a3fb68d14d36819b09983dca3f3792b69abc99b3f3f5afd33d28cba0999b8968f72566f05de37d660436b2b1da3001d81f914ab0dc20eb913507e7e526d5f96f842dbc6d461b96df2c259d17a61cc499cc16d0ecb910225076caef6a3d6c2fa282b2bf34a1deb87129450563c1427f7e543ebbd7aa31c01a16f7f9ef377414182129f183ba1246f91048c125b304aec8c136a897807710b6a9a73cf827b2c53e733c2eb1df3fb2fb0210be8239b1631db74571a2e30ff1dc5e77a7be632890b5bed671bdaf6e2a960bdf75213571caa91dd52fab661a9e4fa96e23b6d814748fc4bd6a032ca1cbc71a6e2915cc8eb694d63a591eae3f5aec153656fb2311b4ba339a7e871ef872702461a3b251b88bd55cea092af0c1cf7ec9f96f35e46cd359223da833d8e518990775f13a8a47b5df373d0d46a9098a0b23c1dd6aa76b286f6708e7713811025a23a510820428c3768c4dfc8965fb9d34ed574ea7ca0d2eb426afe5f8fe0681ef02a7a0700344716e9466f589aaee49d2860b604052684091a9e0ea23dc12661a18dc0d314a36ae8c600e6a886d4343a2b42f3419ba19e793ede386baaa303a402f234bee6497b9e517ac24aaab0fb01afb0b1707423554a869d1bea6a5bf743f11f32baa913c5500c9df9db6f92f49d30b7adb7cda475c5cbcfbdab514f391b12c3e705687b7ca3381e1363090cbf57ff7a86d1fc0c9b110893d6537435e223b0966964c5e464aa76eb466b04c4d5c7037f19229aa437e7e490d1bf9b19eec700cee2eaf6b694fa0b6b912b86ac7bddd35f7270baf326abc5234e02798047f6100dd53510a41dc8fe6369350a1b458bf042e8204d4453a359a5d107ad266d8bd0a393b07ac21e86143568945dea47579f961da100dcd17c89e800299d1dac22413736f9bcfe75432de59c190fec427a659e0878ae078596ccfca7ce18070706a27da89553eba9079775003ae2cb47c214b82ea2eefefe40da9638e912cfe60822ae649c2057442471a471a9f952eff46a2babf1a83e19a4ddab3794cdac562db101a09b7a278b2dd6fb6e2a9d393579c92db556817f6dbdc7733599f140cb4382e6b319f574a62dbebd2af4cc151e0773ab2fec6239c69681263c76fd83cff19220271f850682cc592aaaa6eea28d6bc0cb73007fd4bd1bd995a099bd82c024f0a2f782d188ed96e6e3bf4087c26d3d237a2b12204a57053ad307c344ae34fa4213bd4ee4f056cb822fe91f44421f97b9553e372050526ddbec9a9810f0b77cd05e7dd08def408b601eea156f02988103cdc6af0fb020894d84e00680fc6cb690af351f694b00379f093c09cf78cb4261d49857bea4be6f8dd3f4a7b32f59a230e3189ede5dddf5124a8caf0482b511ba11705b8e97b0ae8daff89770da3fb7e20cb1d9be77cebb1509930887487b1599d90171d64bffc6e929ec317bf7e5aec2f7740dbe894189de87675d7e978196e17b528ac35b67d091c6b144dccebf6770a01a0077e8d2c021ad57fb50a34e3158e55e57f94290cb6db1438e73c26dd5f03367dc364867a8930faff385c5e3805b3f5d61b5fe90d04e51b1e14373054971a06811bbc967bd9eec33c892ae9c4ef7274d5e4351abbabd2bcb803a5f5c70f02860e11bdf399a1dfe38969bf66e7da76086fd1368b68e47963db7f9565b3130d69008e708fc5f8961db2d87f77fe3aa722795a6c711cdaa2d4da8f38eaef83196be74e4cf2a837a79259f60cff97b4901f7f48941807f07784fcbf4db57ba80c897759c9ca3474d1f1c03abf18050430fe347a17998ad5498c2afd48c8a98415b32a411758c283b4c28c1d5ca62fc3dc246c62fae1b7450b168c5854c3f6f81f25ec496179eca02a8934cceb7f9ab9fc81a0b5c0982e045b5742e3d21791562bf709a403a8ff7f4c3d3ed483bd94c60426e37242a53fb501d465f6771bdd1c01e4364a0bcd48a20d4fff5363f75c85d02ce552a1c66784f8c393df093885b02286068214029f61fe4946410855f18276aac139a43f0b328f281a40f607dc65006694ad288bedeb4d931e515e3d739eb1c0cc95d7c5284f3ae66f43286a98a92480eb742d842cf382e23e07211c6767bc86b65efcaf58199ad5bbcfaeb67b8116ab42ba4929e61ef40822b47b3b77e1411f2f8dbc72c1e930cd08e4eb38f37b0bea7c2020d7ee87ea54c1b3696fdbc146aa584a23042c69e6efe33f7556c1762b3e3ba8c773badbf5104256d6a599fd5b20145beefa188347e080716230875a1f23ad5fd8f118d14793db2a001b386410d01cae4501bde987126554aa1f943809b93a34284851e0092f0ddadd9d27066c2568c8ebf91bbd5e1554c3ebbc6eb509d2ea1544f9c6b1dce97cd8584418ced461121ffcdec9e3dd1fa14f95d2e4ea08cf6a13709220c27a0a49bb9334e11e182a621cde1bde5f384b1b0ad51d19833033a68bb8ad808d6c9c7b07ab154119abbcbb85ef10d40fb251969dc2108c90221aec3e7930b31580525bb5871112787777aea6ad16614536a5a667520debea18baad0cb9f041125b510f5f1fd48f41cd60268c6ec4149908e7f9c9a1c3fcf813a79c776a2eea726579809ef20c267d839f3a208120f8736779796e9dfa7bca253a82820d90f4f5f7fb4b94a870394dbb688fdcc0bb429e43546b8b88743f3f114951cfc8e34a21110a24cfb809985668ea681e55f068d8f260436dfcbaffc91c32edff5ee1777d2ff82c80ea797077225f691258424cf5f5d64b3c312d6f48257b1ca15daafc6d830d0d38e74669add7907e870f0f8e896837af7e0104b15d103b7ad3d25f8de54bcc7ba08a0b3bb9f30e238fe4e3321a523728c554b162110489739821241ca55e4ecbcc5f6f25bfb44763a32e19fe977940ce8800db666317bdcdf1f29834dc6b2f07c60f7076ea4f4c73881e89734d46a31a160380704e0e4d0b30a60fae2bd517e3c939cf57682a076d4f6f364126bb2975f940ee8fdd0976ffd75e34827735162044bff075b04735ee3468e51d80a7a09bed81b48942205981554c5a78f504f763531fdb93d2861ef929be2f873e59dcec85631807b70484a7f6ec144b234ea375bdd48188e15ada0eb56ad1b987d75ed356bb65d5e01bdf10e80176f7be41f9d31008b8274f1b0a8e37108cfd37fa80c5be57fdc6450207b4b4209e983e6aa97e26c81afb4d57059de74276ffde689f883d5ed67abd2ccd9fbc681e291fed67130e43f755601bae4c8849909e9030484cf3c71aea07227f95a36dffb88c8d0131284fba36b597c0607f0ab3b1bb6c215a19fd80cdc92c0ae72660453b0973ebfa890718c2939f1207e3ae5ae18bde7208392f708a9a093cbd866e4426034e015fa66d8b9379509021996d41841c44374f7e4ec77a8e0bcb2604e0783d0a7981f77addc0d9d00dcdba536950e38f1893b138696ca20828cc6f843d63ce7c068774807ac2f6a52bd2adc3649c130a35b854e2965dda9a2a39eab89126cc289a350e2bf0eda765a5bc5da8640047bf12423fd54224d0d6053505f8e689a403440ad11e799b653515ad5ebadac03c3c17a2e6a40ad8b829025a520eabe6104fefc0349b83a1292f1713d4e56131708398777532728fcd2b0b712b5802b896281422c1bc219dfd0d9df3be491a20443ed5119ae62c468d2a22adda3834369b2cefc68f1033b217baa21720c4e4813cd4b52388f3bce5eb3e21ea381f1ee6305814a07f4742b52311720f7aa836766eb57fc2504d89fa68be03b747454dcc12f3b4434342eab6d95eb64c2dccd735983c553b76a009c18e6e2e8e783a9067cda1f5109f7371fcaa2cc1658c50fc513effba99a62f84479ede1c2afee100f375462e12d611ca1e58b350f4713e43436f8d44ed129d36d741772485b57e2cde8766e19956bf15fe1a3f715f462ff70e3e66911ca8b0349ccac50d0c4d59a71edabc1844172fce54e10bcba6f74cbd36c2c6cb8cdf86b07fcf272bbeae03157930ff5bf215a8fbeae00610efde36e8803758c44b71611ad8b7530f77a243c70d35bcbdf3d390e0e1f358a53bb7b271748486ae81cecf1d3bd7b312e91ec7312eb62f7f4465e9f133c90b40b5aabadf897db276170982e3682959027d4f12eee5a6720a6863c259d5569d30604063277d5afa87a04ff1db7cbbd8048b5f6ba32e0972746c42fada98ecc8139f01726b225c661b7131c962a6af431c46da1b18603580e0eb36b1b3a78803784752e95178856a52cdf293be7d20ca28afd91216a390b4c5fa200d13796d66a09e67c0dcad2e5cc6ba969843f35de110a26385709f5eaa333c3dbc672805554507e4492dd88e4e1fb37d30f00b100b0b73ef0f2805b3dcee77ee3c518d4d2b5e10a6e74139d13ec437fb20caf21f6b14258b219d2d7c874ff865b46668c9a98a6f95f7513c4840eac1fcd8c0d5983f2fc417fa028662ef9622af0c8b9a8370044569515953bc3351e7879256a1682f1913c6f71242142890f49878f50a88254967744ee98fc8f39b077df24f896475138b353992d43a58ab679391c189def4abb585c95fc78f8560ed1578334a54ef06c4e242e99763949a6214d2f1e6b2e0084716b5acb458f0132451aca43dbe9002d447989ac471fc47f751e9ab748f01dba71e41478c7ba6cba186c59ab2b22e0ae2cd982005400350f2b1fd5804f85a943324dcd60e01aec98058ec872048fc2a3aa79499437917db1f087117dc5627fb97486c24adaf4cdea43ec70a5ecb1115482b3eccdfff6508c76655387abd8db57b01b170d5e1870faa1384ba4365e7054331d92e9f9f0b1ad97b7b530669a248a4c26ceaf174cf6479efbc7da6ea54099c32957b8e46d8efb77b50d25e3c705ec7679f33abae0b097fd615c45f9d461b03dc3ca49cfa79c813d003bc5fe4ec9cfa94a1acd523d23b88d35ebb5a6e9a2fea8939d0a2391f5ba2f337b17972354cbee48f9f4316611ca4747267fb899a2c3deec9cbe9907b63384af2f8949c18e82d0599a14c794393c329254a5497322b8f054ff0c0c835e7176bef90eebe63c9caf6838cf97942832984d19839ab9a15ff2ee3f4e15a05e49a92af53f986375f372836e8b0871f4c4ec28a690e5e631d2dd1341b55a31770bbeb958ca73627dcb539875b11846f8129714880e708ee260d85ed2385e58c520b25866ee5635882ab26c9019999d6fbf748f1e799852121d87673b8e00f15076cdc02771e293c1ca49972b74ab8790702fbc2d08d89be1c26e413b5ff7ff00f30fdc45ed794caecb4f8e943d341f7ec1f9cbe686e8408302d278a2da6ae5b6328b996f93480f99dcbf66ac56244f780859f9bc5e6d61413b5b5724c4eaff5aa4d5113e19f2072cc4486528b4f864c0b2fdd8dc12c07f70799dcb83bccdf08e82e1fb2354171bec979c58e2dc82f05fb9018859906ff3803fc845ea83bfb869fca929234cedf2c7ed172a4a857031bd37d4a65886916451a01d80e3d40970692f0ab31cc53c737a8ffd3408641dd18b12b59d00880a1a51bf1201219c156f4687c89b3bba7966515c263a6da337c70219b18cbd7e1a7ca07f77287b04f62adf3ba0a00f903a4de156b402aa92faec6737f1be36050a4810eb34741a55cfc86f22b7e734e338f600f09df00574221e9f861fe9b68b5f8501ec4c809a6c85ede1d9410633478be10ac32a14f2d77664f050a16f2b0a73b470a99616c27a96a7b747c365d9191b9a0a92f96eb10e8cfd399d35a186ab9ee7f1680f3102a604fc69d1b1e5058113ac9d917ec32e1cb339755b5536e4715758f", + "public_inputs_hex": "0x1a207628cc6936816ccb62a7b56fdbbf8e975293b677c988644e018fc402e4411dfdc7cb6265f100524012f038ee1f205bf8789b70b46c57463dedb4998f7ac800000000000000000000000000000000c946233fabee579beb961008103a4c8d00000000000000000000000000000000026a297ba8fb72294cced25b49dae7c91806572c19ee2f3532e970d617919b3aa8cdc582782dfad0699badc631f75128000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000022b942ad53baa7c6e1a7616e4f0aa2eedf7d67eacc63696345525988225847479273fc58215abae6a73503410e8209a542c92bbea2ba8e6fb129c971def4ca8452ddcd2c1e0b08c268d942b52a49e7597873ccad9df1b02ea1ab854e7111eb2dd188f793a89424a83a3e746a72d4cdfd5702a7944deb0fa2ee04f84489f4af3c2000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } } diff --git a/scripts/build-circuits.ts b/scripts/build-circuits.ts index 1229cce52c..7dccef540c 100644 --- a/scripts/build-circuits.ts +++ b/scripts/build-circuits.ts @@ -329,7 +329,9 @@ class NoirCircuitBuilder { private writeActiveBinPresetStamp(preset: string, sourceHash: string): void { const stamp: PresetBuildStamp = { preset, - committee: this.options.committee, + // Narrow out 'all': this runs per resolved preset/committee, so 'all' is meaningless here + // and must never be persisted into the stamp that --skip-if-built later compares against. + committee: this.options.committee === 'all' ? undefined : this.options.committee, sourceHash, builtAt: new Date().toISOString(), } @@ -957,7 +959,13 @@ class NoirCircuitBuilder { const hash = createHash('sha256') if (preset !== undefined) { hash.update(`preset:${preset}\n`) - hash.update(`noir_config:${PRESET_NOIR_CONFIG[preset]}\n`) + const tier = PRESET_NOIR_CONFIG[preset] + hash.update(`noir_config:${tier}\n`) + // Hash the contents of the preset's Noir config, not just its name: the baked crypto + // constants (e.g. PK_GENERATION_E_SM_BOUND) live here and must invalidate --skip-if-built + // when they change. Otherwise a bound update silently reuses a stale compiled circuit. + const tierConfigDir = join(this.rootDir, 'circuits', 'lib', 'src', 'configs', tier) + if (existsSync(tierConfigDir)) this.hashDir(tierConfigDir, hash) } if (this.options.committee) { hash.update(`committee:${this.options.committee}\n`) diff --git a/templates/default/Cargo.lock b/templates/default/Cargo.lock index 2cac193f68..336e49d529 100644 --- a/templates/default/Cargo.lock +++ b/templates/default/Cargo.lock @@ -1553,7 +1553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1602,8 +1602,8 @@ dependencies = [ [[package]] name = "fhe" -version = "0.2.0" -source = "git+https://github.com/gnosisguild/fhe.rs#a92478b39625f4c18b91a5033928b35d8a0090cf" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "bincode", "doc-comment", @@ -1628,8 +1628,8 @@ dependencies = [ [[package]] name = "fhe-math" -version = "0.2.0" -source = "git+https://github.com/gnosisguild/fhe.rs#a92478b39625f4c18b91a5033928b35d8a0090cf" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "ethnum", "fhe-traits", @@ -1653,16 +1653,16 @@ dependencies = [ [[package]] name = "fhe-traits" -version = "0.1.1" -source = "git+https://github.com/gnosisguild/fhe.rs#a92478b39625f4c18b91a5033928b35d8a0090cf" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "rand 0.9.2", ] [[package]] name = "fhe-util" -version = "0.1.1" -source = "git+https://github.com/gnosisguild/fhe.rs#a92478b39625f4c18b91a5033928b35d8a0090cf" +version = "0.2.1" +source = "git+https://github.com/gnosisguild/fhe.rs?tag=v0.2.2#f2c1d2258fbeef6dbdf5ade68438203fd80a648e" dependencies = [ "num-bigint-dig", "num-traits", @@ -2962,7 +2962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", - "itertools 0.14.0", + "itertools 0.13.0", "log", "multimap", "petgraph", @@ -2981,7 +2981,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.117", @@ -3388,7 +3388,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3841,7 +3841,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/templates/default/Cargo.toml b/templates/default/Cargo.toml index 2cfd2d51e8..9ca8e25b5d 100644 --- a/templates/default/Cargo.toml +++ b/templates/default/Cargo.toml @@ -7,8 +7,8 @@ members = [ [workspace.dependencies] e3-user-program = { path = "./program" } -fhe = { git = "https://github.com/gnosisguild/fhe.rs" } -fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs" } +fhe = { git = "https://github.com/gnosisguild/fhe.rs", tag = "v0.2.2" } +fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", tag = "v0.2.2" } e3-program-server = { path = "../../crates/program-server" } e3-bfv-client = { path = "../../crates/bfv-client" } e3-fhe-params = { path = "../../crates/fhe-params" } From 79fdb5b9f98608cb82ad4049434db29ee8700d2e Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Mon, 15 Jun 2026 18:40:43 +0500 Subject: [PATCH 23/24] fix: add tests --- .../IBondingRegistry.json | 2 +- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IInterfold.sol/IInterfold.json | 2 +- .../ISlashingManager.json | 2 +- .../InterfoldTicketToken.json | 18 +- .../test/Token/InterfoldToken.spec.ts | 479 ++++++++++++++++++ 6 files changed, 492 insertions(+), 13 deletions(-) diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 651f77bce1..9c70e825e2 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1081,5 +1081,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-8facb7e897e6b871d6535c23335b76111c7915ad" + "buildInfoId": "solc-0_8_28-5e064566757ed1db9a1081a58df17ab403efa32a" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index ff77e3a537..2e12b11936 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1332,5 +1332,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-8facb7e897e6b871d6535c23335b76111c7915ad" + "buildInfoId": "solc-0_8_28-5e064566757ed1db9a1081a58df17ab403efa32a" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 55ff9c5196..3b90bde885 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -2427,5 +2427,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-8facb7e897e6b871d6535c23335b76111c7915ad" + "buildInfoId": "solc-0_8_28-5e064566757ed1db9a1081a58df17ab403efa32a" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index a875cd24cd..44019b3c0b 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1233,5 +1233,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-8facb7e897e6b871d6535c23335b76111c7915ad" + "buildInfoId": "solc-0_8_28-5e064566757ed1db9a1081a58df17ab403efa32a" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json index 321cd53d89..465af827bf 100644 --- a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json +++ b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json @@ -1428,7 +1428,7 @@ "linkReferences": {}, "deployedLinkReferences": {}, "immutableReferences": { - "4593": [ + "4010": [ { "length": 32, "start": 3120 @@ -1442,43 +1442,43 @@ "start": 5419 } ], - "7931": [ + "7066": [ { "length": 32, "start": 5651 } ], - "7933": [ + "7068": [ { "length": 32, "start": 5609 } ], - "7935": [ + "7070": [ { "length": 32, "start": 5567 } ], - "7937": [ + "7072": [ { "length": 32, "start": 5732 } ], - "7939": [ + "7074": [ { "length": 32, "start": 5772 } ], - "7942": [ + "7077": [ { "length": 32, "start": 6200 } ], - "7945": [ + "7080": [ { "length": 32, "start": 6245 @@ -1486,5 +1486,5 @@ ] }, "inputSourceName": "project/contracts/token/InterfoldTicketToken.sol", - "buildInfoId": "solc-0_8_28-8facb7e897e6b871d6535c23335b76111c7915ad" + "buildInfoId": "solc-0_8_28-5e064566757ed1db9a1081a58df17ab403efa32a" } \ No newline at end of file diff --git a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts index d9453b90e8..8488a09a40 100644 --- a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts +++ b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts @@ -1005,6 +1005,147 @@ describe("InterfoldToken", function () { await token.transferableBalanceOf(await alice.getAddress()), ).to.equal(0n); }); + + it("holdUntil keeps everything locked regardless of curve", async function () { + const { token, admin, alice, ccaEnd } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + + // Compute the intended TGE timestamp before firing it. + const TGE_COOLDOWN = 45n * DAY; + const intendedTge = ccaEnd + TGE_COOLDOWN + 1n; + + // Policy: 1-year linear vest, holdUntil = intended TGE + 2 years. + const policyId = await createLinearPolicy(token, admin, "HOLD_TEST", { + vestDuration: 1n * YEAR, + holdUntil: intendedTge + 2n * YEAR, + }); + + // Mint locked allocation DURING Virtual phase. + const amount = ethers.parseEther("1000"); + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount, + policyId, + label: ethers.encodeBytes32String("hold"), + }, + ]); + + // Fire TGE. + await time.increaseTo(intendedTge); + const tgeTx = await token.tge(); + const receipt = await tgeTx.wait(); + const tgeBlock = await ethers.provider.getBlock(receipt!.blockNumber); + const tgeTimestamp = BigInt(tgeBlock!.timestamp); + + // At TGE + 1.5 years: curve says fully unlocked (vestDuration = 1Y), + // but holdUntil = TGE + 2Y keeps everything locked. + await time.increaseTo(tgeTimestamp + (1n * YEAR + YEAR / 2n)); + expect(await token.lockedBalanceOf(aliceAddress)).to.equal(amount); + + // At TGE + 2 years (holdUntil): hold lifts, curve already fully vested. + await time.increaseTo(tgeTimestamp + 2n * YEAR); + expect(await token.lockedBalanceOf(aliceAddress)).to.equal(0n); + }); + + it("MAX_LOCKS_PER_ACCOUNT: 9th active policy reverts", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + const maxLocks = Number(await token.MAX_LOCKS_PER_ACCOUNT()); + + // Create 8 distinct policies and mint 1 wei under each. + for (let i = 0; i < maxLocks; i++) { + const policyId = ethers.encodeBytes32String(`CAP_${i}`); + await createLinearPolicy(token, admin, `CAP_${i}`, { + vestDuration: 1n * YEAR, + }); + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount: 1n, + policyId, + label: ethers.encodeBytes32String(`cap${i}`), + }, + ]); + } + expect(await token.lockCount(aliceAddress)).to.equal(BigInt(maxLocks)); + + // 9th policy should revert. + const ninthId = ethers.encodeBytes32String("CAP_9"); + await createLinearPolicy(token, admin, "CAP_9", { + vestDuration: 1n * YEAR, + }); + await expect( + token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount: 1n, + policyId: ninthId, + label: ethers.encodeBytes32String("toomany"), + }, + ]), + ).to.be.revertedWithCustomError(token, "TooManyLocks"); + }); + + it("MAX_QUEUED_LOCKS_PER_ACCOUNT: 9th queued policy reverts", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + const maxQueued = Number(await token.MAX_QUEUED_LOCKS_PER_ACCOUNT()); + + // Create 8 distinct policies and queue links. + for (let i = 0; i < maxQueued; i++) { + const policyId = ethers.encodeBytes32String(`QCAP_${i}`); + await createLinearPolicy(token, admin, `QCAP_${i}`, { + vestDuration: 1n * YEAR, + }); + await token.connect(admin).linkClaim(aliceAddress, 1n, policyId); + } + expect(await token.queuedLockCount(aliceAddress)).to.equal( + BigInt(maxQueued), + ); + + // 9th queued link should revert. + const ninthId = ethers.encodeBytes32String("QCAP_9"); + await createLinearPolicy(token, admin, "QCAP_9", { + vestDuration: 1n * YEAR, + }); + await expect( + token.connect(admin).linkClaim(aliceAddress, 1n, ninthId), + ).to.be.revertedWithCustomError(token, "TooManyQueuedLocks"); + }); + + it("incrementing an existing policy does not count as a new entry", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + const policyId = await createLinearPolicy(token, admin, "INCREMENT", { + vestDuration: 1n * YEAR, + }); + + // One allocation under the policy. + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount: ethers.parseEther("100"), + policyId, + label: ethers.encodeBytes32String("first"), + }, + ]); + expect(await token.lockCount(aliceAddress)).to.equal(1n); + + // Second allocation under the SAME policy -- increments amount, not a new entry. + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount: ethers.parseEther("100"), + policyId, + label: ethers.encodeBytes32String("second"), + }, + ]); + expect(await token.lockCount(aliceAddress)).to.equal(1n); + + const lock = await token.locks(aliceAddress, 0); + expect(lock.amount).to.equal(ethers.parseEther("200")); + }); }); // ═════════════════════════════════════════════════════════════════════════ @@ -1098,6 +1239,68 @@ describe("InterfoldToken", function () { token.connect(alice).transfer(await bob.getAddress(), amount), ).to.be.revertedWithCustomError(token, "TransferRestricted"); }); + + it("pre-TGE: whitelist does NOT bypass locked-balance invariant", async function () { + const { token, admin, alice, bob } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + const policyId = await createLinearPolicy( + token, + admin, + "WHITELIST_LOCK", + { + vestDuration: 2n * YEAR, + }, + ); + const amount = ethers.parseEther("1000"); + + // Mint locked allocation to Alice. + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount, + policyId, + label: ethers.encodeBytes32String("locked"), + }, + ]); + + // Whitelist Alice. + await token.connect(admin).setTransferWhitelisted(aliceAddress, true); + + // Pre-TGE, whitelist bypasses the transfer gate but NOT the lock invariant. + // Alice has all tokens locked -> transferableBalance is 0 -> transfer reverts. + await expect( + token.connect(alice).transfer(await bob.getAddress(), amount), + ).to.be.revertedWithCustomError(token, "InsufficientUnlockedBalance"); + }); + + it("pre-TGE: CLAIM_SOURCE transfer creates PENDING lock on recipient", async function () { + const { token, admin, alice, claimSource } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + const amount = ethers.parseEther("500"); + + // Mint unlocked tokens to claimSource during Virtual phase. + await token + .connect(admin) + .mint(await claimSource.getAddress(), amount, ethers.ZeroHash); + + // Pre-TGE claim-source transfer to Alice. + await token.connect(claimSource).transfer(aliceAddress, amount); + + // Alice received a PENDING lock (pre-TGE, no bond, so fully locked). + expect(await token.lockedBalanceOf(aliceAddress)).to.equal(amount); + expect(await token.lockCount(aliceAddress)).to.equal(1n); + + const lock = await token.locks(aliceAddress, 0); + expect(lock.policyId).to.equal(ethers.encodeBytes32String("PENDING")); + expect(lock.amount).to.equal(amount); + + // Pre-TGE, Alice cannot transfer it onward at all (transfer gate). + // The locked-balance invariant would also block it, but the pre-TGE + // gate catches it first. Both protections are correct. + await expect( + token.connect(alice).transfer(await claimSource.getAddress(), amount), + ).to.be.revertedWithCustomError(token, "TransferRestricted"); + }); }); // ═════════════════════════════════════════════════════════════════════════ @@ -1171,6 +1374,67 @@ describe("InterfoldToken", function () { expect(await token.lockCount(aliceAddress)).to.equal(0n); expect(await token.lockedBalanceOf(aliceAddress)).to.equal(0n); }); + + it("late TGE: max-length policy tail is truncated at NO_MORE_LOCKS", async function () { + const { token, admin, alice, ccaEnd } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + + // Create a policy whose natural end lands exactly at NO_MORE_LOCKS + // (using the earliest possible TGE: ccaEnd + TGE_COOLDOWN). + const earliestTge = ccaEnd + TGE_COOLDOWN; + // The tail after earliestTge is NO_MORE_LOCKS_DELAY = 4 years. + const policyId = await createLinearPolicy(token, admin, "MAX_TAIL", { + vestDuration: NO_MORE_LOCKS_DELAY, + }); + const amount = ethers.parseEther("1000"); + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount, + policyId, + label: ethers.encodeBytes32String("tail"), + }, + ]); + + // Call TGE slightly late -- 1 day past the earliest possible TGE. + await time.increaseTo(earliestTge + 1n * DAY); + await token.tge(); + + // Advance to NO_MORE_LOCKS. The natural unlock tail would extend 1 day + // past NO_MORE_LOCKS (since TGE was 1 day late), but NO_MORE_LOCKS + // overrides -- locked balance MUST be 0. + await time.increaseTo(await token.NO_MORE_LOCKS()); + expect(await token.lockedBalanceOf(aliceAddress)).to.equal(0n); + }); + + it("absolute sunset without TGE: transfers succeed and no locks are created", async function () { + const { token, admin, alice, bob, claimSource, noMoreLocks } = + await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + const claimSourceAddress = await claimSource.getAddress(); + const amount = ethers.parseEther("500"); + + // Mint unlocked tokens during Virtual phase (before time advance). + await token.connect(admin).mint(aliceAddress, amount, ethers.ZeroHash); + await token + .connect(admin) + .mint(claimSourceAddress, amount, ethers.ZeroHash); + + // Do NOT call tge(). Advance straight to NO_MORE_LOCKS. + await time.increaseTo(noMoreLocks); + + // Regular transfer succeeds. + await token.connect(alice).transfer(await bob.getAddress(), amount); + expect(await token.balanceOf(aliceAddress)).to.equal(0n); + + // lockedBalanceOf returns 0. + expect(await token.lockedBalanceOf(aliceAddress)).to.equal(0n); + + // CLAIM_SOURCE transfer creates no lock. + await token.connect(claimSource).transfer(aliceAddress, amount); + expect(await token.lockedBalanceOf(aliceAddress)).to.equal(0n); + expect(await token.lockCount(aliceAddress)).to.equal(0n); + }); }); // ═════════════════════════════════════════════════════════════════════════ @@ -1470,6 +1734,221 @@ describe("InterfoldToken", function () { }); }); + // ═════════════════════════════════════════════════════════════════════════ + // relinkActiveLock + // ═════════════════════════════════════════════════════════════════════════ + + describe("relinkActiveLock", function () { + it("relink works before TGE", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + + const fromPolicy = await createLinearPolicy(token, admin, "FROM_POL", { + vestDuration: 1n * YEAR, + }); + const toPolicy = await createLinearPolicy(token, admin, "TO_POL", { + vestDuration: 2n * YEAR, + }); + const amount = ethers.parseEther("1000"); + + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount, + policyId: fromPolicy, + label: ethers.encodeBytes32String("src"), + }, + ]); + + // Relink 400 from FROM_POL to TO_POL. + const relinkAmount = ethers.parseEther("400"); + await expect( + token + .connect(admin) + .relinkActiveLock(aliceAddress, fromPolicy, toPolicy, relinkAmount), + ) + .to.emit(token, "ActiveLockRelinked") + .withArgs(aliceAddress, fromPolicy, toPolicy, relinkAmount); + + // FROM_POL should now have 600, TO_POL should have 400. + const locks = [ + await token.locks(aliceAddress, 0), + await token.locks(aliceAddress, 1), + ]; + const byPolicy = new Map( + locks.map((l: { policyId: string; amount: bigint }) => [ + l.policyId, + l.amount, + ]), + ); + expect(byPolicy.get(fromPolicy)).to.equal(ethers.parseEther("600")); + expect(byPolicy.get(toPolicy)).to.equal(relinkAmount); + }); + + it("relink reverts after TGE", async function () { + const { token, admin, alice, ccaEnd } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + + const fromPolicy = await createLinearPolicy(token, admin, "FROM_AFTER", { + vestDuration: 1n * YEAR, + }); + const toPolicy = await createLinearPolicy(token, admin, "TO_AFTER", { + vestDuration: 2n * YEAR, + }); + const amount = ethers.parseEther("1000"); + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount, + policyId: fromPolicy, + label: ethers.encodeBytes32String("src"), + }, + ]); + + // Fire TGE. + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + await token.tge(); + + await expect( + token + .connect(admin) + .relinkActiveLock( + aliceAddress, + fromPolicy, + toPolicy, + ethers.parseEther("100"), + ), + ).to.be.revertedWithCustomError(token, "AlreadyLive"); + }); + + it("relink reverts when amount exceeds source lock", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + + const fromPolicy = await createLinearPolicy(token, admin, "SRC_SMALL", { + vestDuration: 1n * YEAR, + }); + const toPolicy = await createLinearPolicy(token, admin, "DST_BIG", { + vestDuration: 2n * YEAR, + }); + const amount = ethers.parseEther("100"); + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount, + policyId: fromPolicy, + label: ethers.encodeBytes32String("small"), + }, + ]); + + await expect( + token + .connect(admin) + .relinkActiveLock( + aliceAddress, + fromPolicy, + toPolicy, + ethers.parseEther("200"), + ), + ).to.be.revertedWithCustomError(token, "RelinkAmountExceeded"); + }); + + it("relink from PENDING reverts", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + const pendingId = ethers.encodeBytes32String("PENDING"); + const toPolicy = await createLinearPolicy(token, admin, "TO_REAL", { + vestDuration: 2n * YEAR, + }); + + await expect( + token + .connect(admin) + .relinkActiveLock( + aliceAddress, + pendingId, + toPolicy, + ethers.parseEther("1"), + ), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("relink to PENDING reverts", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + const pendingId = ethers.encodeBytes32String("PENDING"); + const fromPolicy = await createLinearPolicy(token, admin, "FROM_REAL", { + vestDuration: 1n * YEAR, + }); + const amount = ethers.parseEther("100"); + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount, + policyId: fromPolicy, + label: ethers.encodeBytes32String("real"), + }, + ]); + + await expect( + token + .connect(admin) + .relinkActiveLock( + aliceAddress, + fromPolicy, + pendingId, + ethers.parseEther("50"), + ), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("relink source == target reverts", async function () { + const { token, admin, alice } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + const policyId = await createLinearPolicy(token, admin, "SAME_POL", { + vestDuration: 1n * YEAR, + }); + const amount = ethers.parseEther("100"); + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount, + policyId, + label: ethers.encodeBytes32String("same"), + }, + ]); + + await expect( + token + .connect(admin) + .relinkActiveLock( + aliceAddress, + policyId, + policyId, + ethers.parseEther("50"), + ), + ).to.be.revertedWithCustomError(token, "InvalidPolicy"); + }); + + it("non-LOCK_MANAGER_ROLE cannot relink", async function () { + const { token, alice } = await loadFixture(deploy); + await expect( + token + .connect(alice) + .relinkActiveLock( + await alice.getAddress(), + ethers.encodeBytes32String("A"), + ethers.encodeBytes32String("B"), + ethers.parseEther("1"), + ), + ).to.be.revertedWithCustomError( + token, + "AccessControlUnauthorizedAccount", + ); + }); + }); + // ═════════════════════════════════════════════════════════════════════════ // BondingRegistry integration (uses deployInterfoldSystem) // ═════════════════════════════════════════════════════════════════════════ From fc1f63d04456384699c71c70087d29b1434282d5 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Mon, 15 Jun 2026 19:47:40 +0500 Subject: [PATCH 24/24] feat: add tests --- crates/init/src/lib.rs | 13 +- .../IBondingRegistry.json | 2 +- .../ICiphernodeRegistry.json | 2 +- .../interfaces/IInterfold.sol/IInterfold.json | 2 +- .../ISlashingManager.json | 2 +- .../InterfoldTicketToken.json | 18 +- .../test/Token/InterfoldToken.spec.ts | 385 +++++++++++++++++- 7 files changed, 403 insertions(+), 21 deletions(-) diff --git a/crates/init/src/lib.rs b/crates/init/src/lib.rs index 9d4b4051d6..cb5563e159 100644 --- a/crates/init/src/lib.rs +++ b/crates/init/src/lib.rs @@ -306,13 +306,12 @@ pub async fn execute( ) -> Result<()> { println!( r" - ░██████████ ░██ - ░██ ░██ - ░██ ░████████ ░███████ ░██ ░██████ ░██ ░██ ░███████ - ░█████████ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ - ░██ ░██ ░██ ░██ ░██ ░███████ ░██ ░██ ░█████████ - ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██░██ ░██ - ░██████████ ░██ ░██ ░███████ ░██ ░█████░██ ░███ ░███████ + ██╗███╗ ██╗████████╗███████╗██████╗ ███████╗ ██████╗ ██╗ ██████╗ + ██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗██╔════╝██╔═══██╗██║ ██╔══██╗ + ██║██╔██╗ ██║ ██║ █████╗ ██████╔╝█████╗ ██║ ██║██║ ██║ ██║ + ██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗██╔══╝ ██║ ██║██║ ██║ ██║ + ██║██║ ╚████║ ██║ ███████╗██║ ██║██║ ╚██████╔╝███████╗██████╔╝ + ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝╚═════╝ " ); diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 9c70e825e2..df44e91615 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -1081,5 +1081,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-5e064566757ed1db9a1081a58df17ab403efa32a" + "buildInfoId": "solc-0_8_28-131befb7b6efbc9af996a8f87e4762d0d84ba904" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index 2e12b11936..4d5a4c9827 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -1332,5 +1332,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-5e064566757ed1db9a1081a58df17ab403efa32a" + "buildInfoId": "solc-0_8_28-131befb7b6efbc9af996a8f87e4762d0d84ba904" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json index 3b90bde885..6dc1dc71e6 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/IInterfold.sol/IInterfold.json @@ -2427,5 +2427,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IInterfold.sol", - "buildInfoId": "solc-0_8_28-5e064566757ed1db9a1081a58df17ab403efa32a" + "buildInfoId": "solc-0_8_28-131befb7b6efbc9af996a8f87e4762d0d84ba904" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json index 44019b3c0b..24271bce16 100644 --- a/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json +++ b/packages/interfold-contracts/artifacts/contracts/interfaces/ISlashingManager.sol/ISlashingManager.json @@ -1233,5 +1233,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ISlashingManager.sol", - "buildInfoId": "solc-0_8_28-5e064566757ed1db9a1081a58df17ab403efa32a" + "buildInfoId": "solc-0_8_28-131befb7b6efbc9af996a8f87e4762d0d84ba904" } \ No newline at end of file diff --git a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json index 465af827bf..6111a835f8 100644 --- a/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json +++ b/packages/interfold-contracts/artifacts/contracts/token/InterfoldTicketToken.sol/InterfoldTicketToken.json @@ -1428,7 +1428,7 @@ "linkReferences": {}, "deployedLinkReferences": {}, "immutableReferences": { - "4010": [ + "4593": [ { "length": 32, "start": 3120 @@ -1442,43 +1442,43 @@ "start": 5419 } ], - "7066": [ + "7931": [ { "length": 32, "start": 5651 } ], - "7068": [ + "7933": [ { "length": 32, "start": 5609 } ], - "7070": [ + "7935": [ { "length": 32, "start": 5567 } ], - "7072": [ + "7937": [ { "length": 32, "start": 5732 } ], - "7074": [ + "7939": [ { "length": 32, "start": 5772 } ], - "7077": [ + "7942": [ { "length": 32, "start": 6200 } ], - "7080": [ + "7945": [ { "length": 32, "start": 6245 @@ -1486,5 +1486,5 @@ ] }, "inputSourceName": "project/contracts/token/InterfoldTicketToken.sol", - "buildInfoId": "solc-0_8_28-5e064566757ed1db9a1081a58df17ab403efa32a" + "buildInfoId": "solc-0_8_28-131befb7b6efbc9af996a8f87e4762d0d84ba904" } \ No newline at end of file diff --git a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts index 8488a09a40..94893703dd 100644 --- a/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts +++ b/packages/interfold-contracts/test/Token/InterfoldToken.spec.ts @@ -528,6 +528,74 @@ describe("InterfoldToken", function () { ]), ).to.be.revertedWithCustomError(token, "MintingClosed"); }); + + it("handles mixed recipients and mixed policies in one batch", async function () { + const { token, admin, alice, bob } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + const bobAddress = await bob.getAddress(); + + const saftPolicy = await createLinearPolicy(token, admin, "SAFT_BATCH", { + vestDuration: 2n * YEAR, + }); + const teamPolicy = await createLinearPolicy(token, admin, "TEAM_BATCH", { + vestDuration: 3n * YEAR, + }); + const ccaPolicy = await createLinearPolicy(token, admin, "CCA_BATCH", { + vestDuration: 1n * YEAR, + }); + + // One batch: Alice gets SAFT + TEAM, Bob gets CCA. + const saftAmount = ethers.parseEther("1000"); + const teamAmount = ethers.parseEther("500"); + const ccaAmount = ethers.parseEther("700"); + + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount: saftAmount, + policyId: saftPolicy, + label: ethers.encodeBytes32String("saft"), + }, + { + recipient: aliceAddress, + amount: teamAmount, + policyId: teamPolicy, + label: ethers.encodeBytes32String("team"), + }, + { + recipient: bobAddress, + amount: ccaAmount, + policyId: ccaPolicy, + label: ethers.encodeBytes32String("cca"), + }, + ]); + + // Alice: 2 locks, 1500 total. + expect(await token.lockCount(aliceAddress)).to.equal(2n); + expect(await token.lockedBalanceOf(aliceAddress)).to.equal( + saftAmount + teamAmount, + ); + expect(await token.balanceOf(aliceAddress)).to.equal( + saftAmount + teamAmount, + ); + + // Bob: 1 lock, 700 total. + expect(await token.lockCount(bobAddress)).to.equal(1n); + expect(await token.lockedBalanceOf(bobAddress)).to.equal(ccaAmount); + expect(await token.balanceOf(bobAddress)).to.equal(ccaAmount); + + // Verify Alice's locks have the correct policies. + const aliceLock0 = await token.locks(aliceAddress, 0); + const aliceLock1 = await token.locks(aliceAddress, 1); + const alicePolicies = new Set([aliceLock0.policyId, aliceLock1.policyId]); + expect(alicePolicies.has(saftPolicy)).to.be.true; + expect(alicePolicies.has(teamPolicy)).to.be.true; + + // Bob's lock is CCA. + const bobLock = await token.locks(bobAddress, 0); + expect(bobLock.policyId).to.equal(ccaPolicy); + expect(bobLock.amount).to.equal(ccaAmount); + }); }); // ═════════════════════════════════════════════════════════════════════════ @@ -964,7 +1032,7 @@ describe("InterfoldToken", function () { ); }); - it("holdUntil keeps everything locked regardless of curve", async function () { + it("lockedBalanceAt follows the TGE-linear curve over time", async function () { // Use deployWithLockAndTge which creates a Tge-anchored lock with holdUntil=0. // Then verify lockedBalanceAt at various timestamps. const { token, alice, amount, tgeTimestamp } = await deployWithLockAndTge( @@ -988,6 +1056,88 @@ describe("InterfoldToken", function () { ).to.equal(0n); }); + it("sums multiple active locks with different curves correctly over time", async function () { + const { token, admin, alice, ccaEnd } = await loadFixture(deploy); + const aliceAddress = await alice.getAddress(); + + // Alice receives three locks with different vesting curves. + const policy24m = await createLinearPolicy(token, admin, "VEST_24M", { + vestDuration: 2n * YEAR, + }); + const policy12m = await createLinearPolicy(token, admin, "VEST_12M", { + vestDuration: 1n * YEAR, + }); + // Absolute policy: unlocks at a specific timestamp (ccaEnd + 180 days). + // Use cliffDuration=1 (1 second) — zero cliff+vest together is invalid. + const policyAbs = await createLinearPolicy(token, admin, "VEST_ABS", { + anchor: 0, + start: ccaEnd + 180n * DAY, + cliffDuration: 1n, + vestDuration: 0n, + }); + + const amount24m = ethers.parseEther("1000"); + const amount12m = ethers.parseEther("600"); + const amountAbs = ethers.parseEther("400"); + const total = amount24m + amount12m + amountAbs; + + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount: amount24m, + policyId: policy24m, + label: ethers.encodeBytes32String("v24"), + }, + { + recipient: aliceAddress, + amount: amount12m, + policyId: policy12m, + label: ethers.encodeBytes32String("v12"), + }, + { + recipient: aliceAddress, + amount: amountAbs, + policyId: policyAbs, + label: ethers.encodeBytes32String("abs"), + }, + ]); + + // Fire TGE. + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + const tgeTx = await token.tge(); + const receipt = await tgeTx.wait(); + const tgeBlock = await ethers.provider.getBlock(receipt!.blockNumber); + const tgeTimestamp = BigInt(tgeBlock!.timestamp); + + // At TGE: everything fully locked (all Tge-anchored + absolute cliff not yet). + expect(await token.lockedBalanceOf(aliceAddress)).to.equal(total); + + // TGE + 6 months: + // 24m policy: 6/24 = 25% unlocked → 750 locked + // 12m policy: 6/12 = 50% unlocked → 300 locked + // Absolute: start=ccaEnd+180d, TGE+6m past that → 0 locked + // Total locked ≈ 750 + 300 + 0 = 1050 + await time.increaseTo(tgeTimestamp + YEAR / 2n); + let locked = await token.lockedBalanceOf(aliceAddress); + expect(locked).to.be.closeTo( + ethers.parseEther("1050"), + ethers.parseEther("0.02"), + ); + + // TGE + 12 months: + // 24m policy: 12/24 = 50% unlocked → 500 locked + // 12m policy: 100% unlocked → 0 locked + // Absolute: unlocked → 0 locked + // Total locked ≈ 500 + await time.increaseTo(tgeTimestamp + YEAR); + locked = await token.lockedBalanceOf(aliceAddress); + expect(locked).to.be.closeTo( + ethers.parseEther("500"), + ethers.parseEther("0.02"), + ); + }); + it("transferableBalanceOf returns full balance when nothing locked", async function () { const { token, alice, amount } = await deployWithUnlockedAndTge( ethers.parseEther("100"), @@ -1732,6 +1882,141 @@ describe("InterfoldToken", function () { ethers.parseEther("0.01"), ); }); + + it("links CCA claim without disturbing existing non-CCA locks", async function () { + const fixture = await loadFixture(deploy); + const { token, admin, alice, claimSource, ccaEnd } = fixture; + const aliceAddress = await alice.getAddress(); + + // Alice already has a Legion/SAFT lock — mint BEFORE TGE. + const legionPolicy = await createLinearPolicy(token, admin, "LEGION", { + vestDuration: 2n * YEAR, + }); + const legionAmount = ethers.parseEther("1000"); + + // Mint extra unlocked tokens to fund the claim transfer. + const claimAmount = ethers.parseEther("500"); + await token + .connect(admin) + .mint(aliceAddress, claimAmount, ethers.ZeroHash); + + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount: legionAmount, + policyId: legionPolicy, + label: ethers.encodeBytes32String("legion"), + }, + ]); + + // Fire TGE. + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + await token.tge(); + + // Now CLAIM_SOURCE sends tokens — becomes PENDING. + await token + .connect(alice) + .transfer(await claimSource.getAddress(), claimAmount); + await token.connect(claimSource).transfer(aliceAddress, claimAmount); + + // Lock before link: LEGION active + PENDING. + expect(await token.lockCount(aliceAddress)).to.equal(2n); + + // linkClaim the PENDING to CCA_POLICY. + const ccaPolicy = await createLinearPolicy(token, admin, "CCA", { + vestDuration: 1n * YEAR, + }); + await token + .connect(admin) + .linkClaim(aliceAddress, claimAmount, ccaPolicy); + + // LEGION still has 1,000; CCA now has 500; no PENDING remains. + expect(await token.lockCount(aliceAddress)).to.equal(2n); + const locks = [ + await token.locks(aliceAddress, 0), + await token.locks(aliceAddress, 1), + ]; + const byPolicy = new Map( + locks.map((l: { policyId: string; amount: bigint }) => [ + l.policyId, + l.amount, + ]), + ); + expect(byPolicy.get(legionPolicy)).to.equal(legionAmount); + expect(byPolicy.get(ccaPolicy)).to.equal(claimAmount); + + // lockedBalanceOf equals sum of both (allow tiny vesting rounding). + const lb = await token.lockedBalanceOf(aliceAddress); + expect(lb).to.be.closeTo( + legionAmount + claimAmount, + ethers.parseEther("0.02"), + ); + }); + + it("supports mixed allocation types for one wallet: unlocked grant + vested allocation + CCA claim", async function () { + const fixture = await loadFixture(deploy); + const { token, admin, alice, claimSource, ccaEnd } = fixture; + const aliceAddress = await alice.getAddress(); + + // 1. Unlocked grant. + const grantAmount = ethers.parseEther("100"); + await token + .connect(admin) + .mint(aliceAddress, grantAmount, ethers.ZeroHash); + + // 2. Legion/SAFT vested allocation. + const legionPolicy = await createLinearPolicy(token, admin, "SAFT", { + vestDuration: 2n * YEAR, + }); + const legionAmount = ethers.parseEther("1000"); + await token.connect(admin).mintAllocations([ + { + recipient: aliceAddress, + amount: legionAmount, + policyId: legionPolicy, + label: ethers.encodeBytes32String("saft"), + }, + ]); + + // 3. CCA claim tokens: mint to claimSource, then later transfer back. + const ccaAmount = ethers.parseEther("500"); + await token + .connect(admin) + .mint(await claimSource.getAddress(), ccaAmount, ethers.ZeroHash); + + // Fire TGE. + const TGE_COOLDOWN = 45n * DAY; + await time.increaseTo(ccaEnd + TGE_COOLDOWN + 1n); + await token.tge(); + + // Now CLAIM_SOURCE sends tokens — becomes PENDING. + await token.connect(claimSource).transfer(aliceAddress, ccaAmount); + + // Link CCA PENDING to a CCA policy. + const ccaPolicy = await createLinearPolicy(token, admin, "CCA_VEST", { + vestDuration: 1n * YEAR, + }); + await token.connect(admin).linkClaim(aliceAddress, ccaAmount, ccaPolicy); + + // Assertions after all allocations are in place. + const totalBalance = grantAmount + legionAmount + ccaAmount; + expect(await token.balanceOf(aliceAddress)).to.equal(totalBalance); + expect(await token.lockCount(aliceAddress)).to.equal(2n); + + // Sum of locked: SAFT locked + CCA locked (grant is unlocked). + const lb = await token.lockedBalanceOf(aliceAddress); + expect(lb).to.be.closeTo( + legionAmount + ccaAmount, + ethers.parseEther("0.02"), + ); + + // Grant portion (100) is unlocked and transferable subject to floor. + // With no bond, floor = lockedBalance ≈ 1500. Wallet = 1600. + // transferable ≈ 1600 - 1500 = 100 (the grant portion). + const tb = await token.transferableBalanceOf(aliceAddress); + expect(tb).to.be.closeTo(grantAmount, ethers.parseEther("0.02")); + }); }); // ═════════════════════════════════════════════════════════════════════════ @@ -1954,6 +2239,104 @@ describe("InterfoldToken", function () { // ═════════════════════════════════════════════════════════════════════════ describe("BondingRegistry integration", function () { + it("bonded balance covers aggregate mixed locks without affecting grant accounting", async function () { + const signers = await ethers.getSigners(); + const [, beneficiary, slasher] = signers; + const beneficiaryAddress = await beneficiary.getAddress(); + const slasherAddress = await slasher.getAddress(); + const sys = await deployInterfoldSystem({ + useMockCiphernodeRegistry: true, + setupOperators: 0, + wireSlashingManager: false, + mintUsdcTo: [], + }); + const { bondingRegistry, licenseToken } = sys; + const bondingRegistryAddress = await bondingRegistry.getAddress(); + + await bondingRegistry.setSlashingManager(slasherAddress); + + // Alice has a mixed allocation: 200 unlocked grant + 1000 SAFT + 500 CCA. + const grantAmount = ethers.parseEther("200"); + const saftAmount = ethers.parseEther("1000"); + const ccaAmount = ethers.parseEther("500"); + const totalTokens = grantAmount + saftAmount + ccaAmount; + + // Mint unlocked grant (NOT locked). + await licenseToken.mint( + beneficiaryAddress, + grantAmount, + ethers.encodeBytes32String("grant"), + ); + + // SAFT vested allocation: mintAllocations creates AND locks tokens. + const saftPolicy = ethers.encodeBytes32String("SAFT_MIX"); + await licenseToken.createLockPolicy(saftPolicy, { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: 2n * YEAR, + }, + }); + await licenseToken.mintAllocations([ + { + recipient: beneficiaryAddress, + amount: saftAmount, + policyId: saftPolicy, + label: ethers.encodeBytes32String("saft"), + }, + ]); + + // CCA vested allocation. + const ccaPolicy = ethers.encodeBytes32String("CCA_MIX"); + await licenseToken.createLockPolicy(ccaPolicy, { + holdUntil: 0n, + unlock: { + anchor: 1, + start: 0n, + cliffDuration: 0n, + vestDuration: 1n * YEAR, + }, + }); + await licenseToken.mintAllocations([ + { + recipient: beneficiaryAddress, + amount: ccaAmount, + policyId: ccaPolicy, + label: ethers.encodeBytes32String("cca"), + }, + ]); + + // Total tokens: grant(200) + SAFT(1000) + CCA(500) = 1700. + + // Bond 1000. + const bondAmount = ethers.parseEther("1000"); + await licenseToken + .connect(beneficiary) + .approve(bondingRegistryAddress, bondAmount); + await bondingRegistry.connect(beneficiary).bondLicense(bondAmount); + + // Wallet = totalTokens - bondAmount = 1700 - 1000 = 700. + expect(await licenseToken.balanceOf(beneficiaryAddress)).to.equal( + totalTokens - bondAmount, + ); + + // Locked ≈ SAFT locked + CCA locked ≈ 1500 (Tge-anchored, no time passed). + const locked = await licenseToken.lockedBalanceOf(beneficiaryAddress); + expect(locked).to.be.closeTo( + saftAmount + ccaAmount, + ethers.parseEther("0.01"), + ); + + // Bonded = 1000 covers 1000 / 1500 of the lock floor. + // mustRetain = max(0, locked - bonded) ≈ 500. + // transferable = max(0, wallet - mustRetain) = 700 - 500 = 200. + // The grant portion (200) is transferable. + const tb = await licenseToken.transferableBalanceOf(beneficiaryAddress); + expect(tb).to.be.closeTo(grantAmount, ethers.parseEther("0.02")); + }); + it("transferableBalanceOf counts bonded INTF toward the locked floor", async function () { const signers = await ethers.getSigners(); const [, beneficiary, slasher] = signers;