# Audit report

> Independently audit revision r2 of the permanent CLAUS core protections. Exact candidate is candidates/phase1/contract/src and manifest. Review all four entrypoints: ProtectedToken, ProtectedHook, TradeExtensionHost and PhaseOneFinalizer, including every inherited legacy contract and fixed delegate target. This is NOT r1: owner explicitly requires replaceable trade programs, so the module directory-only r1 is rejected and must never be activated. Current mainnet remains unchanged. Original r1 paid review remains a separate running job, not authority for this revision.
>
> Check that no owner, new program, unexpected caller, malformed quote or composition can freeze transfers/buys/sells/LP exits, confiscate holder assets, redirect previously accrued rewards or exceed 15% aggregate core plus actual main-pool protocol fees. The hook bounds its TOTAL fee plan to 14.9%, reserving 0.1% for v4 protocol fees and checking actual proportional charges after swap. Six pips allocations sum to one fee, not six independent limits. All four buy/sell/exact-input/exact-output modes, rounding, zero-rate plans, partial fills, max protocol fees, zero stakers, invalid data and integer limits matter.
>
> TradeExtensionHost code is fixed and hash-checked by the core constructor, but activeModule remains replaceable by token owner. Module fee plans are gas-bounded STATICCALLs, with exact six-word output and no arbitrary code/storage authority. Malformed, excessive, write-attempting, reverting or OOG plans fall back to default 2.3%. Do not mistake fee plan approval for arbitrary delegate authority. Plans and module identity are snapshotted in transient slots; test reentrancy and contamination across multi-swaps, internal buybacks and malicious views. Address-dependent quotes still cannot exceed the common ceiling or reject trading. Unrelated gas and market slippage, or independent third-party pool fees, are outside the ceiling.
>
> Only newly earned fees can be minted as a PoolManager claim to the program itself, per its bounded plan; no old vault, NFT backing or LP assets can be taken. Check complete allocation arithmetic and original default fee recipients. Future rates and distribution are intentionally changeable, but past ownership/claims are not. Legacy rate getters represent the DEFAULT policy; actual integrations need the per-trade quote. Confirm no hidden baseline charge stacks on the plan.
>
> No stateful arbitrary CALL executes inside a user's swap. It could poison unlocked PoolManager deltas even without reverting. Instead, fixed host record() attests a trade, commits to its original module/data/amounts, and emits a receipt. Anyone can call execute() separately while PM is locked, at most once after success. It requires original data, original module, gas bounds and callback selector. Failure must roll back only delivery and preserve receipt; replay and replacement must not redirect it. Sender is the PM caller (often a router), not proven beneficial owner. No receipt is proof that a program actually completed business work; no autonomous dispatcher is in this contract release. Examine locked checks, return bombs, forged receipts, reentrancy, callback effects and gas griefing. A malicious optional program must not block the original trade.
>
> The token retains ERC20/ownership/metadata namespaces and removes privileged mint/restriction/upgrade logic. The finalizer atomically upgrades both actual proxies then renounces both actual ProxyAdmin owners; cancellation before completion must recover both even after partial handover. Remaining token owner may select programs and edit metadata, not core protections. Check all codehash/constructor checks, alternative selectors and delegate chains. Fixed legacy burn/liquidity processors remain by exact address AND runtime hash. Existing timed-burn interval is preserved, no new activation. New programs can use their own future allocations for different independent processors.
>
> Mainnet addresses: token 0x1b54e762aa34cf6e28e9c082f2848e28e45da6b8; hook 0x37bfb8ac7c960e558657871d41ca70e07e7dbfff; tokenAdmin 0xfb4b9d2ba3ef17787bfe3fd90c3ea7b319807a57; hookAdmin 0x1f22e4b065b2f15fed5be556dc0fa636048d29bc. Exact baselines in manifest/inventory; fork tests use isolated chain31337 at Ethereum26197295 and injected test-user funds, not production writes. EIP170 size, layout, custody and atomic finalization require scrutiny because final core is irreversible.
>
> Report concrete findings with severity, affected code, exploit preconditions and required fix. Do not deploy, mutate repositories, accept terms, send social posts or spend funds. Passing local cases are not an independent audit. Distinguish verified bugs from hypothetical concerns and limits of review. Trade-freezing, custody, ceiling and alternative-upgrade findings are release-blocking.

