{"assessments":[],"deployments":[],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"032288d0-a9b5-4743-a255-4324888b0e83","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"c8cc8423452209504a990e8c10b052a38edbc481e0c68360bd6ddb13e4495389","dependsOn":["audit_imported_code"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"2d7d84a1a9972f1497e5bc68a704cbf42294d08c62d2868f102ffe3b909b9b51","skillId":"adapt-contract-project","tools":[]},"key":"adapt_contract_project","kind":"code","role":"implement","skillHash":"2d7d84a1a9972f1497e5bc68a704cbf42294d08c62d2868f102ffe3b909b9b51","skillId":"adapt-contract-project","state":"accepted"},{"acceptedSubmissionHash":null,"dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"working"},{"acceptedSubmissionHash":null,"dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"working"},{"acceptedSubmissionHash":"82be208f0184d82ddb146d9df6391ad3761c5dab72a3fb194a42b897923dc3c0","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":null,"dependsOn":["adapt_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"waiting"},{"acceptedSubmissionHash":null,"dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"working"},{"acceptedSubmissionHash":null,"dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"working"},{"acceptedSubmissionHash":null,"dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"failed"},{"acceptedSubmissionHash":null,"dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","state":"working"}],"objective":"ECU is the fixed-supply ERC-20 of My e-farm, a farm game; FarmVault custodies the game’s ECU reserve and pays players against EIP-712 vouchers signed by an off-chain operator, with a guardian that can pause and rotate. AUDIT.md at the repository root is a swarm audit of this code. Fix its findings, apply the decisions below, then deploy to Sepolia as a dry run: ECU, Distributor, FarmVault, team and community vesting. For this testnet deployment use the owner wallet as operator, guardian, both vesting beneficiaries and listings address, and call distribute() so the split is visible on chain. On mainnet later, ECU will be launched through IMD’s custom_token launch (15% pool, 10% swarm, 75% to the Distributor), so design the Distributor as that remainderTo target.\n\n(A) Replace script/Distribute.s.sol’s transfer logic with a one-shot Distributor contract that holds fixed targets set in its constructor (vault, team vesting, community vesting, listings) and a permissionless distribute() that runs once, splitting whatever ECU it holds as 50/75 to the vault, 15/75 to team vesting, 5/75 to community vesting, and the remainder to listings so no dust is stranded. No EOA ever holds the supply. The deploy script reads addresses from env with no defaults and must work under vm.startBroadcast() (finding 1): no address(this) reliance; add a test that drives run() under broadcast. Remove the LIQUIDITY_ADDRESS slice (finding 7): the pool will be factory-owned.\n\n(B) Vesting (findings 6, 8): team = OpenZeppelin VestingWalletCliff, 36-month duration, 12-month cliff. Community = VestingWallet, 12-month linear, no cliff. Both wrapped so renounceOwnership() reverts; transferOwnership stays. Beneficiaries are Safes in production; update .env.example wording.\n\n(C) FarmVault (findings 2, 3, 4, 5, 9): guardian-settable rolling 24-hour outflow cap (maxOutflowPerWindow) checked in withdraw() before transfer, plus a per-voucher ceiling and a MAX_VOUCHER_LIFETIME bound on expiry; initial values set in the constructor. Two-step guardian hand-over (pending/accept). bumpNonce(address to) callable by guardian or operator. Operator epoch in the signed voucher struct, incremented on setOperator. Keep ECDSA EOA-only verification; document trust assumptions in NatSpec.\n\n(D) Update docs/ECONOMY.md §9 and §11.7 to: pool 15% (factory-owned single-sided V4 position), swarm 10%, vault 50%, team 15% (12m cliff / 36m), community 5% (12m linear), listings 5%. The LP lock is replaced by factory custody.\n\n(E) Tests (finding 10): keep existing tests except those encoding removed behaviour (unbounded-voucher fuzz test, old split). Add: broadcast run(), cap enforcement, per-voucher ceiling, expiry bound, two-step guardian, bumpNonce, epoch rotate-back, day-1 and cliff vesting amounts, distributor split sums to the received balance. forge build and forge test must pass with forge 1.8.3.","parentJobId":null,"planHash":"33a4e7f29b7180a43c366f5ce59805c92c28d7c2ca2d997fcdc6dc2ee285f637","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"032288d0-a9b5-4743-a255-4324888b0e83","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1646-ecu-fixed-supply-erc-20-my"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52162","feedbackHash":"668c86fa223a08c3233de6aff573b61a7b72e1b6bad0c45923cf1b259d540361","nodeKey":"adapt_contract_project","submissionHash":"c8cc8423452209504a990e8c10b052a38edbc481e0c68360bd6ddb13e4495389","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51022","feedbackHash":"a1353efc5f7af9d7b8d388bf57424b93d9c5e6c3596f632dc0586ddd53b04f3e","nodeKey":"audit_imported_code","submissionHash":"82be208f0184d82ddb146d9df6391ad3761c5dab72a3fb194a42b897923dc3c0","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"72a018e0d8f4684419c7378b110fdb20bfc29a734ff5406e190564f5dc2ea4e5","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e8e860c7f2306473","findings":[],"hash":"04c93f5ab0769d66c94481df6e3a917e731bd838c8a869f2bc674433439e6951","nodeId":"ab849bf4-b3a7-45a6-a852-5eb8d8005247","outcome":"failed","summary":"wrote outside the task's allowed paths: .env.example","treeHash":null,"usage":{"cachedInputTokens":2755340,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":81820,"runtime":"claude","turns":50,"wallClockMs":1008583}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c35be49d2f8f8def","findings":[{"citation":"resolved","description":"run() wraps deployAll() in vm.startBroadcast(). Under broadcast every CREATE and CALL is sent from the broadcaster EOA, but address(this) is the script contract, which exists only in forge's simulation. The ECU constructor mints all 100,000,000 ECU to that phantom address and the five transfers on lines 90-94 are sent by the broadcaster, which holds 0 ECU. forge 1.8.3 refuses the script outright at vm.startBroadcast() ('Usage of address(this) detected in script contract'); if the ECU creation alone ever landed (e.g. --skip-simulation) the fixed supply would sit at an address with no key and no code, permanently. test/Distribute.t.sol never sees this because it calls deployAll() directly from the test contract. Fix per the requester's decision (A): a one-shot Distributor contract with fixed targets set in its constructor and a permissionless distribute() that splits whatever ECU it holds (50/75 vault, 15/75 team vesting, 5/75 community vesting, remainder to listings); the script reads addresses from env with no defaults, never relies on address(this), and a test drives run() under broadcast. Note for the Distributor: it must not call the token in its constructor (the launch rehearsal runs constructors on an empty chain) and must compute shares from balanceOf(address(this)) at distribute() time, not from a hard-coded total, because on mainnet it receives 75 % of supply as the custom_token remainderTo target, not 100 %.","line":84,"path":"script/Distribute.s.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Distribute} from \"script/Distribute.s.sol\";\n\n/// @notice `run()` is the entry point `forge script ... --broadcast` executes. Under\n///         `vm.startBroadcast()` every CREATE and CALL is sent from the broadcaster, but\n///         `deployAll` mints the supply to `address(this)` (the ephemeral script contract) and\n///         then transfers from the broadcaster, which holds nothing. The call therefore reverts\n///         with ERC20InsufficientBalance(broadcaster, 0, 15e24).\n///\n///         The test drives `run()` through a low-level call so it keeps compiling whatever the\n///         fixed script returns; it passes once `run()` completes under broadcast.\ncontract ProofRunUnderBroadcast is Test {\n    function setUp() public {\n        vm.setEnv(\"LIQUIDITY_ADDRESS\", vm.toString(address(0x11)));\n        vm.setEnv(\"COMMUNITY_BENEFICIARY\", vm.toString(address(0x22)));\n        vm.setEnv(\"TEAM_BENEFICIARY\", vm.toString(address(0x33)));\n        vm.setEnv(\"LISTINGS_MULTISIG\", vm.toString(address(0x44)));\n        vm.setEnv(\"VAULT_OPERATOR\", vm.toString(address(0x55)));\n        vm.setEnv(\"VAULT_GUARDIAN\", vm.toString(address(0x66)));\n        // Names the adapted script is expected to read; harmless if unused.\n        vm.setEnv(\"LISTINGS_ADDRESS\", vm.toString(address(0x44)));\n    }\n\n    function test_runCompletesUnderBroadcast() public {\n        Distribute script = new Distribute();\n        (bool ok, bytes memory ret) = address(script).call(abi.encodeWithSignature(\"run()\"));\n        assertTrue(ok, string.concat(\"run() reverted under broadcast: \", vm.toString(ret)));\n    }\n}","reproduction":"Dry run with forge 1.8.3 and the six env vars set: `LIQUIDITY_ADDRESS=0x..11 COMMUNITY_BENEFICIARY=0x..22 TEAM_BENEFICIARY=0x..33 LISTINGS_MULTISIG=0x..44 VAULT_OPERATOR=0x..55 VAULT_GUARDIAN=0x..66 forge script script/Distribute.s.sol:Distribute`. Expected: ECU, FarmVault and two VestingWallets deployed and 15/15/15/5/50 M transferred with the deployer holding 0. Actual (reproduced on this tree): `Error: script failed: Usage of address(this) detected in script contract. Script contracts are ephemeral and their addresses should not be relied upon.` In a Foundry test that sets the env vars and calls script.run(), the call reverts with ERC20InsufficientBalance(0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38, 0, 15000000000000000000000000) (selector 0xe450d38c): the broadcaster holds none of the supply minted to the script address. The attached proof fails with that revert data; the existing suite passes 41/41 because it only calls deployAll().","severity":"high","snippet":"        deployment.ecu = new ECU(address(this));","title":"Distribute.run() mints the supply to the ephemeral script address, so the launch cannot be broadcast and would strand 100 M ECU if it were"},{"citation":"resolved","description":"withdraw() checks pause, zero address, amount != 0, expiry, nonce and the operator signature, then transfers whatever amount was signed. There is no per-voucher maximum, no rolling-window cap, no fraction-of-balance bound and no upper bound on expiry. The operator is, per .env.example, 'the backend's hot key'. ECONOMY.md bounds every legitimate exit far below the custody: §3.3 'The reserve's only outflow is the Exchange, bounded by B = R·ln2/730 + EMA30' (about 33,000 ECU/day at R = 35 M), §1 caps each farm per day behind a 48 h delay, and §9 gate 6 keeps net ECU withdrawn over any rolling 7 days under 15 % of the pool's ECU side (2.25 M at launch). None of that survives a key compromise: the attacker signs to=attacker, amount=50_000_000e18, nonce=0 and submits it in one block. The guardian's pause and setOperator are reactive and need a 2-of-3 Safe execution, which cannot land before a single drain transaction. Fix per decision (C): a guardian-settable rolling 24-hour cap (maxOutflowPerWindow) checked in withdraw() before the transfer, a per-voucher ceiling and a MAX_VOUCHER_LIFETIME bound on expiry, all with initial values set in the constructor. A legitimate surge waits for the next window or a guardian raise; a leaked key is limited to one window's budget.","line":136,"path":"src/FarmVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ECU} from \"src/ECU.sol\";\nimport {FarmVault} from \"src/FarmVault.sol\";\n\n/// @notice Blast radius of a leaked operator key. ECONOMY.md bounds every legitimate exit far\n///         below the custody (Exchange budget ~33,000 ECU/day; gate 6: <= 2.25 M per 7 days).\n///         FarmVault enforces no ceiling: one voucher for 50,000,000 ECU clears in one\n///         transaction. Passes once any per-voucher or per-window cap below the balance exists.\ncontract ProofOutflowCap is Test {\n    ECU internal ecu;\n    FarmVault internal vault;\n\n    uint256 internal operatorKey = 0xA11CE5;\n    address internal operator;\n    address internal guardian = address(0x6A12D);\n    address internal attacker = address(0xBAD);\n\n    uint256 internal constant GAME_SIDE = 50_000_000e18;\n\n    function setUp() public {\n        operator = vm.addr(operatorKey);\n        ecu = new ECU(address(this));\n        vault = new FarmVault(ecu, operator, guardian);\n        ecu.transfer(address(vault), GAME_SIDE);\n    }\n\n    function test_oneVoucherCannotDrainTheEntireCustody() public {\n        uint256 expiry = block.timestamp + 1 hours;\n        bytes32 digest = vault.voucherDigest(attacker, GAME_SIDE, 0, expiry);\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorKey, digest);\n\n        vm.expectRevert();\n        vault.withdraw(attacker, GAME_SIDE, 0, expiry, abi.encodePacked(r, s, v));\n\n        assertEq(ecu.balanceOf(address(vault)), GAME_SIDE, \"custody drained in one transaction\");\n        assertEq(ecu.balanceOf(attacker), 0);\n    }\n}","reproduction":"State: FarmVault funded with 50_000_000e18 ECU (what Distribute deposits), operator = vm.addr(0xA11CE5). Input: sig = operator's EIP-712 signature over voucherDigest(0xBAD, 50_000_000e18, 0, block.timestamp + 1 hours); anyone calls withdraw(0xBAD, 50_000_000e18, 0, expiry, sig). Expected under ECONOMY.md §3.3 / §9 gate 6: refused (about 1,500x the daily budget, 22x the weekly bound). Actual: Withdrawn(0xBAD, 5e25, 0) is emitted, balanceOf(0xBAD) == 50_000_000e18, vault balance 0, in one transaction. The attached proof fails on this tree with 'next call did not revert as expected' and passes once any window or per-voucher cap below the vault balance is enforced.","severity":"high","snippet":"        ecu.safeTransfer(to, amount);","title":"FarmVault enforces no outflow bound: one voucher from the hot operator key moves the entire 50 M ECU custody in a single transaction"},{"citation":"resolved","description":"The guardian is the only account that can pause, unpause, rotate the operator or hand over. setGuardian writes the new address immediately after a zero-address check; there is no pending/accept step, no timelock and no other role that could restore it. A mistyped address, a Safe that exists only on another chain, or a contract that cannot send transactions takes effect at once and leaves the 50 M custody with a live operator key and no emergency control for the life of the contract, which also makes the missing outflow cap unrecoverable rather than merely slow to react to. The function will be exercised at the most delicate moment (migrating from the launch Safe). Fix per decision (C): two-step hand-over (pendingGuardian set by the current guardian, acceptGuardian() called by the new address), so the outgoing guardian keeps its powers until the new one proves it can transact.","line":177,"path":"src/FarmVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ECU} from \"src/ECU.sol\";\nimport {FarmVault} from \"src/FarmVault.sol\";\n\n/// @notice The guardian is the only pause / rotation control. Handing it to an address nobody\n///         controls must not take effect until that address proves it can transact. Today\n///         `setGuardian(0x1234)` is final: the outgoing guardian loses pause and setOperator at\n///         once, and the custody has no emergency control for the life of the contract.\n///         `setGuardian` is reached through a low-level call so the test compiles whatever the\n///         fixed contract names its two-step functions.\ncontract ProofGuardianTwoStep is Test {\n    ECU internal ecu;\n    FarmVault internal vault;\n    address internal guardian = address(0x6A12D);\n    address internal nobody = address(0x1234);\n\n    function setUp() public {\n        ecu = new ECU(address(this));\n        vault = new FarmVault(ecu, vm.addr(0xA11CE5), guardian);\n        ecu.transfer(address(vault), 50_000_000e18);\n    }\n\n    function test_handOverIsNotFinalUntilAccepted() public {\n        vm.prank(guardian);\n        (bool ok,) = address(vault).call(abi.encodeWithSignature(\"setGuardian(address)\", nobody));\n        ok; // whether the old name exists or not, the outgoing guardian must still be in control\n\n        assertEq(vault.guardian(), guardian, \"guardian replaced before the new address accepted\");\n\n        vm.prank(guardian);\n        vault.pauseWithdrawals();\n        assertTrue(vault.withdrawalsPaused(), \"outgoing guardian lost the pause\");\n    }\n}","reproduction":"State: vault with guardian G = 0x6A12D. Input: G calls setGuardian(0x1234) where nobody holds 0x1234's key. Then G calls pauseWithdrawals(). Expected: hand-over pending until 0x1234 accepts, G still able to pause. Actual: guardian() == 0x1234 immediately; pauseWithdrawals() from G reverts NotGuardian(); 0x1234 can never call anything, so pause and setOperator are unreachable forever. The attached proof fails on this tree with 'guardian replaced before the new address accepted: 0x...1234 != 0x...6A12D'.","severity":"medium","snippet":"        guardian = newGuardian;","title":"setGuardian is single-step: a wrong address permanently removes the only pause and operator-rotation control"},{"citation":"resolved","description":"Validity depends only on the current operator having signed, block.timestamp <= expiry (no on-chain maximum lifetime; type(uint256).max is accepted) and the nonce matching nonces[to], which only advances on a successful withdrawal. (a) Once signed, the only on-chain ways to stop a voucher are the global pause (which only delays it until unpause) or a global setOperator rotation that invalidates every honest in-flight voucher too; ECONOMY.md §12 blocks withdrawals at risk >= 70, so a farm frozen after signing is still paid until expiry. (b) A corrected voucher at the same nonce does not supersede the first: whichever the holder submits first wins. (c) If voucher n for a recipient expires or is withheld, vouchers n+1.. are dead until the operator re-signs n. Fix per decision (C): bumpNonce(address to) callable by guardian or operator, and `expiry <= block.timestamp + MAX_VOUCHER_LIFETIME` enforced in withdraw().","line":122,"path":"src/FarmVault.sol","reproduction":"(a) Operator signs (player, 10_000_000e18, nonce 0, expiry = type(uint256).max). Guardian calls pauseWithdrawals() then unpauseWithdrawals(). vm.warp(+3650 days); anyone calls withdraw with that voucher: succeeds, balanceOf(player) == 10_000_000e18 (test/scratch/Repro_Lows.t.sol test_unboundedExpirySurvivesPauseAndYears passes on this tree). (c) Sign (player, 100e18, 0, t0+1h) and (player, 100e18, 1, t0+30d); vm.warp(t0+2h): nonce 0 reverts VoucherExpired(t0+1h), nonce 1 reverts WrongNonce(0, 1); nothing on chain can skip nonce 0 (test_expiredVoucherBlocksTheQueue passes). Expected: a bounded lifetime and a way to void one recipient's queue without pausing everyone.","severity":"low","snippet":"        if (block.timestamp > expiry) revert VoucherExpired(expiry);","title":"A signed voucher cannot be revoked, expiry is unbounded, and one unspent nonce blocks every later voucher for that recipient"},{"citation":"resolved","description":"The NatSpec says vouchers signed by the old operator 'stop working immediately', but nothing in the digest or storage records which operator generation a voucher belongs to; validity is decided only by signer == operator at submission time. A planned roll A -> B -> A, or re-installing a key believed clean, makes every unspent, unexpired voucher signed by A valid again, including ones a leaked copy of A produced with expiry = type(uint256).max. Fix per decision (C): an operator epoch in the signed voucher struct (new VOUCHER_TYPEHASH), incremented on setOperator, so a voucher invalidated by rotation stays invalid.","line":170,"path":"src/FarmVault.sol","reproduction":"Operator A = vm.addr(0xA11CE5) signs (player, 1000e18, nonce 0, expiry max). Guardian calls setOperator(B): withdraw with the voucher reverts BadSignature. Guardian calls setOperator(A): the same voucher now pays 1000e18 (balanceOf(player) == 1000e18). Expected: a voucher invalidated by rotation stays invalid. test/scratch/Repro_Lows.t.sol test_rotatingBackRevivesOldVouchers passes on this tree.","severity":"low","snippet":"        operator = newOperator;","title":"Rotating the operator back to a previous key revives every unspent voucher that key ever signed"},{"citation":"resolved","description":"Both OpenZeppelin VestingWallets start at the launch block's timestamp and vest linearly from the first second with no cliff; release(token) is permissionless. ECONOMY.md §11.7 lists both slices as 'Liquid? No' and says 'the circulating float at launch is the 15 % in the pool'; §9 says 'team (3 y vest)' and gives no community duration, so the 365-day constant exists only in the script (its comment says 'unless overridden' although nothing can override it). After one day 13,698.63 ECU (team) + 41,095.89 ECU (community) is releasable, about 1.7x the daily Exchange budget; after 30 days about 1.64 M. Fix per decision (B): team = VestingWalletCliff with 36-month duration and 12-month cliff, community = VestingWallet 12-month linear with no cliff, and record both in ECONOMY.md §9 and §11.7 (team 15 % 12m cliff / 36m, community 5 % 12m linear). The existing test_nothingHasVestedAtLaunch checks only the exact launch second.","line":88,"path":"script/Distribute.s.sol","reproduction":"deployAll(to, START=1_800_000_000); vm.warp(START + 1 days): teamVesting.releasable(ecu) == 13698630136986301369863 and communityVesting.releasable(ecu) == 41095890410958904109589; vm.prank(0xDEAD); communityVesting.release(ecu) moves 41095890410958904109589 wei to the community beneficiary. Expected per §11.7 'Liquid? No': 0 for the team slice until the 12-month cliff. test/scratch/Repro_Lows.t.sol test_scriptVestingAndLp passes on this tree.","severity":"low","snippet":"        deployment.teamVesting = new VestingWallet(to.teamBeneficiary, startAt, TEAM_VESTING_SECONDS);","title":"Vesting wallets have no cliff: about 54,800 ECU of team plus community tokens are releasable on day one, and the community schedule is not in the spec"},{"citation":"resolved","description":"ECONOMY.md §9 reads '15 % DEX LP (locked 2 y)' and §11.7 says this slice sits in the Uniswap V4 pool on day one. The script transfers 15 M ECU to LIQUIDITY_ADDRESS with no lock, vesting wallet or requirement that the address be a contract, so the holder of LIQUIDITY_ADDRESS can sell the entire launch float immediately. Per decision (A)/(D) the slice is removed from the script altogether: on mainnet the pool is created and owned by the launch factory (15 % pool, 10 % swarm, 75 % to the Distributor), so the LIQUIDITY_ADDRESS env var and the LP transfer go, and ECONOMY.md §9/§11.7 should say 'factory-owned single-sided V4 position' in place of the 2-year LP lock.","line":90,"path":"script/Distribute.s.sol","reproduction":"deployAll with liquidity = 0x11 (an EOA). vm.prank(0x11); ecu.transfer(0x99, 15_000_000e18) succeeds at block.timestamp == start; balanceOf(0x99) == 15_000_000e18. Expected per §9: the LP allocation is locked for 2 years (or, after adaptation, never passes through an EOA at all). test/scratch/Repro_Lows.t.sol test_scriptVestingAndLp passes on this tree.","severity":"low","snippet":"        deployment.ecu.transfer(to.liquidity, _slice(LP_SHARE));","title":"The 15 % DEX liquidity slice is sent unlocked to a plain address although ECONOMY.md §9 specifies 'DEX LP (locked 2 y)'"},{"citation":"resolved","description":"OpenZeppelin 5.7 VestingWallet is Ownable with the beneficiary as owner, and release(token) pays owner(). .env.example line 11 states 'These addresses cannot be changed afterwards', which is false: the beneficiary can call transferOwnership (OZ's own note: 'it is possible to sell unvested tokens') and renounceOwnership, after which owner() == address(0) and every release(token) reverts with ERC20InvalidReceiver, locking the remaining slice permanently with no recovery. Fix per decision (B): wrap both wallets so renounceOwnership() reverts while transferOwnership stays; beneficiaries are Safes in production; reword .env.example to say the beneficiary is a transferable owner that must be a Safe.","line":87,"path":"script/Distribute.s.sol","reproduction":"deployAll(to, START) with teamBeneficiary = 0x33. vm.prank(0x33); teamVesting.transferOwnership(0x77): owner() == 0x77 (expected per .env.example: not changeable). vm.prank(0x77); teamVesting.renounceOwnership(); vm.warp(START + 30 days); teamVesting.release(ecu) reverts; the 15 M stays in the wallet unreachable. test/scratch/Repro_Lows.t.sol test_scriptVestingAndLp passes on this tree.","severity":"low","snippet":"        deployment.communityVesting = new VestingWallet(to.communityBeneficiary, startAt, COMMUNITY_VESTING_SECONDS);","title":"VestingWallet beneficiaries can be changed or renounced, contradicting .env.example, and renouncing strands the slice forever"},{"citation":"resolved","description":"Documented powers, not bypasses; they should be stated in NatSpec per decision (C). (a) setOperator has no delay, so whoever controls the guardian address can appoint a key it holds and sign a voucher for the whole balance in the next transaction; the 2-of-3 Safe signers are a path to all 50 M ECU (bounded only by the cap once added), and a guardian hand-over is also a custody hand-over. (b) Verification uses ECDSA.tryRecover only (65-byte signatures, high-s rejected, zero signer impossible since operator != 0); there is no ERC-1271 path, so if the operator is ever a contract no voucher can validate until the guardian rotates to an EOA. This is the agreed design (keep EOA-only). (c) The guardian can pause withdrawals indefinitely while deposits keep flowing. No malleability issue exists.","line":129,"path":"src/FarmVault.sol","reproduction":"(a) guardian calls setOperator(vm.addr(0xB0B)), then anyone calls withdraw(guardian, 50_000_000e18, 0, expiry, sig_by_0xB0B): succeeds, balanceOf(guardian) == 50_000_000e18 (test/scratch/Repro_Lows.t.sol test_guardianAppointsItselfAndDrains passes on this tree). (b) new FarmVault(ecu, address(aContract), guardian): every withdraw reverts BadSignature because tryRecover never returns a contract address.","severity":"info","snippet":"        if (err != ECDSA.RecoverError.NoError || signer != operator) revert BadSignature();","title":"Trust assumptions: the guardian has instant full custody via setOperator; the operator must be an EOA (no ERC-1271); pause is one-way for deposits"},{"citation":"resolved","description":"DistributeTest calls deployAll directly, so the script contract is both the ECU distributor and the caller of every transfer; the broadcast path through run(), where sender and address(this) differ, is never executed. FarmVaultTest's testFuzz_anyAmountTheVaultHoldsCanBeVouchered asserts that any amount up to the balance clears on one voucher, so adding the cap must remove this test rather than keep it. Also untested: high-s signatures, a voucher submitted exactly at expiry (accepted by design), vesting amounts after the launch second, operator rotate-back, guardian hand-over to an unreachable address, and any multi-day withdrawal sequence. Per decision (E), add: run() under broadcast, cap enforcement, per-voucher ceiling, expiry bound, two-step guardian, bumpNonce, epoch rotate-back, day-1 and cliff vesting amounts, and the Distributor split summing to the received balance.","line":135,"path":"test/FarmVault.t.sol","reproduction":"`forge test` passes 41/41 on this tree while test/scratch/Proof_RunUnderBroadcast.t.sol fails with ERC20InsufficientBalance and test/scratch/Proof_OutflowCap.t.sol fails with 'next call did not revert as expected'.","severity":"info","snippet":"    function testFuzz_anyAmountTheVaultHoldsCanBeVouchered(uint128 amount) public {","title":"Test suite never exercises the broadcast path or any outflow bound; the fuzz test encodes the unbounded voucher as expected behaviour"},{"citation":"resolved","description":"_slice multiplies a constant TOTAL by the share and the five transfers assume the caller holds exactly 100 M. On mainnet the token is created by the custom_token launch and the Distributor receives 75 % of supply (75,000,000e18) as remainderTo; the pool and swarm slices never pass through it. Any port of this arithmetic into the Distributor would try to move 100 M out of a 75 M balance and revert on the fourth transfer, or, if the LP line is simply deleted, leave 10 M stranded. Per decision (A) the Distributor must read balanceOf(address(this)) in distribute(), send balance*50/75 to the vault, balance*15/75 to team vesting, balance*5/75 to community vesting and the exact remainder to listings, and record that it ran so it cannot run twice.","line":103,"path":"script/Distribute.s.sol","reproduction":"State: a Distributor holding 75_000_000e18 ECU that reuses _slice on TOTAL = 100_000_000e18. Transfers: 15 M (LP) + 15 M (community) + 15 M (team) + 5 M (listings) = 50 M succeed, then transfer(vault, 50_000_000e18) reverts ERC20InsufficientBalance(distributor, 25_000_000e18, 50_000_000e18). Expected: 50 M vault, 15 M team, 5 M community, 5 M listings, 0 left.","severity":"info","snippet":"        return (TOTAL * share) / 100;","title":"Shares are computed from a hard-coded 100 M total, not from the balance received, so the logic cannot serve as the 75 % remainderTo target"}],"hash":"82be208f0184d82ddb146d9df6391ad3761c5dab72a3fb194a42b897923dc3c0","nodeId":"88425f1b-8caa-48f3-af6a-bef251c638c8","outcome":"completed","summary":"The review is complete. The findings file is written at the repository root, no tracked file was changed, and the scratch directory holds only the proofs and reproductions.\n\n**Scope read.** I read all three source files in full: `src/ECU.sol`, `src/FarmVault.sol` and `script/Distribute.s.sol`, plus both existing test files, `.env.example`, `foundry.toml`, AUDIT.md and the relevant sections of ECONOMY.md. The vendored OpenZeppelin 5.7.0 VestingWallet, VestingWalletCliff, ECDSA and EIP712 were consulted for the behaviour the findings depend on. Nothing in scope was unreachable. The interfaces under `src/interfaces/` are declarations only and have no state-changing code to review.\n\n**Result: 11 findings, 3 with Foundry proofs.** The code is the same tree AUDIT.md examined, and every one of its findings reproduced on this machine. I re-derived each from the code and confirmed it with a test rather than taking the report on trust.\n\n- **High, broadcast path.** `run()` mints the whole supply to the ephemeral script address and transfers from the broadcaster. The dry run fails under forge 1.8.3 with the \"Usage of address(this)\" guard, and a test calling `run()` reverts with ERC20InsufficientBalance. The proof drives `run()` through a low-level call so it still compiles after the Distributor rewrite.\n- **High, no outflow bound.** One operator-signed voucher for 50,000,000 ECU clears in a single transaction. Proof attached.\n- **Medium, single-step guardian.** Handing over to an unreachable address removes pause and rotation forever. The proof calls `setGuardian` through a low-level call so the two-step rename does not break it.\n- **Low (5).** Unbounded expiry and no per-recipient revocation, rotate-back revives old vouchers, day-one vesting with no cliff, LP slice sent unlocked to an EOA, and renounceable vesting beneficiaries that strand the slice. Each has a passing reproduction in the scratch tests.\n- **Info (3).** Trust assumptions to document in NatSpec, the test gaps including the fuzz test that must be deleted with the cap, and one note of my own: the share arithmetic uses a hard-coded 100 M total, which would overdraw a 75 M remainderTo balance if ported into the Distributor unchanged.\n\n**Launch-forbidden features.** None found. ECU has no owner, mint, pause, blacklist or hook. FarmVault's pause stops only vault payouts, not holder balances. No DELEGATECALL, CALLCODE, SELFDESTRUCT, proxy or initializer exists in the tree.\n\n**Two notes for the adapter, outside the findings.** The foundry.toml RPC aliases cover only Base and Base Sepolia, and the file may not be edited, so the Sepolia dry run must pass its RPC URL on the command line. The existing suite passes 41 of 41 tests on this tree, so no finding fails a test today until the proofs are adopted.","treeHash":null,"usage":{"cachedInputTokens":866019,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":24755,"runtime":"claude","turns":24,"wallClockMs":351596}},{"artifacts":[],"attempt":2,"bundleHash":"9ac72228eda48888c57c69093314a8bc6dd642bd532562a88c6ba1fb703a7713","device":"f9cb4fd544aa3c68","findings":[],"hash":"c8cc8423452209504a990e8c10b052a38edbc481e0c68360bd6ddb13e4495389","nodeId":"ab849bf4-b3a7-45a6-a852-5eb8d8005247","outcome":"completed","summary":"These are lint notes, not errors (the same style of note was already present in the original tests), and the window-cap timestamp comparison is intended. The work is complete.\n\n## Recap\n\n**Outcome:** the project is adapted for launch, all 11 audit findings reproduced and are fixed with tests, and `forge build` plus `forge test` pass on forge 1.8.3 (91 tests, including with the verifier's fuzz seed). The deploy script also simulates to completion under `forge script` without an RPC, which the old one could not.\n\n**What changed**\n\n- `src/FarmVault.sol`: guardian-settable 24-hour outflow cap and per-voucher ceiling checked before transfer, a 7-day voucher lifetime bound, two-step guardian hand-over, `bumpNonce` for guardian or operator, an operator epoch inside the signed voucher struct, and NatSpec stating the trust assumptions. Relayer calldata is unchanged. The backend signer must add the `epoch` field to its EIP-712 type.\n- `src/Distributor.sol` (new): four immutable targets, permissionless one-shot `distribute()` splitting the received balance 50/15/5 over 75 with the remainder to listings. The constructor makes no external call, so it deploys on an empty chain.\n- `src/TeamVesting.sol` and `src/CommunityVesting.sol` (new): OpenZeppelin wrappers with fixed schedules (36 months with a 12-month cliff, and 12 months linear), `renounceOwnership` disabled, `transferOwnership` kept.\n- `script/Distribute.s.sol`: no supply held by the script and no `address(this)`. It derives the token address from the broadcaster's nonce, deploys all five contracts, then calls `distribute()`. The liquidity slice and env var are gone.\n- Tests: rewrote the two tests that encoded removed behaviour, and added coverage for broadcast `run()`, caps, lifetime bound, two-step guardian, nonce bump, epoch rotate-back, day-one and cliff vesting amounts, and the split summing to the received balance.\n- `docs/ECONOMY.md` §9 and §11.7, README, and `ADAPTATION.md` listing each change and its finding.\n\n**Two things to know**\n\n- `.env.example` was deliberately left untouched because `.env` files are outside the allowed paths and that is what got the previous attempt rejected. The requested wording is in the README and flagged in `ADAPTATION.md` for the requester to copy over.\n- `ECU` and `FarmVault` reference each other's addresses at construction, so a single factory transaction cannot create both with backward references. The dry-run script resolves this with the nonce, and on mainnet the token comes from the custom_token launch first. `ADAPTATION.md` spells out the deployment order for the manifest step.","treeHash":"8fb29ad43616b991acb67eca96822ce4a7d243c8","usage":{"cachedInputTokens":2020184,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":68524,"runtime":"claude","turns":44,"wallClockMs":831531}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fa99051b60a858d6","findings":[],"hash":"fba8a15b7149f6428216f45f4edb43436819562495c4a288fdf5ff343a2a28ca","nodeId":"050212d0-bcaf-494f-b279-cf3e9a3dc9a6","outcome":"failed","summary":"needs input: A deployable constructor order. The accepted contracts form a cycle: ECU(distributor) mints its whole supply to the Distributor, Distributor(vault, teamVesting, communityVesting, listings) fixes the FarmVault as an immutable, and FarmVault(ecu, operator, guardian, maxOutflowPerWindow, maxVoucherAmount) fixes ECU as an immutable; all three reject address(0). A launch.json may only reference contracts earlier in its list, and the factory deploys with CREATE2 (each address depends on its constructor arguments), so no ordering of the five contracts can be written and no static address can be precomputed. ADAPTATION.md records the same: the script resolves the cycle with the broadcaster's nonce, which the factory does not have. The only manifest the schema accepts would mint ECU to $owner (the owner wallet) and have the owner forward 100 M ECU to the Distributor before anyone calls distribute(); that contradicts the brief's 'No EOA ever holds the supply' and is a stand-in for the Distributo","treeHash":null,"usage":{"cachedInputTokens":549155,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":11672,"runtime":"claude","turns":18,"wallClockMs":180959}}],"verification":[{"checks":[{"durationMs":1860,"exitCode":0,"name":"build","output":"Compiling 66 files with Solc 0.8.28\nSolc 0.8.28 finished in 1.78s\nCompiler run successful!\n","passed":true},{"durationMs":108,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 18 tests for test/Distribute.t.sol:DistributeTest\n[PASS] test_communityVestingHoldsItsShare() (gas: 10029)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_dayOneReleasesNothingForTheTeamAndOneDayForTheCommunity() (gas: 37342)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_distributeCannotRunTwice() (gas: 31533)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_everythingIsDistributedAndNothingIsLeft() (gas: 56037)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_listingsGetsTheRemainder() (gas: 10575)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_nothingHasVestedAtLaunch() (gas: 32016)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_runCompletesUnderBroadcastAndTheScriptHoldsNothing() (gas: 32458)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_teamVestingHoldsItsShare() (gas: 10073)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_theCommunityScheduleRunsOneYear() (gas: 22413)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_theDistributorTargetsWhatTheScriptDeployed() (gas: 30363)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_theTeamScheduleRunsThreeYearsWithAOneYearCliff() (gas: 29994)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_theTeamSliceIsHalfwayAtEighteenMonths() (gas: 24455)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_theTeamSliceUnlocksAThirdAtTheCliff() (gas: 42712)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_theTokenHasNoOwnerAfterDistribution() (gas: 5533)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_theTokenMintedToTheDistributorNotToAnEoa() (gas: 3232961)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_theVaultHoldsTheGameSideHalf() (gas: 10061)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_theVaultIsWiredToTheTokenItsKeysAndItsCaps() (gas: 59853)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\n[PASS] test_vestingPaysTheBeneficiaryAtTheEnd() (gas: 197493)\nLogs:\n  ECU                 0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76\n  FarmVault           0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496\n  Distributor         0xBb2180ebd78ce97360503434eD37fcf4a1Df61c3\n  Team vesting        0x34A1D3fff3958843C43aD80F30b94c510645C316\n  Community vesting   0x90193C961A926261B756D1E5bb255e67ff9498A1\n  Listings            0x0000000000000000000000000000000000000044\n\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 1.94ms (869.24µs CPU time)\n\nRan 13 tests for test/Vesting.t.sol:VestingTest\n[PASS] testFuzz_releasedNeverExceedsTheSchedule(uint256) (runs: 512, μ: 169776, ~: 153556)\n[PASS] test_constructorsAreNotPayable() (gas: 6415)\n[PASS] test_constructorsRefuseAZeroBeneficiary() (gas: 4237)\n[PASS] test_dayOneAmounts() (gas: 142543)\n[PASS] test_nothingIsReleasableAtLaunch() (gas: 31981)\n[PASS] test_onlyTheOwnerTransfers() (gas: 32643)\n[PASS] test_renounceIsDisabledOnBoth() (gas: 48601)\n[PASS] test_theCommunityScheduleIsLinearWithNoCliff() (gas: 38381)\n[PASS] test_theSchedulesAreFixedInCode() (gas: 37710)\n[PASS] test_theTeamCliffReleasesNothingUntilItEnds() (gas: 185100)\n[PASS] test_theTeamScheduleIsLinearAfterTheCliff() (gas: 57553)\n[PASS] test_transferOwnershipStaysForMigratingSafes() (gas: 142073)\n[PASS] test_transferToZeroIsRefused() (gas: 34753)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 15.18ms (15.92ms CPU time)\n\nRan 9 tests for test/Distributor.t.sol:DistributorTest\n[PASS] testFuzz_theSplitSumsToTheReceivedBalance(uint256) (runs: 512, μ: 260288, ~: 263107)\n[PASS] test_constructsBeforeTheVaultExists() (gas: 13571)\n[PASS] test_distributeRunsOnce() (gas: 299977)\n[PASS] test_distributeWithNothingDoesNotConsumeTheShot() (gas: 286011)\n[PASS] test_hasNoPrivilegedCaller() (gas: 5505)\n[PASS] test_refusesAZeroTarget() (gas: 10330)\n[PASS] test_seventyFivePercentSplitsToTheSpecTable() (gas: 275142)\n[PASS] test_targetsAreFixedAtConstruction() (gas: 56539)\n[PASS] test_theWholeSupplySplitsInTheSameProportions() (gas: 247587)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 16.22ms (16.89ms CPU time)\n\nRan 42 tests for test/FarmVault.t.sol:FarmVaultTest\n[PASS] testFuzz_anyAmountUpToTheVoucherCeilingClears(uint128) (runs: 512, μ: 142773, ~: 142560)\n[PASS] test_aMalleableSignatureIsRefused() (gas: 62888)\n[PASS] test_aMistypedGuardianCanBeCorrectedBeforeAcceptance() (gas: 145692)\n[PASS] test_aPartialWindowOnlyAllowsTheRemainder() (gas: 336713)\n[PASS] test_aTamperedAmountIsRefused() (gas: 68375)\n[PASS] test_aTamperedRecipientIsRefused() (gas: 68363)\n[PASS] test_aVoucherAboveTheCeilingIsRefusedEvenIfSigned() (gas: 58025)\n[PASS] test_aVoucherAtExactlyTheLifetimeBoundIsAccepted() (gas: 137867)\n[PASS] test_aVoucherCannotBeReplayed() (gas: 166872)\n[PASS] test_aVoucherCannotOutliveTheLifetimeBound() (gas: 121890)\n[PASS] test_aVoucherIsStillGoodAtTheExactExpiry() (gas: 135121)\n[PASS] test_anExpiredVoucherIsRefused() (gas: 58107)\n[PASS] test_anOperatorVoucherPaysOut() (gas: 147066)\n[PASS] test_bumpNonceUnblocksAQueue() (gas: 263031)\n[PASS] test_bumpNonceVoidsASignedVoucher() (gas: 114528)\n[PASS] test_constructorRefusesZeroAddresses() (gas: 8390)\n[PASS] test_constructorRefusesZeroCaps() (gas: 10453)\n[PASS] test_constructorSetsTheCaps() (gas: 41775)\n[PASS] test_depositNeedsAnAllowance() (gas: 43132)\n[PASS] test_depositPullsAndLogs() (gas: 119273)\n[PASS] test_depositRefusesZero() (gas: 37336)\n[PASS] test_depositsKeepWorkingWhilePaused() (gas: 157088)\n[PASS] test_loweringTheCapBelowSpentJustClosesTheWindow() (gas: 242805)\n[PASS] test_onlyTheGuardianNominates() (gas: 61677)\n[PASS] test_onlyTheGuardianOrOperatorBumps() (gas: 66131)\n[PASS] test_onlyTheGuardianPauses() (gas: 34097)\n[PASS] test_onlyTheGuardianRotates() (gas: 34589)\n[PASS] test_onlyTheNomineeAccepts() (gas: 108841)\n[PASS] test_pausingStopsWithdrawalsOnly() (gas: 242619)\n[PASS] test_rotatingBackDoesNotReviveOldVouchers() (gas: 309348)\n[PASS] test_rotatingTheOperatorRetiresTheOldSignatures() (gas: 124978)\n[PASS] test_someoneElsesSignatureIsWorthless() (gas: 66176)\n[PASS] test_theDomainIsTheGames() (gas: 6033)\n[PASS] test_theEpochIsPartOfTheDigest() (gas: 27376)\n[PASS] test_theGuardianCanRotateTheOperator() (gas: 199111)\n[PASS] test_theGuardianHandOverIsTwoStep() (gas: 255145)\n[PASS] test_theGuardianRaisesTheCapsAndOthersCannot() (gas: 142934)\n[PASS] test_theVoucherTypeCarriesTheEpoch() (gas: 5536)\n[PASS] test_theWholeCustodyCannotLeaveOnOneVoucher() (gas: 74707)\n[PASS] test_theWindowCapStopsTheSixthVoucher() (gas: 571052)\n[PASS] test_theWindowReopensAfterTwentyFourHours() (gas: 703052)\n[PASS] test_vouchersMustBeSpentInOrder() (gas: 59973)\nSuite result: ok. 42 passed; 0 failed; 0 skipped; finished in 36.84ms (52.05ms CPU time)\n\nRan 9 tests for test/ECU.t.sol:ECUTest\n[PASS] testFuzz_transferFromMovesTheWholeAmount(uint128) (runs: 512, μ: 175514, ~: 175343)\n[PASS] testFuzz_transferMovesTheWholeAmount(uint128) (runs: 512, μ: 84822, ~: 84640)\n[PASS] test_aHolderCanBurnItsOwn() (gas: 109212)\n[PASS] test_burningNeedsYourOwnTokensOrAnAllowance() (gas: 91457)\n[PASS] test_hasEighteenDecimalsToMatchTheLedger() (gas: 5593)\n[PASS] test_hasNoMintFunction() (gas: 28675)\n[PASS] test_isNamedForTheGame() (gas: 23880)\n[PASS] test_mintsTheWholeSupplyOnce() (gas: 17385)\n[PASS] test_refusesAZeroDistributor() (gas: 3714)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 36.88ms (71.69ms CPU time)\n\nRan 5 test suites in 37.80ms (107.05ms CPU time): 91 tests passed, 0 failed, 0 skipped (91 total tests)\n","passed":true},{"durationMs":41,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CommunityVesting.receive()\",\"CommunityVesting.release()\",\"CommunityVesting.release(address)\",\"CommunityVesting.transferOwnership(address)\",\"Distributor.distribute()\",\"ECU.approve(address,uint256)\",\"ECU.burn(uint256)\",\"ECU.burnFrom(address,uint256)\",\"ECU.transfer(address,uint256)\",\"ECU.transferFrom(address,address,uint256)\",\"FarmVault.acceptGuardian()\",\"FarmVault.bumpNonce(address)\",\"FarmVault.deposit(uint256)\",\"FarmVault.pauseWithdrawals()\",\"FarmVault.setGuardian(address)\",\"FarmVault.setMaxOutflowPerWindow(uint256)\",\"FarmVault.setMaxVoucherAmount(uint256)\",\"FarmVault.setOperator(address)\",\"FarmVault.unpauseWithdrawals()\",\"FarmVault.withdraw(address,uint256,uint256,uint256,bytes)\",\"TeamVesting.receive()\",\"TeamVesting.release()\",\"TeamVesting.release(address)\",\"TeamVesting.transferOwnership(address)\"],\"files\":{\".env.example\":30,\".gitignore\":6,\".gitmodules\":6,\"ADAPTATION.md\":174,\"AUDIT.md\":261,\"README.md\":196,\"docs/ECONOMY.md\":734,\"docs/MECHANICS.md\":297,\"foundry.lock\":14,\"foundry.toml\":33,\"script/Distribute.s.sol\":110,\"src/CommunityVesting.sol\":28,\"src/Distributor.sol\":109,\"src/ECU.sol\":44,\"src/FarmVault.sol\":343,\"src/TeamVesting.sol\":35,\"src/interfaces/IFarmAnimals.sol\":30,\"src/interfaces/IFarmPlots.sol\":32,\"test/Distribute.t.sol\":184,\"test/Distributor.t.sol\":139,\"test/ECU.t.sol\":101,\"test/FarmVault.t.sol\":553,\"test/Vesting.t.sol\":161},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":2428,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/Distributor.sol:87: Distributor.distribute() (src/Distributor.sol#87-108) uses a dangerous strict equality:\n[medium/medium] unused-return at src/FarmVault.sol:192: FarmVault.withdraw(address,uint256,uint256,uint256,bytes) (src/FarmVault.sol#192-229) ignores return value by (signer,err,None) = ECDSA.tryRecover(digest,signature) (src/FarmVault.sol#208)\n[low/medium] timestamp at src/FarmVault.sol:192: FarmVault.withdraw(address,uint256,uint256,uint256,bytes) (src/FarmVault.sol#192-229) uses timestamp for comparisons\n[low/medium] timestamp at src/FarmVault.sol:253: FarmVault.remainingInWindow() (src/FarmVault.sol#253-257) uses timestamp for comparisons","passed":true},{"durationMs":516,"exitCode":0,"name":"aderyn","output":"[high] contract-locks-ether at src/CommunityVesting.sol:14: Contract locks Ether without a withdraw function (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c8cc8423452209504a990e8c10b052a38edbc481e0c68360bd6ddb13e4495389","verifiedTreeHash":"8fb29ad43616b991acb67eca96822ce4a7d243c8","verifierVersion":"0.1.0+7129bf9d"}]}