| | |
|---|---|
| Repository | https://github.com/contractclaus/claus-network-contracts |
| Commit | `78274103bdc9c11ad715a38d262c65cf27d4d51e` |
| Job | `a9a19d30-475b-4c9a-9339-046c2d5004ee` |
| Judged | 2026-10-15 11:18 UTC |
| Findings | 1 high · 1 medium · 1 low · 5 info |

Four agents audited the code as it is at `7827410`, each in one area (math, permissions, economics, control flow),
and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the repository was changed or deployed.

## Findings

### 1. High: Any trader strips the active program's fee plan and receipt by attaching more than 4096 bytes of hookData

`candidates/phase1/contract/src/TradeExtensionHost.sol:67`

```
        if(data.length>4096) return(address(0),packed);
```

TradeExtensionHost.quote() is the only source of the per-trade fee plan. When the swapper-supplied hookData is longer than 4096 bytes it returns (address(0), DEFAULT_PLAN) before the owner-selected program is consulted. hookData is chosen freely by whoever calls PoolManager.swap (the Universal Router and V4Router forward ExactInput/ExactOutput hookData verbatim; ProtectedHook._beforeSwapExtension forwards it unchanged). ProtectedHook then snapshots module=address(0), FeeNftStakingHook._accrue computes moduleEth=0 and routes the 2.3% default to the default recipients, and ProtectedHook._afterSwapExtension skips extensions.record() because module==0, so no TradeRecorded receipt exists. The fallback was designed for malformed, reverting, oversized or out-of-gas PROGRAM responses; this branch is triggered by the counterparty, while the program is healthy. Consequences while any program is active: every rate above 2.3% (up to 14.9%) is voluntary for the trader at a cost of about 4 KB of zero calldata (~65k gas); the program's own allocation (pips index 5) is never minted; and a trader can opt out of any program that reacts to receipts. Holders are not harmed and the 15% ceiling, custody and trade liveness are intact, but the central r2 requirement (owner-selected replaceable programs that price trades) is unenforceable by an unprivileged party. Merged from four specialist reports (audit_flow, audit_math, audit_permissions, audit_economics). The related gas-starvation branch on line 68 (gasleft()<QUOTE_GAS+25_000 returns the default plan but keeps the module) was probed by audit_math on the fork and not shown reachable inside a swap that can still complete (~190k gas is needed after the quote), so it is not reported as a defect. Required fix: do not let the length of trader data select the plan. Quote the program with the data truncated (e.g. bytes calldata quoted = data.length>4096 ? data[:0] : data, or data[:4096]) while keeping module and plan, and hash the same truncated data in ProtectedHook._afterSwapExtension so execute() still binds to the quoted input; alternatively revert the swap on oversized data (the trader chose the payload). Any fix must fit in the 315 bytes of EIP-170 headroom left in ProtectedHook if it touches the hook.

**Reproduction**

Unit (no fork, run from candidates/phase1/contract): owner installs a module whose quote returns [0,0,0,0,0,149000]. host.quote(trader,true,-1 ether,new bytes(4096)) returns (module, rate 149000); host.quote(trader,true,-1 ether,new bytes(4097)) returns (address(0), rate 23000). Expected: the program's plan (or a revert chosen by the trader); actual: default 2.3% plan with module detached. Fork (local anvil chain 31337 at Ethereum block 26197295, candidate finalized, same module active): ExactInputSingle buy of 0.01 ETH via router 0x23617e59A5925b2A4Bf75d73ff6711cD0b29De85 with empty hookData mints 1,490,000,000,000,000 wei of ERC6909 claims to the module and host.nextReceipt becomes 1; the identical buy with hookData=new bytes(4097) mints 0 to the module, mints 130,000,000,000,000 wei (1.3% of the default plan) to the live projectRevenue 0x49A91293a8dFf24d5c54b798C45376301D29b9f3, and nextReceipt stays 1 (no TradeRecorded). All three specialist proofs fail on the current code for this reason; the attached one is kept.

**Proof**: a Foundry test that fails on this code and passes once it is fixed.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Test} from "forge-std/Test.sol";
import {TradeExtensionHost, IClausTradeModule} from "src/TradeExtensionHost.sol";

/// Program that charges the maximum 14.9%, entirely to itself, for every trade.
contract MaxFeeProgram {
    function quote(address, bool, int256, uint160, uint128, bytes calldata) external pure returns (uint256[6] memory a) {
        a = [uint256(0), 0, 0, 0, 0, 149000];
    }
    function onTrade(TradeExtensionHost.Trade calldata, bytes calldata) external pure returns (bytes4) {
        return IClausTradeModule.onTrade.selector;
    }
}

/// Stand-in for the token: only owner() is consulted by TradeExtensionHost.setModule.
contract OwnerStub {
    address public immutable owner;
    constructor(address o) { owner = o; }
}

/// Stand-in for the PoolManager: quote() only reads slot0 and liquidity through extsload(bytes32).
contract ManagerStub {
    fallback() external {
        assembly { mstore(0, 1) return(0, 32) }
    }
}

contract OversizedDataBypassTest is Test {
    TradeExtensionHost host;
    MaxFeeProgram program;
    address trader = address(0xA11CE);

    function setUp() public {
        vm.etch(0x1b54E762aa34CF6E28E9C082F2848e28E45DA6b8, address(new OwnerStub(address(this))).code);
        vm.etch(0x000000000004444c5dc75cB358380D2e3dE08A90, address(new ManagerStub()).code);
        host = new TradeExtensionHost();
        program = new MaxFeeProgram();
        host.setModule(address(program));
    }

    function rate(uint256 packed) internal pure returns (uint256) { return (packed & 0xffffff) - 1; }

    /// A trader must not be able to lower the quoted program fee, or detach the program
    /// from the trade, merely by attaching more than 4096 bytes of hookData.
    function testHookDataLengthCannotDropTheProgramFee() public view {
        (address m, uint256 packed) = host.quote(trader, true, -1 ether, new bytes(4096));
        assertEq(m, address(program));
        assertEq(rate(packed), 149000);

        // Either the host still quotes the program's rate for the same trade, or it rejects the
        // oversized data outright. Silently falling back to the 2.3% default is the defect.
        try host.quote(trader, true, -1 ether, new bytes(4097)) returns (address m2, uint256 packed2) {
            assertEq(m2, address(program), "program detached by hookData length");
            assertEq(rate(packed2), 149000, "program fee replaced by default");
        } catch {}
    }
}
```

### 2. Medium: Irreversible finalize() never binds the candidate hook's fee destinations to the live proxy's; a self-consistent substitute set passes every constructor and finalize check

`candidates/phase1/contract/src/PhaseOneFinalizer.sol:44`

```
        if(IProtectedCore(token).protectionVersion()!=1 || IProtectedCore(hook).protectionVersion()!=1
```

finalize() is the only gate before both ProxyAdmins are renounced, yet after the upgrade it checks only protectionVersion(), protectionKind() and maximumTotalTaxPips() of the two proxies, plus the implementation codehash it snapshotted in its own constructor. ProtectedHook's constructor (ProtectedHook.sol lines 51-56) pins only MANAGER, the reserve's token/hook, FeeProcessor (exact address + runtime hash) and LpRemainderProcessor (exact address + runtime hash). The destinations that receive 21,500 of the 23,000 default pips and all future program allocations are accepted from constructor arguments and validated only for mutual consistency: FeeEfficientHook.sol:114 requires project_/platform_.poolManager()==manager; FeeLpRewardsHook.sol:66-73 requires lpRewards_ to echo poolManager/positionManager/hook/poolId/epochStart/EPOCH/projectRevenue; FeeNftStakingHook.sol:26-31 requires staking_ to echo collection/poolManager/projectRevenue/hook/epochStart/EPOCH. Any purpose-built contract satisfies these. Nothing compares the candidate's projectRevenue(), platformRevenue(), lpRewards(), nftStaking(), nft(), privacyBuy(), predictionOracle() or ethUsdFeed() with what the live hook proxy 0x37bf...bfff returns before the upgrade (live: project 0x49A91293a8dFf24d5c54b798C45376301D29b9f3, platform 0xF1749E78e76CfE1119206833aE93beD2dede5353, lpRewards 0xB2304d3DE407398de441b147e2c64c7c48DAf44E, nftStaking 0x4e5916428452891E1deb59d162c20266ED08CAD4). The manifest's artifact hashes are compiler templates, not immutable-populated deployment hashes, so they do not close the gap either. Because both admins are renounced in the same transaction and the remaining token owner has no power over core destinations, a wrong binding is permanent: all future project/platform/LP-reward/NFT-reward claims go to the substitute contracts, and ClausNftStaking.stakingOpen() (ClausNftStaking.sol:98-103, which compares hook.nftStaking() with itself) turns false for the real vault, so recordReward and new stakes on it are refused. Precondition: the owner (or whoever assembles the hookImpl_ argument: deployer script, build pipeline) supplies an implementation built with substitute dependencies. This is a missing invariant check in an irreversible privileged step, not an unprivileged bypass, hence medium. Merged from audit_flow (medium), audit_math (medium) and audit_economics (low). Required fix: in finalize(), before upgrading, read projectRevenue(), platformRevenue(), lpRewards(), nftStaking(), nft(), liquidityReserve(), fomoReserve(), processor(), lpRemainderProcessor(), predictionOracle(), ethUsdFeed() and privacyBuy() through the hook proxy and require the same values from the proxy after the upgrade (also require extensions().activeModule()==address(0) and nextReceipt()==0); or pin those addresses as exact constants in ProtectedHook's constructor the way processor/remainder already are.

**Reproduction**

Local anvil fork, chain 31337 at Ethereum block 26197295 (test/scratch/JudgeFork.t.sol::testSubstituteDestinationsPassFinalize). Deploy FakeProject{poolManager()=PM}, FakePlatform{poolManager()=PM, recordAllocation() no-op}, FakeLp{poolManager()=PM, positionManager()=live reserve.positionManager(), hook()=HOOK, poolId()=main pool id, epochStart()=live nft.epochStart(), EPOCH()=1 days, projectRevenue()=FakeProject, totalLiquidity()=1, recordReward() no-op}, FakeStaking{collection()=live nft, poolManager()=PM, projectRevenue()=FakeProject, hook()=HOOK, epochStart()=live nft.epochStart(), EPOCH()=1 days, startsAt()=epochStart, totalStaked()=1, recordReward() no-op}. new ProtectedHook(BaseConfig{live manager/reserve/feed/fomo/attester/questionHash/processor 0x271f...7340/nft/oracle, project=FakeProject, platform=FakePlatform, spacing 1}, live privacyBuy, FakeLp, live remainder 0x5162...823d, FakeStaking, new TradeExtensionHost()) constructs without revert. new PhaseOneFinalizer(OWNER, TOKEN, HOOK, TA, HA, new ProtectedToken(), thatHook); OWNER transfers both admins and calls finalize(). Expected: revert because the implementation's destinations differ from the live proxy's. Actual: finalize() succeeds, TA.owner()==HA.owner()==address(0), hook.projectRevenue()==FakeProject. A 0.005 ETH exact-input buy via the router then mints 65,000,000,000,000 wei to FakeProject (13000 pips), 15,000,000,000,000 to FakePlatform (3000), 10,000,000,000,000 to FakeLp (2000) and 17,500,000,000,000 to FakeStaking (3500); the live project, platform, lpRewards and staking vault claim balances are unchanged; ClausNftStaking(0x4e59...).stakingOpen() was true before the upgrade and is false after it; no upgrade path remains.

### 3. Low: With the v4 protocol fee set, dust main-pool trades revert on the 15% ceiling check because the protocol fee rounds up to 1 wei

`candidates/phase1/contract/src/legacy/FeeEfficientHook.sol:330`

```
            if(fee+protocol>gross*150_000/1_000_000) revert TotalTaxExceeded();
```

_checkTotalTax compares the floored hook fee plus the protocol fee actually accrued during the swap against floor(gross*15%). Uniswap v4's SwapMath.computeSwapStep charges amountRemaining - floor(amountRemaining*(1e6-fee)/1e6) as the fee when the target is not reached, i.e. a full 1 wei on any input below 1000 wei; with lpFee 0 the whole step fee becomes protocol fee. For gross < 7 wei the allowance floor(gross*0.15) is 0 while protocol is 1, so every 1-6 wei exact-input buy reverts; in the oneForZero branch (line 335) a 1 wei token sell hits protocol==input and reverts; with a 14.9% program a 10 wei buy gives fee=1, protocol=1, allowance=1 and reverts. Without the protocol fee the same trades succeed, and the live pool currently has protocol fee 0, so this is a boundary where the 'ceiling never rejects trading' claim fails only for sub-100-wei trades with no economic value. Not a trade freeze, hence low. Merged from audit_math (low) and audit_permissions (low). Fix: compare on a common integer basis without double flooring, e.g. (fee+protocol)*1_000_000 > gross*150_000 or tolerate the 1 wei rounding when protocol<=1, keeping the ceiling semantics for every non-dust trade.

**Reproduction**

Local anvil fork, chain 31337 at Ethereum block 26197295, candidate finalized with live dependencies (test/scratch/JudgeFork.t.sol::testDustTradesRevertUnderProtocolFee). (1) ExactInputSingle buy of 3 wei with protocol fee 0 succeeds. (2) vm.prank(PM.protocolFeeController()); PM.setProtocolFee(main, 1000|(1000<<12)). ExactInputSingle buys of 1,2,3,4,5,6 wei via the router all revert with 0x90bfb865 Wrap__FailedHookCall(hook 0x37bf...bfff, beforeSwap/afterSwap selector, 0xe8f1a8aa TotalTaxExceeded); 7 and 8 wei succeed. Exact-input sell of 1 wei CLAUS reverts with the same error; 100 wei succeeds. (3) With a program quoting [0,0,0,0,0,149000]: a 10 wei buy reverts with TotalTaxExceeded, a 100 wei buy succeeds. Expected: a trade whose hook fee is within plan is never rejected by the ceiling check; actual: rejected for these inputs.

### 4. Info: Trust assumption: token owner can switch the active trade program instantly to any contract charging up to 14.9%, and renouncing token ownership pins the current program forever

`candidates/phase1/contract/src/TradeExtensionHost.sol:56`

```
        address previous=activeModule;activeModule=module;emit ModuleChanged(previous,module);
```

Documented design, reported as a privileged-power trust assumption, not a defect. setModule() takes effect in the same block with no delay, announcement or two-step, and a program's quote may depend on sender, amount and data. The token owner (0xBb145cA83272c3806D4dDc75cA1D5514789cF1C5, Ownable2Step owner of the proxy) can therefore front-run a pending swap with a program charging 14.9%, or run a program that charges 14.9% to everyone except favoured addresses (economically close to a trade restriction without exceeding the common ceiling or rejecting trades). A quote obtained through quoteTradeFee() before the swap is not binding; users are protected only by router slippage bounds. Separately, Ownable2StepUpgradeable.renounceOwnership() on the token makes IClausGovernor(TOKEN).owner() return address(0), after which setModule() can never succeed and whatever program is active stays active permanently. None of this exceeds the 15% aggregate ceiling, blocks trading, or touches existing claims, NFT backing or LP positions. Merged from audit_flow, audit_permissions and audit_economics (all info). If the owner wants quotes to be reliable, a short pending/activation delay with an event-visible effective block would narrow the window without changing the design.

**Reproduction**

Code path: setModule() lines 52-57 has only the owner check and module sanity checks and writes activeModule immediately. Project test testReplaceableProgramFeeBudgetAndFourTradeModes shows the sequence: quoteTradeFee(ROUTER,true,-0.005e18,'') returns 23000 before setModule; after vm.prank(OWNER) host.setModule(module charging 149000) in the same block, ALICE's 0.005 ETH exact-input buy is charged 745,000,000,000,000 wei (14.9%). Expected per design: allowed; actual: allowed. Renounce path: after T.renounceOwnership(), any setModule(x) from any address reverts NotAuthorized because owner()==address(0).

### 5. Info: Receipts are recorded and deliverable for trades the program never priced, and quoteTradeFee() cannot distinguish a fallback from a genuine 2.3% quote

`candidates/phase1/contract/src/TradeExtensionHost.sol:77`

```
        if(!ok) return(module,packed);
```

When the program's STATICCALL fails, runs out of gas, returns the wrong size, or returns an allocation above MAX_RATE (lines 77, 81, 85), quote() returns (module, DEFAULT_PLAN) with the module retained. ProtectedHook then charges 2.3%, mints nothing to the program (pips index 5 is 0 in the default plan) and still calls extensions.record(module, ...), so a receipt bound to that program exists and anyone may execute() it, delivering onTrade for a trade the program did not price. moduleFeeEth is 0 in such receipts, so a careful program can detect the case, but programs that treat every delivered receipt as a paid trade will mis-account. The same (module, DEFAULT_PLAN) tuple is what ProtectedHook.quoteTradeFee() returns, so integrators cannot tell a fallback from a healthy program quoting exactly 23000. No funds move and the ceiling holds, so this is informational. Merged from audit_permissions (info) and the integrator note in audit_flow. Fix options: return (address(0), DEFAULT_PLAN) on all fallbacks so no receipt is recorded (consistent with the oversized-data branch, which already does this), or carry a fallback flag in the Trade struct / quoteTradeFee result.

**Reproduction**

Code path: quote() lines 77/81/85 return the module with the default plan; ProtectedHook._afterSwapExtension (ProtectedHook.sol:72-76) records whenever ClausFeeBudget.module()!=0. Project test testOptionalBrokenModuleCannotBlockTrading installs RevertingModule (fallback reverts) and swaps 0.001 ETH and 10 CLAUS successfully at 23000 pips; by the code path each swap increments host.nextReceipt and emits TradeRecorded naming RevertingModule with moduleFeeEth 0, and host.execute(trade,'',100000) then calls RevertingModule.onTrade. Expected per README ('receipt attests the trade's original module/data/amounts'): receipts only for trades priced by their program; actual: receipt for an unpriced trade.

### 6. Info: Pre-staking-start legacy accrual branch cannot handle plans below 2% (dead on mainnet because the staking vault started 2026-10-10)

`candidates/phase1/contract/src/legacy/privacy/FeeNftStakingHook.sol:44`

```
            super._accrue(gross, totalFee);
```

If block.timestamp < nftStakingStartsAt, _accrue delegates to FeeLpRewardsHook._accrue (FeeLpRewardsHook.sol:155-161), which computes project = gross/50 (2%) via FeeMath.projectFromGross and platform = totalFee - project with checked arithmetic. Any plan whose total rate is below 2%, including the supported zero-fee plan, makes totalFee < project and the subtraction reverts with Panic(0x11), which would revert every main-pool swap while such a program is active. On the pinned mainnet state this branch is unreachable: ClausNftStaking at 0x4e5916428452891E1deb59d162c20266ED08CAD4 reports startsAt = 1791672095 (2026-10-10), earlier than the fork block timestamp 1792059851, and nftStakingStartsAt is an immutable copied from it, so ProtectedHook always takes the plan-driven branch. Reported so the dead branch is not revived (e.g. on a redeploy with a future vault) without removing the hard 2% floor; the minimal fix is to delete the legacy branch from the final core or route it through ClausFeeBudget as well. From audit_permissions (info), verified.

**Reproduction**

Verified on the local fork at block 26197295: vault.startsAt() = 1791672095 < block.timestamp = 1792059851 (test/scratch/JudgeFork.t.sol::testVaultStartedAndSizes), so the condition on line 43 is false for the mainnet deployment. Only reachable with a ClausNftStaking whose startsAt is in the future: with nftStakingStartsAt > block.timestamp and an active program returning [0,0,0,0,0,0], an exact-input buy of 1 ETH gives totalFee = 0, project = 0.02 ETH, and platform = totalFee - project reverts with Panic(0x11), failing the swap. Expected: zero-fee plan accepted (README: 'Zero-fee plans are supported'); actual: revert in that configuration.

### 7. Info: ProtectedHook runtime is 24261 bytes, 315 bytes under the EIP-170 limit; any post-review change risks an undeployable core

`candidates/phase1/contract/src/ProtectedHook.sol:27`

```
contract ProtectedHook is FeeNftStakingHook {
```

forge build with the candidate profile (0.8.26, cancun, via-IR, optimizer 200) produces 24261 bytes of runtime for ProtectedHook, matching the manifest. Immutables are already included in that size, so the deployed code is the same size, leaving 315 bytes for any fix arising from this review (the hookData truncation for the high finding touches ProtectedHook._afterSwapExtension if the hashed data must match the quoted data). Not a defect; recorded because the finalizer pins the implementation codehash and a size overrun discovered at deployment would require a new review cycle. From audit_permissions (info), verified.

**Reproduction**

cd candidates/phase1/contract && forge build; python3 -c reads out/ProtectedHook.sol/ProtectedHook.json deployedBytecode.object: 24261 bytes (limit 24576). Project test testAtomicSealPreservesCustodyAndRemovesBothUpgradeOwners asserts address(candidate).code.length <= 24576.

### 8. Info: Review scope, verification performed and limits

`candidates/phase1/manifest.json:2`

```
  "version": "r2",
```

Judge pass over the four specialist reports (audit_flow, audit_math, audit_permissions, audit_economics; 16 findings in total) for the r2 candidate at candidates/phase1/contract/src and manifest. Read in full: ProtectedToken, ProtectedHook, TradeExtensionHost, PhaseOneFinalizer, ClausFeeBudget, FeeMath, FeeCalls, HookBase, FeeEfficientHook, FeeNftStakingHook, FeeLpRewardsHook, plus the constructors of FeeNftHook, FeePredictionHook, FeePrivacyHook, FeeBatchedPrivacyHook and ClausNftStaking.stakingOpen/recordReward. Compiled the candidate with its own profile. Ran the three supplied proof tests (all fail on the current code for the stated reason). Started a local anvil fork (chain 31337) of Ethereum at block 26197295 from a public archive endpoint and reproduced the destination-substitution, dust-trade and oversized-hookData findings there, and confirmed the staking vault's startsAt is in the past. The project's own 12 fork tests were run against the same fork and all pass. Duplicates were merged: the oversized-hookData bypass (4 reports) into one high; destination binding (3 reports) into one medium; dust ceiling reverts (2 reports) into one low; instant setModule trust assumption (3 reports) into one info. Dropped: audit_flow's gas-starvation sub-claim (not shown reachable inside a completable swap, per audit_math's fork probe; not independently measured here). Limits: no Slither/Mythril, no long fuzz runs; the live LP-rewards vault, ClausNFT/NftRewards, ProjectRevenue/PlatformRevenue, FeeProcessor and LpRemainderProcessor bytecode were not independently disassembled; the ERC-7201 storage layout compatibility of ProtectedToken with the live token implementation was checked only through the project's post-upgrade fork tests (owner, balances, transfers, metadata); transient-slot contamination across batched multi-hop swaps and the timed-buyback stream arithmetic were not re-probed beyond the project's tests. No release-blocking (trade-freezing, custody, ceiling or alternative-upgrade) defect was reproduced; the high finding is an unprivileged economic bypass of program fees and receipts, not a loss of user funds.

**Reproduction**

Not a defect. See the individual findings for failing inputs.

---

Judge's submission `dfba283b9e0d4842cd77d7fcaeeb59b16f26407d369928f4e60b5de89ae5baa1`, accepted on the IdentityMD network. Acceptance means the report met the job's checks;
it is not a guarantee that the code has no other defects.
